Estos cuatro programas son, cada uno, una aplicación completa de una llamada confidencial: un problema, una interfaz, un guest en Rust, las herramientas del lado del host, y un despliegue y una llamada en el testnet en vivo. Son más largos que los tutoriales y cada uno se apoya en una parte distinta de la máquina. Escribir un programa en Rust o C es la guía del modelo que todos usan. Léala primero si read_input, el segmento público o las ocho ranuras de salida le son nuevos.
Todo lo que aparece aquí se ejecutó por primera vez contra chain 18 con fullnode v0.6.7, el 2026-09-30, y cada salida de estas páginas está pegada de esas ejecuciones. Los programas no pasan de una a otra tras un corte de cadena, así que el 2026-10-01 los cuatro se desplegaron de nuevo, sin cambios, en chain 19 y luego en chain 20 (fullnode v0.6.8), y las llamadas se repitieron cada vez. El id de un programa es un hash del código y del segmento público, no de la cadena, así que todos los ids son los mismos; los hashes de transacción de las transcripciones son los de chain 18, y la tabla siguiente enlaza los de chain 20. En chain 20 un programa también puede conservar estado entre llamadas (RPL-2); estos cuatro no lo usan, y Estado del programa y el repositorio example-programs cubren los programas que sí lo hacen.
Los cuatro programas
| programa | qué demuestra una llamada | se apoya en | palabras | nivel |
|---|---|---|---|---|
| Una votación anónima | quien llama es miembro de una lista publicada, sin decir cuál, y no ha votado antes | Keccak-256, un segmento público | 427 | 12 |
| Una prueba de pasivos | los saldos privados de los clientes suman no más que una reserva publicada, bajo una raíz que cada cliente puede comprobar | SHA-256, un árbol construido dentro del programa | 994 | 14 |
| Un libro mayor fuera de la cadena | un lote de transferencias lleva un libro mayor de un estado al siguiente, con cada emisor autorizado y sin que se cree nada | Poseidon2, estado transportado entre llamadas | 773 | 14 |
| Una decisión de crédito privada | un modelo publicado aprueba o rechaza datos privados de un solicitante que un certificador avaló | aritmética de enteros, un modelo en el segmento público | 462 | 12 |
Los cuatro están en vivo en chain 20. Cada llamada de la tabla repite, con las mismas entradas, la llamada de chain 18 que se muestra en la página de ese programa:
| programa | id del programa | desplegado en chain 20 en | primera llamada en chain 20 |
|---|---|---|---|
| votación anónima | ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 | f6ae5528…0f82 | 3a304e7a…427b |
| prueba de pasivos | fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0 | a8bc281b…17d9 | 6116c15f…d62a |
| libro mayor fuera de la cadena | c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab | 72cc95f2…3d51 | 9c9998bb…66eb |
| decisión de crédito privada | 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c | 413e6658…e42b | d3ceb306…124a |
El explorador lista cada programa y sus llamadas en randscan.org/programs. Un testnet se reemplaza de vez en cuando y los programas no pasan de una cadena a otra: tras el próximo corte estos ids habrán desaparecido y habrá que desplegar los programas de nuevo.
Preparación
La cadena de herramientas. Los guests se compilan con rand-guest del repositorio de circuitos, en el commit que vendorizan tanto fullnode v0.6.7 como v0.6.8:
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits
rustup toolchain install 1.98.1
rustup target add riscv32im-unknown-none-elf --toolchain 1.98.1
(cd rand-guest && cargo +1.98.1 build --release)
alias rand-guest="$PWD/rand-guest/target/release/rand-guest"
El commit importa por dos razones. hc es función del código fuente y de la cadena de herramientas, así que un guest recompilado en otro commit de guest-sdk puede obtener un hc distinto. Y las firmas del SDK cambiaron: en aeacf31, keccak y sha256_compress reciben referencias a arreglos donde commits anteriores recibían punteros sin procesar.
Los fuentes. Los cuatro guests, sus scripts del host y los datos de demostración son una sola descarga. Descomprímala dentro del checkout, porque rand-guest build encuentra guest-sdk buscando hacia arriba desde el directorio del guest:
mkdir -p my-guests
curl -sSf https://randprotocol.org/examples/rand-example-programs.tar.gz | tar -xz --strip-components=1 -C my-guests
ls my-guests
# allowlist ledger ledger-host score solvency
Los archivos también pueden consultarse uno por uno en https://randprotocol.org/examples/programs/, por ejemplo allowlist/src/main.rs.
La wallet. Desplegar y llamar requieren la wallet rand de fullnode v0.6.8 (cargo build --release -p randprotocol-client en un checkout de fullnode en la etiqueta v0.6.8, o el rand adjunto a la versión v0.6.8), una clave y RAND. Una wallet más antigua no puede transaccionar en chain 20: chain 20 vincula cada prueba y cada mensaje firmado a su génesis, y una wallet v0.6.7 no lo hace.
Un RPC. El endpoint público, https://rpc.randprotocol.org, basta para todo lo que aparece en estas páginas: leer programas y recibos, sincronizar una wallet, desplegar y llamar. La primera sincronización de una wallet lee cada encabezado de bloque y cada bloque que contiene una nota, 645 solicitudes a la altura de chain 18 el 2026-09-30, y tardó unos tres minutos a través del endpoint. El endpoint permite a cada dirección 1,200 lecturas y 6 envíos por minuto. Un nodo propio (Ejecutar un nodo) no tiene esos límites.
export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json
En chain 18, uno de los votos contados en la página de la votación anónima, fdbcc960…fc2b, se demostró en un portátil y se envió solo a través del endpoint público. En chain 19 y chain 20, las trece transacciones de cada vez, los cuatro despliegues y las nueve llamadas, siguieron ese camino.
Qué cambió chain 18 para un programa
Una llamada declara un límite de gas y lo paga. El conjunto de restricciones 8 mide cada ejecución. Un ciclo ordinario cuesta 1 de gas. Las excepciones son las syscalls de hash:
| instrucción | gas |
|---|---|
cualquier instrucción RV32IM, HALT, WRITE_OUTPUT, READ_INPUT, READ_PUBLIC | 1 |
POSEIDON2 sobre n palabras | 3 + 3 × ⌈n / 4⌉ |
SHA256, una compresión | 64 |
KECCAK, una permutación | 192 |
La prueba lleva un nuevo valor público, el límite declarado, y el circuito exige que el gas de la ejecución sea igual o menor que él. El gas exacto permanece privado. rand call ejecuta primero el programa en el emulador e imprime ambos:
gas: 5636 (declaring 6144, tier 12)
Por defecto, la wallet redondea el gas exacto hacia arriba a un múltiplo de 2^(tier − 2), que es 1,024 en el nivel 12 y 4,096 en el nivel 14, de modo que el límite declarado dice poco sobre la ejecución. --gas-limit max declara el techo del nivel y no dice nada más allá del nivel. --gas-limit N declara una cifra exacta.
La comisión depende del límite declarado y de los bytes.
floor = 0.001 RAND + gas price × declared limit + byte price × ⌈bytes / 1024⌉
Los precios partieron de 100 unidades por gas y 800 unidades por KiB (una unidad es 10⁻⁹ RAND), y se mueven un poco en cada bloque hacia un objetivo. rand_getLimits sirve los vigentes. La wallet añade un margen de dos pasos de precio, un factor de 1.125², porque una llamada aterriza dos o tres bloques después de ser tasada.
Un detalle decide la mayor parte de la comisión. Desde chain 18, la prueba de una llamada está vinculada a su propia transacción, de modo que la wallet tiene que fijar la comisión antes de que exista la prueba, y tasa la prueba al tope de la cadena, 4 MiB, en lugar de a su tamaño real. Para la votación anónima eso es (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6,191,437 unidades, 0.0062 RAND, de los cuales el gas es cerca de un octavo. Las cuatro llamadas de estas páginas cuestan entre 0.0057 y 0.0075 RAND cada una. --fee anula el valor por defecto.
Cada bundle son dos pruebas. Chain 18 ejecuta autorización dividida: el bundle que paga una comisión lleva una pequeña prueba de autorización de nivel 10 junto a la prueba del bundle de nivel 14. La wallet genera ambas e imprime una línea por cada una. Una llamada son, por tanto, tres pruebas: la llamada, la autorización y el bundle. En un portátil con Apple Silicon, las cuatro llamadas de aquí tardaron entre tres y cinco minutos y medio cada una, con un pico de 6.1 a 6.6 GB de memoria. La prueba del bundle son unos dos minutos de eso en todos los casos. La prueba de llamada de nivel 12 añade medio minuto y la de nivel 14 añade dos minutos.
Los topes que una llamada debe respetar.
| límite | chains 18 a 20 |
|---|---|
| nivel | como máximo 14: 16,383 ciclos, filas de digest incluidas |
| permutaciones Poseidon2 | 2,048 en el nivel 14, incluidos los digests del programa, de la entrada y del segmento público |
| permutaciones Keccak | 128 |
| compresiones SHA-256 | 128 |
| prueba de llamada | 4,194,304 bytes (4 MiB) |
| programa | 8,180 palabras sin segmento público, algunas menos con uno: chain 20, como chain 18 y chain 19, rechaza un despliegue que ninguna llamada de nivel 14 podría contener (ProgramUncallable), sea cual sea el valor que permita max_program_words (65,535) |
| segmento público | 32,768 palabras, fijadas al desplegar |
| entrada privada con una transcripción sellada | 16,071 palabras |
rand-guest run --tier 14 comprueba los dos primeros antes de desplegar. Una tabla de hash agranda la prueba. La prueba de nivel 14 del libro mayor, sin tabla de hash, pesa 1.50 MB. La tabla SHA-256 lleva la prueba de pasivos a 1.91 MB, y la tabla Keccak lleva la de la votación a 3.35 MB, que es la mayor parte del tope de 4 MiB.
Convenciones que comparten los cuatro
- La ranura 0 es un estado, y 0 nunca es uno válido. Todo resultado, los fallos incluidos, tiene un número desde 1 en adelante, y un fallo se detiene limpiamente para que pueda demostrarse. Un recibo con todo en cero no puede confundirse con un resultado.
- La ranura 7 no se toca. Un guest de Rust que entra en pánico escribe ahí
0xdeadbeef(3.735.928.559). Un lector comprueba primero la ranura 7. - Las ranuras 2 a 6 transportan un valor de 160 bits: un nullifier, una raíz, un enlace, un compromiso. Un hash completo de 256 bits son ocho palabras y no dejaría espacio para un estado. Truncar a cinco palabras conserva 80 bits de resistencia a colisiones, y cada página explica qué protege eso.
- El orden de bytes sigue al hash. Los valores de Keccak son palabras little-endian y los de SHA-256 son palabras big-endian, porque así es como cada syscall lee su bloque. Un script del host y un guest que no coincidan en esto producen resúmenes que simplemente nunca coinciden.
- Todo lo que varía por instancia va en el segmento público; todo lo que varía por llamada es entrada privada. La raíz de una votación, una cifra de reservas y los pesos de un modelo se fijan al desplegar y quedan ligados al id del programa. Una llamada no lleva palabras públicas propias.
- Las palabras de entrada son decimales en la línea de comandos. Las páginas convierten un archivo de testigo en flags con
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt).
Llama tú mismo
Con las fuentes descomprimidas, la cadena de herramientas compilada y una wallet con fondos de v0.6.8, votar como uno de los miembros de demostración son cuatro comandos:
cd my-guests/allowlist
python3 allowlist.py witness 10 1 demo-secrets.txt 6 1 > witness.txt # member 6, choice 1
rand-guest build . --max-words 65535 # only needed to run it in the emulator first
rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
Llamar no necesita la imagen: rand call obtiene del nodo el código y el segmento público del programa, comprueba que su hash coincide con el id y genera la prueba sobre ellos.
Leer un resultado no requiere wallet ni nodo. Cualquier recibo se obtiene con una sola solicitud al endpoint público:
$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"rand_getReceipt","params":["8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"]}'
{"id":1,"jsonrpc":"2.0","result":{"h_in":"ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6","h_pub":"2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510","height":98335,"index":0,"outputs":[1,2,392974773,873526743,1921224601,3950868247,2248722827,0],"program":"ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970","tier":12,"tx":"8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"}}
La API del explorador ofrece las llamadas de un programa en una sola solicitud: https://randscan.org/api/v1/programs/<program id> devuelve call_count y recent_calls (API del explorador).
Lo que ninguno hace
Un programa en Rand es una prueba sobre un cálculo, y la cadena no actúa sobre ninguna de sus salidas. Ninguno de estos programas mueve RAND ni un token, mantiene un saldo o recuerda una llamada anterior. El voto lo cuenta quien lee los recibos. Los pasivos se comparan con reservas que necesitan su propia evidencia. Los saldos del libro mayor son números en un árbol fuera de la cadena. La decisión de crédito es un bit que un prestamista decide respetar. Cada página termina con una lista de lo que su prueba deja en manos de quienes la usan.