Rand Protocol
Documentación
Programas RISC-V

Desplegar un programa RISC-V

Dos caminos hacia una imagen desplegable sin Rust: un programa en C compilado por rand-guest con el clang fijado, y un programa escrito a mano, ensamblado y empaquetado, o bien empaquetado byte a byte en el contenedor de imágenes.

Cualquier programa que sea código máquina RV32IM puede desplegarse, sea cual sea su origen. Esta página muestra dos caminos: C mediante rand-guest build --lang c, y un programa que usted escribe en ensamblador y empaqueta por su cuenta. Ambos terminan en el mismo contenedor de imagen que produce Desplegar un programa en Rust, y ambos se despliegan con rand program deploy. Configure primero el checkout de circuits y rand-guest, como en el paso 1 de esa página. Para las cadenas 18 a 20, el checkout está en el commit aeacf31, que fullnode v0.6.7 y v0.6.8 fijan ambos. Las salidas que se muestran a continuación se produjeron el 2026-09-19 en 7ef3220 y no se han vuelto a ejecutar. El directorio rand-guest, incluidos guest.h y rt.c, es el mismo en ambos commits.

C mediante clang

Qué necesita

herramientaversióncómo
clang23.1.1, con un backend RISC-Vbrew install llvm en macOS; el clang del sistema de Apple no tiene backend RISC-V. O defina $CLANG con un binario 23.1.1
rust-lldde Rust 1.98.1rustup +1.98.1 component add llvm-tools

rand-guest rechaza cualquier clang cuya versión no sea 23.1.1. RAND_GUEST_CLANG_UNPINNED=1 compila con otro, con una advertencia de que hc no coincidirá con las imágenes publicadas.

Escribir el guest

Un directorio de guest en C contiene solo archivos .c (y un .ld opcional). rand-guest aporta guest.h, _start y un pequeño runtime. No añada su propio guest.h ni start.S: build --lang c rechaza un directorio que tenga cualquiera de los dos.

Este es el archivo guests-compiled/c-fib/fib.c confirmado en el repositorio:

#include "guest.h"

static uint32_t fib(uint32_t n) {
    uint32_t a = 0, b = 1;
    for (uint32_t i = 0; i < n; i++) {
        uint32_t c = a + b;
        a = b;
        b = c;
    }
    return a;
}

void main(void) {
    rand_write_output(0, fib(rand_read_input(0)));
    rand_halt();
}

guest.h declara rand_read_input, rand_read_public, rand_write_output, rand_poseidon2, rand_keccak, rand_sha256_compress y rand_halt. No tiene wrapper para la syscall 7, el POSEIDON2_LEN con límite de longitud que la máquina ejecuta desde el conjunto de restricciones 7, y rand-guest check en aeacf31 rechaza un ecall con a7 = 7 (zkp-circuits issue #1). Por tanto, un guest en C o en ensamblador para la cadena 20 usa las syscalls 0 a 6.

Compilar, ejecutar, inspeccionar

$ rand-guest build --lang c guests-compiled/c-fib --out c-fib.bin
25 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote c-fib.bin and its .sha256 (25 words, hc 76e723cb4c218f9a7f657f39b9f95ee2353d8fbeb601aaea394afa29928f3aee, program id c213cf5c94679cab4585ed1e81b61736a7d2fe5605dc7884e4acec4144665999)

$ rand-guest run c-fib.bin --input 20 --tier 10
out[0] = 6765
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 116
tier 10
tier 10: fits (cycles 126 of 1023, Poseidon2 permutations 10 of 128)

fib(20) = 6765, la misma respuesta que el guest fib en Rust.

build --lang c compila todos los archivos .c del directorio (en orden alfabético), además de los start.S y rt.c incluidos, con:

clang --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax
      -nostdlib -ffreestanding -fno-builtin -ffunction-sections -fdata-sections -Os
      -ffile-prefix-map=<checkout>=/rand-circuits -c

y los enlaza con rust-lld -flavor gnu --gc-sections -T <ld> de la toolchain de Rust fijada. --gc-sections conserva solo lo que el guest llama.

El runtime de C

-ffreestanding -fno-builtin impiden que clang reconozca funciones de biblioteca, pero no que emita llamadas a ellas. Una copia de struct se convierte en memcpy, un inicializador de ceros grande se convierte en memset, y una división de 64 bits por un valor en tiempo de ejecución se convierte en una llamada a una función auxiliar, porque RV32IM solo divide valores de 32 bits. rt.c define estas ocho:

funciónse usa para
memcpy, memmove, memset, memcmpcopias, rellenos, comparaciones
__udivdi3, __umoddi3, __divdi3, __moddi3/ y % de 64 bits

Son bucles de bytes y división por desplazamiento y resta. Son lentas: en un bucle caliente, copie y divida en palabras de 32 bits.

Un programa escrito a mano

También puede escribir usted mismo el código máquina: ensamblarlo, enlazarlo con el mismo linker script y empaquetar el ELF. Este programa suma dos entradas privadas y publica la suma.

add.S:

# Reads private inputs 0 and 1, writes their sum to output slot 0, halts.
    .section .text._start
    .globl _start
_start:
    li   a7, 2          # READ_INPUT
    li   a0, 0
    ecall               # a0 = input 0
    mv   t0, a0
    li   a7, 2          # READ_INPUT
    li   a0, 1
    ecall               # a0 = input 1
    add  t1, t0, a0
    li   a7, 1          # WRITE_OUTPUT slot, word
    li   a0, 0
    mv   a1, t1
    ecall
    li   a7, 0          # HALT
    ecall

Defina cada número de syscall con un li a7, n justo antes de su ecall. Así el verificador de comprobación puede validar el número.

Ensamble con el clang fijado, enlace con rust-lld y guest-sdk/guest.ld, y luego empaquete. Ejecute desde dentro del checkout de circuits:

CLANG=/opt/homebrew/opt/llvm/bin/clang
LLD=$(rustc +1.98.1 --print sysroot)/lib/rustlib/$(rustc +1.98.1 -vV | sed -n 's/host: //p')/bin/rust-lld
$CLANG --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax -c add.S -o add.o
$LLD -flavor gnu -T guest-sdk/guest.ld add.o -o add.elf
$ rand-guest pack add.elf --out add.bin
wrote add.bin (80 bytes) and add.bin.sha256

$ rand-guest check add.bin
14 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK

$ rand-guest run add.bin --input 40 --input 2
out[0] = 42
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 14
tier 10

$ rand-guest info add.bin
form: image container
text 14 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 14 words from base_pc 0x1000
hc 42986ac1f93396d7de75f6757149523e7f1e9c5999d748af9b1915ada8ee14bd
program id 3b34c9d16adbd81a4849148675a914d10e5da0bd5b6aa64967e5f8a5fc75d941
14 words against a cap of 4096: fits

pack toma el .text del ELF como segmento de texto y cada una de las demás secciones PROGBITS asignadas como un único segmento de datos. .bss se omite, porque la RAM empieza en cero.

Empaquetar el contenedor a mano

El contenedor de imagen consta de seis palabras de cabecera y después las palabras mismas (La ISA RISC-V). Puede escribir uno sin ninguna toolchain. Este script de Python escribe los mismos 80 bytes que escribió pack más arriba:

import struct

text = [
    0x00200893,  # li   a7, 2      READ_INPUT
    0x00000513,  # li   a0, 0
    0x00000073,  # ecall           a0 = input 0
    0x00050293,  # mv   t0, a0
    0x00200893,  # li   a7, 2      READ_INPUT
    0x00100513,  # li   a0, 1
    0x00000073,  # ecall           a0 = input 1
    0x00a28333,  # add  t1, t0, a0
    0x00100893,  # li   a7, 1      WRITE_OUTPUT
    0x00000513,  # li   a0, 0      slot 0
    0x00030593,  # mv   a1, t1     word
    0x00000073,  # ecall
    0x00000893,  # li   a7, 0      HALT
    0x00000073,  # ecall
]
#          magic ("RAND")  version  text_base  n_text     data_base  n_data
header = [0x444E4152,      1,       0x1000,    len(text), 0,         0]
with open("add-hand.bin", "wb") as f:
    f.write(struct.pack(f"<{len(header) + len(text)}I", *header, *text))

rand-guest info add-hand.bin imprime el mismo hc 42986ac1…14bd, y cmp add-hand.bin add.bin no encuentra ninguna diferencia. Ejecute siempre rand-guest check sobre una imagen empaquetada a mano: el nodo rechaza cualquier palabra que no se decodifique, pero solo en el momento del despliegue.

La wallet también despliega dos formas más antiguas: un archivo .json { "base_pc", "words" } y un .bin sin formato de palabras little-endian cargadas en la base 0. El contenedor es la única forma con un segmento de datos.

Desplegar y llamar

Todos los caminos anteriores terminan igual. Con una wallet rand de fullnode v0.3 o posterior (en la cadena 20, la cadena activa, v0.6.8 o posterior: una wallet v0.6.7 no puede enviar allí, porque la cadena 20 vincula cada prueba y cada firma a su hash de génesis; véase Desplegar un programa en Rust):

rand program deploy add.bin
rand call <program id> --input 40 --input 2

La comisión de despliegue es 0.001 RAND + 0.0001 RAND × words: 0.0024 RAND para las 14 palabras de add.bin y 0.0035 RAND para las 25 de c-fib.bin. Ambos están por debajo de 4,096 palabras, de modo que caben en el límite de cualquier cadena: el predeterminado de 4,096 y el de 65,535 de la cadena 20. La llamada se prueba localmente en el nivel 10. En la cadena 15 costó 0.002 RAND. En la cadena 20 la comisión ya no es 0.001 + call_fee(tier): el mínimo es BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, rand call imprime gas: <exact> (declaring <limit>, tier <t>) y la comisión antes de generar la prueba, y cuatro llamadas medidas en la cadena 18 costaron de 0.0057 a 0.0075 RAND cada una (Programas de ejemplo). Cada despliegue y cada llamada en la cadena 20 llevan además dos pruebas del lado del bundle: una autorización de gasto de nivel 10 y el bundle de nivel 14. Desplegar un programa en Rust describe lo que hace cada comando.

Para que otros puedan comprobar lo que usted desplegó, publique el código fuente y su commit de circuits (aeacf31 para las cadenas 18 a 20). Con clang 23.1.1 y Rust 1.98.1, rand-guest build da el mismo hc en cualquier máquina.

Verificado por última vez con el commit c0878c3f del fullnode (cadena 21) el 2026-10-12, y circuits `7ef3220` (`rand-guest/README.md`, `guests-compiled/c-fib`, `guest-sdk/guest.ld`); every `rand-guest` output on this page was produced on 2026-09-19, circuits `aeacf31` (`rand-guest/guest.h`, `rand-guest/src/check.rs`; `git diff 7ef3220 aeacf31 -- guest-sdk rand-guest`), read on 2026-09-30; no output on this page was re-run at that commit, fullnode tag v0.6.8 `c9c9bd3c` (`docs/deploy.md` on `binding_domain`, `.github/workflows/ci.yml` circuits pin) and `rand_getLimits` on chain 20, read on 2026-10-01; nothing on this page was deployed or called on chain 20, fullnode tag v0.8.0 `c0878c3f` (`CHANGELOG.md`, `docs/zkvm.md` §8a) and `rand_getLimits` on chain 21, read on 2026-10-12; nothing on this page was deployed or called on chain 21. Cuando el código y esta página no coinciden, el código tiene razón; avísenos.