El objetivo de este desafío es encontrar una implementación imposiblemente corta de la siguiente función p
, en el idioma que elija. Aquí está el código C que lo implementa (vea
este enlace TIO que también imprime sus resultados) y una página de Wikipedia que lo contiene.
unsigned char pi[] = {
252,238,221,17,207,110,49,22,251,196,250,218,35,197,4,77,
233,119,240,219,147,46,153,186,23,54,241,187,20,205,95,193,
249,24,101,90,226,92,239,33,129,28,60,66,139,1,142,79,
5,132,2,174,227,106,143,160,6,11,237,152,127,212,211,31,
235,52,44,81,234,200,72,171,242,42,104,162,253,58,206,204,
181,112,14,86,8,12,118,18,191,114,19,71,156,183,93,135,
21,161,150,41,16,123,154,199,243,145,120,111,157,158,178,177,
50,117,25,61,255,53,138,126,109,84,198,128,195,189,13,87,
223,245,36,169,62,168,67,201,215,121,214,246,124,34,185,3,
224,15,236,222,122,148,176,188,220,232,40,80,78,51,10,74,
167,151,96,115,30,0,98,68,26,184,56,130,100,159,38,65,
173,69,70,146,39,94,85,47,140,163,165,125,105,213,149,59,
7,88,179,64,134,172,29,247,48,55,107,228,136,217,231,137,
225,27,131,73,76,63,248,254,141,83,170,144,202,216,133,97,
32,113,103,164,45,43,9,91,203,155,37,208,190,229,108,82,
89,166,116,210,230,244,180,192,209,102,175,194,57,75,99,182,
};
unsigned char p(unsigned char x) {
return pi[x];
}
Que es p
p
es un componente de dos estándares criptográficos rusos, a saber, la función hash Streebog y el cifrado de bloque Kuznyechik . En este artículo (y durante las reuniones de ISO), los diseñadores de estos algoritmos afirmaron que generaron la matriz pi
seleccionando permutaciones aleatorias de 8 bits.
Implementaciones "imposibles"
Hay permutaciones en 8 bits. Por lo tanto, para una permutación aleatoria dada, no se espera que un programa que la implemente necesite menos de 1683 bits.
Sin embargo, hemos encontrado múltiples implementaciones anormalmente pequeñas (que enumeramos aquí ), por ejemplo, el siguiente programa en C:
p(x){unsigned char*k="@`rFTDVbpPBvdtfR@\xacp?\xe2>4\xa6\xe9{z\xe3q5\xa7\xe8",l=0,b=17;while(--l&&x^1)x=2*x^x/128*285;return l%b?k[l%b]^k[b+l/b]^b:k[l/b]^188;}
que contiene solo 158 caracteres y, por lo tanto, cabe en 1264 bits. Haga clic aquí para ver que funciona.
Hablamos de una implementación corta "imposiblemente" porque, si la permutación fuera el resultado de un proceso aleatorio (como afirman sus diseñadores), entonces no existiría un programa tan corto (consulte esta página para obtener más detalles).
Implementación de referencia
Una versión más legible del código C anterior es:
unsigned char p(unsigned char x){
unsigned char
s[]={1,221,146,79,147,153,11,68,214,215,78,220,152,10,69},
k[]={0,32,50,6,20,4,22,34,48,16,2,54,36,52,38,18,0};
if(x != 0) {
unsigned char l=1, a=2;
while(a!=x) {
a=(a<<1)^(a>>7)*29;
l++;
}
unsigned char i = l % 17, j = l / 17;
if (i != 0) return 252^k[i]^s[j];
else return 252^k[j];
}
else return 252;
}
La tabla k
es tal que k[x] = L(16-x)
, donde L
es lineal en el sentido de que L(x^y)==L(x)^L(y)
, y donde, como en C, ^
denota el XOR. Sin embargo, no logramos aprovechar esta propiedad para acortar nuestra implementación. No conocemos ninguna estructura s
que pueda permitir una implementación más simple, aunque su salida siempre está en el subcampo, es decir donde la exponenciación se realiza en el campo finito. ¡Por supuesto, usted es absolutamente libre de usar una expresión más simple de si encuentra una!s
El ciclo while corresponde a la evaluación de un logaritmo discreto en el campo finito con 256 elementos. Funciona a través de una simple búsqueda de fuerza bruta: la variable ficticia a
está configurada para ser un generador del campo finito, y se multiplica por este generador hasta que el resultado sea igual a x
. Cuando es el caso, tenemos que l
es el registro discreto de x
. Esta función no está definida en 0, de ahí el caso especial correspondiente a la if
declaración.
La multiplicación por el generador puede verse como una multiplicación por en que luego se reduce el módulo del polinomio . El papel de la es garantizar que la variable permanezca en 8 bits. Alternativamente, podríamos usar , en cuyo caso podría ser un (o cualquier otro tipo de entero). Por otro lado, es necesario comenzar con lo que necesitamos tener cuando es igual a 1.unsigned char
a
a=(a<<1)^(a>>7)*(256^29)
a
int
l=1,a=2
l=255
x
En nuestro documentop
se presentan más detalles sobre las propiedades de , con un resumen de la mayoría de nuestras optimizaciones para obtener la implementación breve anterior.
Reglas
Proponga un programa que implemente la función p
en menos de 1683 bits. Mientras más corto sea el programa, más anormal es, para un idioma dado, más corto es mejor. Si su idioma tiene Kuznyechik, Streebog o p
como incorporado, no puede usarlos.
La métrica que usamos para determinar la mejor implementación es la longitud del programa en bytes. Usamos la longitud de bits en nuestro trabajo académico, pero nos quedamos con los bytes aquí por simplicidad.
Si su lenguaje no tiene una noción clara de función, argumento o salida, la codificación depende de usted para definir, pero trucos como codificar el valor pi[x]
como x
obviamente están prohibidos.
Ya hemos presentado un trabajo de investigación con nuestros hallazgos sobre este tema. Está disponible aquí . Sin embargo, si se publica en un lugar científico, con gusto reconoceremos a los autores de las mejores implementaciones.
Por cierto, ¡gracias a xnor por su ayuda al redactar esta pregunta!
1683 bits at most
restricción estricta [sic?] O la meta?