Rand Protocol
Documentación
Programas RISC-V

La ISA RISC-V

Qué significa RV32IM en Rand: las instrucciones que el zkVM demuestra, lo que el verificador de comprobación rechaza, las ocho llamadas al sistema, la memoria, el contenedor de imagen en el que se distribuye un programa y el límite de tamaño de los programas.

Lee esta página antes de escribir un programa para Rand. Indica en qué máquina se ejecuta tu código, qué rechaza la cadena de herramientas, cómo se comunica un programa con el exterior y qué archivo se despliega. Las páginas siguientes se apoyan en ella: Deploy a Rust program, Deploy a RISC-V program y los dos transpiladores.

La máquina

Los programas de Rand son RV32IM: el conjunto base de enteros de 32 bits de RISC-V más la extensión de multiplicación y división. Se ejecutan dentro de la zkVM de Rand, que demuestra cada ejecución con un STARK por lotes de Plonky3. El verificador nunca ve una instrucción. Comprueba la prueba contra hc, el resumen Poseidon2 del programa calculado dentro del circuito.

grupoinstrucciones
superiores / saltosLUI AUIPC JAL JALR
bifurcacionesBEQ BNE BLT BGE BLTU BGEU
cargas / almacenamientosLW SW LB LH LBU LHU SB SH
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL MULH MULHU MULHSU DIV DIVU REM REMU
sistemasolo ECALL

JALR pone a cero el bit 0 de su destino, como establece RV32I, y su codificación debe tener funct3 = 0. Ambas reglas llegaron con el conjunto de restricciones 7 (chain 16); antes, un destino impar no podía leerse y la ejecución no podía demostrarse. Un destino que sea par pero no múltiplo de cuatro sigue sin poder leerse.

No hay coma flotante, ni operaciones atómicas (la extensión A), ni instrucciones comprimidas (la extensión C), ni registros de 64 bits. Una división de 64 bits en C se convierte en una llamada a una función auxiliar de software; véase la ruta de C.

Qué se rechaza

rand-guest check pasa cada palabra del texto por el decodificador propio de la máquina. Un programa que lo supera no puede fallar en el circuito por motivos de codificación, de número de llamada al sistema o de disposición. El verificador rechaza:

hallazgoqué es
Compresseduna codificación RVC de 16 bits; la máquina solo decodifica palabras de 32 bits
Undecodableun código de operación, un funct o una cantidad de desplazamiento que la máquina no implementa
FenceFENCE o FENCE.I; la máquina no tiene ordenación de memoria
Csrcualquier instrucción CSR; la máquina no tiene CSR
EbreakEBREAK; las trampas no están modeladas
Syscallun ecall cuyo a7 lo fija un li justo antes y que no designa ninguna llamada al sistema
Capmás palabras que --max-words, contadas como las cuenta el cargador
Layoutuna base de texto que no está alineada a palabra

Un programa de cuatro palabras con un fence, un ebreak y el ecall número 9, tal como lo informa rand-guest check (código de salida 1):

$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000  0x0ff0000f  Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004  0x00100073  Ebreak: EBREAK: traps are not modelled
0x0000100c  0x00000073  Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED

Un ecall cuyo a7 el verificador no puede ver (fijado por algo distinto de un li, o tras una bifurcación) se cuenta como unresolved_ecalls, no se rechaza. La máquina sigue lanzando una trampa ante un número erróneo en tiempo de ejecución.

El verificador no comprueba los valores de los punteros, las longitudes ni los índices de entrada, ni la pila de 64 KiB frente a la RAM, ni la alineación de las direcciones calculadas en tiempo de ejecución. Un acceso desalineado es una violación de restricción, de modo que la cadena de herramientas compila con -unaligned-scalar-mem. rand-guest run informa de cualquier trampa en tiempo de ejecución.

Llamadas al sistema

ECALL lee el número de la llamada al sistema de a7, el primer argumento de a0 y el segundo de a1. Un valor vuelve en a0.

#llamadaefectogasRust (guest-sdk)C (guest.h)
0HALTtermina la ejecución1halt()rand_halt()
1WRITE_OUTPUT slot wordfija la salida pública slot < 8, como máximo una vez por ranura; las ranuras sin escribir valen cero1write_output(slot, word)rand_write_output
2READ_INPUT idxdevuelve la palabra de entrada privada idx, ligada al compromiso con sal H_IN1read_input(idx)rand_read_input
3POSEIDON2 ptr ncalcula el hash de n ≤ 4096 palabras en la dirección de palabra ptr y escribe el resumen de 8 palabras sobre ptr..ptr+8; sin relleno3 + 3·⌈n/4⌉unsafe poseidon2(ptr, n)rand_poseidon2
4KECCAK ptruna permutación Keccak-f[1600] in situ sobre 50 palabras192keccak(state), keccak256(msg)rand_keccak
5SHA256 ptruna compresión SHA-256 in situ sobre 24 palabras64sha256_compress(buf), sha256(msg)rand_sha256_compress
6READ_PUBLIC idxdevuelve la palabra idx del segmento público, ligada a H_PUB, sin sal1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr nPOSEIDON2 con la longitud n ligada a la esponja, al menos una permutación (conjunto de restricciones 7)3 + 3·max(⌈n/4⌉, 1)poseidon2_len(buf, n)ninguno

Cualquier otra instrucción cuesta 1 de gas: el gas es uno por fila de la cpu, y solo las llamadas de hash pesan más (conjunto de restricciones 8, desde chain 18). Una llamada a Poseidon2 es su fila de ecall, dos filas de reescritura y ⌈n/4⌉ filas de absorción a 3 de gas cada una.

POSEIDON2 no rellena. La esponja sobrescribe sus primeros carriles con cada bloque de cuatro palabras, de modo que [a], [a, 0], [a, 0, 0] y [a, 0, 0, 0] producen un mismo resumen. Un programa que calcula el hash de datos cuya longitud puede variar debe incluir la longitud en el mensaje, aplicar el hash a una codificación de longitud fija, o llamar a POSEIDON2_LEN, que siembra n en la capacidad de la esponja y da un resumen distinto para las mismas palabras. En el SDK de Rust, poseidon2 es una unsafe fn sobre un puntero en bruto y poseidon2_len es la llamada segura sobre un slice; keccak y sha256_compress toman &mut [u32; 50] y &mut [u32; 24].

Una carencia de la cadena de herramientas. En los circuitos aeacf31, la máquina, el emulador y el SDK de Rust implementan la llamada al sistema 7, pero rand-guest check no: su lista de llamadas termina en 6, así que informa de un ecall con a7 = 7 como un hallazgo Syscall y rand-guest build sale con código distinto de cero ante un guest que llama a poseidon2_len (issue #1 de circuits). guest.h tampoco tiene un envoltorio para ella. Hasta que se corrija, un guest construido con la herramienta estándar usa POSEIDON2 sobre mensajes de longitud fija.

Una prueba publica exactamente esto: hc, H_IN, H_PUB, el nivel, el límite de gas declarado y las ocho palabras de salida. Hoy se aplican dos límites en la cadena:

  • El segmento público se fija en el despliegue. En chain 20, rand program deploy --public fija las palabras públicas de un programa (como máximo max_program_public_words, 32.768), y cada llamada se comprueba contra ellas. No hay palabras públicas propias del programa por llamada. Chain 20, como chain 19, añade ocho palabras al segmento de cada llamada tras ellas, el enlace de la llamada que vincula la prueba con su transacción (en chain 20 incluye además el hash del génesis); el guest lee sus propias palabras en los mismos índices que antes. Un Invoke de RPL-2 añade la transición declarada tras el enlace, que el programa lee desde public_len + 8 (Programs). En una cadena sin los campos de límite, como chain 12, el segmento público está vacío y READ_PUBLIC no tiene nada que leer.
  • Las palabras de entrada privada de una llamada están acotadas por el sobre sellado. El valor de max_call_envelope_bytes de chain 20, 65.536, admite 16.071 palabras; una cadena sin el campo admite 4.295.

Una llamada a KECCAK o SHA256 añade una tabla a la prueba, y la cabecera de la prueba muestra entonces que la llamada calculó hashes. Una prueba con keccak ocupa unos 3,3 MB: por encima del límite predeterminado de 2 MiB para una prueba, por debajo de los 4 MiB de chain 20, y una llamada puede usar como máximo 128 permutaciones Keccak-f y 128 compresiones SHA-256 (Programs).

Memoria

  • 32 registros; x0 es cero.
  • La RAM empieza en cero. Toda dirección es inferior a 2³⁰.
  • El script de enlazado predeterminado, guest-sdk/guest.ld, coloca el programa en 0x1000, luego .rodata, .data y .bss, y después una pila de 64 KiB en __stack_top.
  • Un programa con un segmento de datos necesita espacio bajo su texto para el prólogo del cargador (siguiente sección). Su propio .ld eleva ORIGIN a 0x10000, como hacen evm.ld y sbpf.ld.

El contenedor de imagen

rand-guest build y rand-guest pack escriben un único formato de archivo, el contenedor de imagen. Todos los números son palabras de 32 bits en little-endian:

[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…

El cargador convierte el contenedor en un programa { base_pc, words }:

  1. Escribe un par li/sw por cada palabra de datos distinta de cero. Las palabras en cero se omiten, porque la RAM ya se lee como cero.
  2. Coloca ese prólogo justo debajo de text_base, de modo que base_pc = text_base − 4 × palabras del prólogo. El prólogo continúa sin interrupción en el _start del propio programa.
  3. hc y el identificador del programa se calculan sobre todo words, prólogo incluido. Así, hc liga tanto los datos como el código.

Un contenedor sin datos no da prólogo, y produce el mismo programa y el mismo hc que un binario plano sin cabecera en la misma base.

rand program deploy en fullnode v0.3 y posteriores reconoce el contenedor por su primera palabra. 0x444e4152 nunca se decodifica como instrucción, de modo que un .bin sin procesar no puede confundirse con un contenedor. Un archivo que empieza con el número mágico pero está malformado es un error, no un caso de reserva.

Dos identidades

nombrequé esquién lo usa
hcel digest Poseidon2 del circuito de (base_pc, len, words)toda prueba de una llamada se verifica contra él; rand_getProgram lo muestra como code_hash
program idblake3("rand-program" ‖ base_pc ‖ words); un deploy con --public vincula también las palabras públicas, bajo la etiqueta rand-program-2la clave con la que se almacena y se llama a un programa desplegado

rand-guest build y rand-guest info imprimen ambos. hc es vinculante pero no oculta: cualquiera que tenga un programa candidato puede confirmarlo.

El límite de programa

Un deploy puede incluir como máximo el límite de programa de la cadena, en palabras.

cadenalímite
un génesis sin max_program_words (chain 12)4,096
un génesis con max_program_wordsese valor, 1..=65535
el límite estricto de la zkVM, en cualquier cadena65,535

El límite forma parte del hash del génesis, por lo que no puede cambiar en una cadena en funcionamiento. rand-guest comprueba contra --max-words (por defecto 4,096). rand program deploy consulta al nodo con rand_estimateFee y rechaza un programa que excede el límite antes de generar cualquier prueba.

Chain 20, la cadena activa, eleva el límite. Su hash de génesis es 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135 y su max_program_words es 65,535. Mantiene los límites que introdujo chain 13 (la cadena de v0.4), salvo el límite de prueba, que las cadenas 17 a 20 reducen a la mitad. Sus demás límites:

límitechain 20
max_proof_bytes4,194,304 (4 MiB); 8 MiB en las cadenas 13 a 16
max_block_bytes20,971,520 (20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,768

En chain 20, igual que en chain 19, el límite de palabras no es el límite que realmente restringe. Su génesis activa hardening_v6, según el cual se rechaza un deploy (ProgramUncallable) cuando ninguna llamada podría llegar a probar el programa. Una llamada está limitada al tier 14, cuya tabla Poseidon2 contiene 2,048 permutaciones. El digest del programa consume una por cada cuatro palabras, la fila de sal de la entrada una y las ocho palabras de vinculación de la llamada dos, lo que deja 2,045: 8,180 palabras para un programa sin entrada pública, y menos con una (8,168 con nueve palabras públicas). El pool de cada nodo rechaza un deploy así en cualquier cadena.

La agregación de bloques está desactivada en chain 20: su génesis no tiene sección aggregation. Chain 20 está activa desde el 2026-10-01 06:17 UTC en fullnode v0.6.8, un hard fork cortado desde el estado de chain 19 en la altura 7,258 (chain 19 se detuvo en 7,700); chain 19 (activa desde las 03:43 UTC del mismo día) fue a su vez un re-génesis de chain 18 en la altura 141,935. Los programas no se trasladan a través de un corte, por lo que ningún programa desplegado en una cadena anterior existe en chain 20. Chain 20 es además la primera cadena con estado de programa RPL-2 (Programas). Funciona en la red a través de estos nodos de arranque:

/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
imagenpalabrascabe en 4,096cabe en 65,535se despliega en chain 20 (8,180 invocables)
fib (Rust)26sísísí
c-fib (C)25sísísí
ERC-20, traducido (etapa dos)11,686nosíno
evm.bin, el intérprete de EVM18,009nosíno
sbpf.bin, el intérprete de sBPF8,317nosíno
SPL Token, traducido65,096nosí, con 439 de margenno

Tiers

El tier t de una prueba acota la ejecución a 2ᵗ − 1 filas: los ciclos más las filas de digest (una por cada cuatro palabras del programa, una por cada cuatro palabras de la entrada y el digest de la entrada pública). El tier también fija un presupuesto separado de Poseidon2. rand-guest run --tier t informa de ambos:

tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)

El tier es público, y también lo es el límite de gas que declara la prueba. En chain 20 la comisión de la llamada depende de ese límite y del tamaño de la prueba, no del tier: al menos 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉, a los precios vigentes de la cadena, que parten de 100 y 800 unidades de 10⁻⁹ RAND, se mueven con la ocupación de los bloques y en chain 20 se detienen en 10,000 y 80,000 unidades (Comisiones). Un límite no puede superar el techo del encabezado, (2ᵗ − 1) + 2ᵗ⁻² más 191 por cada bloque de Keccak y 63 por cada bloque de SHA-256 que declare la prueba: 1,279 en el tier 10 y 20,479 en el tier 14 sin tabla de hash. rand-guest run informa de ciclos y permutaciones, no de gas. rand call ejecuta primero el programa e imprime el gas que consume y el límite que declara, por defecto el gas exacto redondeado al siguiente múltiplo de 2ᵗ⁻², antes de generar la prueba:

gas: 5636 (declaring 6144, tier 12)

Esa línea proviene de una llamada en chain 18 el 2026-09-30 (Una votación anónima). En una cadena sin sección gas en el génesis, chain 15 y anteriores, la comisión seguía una tabla por tiers: 0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND. La memoria que necesita una prueba crece de forma pronunciada con el tier; véase Hardware de validadores y agregadores.

Desde fullnode v0.5.6 (2026-09-25), una llamada puede declarar como máximo el tier 14: un nodo rechaza con CallTierTooHigh una prueba de llamada de tier superior antes de construir una verifier key, y un bloque que contenga una es inválido. El presupuesto del tier 14 es de 16,383 filas y 2,048 permutaciones de Poseidon2, y el digest del propio programa consume cerca de una permutación por cada cuatro palabras, de modo que un programa de más de 8,180 palabras no puede invocarse, y chain 20, igual que chain 19, rechaza su deploy. Escribir un programa en Rust o C recorre el presupuesto.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12, y circuits `aeacf31` (`rand-guest/README.md`, `rand-guest/src/check.rs`, `research/docs/01-isa.md`, `guest-sdk/`), fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `ledger/program_state.rs`, `deploy/cut-chain20-genesis.sh`), chain 20's genesis and `rand_getLimits`, read on 2026-10-01, fullnode tag v0.8.0 `c0878c3f` (`docs/zkvm.md` §8a, constraint set 9), `deploy/genesis-chain21.json` and `rand_getLimits` on chain 21, read on 2026-10-12. Cuando el código y esta página no coinciden, el código tiene razón; avísenos.