Rand Protocol
Documentación
Guías

Cómo funciona la zkVM

Cómo demuestra Rand que un programa se ejecutó correctamente sin revelar su entrada: la máquina RISC-V, la traza de ejecución dividida en nueve tablas, los buses que las conectan, las reglas como identidades polinómicas, el compromiso, FRI, Fiat-Shamir, de dónde proviene el conocimiento cero, qué comprueba un validador y por qué cada parámetro de FRI y de la función hash tiene el valor que tiene.

Lee esta página para entender la máquina que hay detrás de cada prueba en Rand: qué demuestra, cómo la ejecución de un programa se convierte en algo que un validador puede comprobar en milisegundos y de dónde viene la privacidad. Para saber qué cuesta una prueba y cómo la genera la wallet, consulta Proofs. Para los parámetros y las construcciones de hash, consulta Cryptography. Para el conjunto de instrucciones con el que se escribe un programa, consulta The RISC-V ISA.

El único enunciado que demuestra toda prueba

La zkVM demuestra un único tipo de enunciado, para todos los programas que ejecuta la cadena:

Un programa cuyo digest es hc, iniciado en base_pc, ejecutado sobre una entrada privada comprometida mediante H_IN y un segmento de entrada pública comprometido mediante H_PUB, se detuvo en un máximo de 2ᵗ − 1 ciclos, gastó como máximo GAS_LIMIT de gas y escribió estas ocho palabras de salida.

Una transferencia protegida es ese enunciado sobre un programa fijo, el guest del bundle de activos ocultos, cuyo hc está fijado en el archivo génesis como hc_bundle. Desde la cadena 17 se emite dos veces por transferencia: una segunda prueba, pequeña, sobre el guest de autenticación (hc_auth en el archivo génesis) demuestra que el remitente posee la clave de gasto (Proofs). Una llamada confidencial es el mismo enunciado sobre un programa desplegado. Hay una sola máquina, un solo verificador y un solo conjunto de restricciones para todos ellos. Desplegar un programa es, por tanto, una operación de datos (almacenar sus palabras y su hc), no criptográfica: no hay un circuito por programa ni una configuración de confianza (trusted setup).

El sistema de pruebas es un STARK construido con Plonky3. No necesita más que una función hash: no hay ceremonia de configuración ni curvas elípticas, así que no hay nada que el algoritmo de Shor pueda romper.

El flujo de trabajo

 secret input ──► run the program ──► execution trace ──► polynomials ──► commit ──► FRI ──► proof
 (keys,           (RV32IM emulator)   (nine tables,      (one per       (8× code,   (80 queries,
  notes, paths)                        one row per step)  column)        Merkle)     20-bit grinding)

 every validator:  recompute the challenges from the Merkle roots, open 80 random positions,
                   check the rules there  →  ≈ 16 ms

La fila superior se ejecuta una vez, en la máquina del remitente, en unos 100 segundos para un bundle y unos 7 segundos para la prueba de autorización que lo acompaña. La fila inferior se ejecuta en cada validador, únicamente a partir de los campos públicos y de la prueba.

1. Ejecutar el programa y registrar todo

La máquina es un procesador RISC-V de 32 bits: RV32IM sin FENCE, las instrucciones CSR ni EBREAK (The RISC-V ISA). El probador primero ejecuta el programa en un emulador y registra una fila por cada ciclo de reloj. Ese registro es la traza de ejecución. Quien la tuviera podría reproducir la ejecución paso a paso; el resto del esquema existe para que un verificador pueda convencerse de que la traza es correcta sin tenerla.

Un programa se comunica con el exterior mediante ocho syscalls y nada más:

syscalln.ºqué hace
HALT0se detiene; las ocho palabras de salida quedan fijas
WRITE_OUTPUT1establece una de las ocho palabras de salida públicas
READ_INPUT2lee una palabra de la entrada privada
POSEIDON23calcula el hash de un búfer en memoria, in situ
KECCAK4una permutación Keccak-f[1600]
SHA2565una compresión SHA-256
READ_PUBLIC6lee una palabra del segmento de entrada pública
POSEIDON2_LEN7POSEIDON2 con la longitud del mensaje incorporada al hash (conjunto de restricciones 7)

Los compromisos de nota, los nullifiers y las rutas de Merkle no están integrados en la máquina. Son código guest ordinario que prepara en memoria un mensaje con etiqueta de dominio y llama a POSEIDON2. Esa syscall no aplica relleno, de modo que [a] y [a, 0] dan el mismo hash. Los guests incluidos la llaman solo con mensajes de longitud fija. Un programa nuevo que calcule el hash de datos cuya longitud pueda variar usa POSEIDON2_LEN (The RISC-V ISA).

No existe el aborto

El procesador no tiene ninguna instrucción que se niegue a terminar, así que un programa no puede detenerse cuando falla una comprobación. El guest del bundle registra el fallo en su lugar: cada comprobación fallida hace un OR de un 1 en un registro llamado bad, y bad es la última palabra incorporada al hash del digest de salida:

D = H(64; anchor, nf₀..₃, cm₀..₃, fee, burn_a, burn_r, burn_asset, time, c, bad)

c es el compromiso de autorización que la prueba de autenticación también publica (desde la cadena 17; el digest de las cadenas 14 a 16 no tenía c).

La cadena nunca recibe del remitente la preimagen de D. La reconstruye a partir de los campos públicos de la transacción, establece bad = 0 por sí misma, calcula el hash y compara. Un tramposo puede generar una prueba perfectamente válida de una ejecución en la que falló una comprobación, pero esa ejecución imprimió H(…, 1), y la cadena busca H(…, 0). Hacerlas coincidir requiere una colisión de hash.

2. Dividir la traza en nueve tablas

Una sola tabla que describiera un procesador completo sería innecesariamente ancha, así que la traza se divide en tablas especializadas, que se demuestran juntas como un único lote:

tablafilas en el nivel tqué registra
programdeclaradas, ≥ 2⁷las palabras de instrucción del programa, decodificadas en el circuito; fija lo que la CPU puede leer
cpu2ᵗuna fila por ciclo, precedida por filas que calculan el hash del programa, de la entrada privada y del segmento público; contiene los 35 valores públicos
memorydeclaradas, ≥ 2ᵗ⁺²registros y RAM, ordenados por (espacio, dirección, tiempo)
alu2ᵗ⁺¹cada operación aritmética, lógica, de desplazamiento, de comparación, de multiplicación y de división, sobre limbs de bytes
range, nibble256 cada unatablas de consulta fijas: bytes, potencias de dos, AND/OR/XOR de 4 bits
poseidon22ᵗ⁺²la permutación hash, una fila por ronda
inputdeclaradas, ≥ 2⁷las palabras comprometidas de la entrada privada
publicdeclaradas, ≥ 2⁷las palabras del segmento público (en un bundle, el vínculo con la transacción)

Otras dos tablas, keccak y sha256, solo están presentes en una prueba cuyo programa las llamó, cada una con al menos 2⁷ filas cuando existe; el guest del bundle no usa ninguna. El mínimo de 2⁷ filas en cada tabla declarada llegó con el conjunto de restricciones 7 (section 9).

El nivel t es uno de 10, 12, 14, 16, 18 o 20 y dimensiona las tablas. Todo bundle se demuestra en el nivel 14: una tabla de CPU de 16 384 filas, de las cuales el guest usa unas 12 400. El guest de autenticación ejecuta 250 ciclos en el nivel 10. El resto es relleno, de modo que una prueba revela qué nivel necesitó un programa, nunca cuántos pasos dio dentro de él (Tiers).

La tabla de memoria es lo que hace comprobable la RAM. Como está ordenada por dirección y luego por tiempo, «una lectura devuelve el último valor escrito en esa dirección» se convierte en una regla entre dos filas adyacentes.

3. Conectar las tablas con buses

¿Cómo sabe el verificador que la suma que usó la CPU es la que calculó la ALU? Mediante buses. Cuando la CPU necesita una suma, envía un mensaje (op, a, b, c); la ALU recibe un mensaje por cada fila que calcula. La máquina tiene diecisiete buses:

busentre
PROGRAM, PROGRAM_WORDcpu ← program: lecturas de instrucciones y las palabras que absorbe el digest del programa
MEMORYcpu ↔ memory: cada lectura y escritura
ALUcpu → alu
RANGE8, POW2, AND4, OR4, XOR4comprobaciones de rango de bytes, desplazamientos y operaciones bit a bit contra las tablas fijas
POSEIDON2cpu → poseidon2: un mensaje por permutación
INPUT_DIGEST, INPUT_READcpu ← input: las palabras que absorbe el digest de la entrada y las lecturas del programa
PUBLIC_DIGEST, PUBLIC_READcpu ← public: el mismo par para el segmento público
KECCAK, SHA256cpu → las tablas de hash opcionales
BLINDcada tabla → la siguiente tabla en un ciclo fijo: un valor aleatorio cada una, en la primera fila (conjunto de restricciones 7)

Los buses se comprueban con un argumento de derivada logarítmica (LogUp). Una vez comprometidas las tablas, se extrae un valor aleatorio α, y cada tabla mantiene una suma acumulada de ±1/(α − m) sobre los mensajes que envía o recibe. Las sumas se cancelan exactamente si y solo si cada mensaje enviado fue recibido, el mismo multiconjunto en ambos lados. Un solo mensaje falsificado deja un residuo que un α aleatorio expone con probabilidad abrumadora. El bus de memoria usa un pariente cercano, una comprobación de permutación de multiconjuntos.

Cada tabla publica el valor final de su suma acumulada, su terminal, y el verificador comprueba que los terminales suman cero. Hasta el conjunto de restricciones 6, un terminal era una función comprobable del contenido de su tabla. Desde el conjunto de restricciones 7, cada tabla incorpora cinco columnas adicionales y, en su primera fila, envía un valor aleatorio y recibe el de su vecina en el bus BLIND. Cada terminal se desplaza por un elemento uniforme del cuerpo de desafíos, y la suma sigue siendo cero.

Los pares de buses de digest y de lectura son deliberadamente distintos. LogUp equilibra por mensaje, no por consumidor, así que un bus compartido permitiría a un probador omitir una palabra del digest mientras una lectura de esa palabra siguiera teniendo éxito.

4. Anclar la traza al mundo exterior

Treinta y cinco valores públicos salen de la traza, todos en la tabla de CPU:

valoresqué son
1el punto de entrada pc
1el nivel (tier)
8las palabras de salida (para un bundle, el digest D)
8hc, el digest del programa, calculado dentro de la traza a partir de las palabras de la tabla del programa
8H_IN, el digest de la entrada privada, calculado sobre la entrada con una sal fresca de 128 bits al principio
8H_PUB, el digest del segmento público, sin sal
1GAS_LIMIT, el tope de gas que declara el prover (conjunto de restricciones 8)

Como hc se calcula en el circuito, una prueba sobre un programa distinto no puede pasar por una prueba sobre el guest del bundle. El verifier nunca ve una palabra de instrucción; compara hc con el digest que espera. Como H_IN lleva sal, vincula la entrada privada sin revelarla, incluso a quien pudiera adivinarla. Como H_PUB no lleva sal, el verifier puede recalcularlo a partir de palabras que ya tiene. Para un bundle, esas palabras son el vínculo de la transacción (transaction binding), blake3("rand-tx-bind-1" ‖ chain_id ‖ tx) sobre la transacción con los bytes de su prueba en blanco, de modo que una prueba copiada a una transacción con otro destino, memo, envoltorio o chain id no se verifica. En la chain 20 (genesis binding_domain: 1) es blake3("rand-tx-bind-2" ‖ genesis_hash ‖ chain_id ‖ tx), de modo que una prueba no se verifica en otra cadena ni siquiera con el mismo chain id; las palabras son una entrada pública, así que esto no cambió nada en el circuito. La prueba de autorización se hace sobre las mismas ocho palabras. Una prueba de llamada en la chain 20 se vincula de la misma manera: su segmento son las palabras públicas del programa fijadas en el despliegue, seguidas de ocho palabras de un vínculo de llamada (rand-call-bind-2, rand-call-bind-1 antes de la chain 20), de modo que una prueba de llamada copiada bajo otro bundle de comisión no se verifica.

El gas es la medida que usa la máquina del peso de una ejecución, y está en el circuito desde el conjunto de restricciones 8. La tabla de la CPU lleva una columna GAS que empieza en 1 y suma 1 por cada fila, 3 por una fila de absorción de Poseidon2, 64 por una fila SHA256 y 192 por una fila KECCAK. En la fila HALT, el circuito demuestra GAS ≤ GAS_LIMIT mediante cuatro límbs con comprobación de rango de la diferencia, que nunca se abren: el límite es público y el gas que gastó una ejecución no lo es. Antes de construir una clave, el verifier rechaza un límite superior a lo que podría necesitar el propio encabezado de la prueba:

gas_max(t, k, s) = (2ᵗ − 1) + 2ᵗ⁻² + 191·(2ᵏ / 32) + 63·(2ˢ / 64)

con k y s las alturas logarítmicas declaradas de las tablas keccak y sha256, y cada término igual a cero si la tabla está ausente. El techo de un encabezado de tier 10 sin hashes es 1,279 y el de uno de tier 14 es 20,479. La cadena fija el precio de una llamada según su límite declarado (Comisiones).

5. Convertir las reglas en identidades polinómicas

Cada columna es una lista de números del cuerpo Goldilocks, enteros módulo p = 2⁶⁴ − 2³² + 1. Una lista de n números equivale a un polinomio de grado inferior a n: el que pasa por ellos cuando se disponen sobre un subgrupo {1, ω, ω², …} del cuerpo. Goldilocks tiene subgrupos de ese tipo de todos los tamaños que son potencia de dos hasta 2³², de modo que la conversión es una transformada rápida de Fourier.

Una regla de la máquina se convierte entonces en una ecuación entre polinomios de columna. «El contador de programa avanza cuatro salvo que la instrucción salte» se convierte, esquemáticamente, en:

is_jump(x)·(pc(ωx) − target(x)) + (1 − is_jump(x))·(pc(ωx) − pc(x) − 4) = 0

para todo x del subgrupo, donde pc(ωx) significa «la fila siguiente». El conjunto completo de estas reglas de una tabla es su AIR (representación algebraica intermedia). Las reglas de la CPU y de la ALU llegan a grado 8; las demás se mantienen entre 2 y 4.

Un polinomio que vale cero en todos los puntos del subgrupo es divisible por Z(x) = xⁿ − 1. El prover combina todas las reglas, con pesos aleatorios, en un único polinomio C(x) y lo divide por Z(x). Si todas las reglas se cumplen en todas las filas, el cociente es un auténtico polinomio de grado bajo. Si una regla falla en una fila, no lo es. «¿Fue correcto cada paso de una ejecución de 16,000 ciclos?» se ha convertido en «¿está esta función cerca de un polinomio de grado bajo?», y esa pregunta puede responderse mirando unos pocos valores elegidos al azar.

6. Comprometerse antes de conocer las preguntas

El prover debe fijar sus polinomios antes de ver las preguntas aleatorias del verifier, o podría adaptar sus respuestas. Evalúa cada columna en un dominio ocho veces mayor que la tabla (blow-up 8, tasa de código 1/8) y publica una raíz de Merkle sobre las evaluaciones. Esa redundancia es un código de Reed–Solomon: un polinomio de grado bajo queda fijado por cualquier octava parte de sus valores, de modo que un tramposo que cambie incluso una sola fila de la traza tiene que cambiar la mayor parte de la palabra de código extendida para seguir siendo consistente, y las comprobaciones puntuales aleatorias caerán sobre el daño.

Los árboles de Merkle usan Poseidon2 sobre Goldilocks: ancho 8, S-box x⁷, 4 rondas completas, 22 rondas parciales y 4 rondas completas, con digests de cuatro elementos del cuerpo (unos 256 bits). Cada hoja se procesa con un hash junto con cuatro elementos de aleatoriedad fresca, de modo que una raíz no revela nada de los valores que hay debajo. La misma permutación es el compromiso de nota y el hash del nullifier dentro de la máquina. Sus 86 constantes de ronda son una tabla literal en el código fuente del nodo (poseidon2_constants.rs), de modo que ninguna actualización de biblioteca puede cambiarlas.

7. Comprobación puntual con FRI

El verifier ya tiene las raíces de Merkle. Primero vincula el cociente con la traza: elige un punto aleatorio z fuera del dominio de la tabla, pide el valor de cada columna y el del cociente en ese punto, y comprueba directamente C(z) = Q(z)·Z(z). Falta confirmar que las funciones comprometidas son realmente polinomios de grado bajo, y eso es lo que comprueba FRI.

FRI funciona por plegado. Se divide un polinomio en sus partes par e impar, f(x) = fₑ(x²) + x·fₒ(x²). Para un β aleatorio del verifier, f′(y) = fₑ(y) + β·fₒ(y) tiene la mitad de grado sobre un dominio de la mitad de tamaño. El prover se compromete con f′, recibe un nuevo β y vuelve a plegar (Rand pliega hasta por ocho en cada ronda) hasta que queda una constante, que envía en claro. Una consulta elige entonces una posición aleatoria en la primera capa y la sigue hacia abajo, comprobando en cada capa que los valores abiertos arriba se pliegan en el de abajo. Una función que estaba lejos de ser de grado bajo no puede mantener consistentes todas esas promesas.

Rand ejecuta 80 consultas. Bajo el teorema de brechas de proximidad demostrado, cada consulta deja a un tramposo como máximo una probabilidad de 9/16, de modo que 80 consultas dan (9/16)⁸⁰ ≈ 2⁻⁶⁶·⁴. Bajo la conjetura ethSTARK, en la que se apoyan los STARK desplegados, cada consulta deja 1/8, que es mucho menor.

8. Eliminar la conversación

Todo lo anterior se describió como un diálogo, pero una transacción tiene que ser un único mensaje que cualquier validator pueda comprobar por sí solo. La transformación Fiat–Shamir elimina al verifier: el prover calcula cada desafío «aleatorio» como un hash de todo lo comprometido hasta ese momento, con una esponja dúplex de Poseidon2. Cambiar cualquier compromiso cambia todos los desafíos posteriores, de modo que el prover no puede elegir compromisos que se ajusten a los desafíos.

Dos medidas adicionales cierran las brechas que esto deja:

  • Cuerpo de extensión. Un cuerpo de 2⁶⁴ elementos es demasiado pequeño para extraer desafíos de él: un tramposo necesita que un desafío caiga en uno de los pocos valores malos de un polinomio, y lo consigue con una probabilidad de aproximadamente N/2⁶⁴ por muchas consultas que se ejecuten. Por eso la traza permanece en Goldilocks, pero cada desafío se extrae de su extensión cuadrática, de unos 2¹²⁸ elementos.
  • Grinding. Antes de derivar las posiciones de consulta, el prover debe encontrar un nonce cuyo hash con la transcripción empiece con 20 bits en cero, unos un millón de hashes. Un prover honesto lo paga una vez. Un tramposo que vuelve a tirar las posiciones de consulta hasta que esquiven su falsificación lo paga en cada intento. Es la única prueba de trabajo que existe en Rand, y no tiene nada que ver con el consenso.

En suma: 66.4 bits de las consultas más 20 del grinding dan 86 bits demostrados, y unos 100 bits bajo la conjetura, donde el término del cuerpo de extensión es el límite.

9. De dónde viene el conocimiento cero

Un STARK no es de conocimiento cero por sí solo: los valores abiertos en las posiciones de consulta son valores de los polinomios de la traza y, sin cuidado, filtran información. La configuración de Rand añade cuatro capas:

  1. cada polinomio comprometido se aleatoriza (el FRI con ocultamiento de Plonky3 mezcla cuatro palabras de código aleatorias), de modo que los 80 valores abiertos pueden reproducirse, en distribución, por alguien que nunca vio la traza;
  2. cada hoja de Merkle lleva sal, de modo que las raíces y las rutas de autenticación no revelan nada;
  3. la aleatoriedad del prover se extrae de nuevo del sistema operativo para cada prueba, de modo que dos pruebas del mismo pago parecen no guardar relación;
  4. cada tabla declarada tiene al menos 2⁷ filas y cada terminal LogUp está cegado (conjunto de restricciones 7). Una prueba abre cada tabla en 80 puntos de consulta y dos puntos fuera del dominio, y el compromiso con ocultamiento mezcla tantas filas aleatorias como filas tiene la tabla, de modo que una tabla queda oculta solo mientras tenga al menos 82 filas. 128 es la menor potencia de dos que lo cumple.

La capa 4 es una corrección. La revisión del 2026-09-27 recuperó, a partir de pruebas de producción, las entradas privadas de las llamadas con 31 palabras de entrada o menos, leyó los recuentos de lectura (fetch) de un programa a partir de su tabla de programa corta y distinguió las ranuras de entrada reales de un bundle de sus ranuras ficticias por sus terminales sin cegar. Esas pruebas son las generadas en la chain 15 y anteriores. La chain 16 (conjunto de restricciones 7, con el guest de bundle sin ramificaciones) cerró las tres, y las chains 18 a 20 mantienen las correcciones.

La garantía es conocimiento cero estadístico en el modelo de oráculo aleatorio. Lo que una prueba no oculta es exacto: su tier, las alturas de tabla que declara y sus valores públicos, entre ellos el límite de gas. Una altura declarada de keccak o sha256 igual a 0 significa que la ejecución nunca usó ese hash, de modo que la cabecera de la prueba de una llamada muestra si la llamada calculó hashes y, por encima del piso, con qué frecuencia, con un margen de un factor de dos. En un bundle, los valores públicos y los campos son el ancla, cuatro nullifiers, cuatro compromisos, la comisión, las quemas, el activo quemado, el tiempo, el compromiso de autorización y el vínculo de la transacción. El activo que mueve una transferencia es una entrada privada, por lo que un pago en zUSD y un pago en RAND se ven igual en la cadena. hc es determinista, así que cualquiera puede saber que una prueba corresponde al guest de bundle (toda prueba que mueve valor lo es) o, en el caso de una llamada, qué programa desplegado ejecutó.

10. Qué comprueba un validator

Machine::verify_public(hc, words, proof) ejecuta, de la más barata a la más cara:

  1. el tier declarado de la prueba y las alturas de tabla están acotados (check_declared_heights) antes de dimensionar nada a partir de ellos, de modo que una cabecera mal formada cuesta unas pocas comparaciones y no una construcción de clave de varios segundos; un bundle debe declarar el tier 14 sin ninguna tabla de hash, y una prueba de autorización el tier 10;
  2. hay 35 valores públicos, cada uno un elemento de campo canónico, con pv[HC] = hc, pv[TIER] igual al tier de la prueba, cada palabra de salida por debajo de 2³², y GAS_LIMIT como máximo gas_max de la cabecera;
  3. el STARK por lotes se verifica con una clave de verificador almacenada en caché por forma (tier, program, input, keccak, sha256, public heights), no por programa;
  4. H_PUB es igual al resumen de words, que en un bundle es el vínculo de la transacción que el propio nodo calculó.

Antes de todo esto, el nodo examina cada prueba enviada o propagada por gossip para comprobar que su codificación sea canónica: su calendario de plegado FRI debe ser el que produce un prover honesto para los grados que declara la cabecera. Desde fullnode v0.6.8 (audit v6, ZKV-4), el examen rechaza primero cualquier grado declarado superior a 25 bits (MAX_HONEST_DEGREE_BITS, el techo de 2²⁴ de la tabla de memoria más el doblado de conocimiento cero, el máximo que lleva una prueba aceptada). Antes de ese límite, una cabecera que declaraba un grado enorme hacía que el nodo volviera a derivar un calendario de plegado inmenso en su bucle de eventos, bloqueándolo durante el tiempo que el remitente quisiera; el verifier habría rechazado la prueba de todos modos, así que el límite solo cambia el costo de decir que no. Un pánico dentro del examen ahora rechaza esa única transacción en lugar de detener el nodo.

Luego el libro mayor recalcula el resumen del bundle D con bad = 0 y lo compara con las ocho palabras de salida de la prueba. En la chain 20 exige además que el GAS_LIMIT de una prueba de bundle sea exactamente el bundle_gas_limit del génesis, 20,479, y el de una prueba de autorización 1,279, de modo que toda transferencia declare los mismos valores. Con una clave en caché, la comprobación completa toma unos 16 ms.

Los parámetros y por qué se eligieron

Cada número que sigue es de consenso: el verifier lo comprueba, una prueba hecha con cualquier otro valor no se verifica, y cambiar uno implica un nuevo conjunto de restricciones y un hard fork (Constraint sets). El perfil FRI lo fija el archivo de génesis (fri_profile: production en la chain 20), nunca la prueba.

parámetrovalorpor qué este valor
campo baseGoldilocks, p = 2⁶⁴ − 2³² + 1Un primo de 64 bits cuya aritmética se reduce a unas pocas instrucciones de máquina, con subgrupos multiplicativos de todos los tamaños que son potencia de dos hasta 2³², de modo que convertir una columna en un polinomio es una transformada rápida de Fourier. La traza vive aquí porque es el campo barato.
campo de desafíosextensión cuadrática F_p² (≈ 2¹²⁸)Unos desafíos extraídos del propio Goldilocks dejarían un piso de solidez de aproximadamente N/2⁶⁴ que ninguna cantidad de consultas puede reducir. La extensión lleva ese término a un máximo de 2⁻¹⁰⁰ para trazas de hasta 2²⁵ filas. Solo los desafíos pagan por el campo más grande; la traza no. Una extensión cúbica (≈ 2¹⁹²) es el margen previsto si algún día se exigen 128 bits.
expansión (blow-up)8 (tasa ρ = 1/8, log_blowup = 3)Dos razones a la vez. Las tablas de CPU y ALU tienen reglas de grado 8, y con el indicador de conocimiento cero el cociente tiene grado 9, que es lo máximo que puede soportar una expansión de 8; una regla de mayor grado obligaría a una expansión de 16. Además, la capacidad de cada consulta para detectar a un tramposo depende de la tasa: (1 + ρ)/2 = 9/16 por consulta demostrado, ρ = 1/8 conjeturado. Una expansión mayor permitiría menos consultas, a costa de una extensión de bajo grado proporcionalmente más grande que el prover debe calcular y mantener en memoria.
consultas FRI80El número que mantiene la solidez demostrada en cerca de 86 bits. Una vez se redujo a 27 (conjuntos de restricciones 3 y 4), lo que cumplía un objetivo conjeturado de 100 bits (3·27 + 20 = 101) pero dejaba solo unos 42 bits demostrados; la auditoría zk del 2026-09-12 (hallazgo ZM1) restableció 80. Las consultas son la mayor parte del tamaño de una prueba, y por eso una prueba de bundle ocupa ≈ 1,50 MB. Para 100 bits demostrables harían falta 97 consultas.
grinding20 bits, en la fase de consultasAntes de sortear las posiciones de las consultas, el prover debe encontrar un nonce cuyo hash comience con 20 bits en cero: alrededor de un millón de hashes, que un prover honesto paga una sola vez y un tramposo paga en cada reintento al volver a sortear posiciones. Añade 20 bits a bajo costo; más grinding (34 bits también darían 100 demostrables) vuelve más lenta cada prueba honesta. La fase de compromiso no tiene grinding.
aridad de plegadohasta 8 por ronda (max_log_arity = 3)Plegar de ocho en ocho en lugar de dos en dos da un tercio de las capas FRI, de modo que cada consulta abre menos rutas de Merkle y la prueba es más pequeña, a cambio de un poco más de trabajo por capa.
polinomio finaluna constante (log_final_poly_len = 0)FRI pliega hasta el final y envía un único valor en claro, la comprobación final más simple para el verifier.
hash (en el circuito, Merkle, transcripción)Poseidon2, ancho 8, S-box x⁷, 4 + 22 + 4 rondas, tasa 4, capacidad 4Un hash diseñado para ser barato como aritmética de campo, de modo que calcular hashes dentro de una prueba (compromisos, nullifiers, rutas de Merkle, el digest del programa) cuesta filas, no miles de operaciones de bits. El exponente de la S-box no debe compartir ningún factor con p − 1 = 2³²·3·5·17·257·65537, y 7 es el menor que cumple esa condición. El ancho 8 con tasa 4 da un digest de cuatro elementos, de unos 256 bits. Las 86 constantes de ronda son una tabla literal en el nodo (poseidon2_constants.rs).
ancho del digest4 elementos de campo (≈ 256 bits)Suficiente para los roles de preimagen (nullifiers, compromisos: 128 bits frente a Grover). No es suficiente para una resistencia a colisiones cuántica de 128 bits en la transcripción (2²⁵⁶/³ ≈ 2⁸⁵); el diseño del whitepaper pide un hash de transcripción de 384 bits, y la cadena en funcionamiento no lo ha adoptado.
ocultamiento4 palabras de código aleatorias en FRI; 4 elementos de sal por hoja de Merkle; aleatoriedad nueva del sistema operativo en cada pruebaLo mínimo que permite simular las 80 posiciones abiertas y las rutas de Merkle solo a partir de valores públicos, de modo que la prueba es de conocimiento cero estadístico. Dos pruebas del mismo pago, hechas en momentos distintos, parecen no guardar relación.
piso de tablas2⁷ filas para cada tabla declarada (conjunto de restricciones 7)Una prueba abre 80 puntos de consulta y 2 puntos fuera del dominio de cada tabla, 82 en total, y una tabla de h filas se oculta con h filas aleatorias. 64 filas son muy pocas y 128 es la siguiente potencia de dos. Un número de consultas superior a 126 exigiría 2⁸.
ocultamiento terminalun valor de F_p² por tabla, en el bus BLIND (conjunto de restricciones 7)Un terminal LogUp se calcula a partir de la traza en bruto, así que sin un ocultamiento es un compromiso sin sal con su tabla. El ocultamiento es un elemento del campo de extensión porque una sola coordenada del campo base confinaría el desplazamiento a un conjunto delgado contra el cual se podría probar un testigo candidato.
pesos de gas1 por fila; 3 por fila de absorción de Poseidon2; 64 por fila SHA256; 192 por fila KECCAK (conjunto de restricciones 8)Cada peso cuenta las filas que esa fila arrastra a otras tablas: una fila de absorción, una permutación de 32 filas; una fila SHA256, 64 filas de 466 columnas; una fila KECCAK, 32 filas de 2.612 columnas y 100 filas de memoria.
grado máximo de las restricciones8Fijado por la expansión indicada arriba; el grado de cada tabla está fijado por una prueba, de modo que una regla nueva no pueda superarlo en silencio.
niveles (tiers)10, 12, 14, 16, 18, 20Las tablas se dimensionan en seis pasos fijos, cada uno cuatro veces el anterior, y una prueba revela solo su nivel: un cubo grueso y con relleno de la duración de la ejecución, no su número de ciclos. Todo bundle es de nivel 14 y toda prueba de autorización de nivel 10; las llamadas están limitadas al nivel 14 desde fullnode v0.5.6.
traza máxima2²⁴ filas (2²⁵ con el bit de conocimiento cero)El tamaño para el que está demostrado el término del campo de extensión; una traza más larga se rechaza en lugar de demostrarse bajo una afirmación de solidez que no se sostiene.
valores públicos35 elementos de campopc de entrada, nivel, ocho palabras de salida, los tres digests de ocho palabras hc, H_IN, H_PUB, y el límite de gas declarado: el enunciado más pequeño que nombra el programa, la entrada privada, la entrada pública y una cota del trabajo. Eran 34 hasta el conjunto de restricciones 7.
límite del tamaño de la prueba2 MiB por defecto; 4 MiB en la cadena 20 (max_proof_bytes)2 MiB está por encima de toda prueba que producen los guests desplegados sin tabla de hash y por debajo de una que lleve keccak (≈ 3,3 MB); el génesis de la cadena 20 fija 4.194.304 para admitir ambas, como hicieron las cadenas 17 a 19. Las cadenas 13 a 16 fijaron 8 MiB. El límite se comprueba antes de decodificar una prueba.

Lo que suman estos números: 86 bits de solidez demostrada, unos 100 bajo la conjetura ethSTARK (donde el límite lo pone el campo de extensión, no las consultas), y unos 85 bits frente a un adversario cuántico, fijados por el hash de transcripción de 256 bits. El whitepaper mantiene a propósito ambas cifras de solidez una junto a la otra: todo FRI-STARK desplegado se apoya en la conjetura para sus últimos bits, y quien dimensione un despliegue debe saber qué supuesto sostiene cuántos bits.

Pruebas sobre pruebas

Con cerca de 1,50 MB, una prueba de bundle es pesada, la prueba de autorización de 1,37 MB viaja a su lado, y cada nodo las almacena todas. El remedio que está construido es la recursión: una prueba cuyo enunciado es «verifiqué estas pruebas». El fullnode incorpora para ello una segunda máquina, más pequeña, la máquina virtual de recursión, cuya palabra nativa es un elemento de Goldilocks y cuyas instrucciones son las operaciones propias del verifier (aritmética de campo, Poseidon2, pasos de Merkle y de FRI). Su programa de agregación reproduce la verificación completa de N pruebas de bundle y publica un digest sobre sus valores públicos, ligado a la cadena, al aggregator y a un nonce. Verificar una prueba de bundle dentro de ella costó 1.968.619 filas con el conjunto de restricciones 6 y 2.044.506 con el conjunto 7; no se ha vuelto a medir con el conjunto 8. Está implementada y desactivada por el génesis en todas las cadenas hasta ahora, incluida la cadena 20. El programa de agregación verifica únicamente pruebas de bundle, de modo que en una cadena con autorización dividida la prueba de autorización seguiría existiendo. Además, sus pruebas de producción requieren más memoria de la prevista: medido con el conjunto de restricciones 7, 133 GB o más (Upcoming: block aggregation).

Lo que el esquema no afirma

  • El margen cuántico es de unos 85 bits, no de 128. La búsqueda cuántica de colisiones sobre un hash de n bits cuesta aproximadamente 2ⁿ/³. El diseño del whitepaper prevé un hash de transcripción de 384 bits; la cadena en funcionamiento usa el digest de 256 bits de Poseidon2. Ampliarlo supone un nuevo conjunto de restricciones y un hard fork.
  • 100 bits es una conjetura; 86 está demostrado. Elevar la cifra demostrada a 100 costaría 97 consultas, aproximadamente una quinta parte más de datos de consulta por prueba.
  • Las constantes de Poseidon2 son un sorteo con semilla, no una instanciación publicada. No se conoce ningún ataque contra ello; adoptar un conjunto estándar cambiaría todos los hashes de la cadena.
  • Algunas afirmaciones se comprueban junto a la prueba, no dentro de ella. El verificador compara el valor público TIER con la forma de la prueba, pero el circuito no lo restringe (auditoría ZL1), y la disciplina de contaminación (taint) del guest del bundle se respalda con un argumento estructural, un arnés de guest debilitado y fuzzing de mutación, en lugar de verificarse formalmente por máquina.
  • El encabezado de una prueba no está oculto. El nivel y las alturas de las dos tablas de hash están a la vista, de modo que una prueba de llamada muestra si la llamada usó KECCAK o SHA256. Existe una opción del prover que rellena ambas tablas y ninguna wallet la activa: lleva una prueba de nivel 10 de unos 1,3 MB a unos 3,6 MB.
  • La syscall POSEIDON2 no es inyectiva sobre entradas de longitud variable. Se mantiene tal como está porque cada compromiso, nullifier, raíz y digest de programa depende de ella. Ningún guest distribuido usa todavía POSEIDON2_LEN.
  • La generación de pruebas es lenta y la realiza el emisor, o un prover con el que el emisor se empareja: unos 100 s y 5,7 GB en una CPU de portátil para un bundle. Los kernels de GPU se ejecutaron por primera vez en una GPU (una NVIDIA H100) el 2026-09-28 y coincidieron con la referencia de CPU; la wallet sigue generando pruebas en la CPU.

Dónde leer más

  • La sección «The Zero-Knowledge Scheme: How a Rand Proof Works» del whitepaper (randprotocol.tex) recorre los mismos siete ingredientes con los teoremas de solidez.
  • El paper de implementación (randprotocol_implementation.tex) especifica el conjunto de instrucciones y las syscalls, el guest del bundle, la esponja y los digests, cada tabla y bus, la configuración de la máquina y la VM de recursión.
  • El código: crates/randprotocol-zkvm/src/ en el fullnode — emulator.rs, tables/, machine.rs, gas.rs, guests.rs, hash.rs, poseidon2_constants.rs.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12, y fullnode `90e7716` (v0.6.7 on chain 18, `crates/randprotocol-zkvm`, constraint set 8), fullnode `c9c9bd3c` (v0.6.8 on chain 20: ZKV-4 in `crates/randprotocol-zkvm/src/executor.rs`, `binding_domain` in `crates/randprotocol-core/src/types/binding.rs`; no constraint change), fullnode `c0878c3f` (v0.8.0 on chain 21: `docs/zkvm.md` §8a, constraint set 9 — the `MODQ` syscall and the optional `modq` table; `docs/shielded.md` §2, the multisig auth guest's pinned shape), circuits `aeacf31`, `research/docs/02-tables-and-buses.md`, `research/docs/03-privacy.md` (the FRI profile, the degree ceiling, the table floor and the blinded terminals), whitepapers `8f4275b` (`randprotocol.tex` §“The Zero-Knowledge Scheme”, `randprotocol_implementation.tex` §§4.10–4.15). Cuando el código y esta página no coinciden, el código tiene razón; avísenos.