Rand Protocol
Documentação
Programas RISC-V

Implantar um programa RISC-V

Dois caminhos para uma imagem implantável sem Rust: um programa em C compilado pelo rand-guest com o clang fixado, e um programa escrito à mão em assembly, montado e empacotado, ou empacotado byte a byte no contêiner de imagem.

Qualquer programa que seja código de máquina RV32IM pode ser implantado, seja qual for a ferramenta que o produziu. Esta página mostra dois caminhos: C por meio de rand-guest build --lang c, e um programa que você escreve em assembly e empacota por conta própria. Ambos terminam no mesmo contêiner de imagem que Deploy a Rust program produz, e ambos são implantados com rand program deploy. Configure primeiro o checkout dos circuits e o rand-guest, como no passo 1 dessa página. Para as chains 18 a 20, o checkout está no commit aeacf31, que o fullnode v0.6.7 e o v0.6.8 fixam ambos. As saídas abaixo foram produzidas em 2026-09-19 no 7ef3220 e não foram executadas de novo. O diretório rand-guest, incluindo guest.h e rt.c, é o mesmo nos dois commits.

C via clang

O que você precisa

ferramentaversãocomo
clang23.1.1, com um backend RISC-Vbrew install llvm no macOS; o clang do sistema da Apple não tem backend RISC-V. Ou defina $CLANG como um binário 23.1.1
rust-llddo Rust 1.98.1rustup +1.98.1 component add llvm-tools

O rand-guest recusa qualquer clang cuja versão não seja 23.1.1. RAND_GUEST_CLANG_UNPINNED=1 compila com outro, com um aviso de que o hc não corresponderá às imagens publicadas.

Escreva o guest

Um diretório de guest em C contém apenas arquivos .c (e um .ld opcional). O rand-guest fornece guest.h, _start e um pequeno runtime. Não adicione o seu próprio guest.h nem start.S: build --lang c recusa um diretório que tenha qualquer um dos dois.

Este é o guests-compiled/c-fib/fib.c versionado:

#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 e rand_halt. Não tem wrapper para a syscall 7, o POSEIDON2_LEN com limite de comprimento que a máquina executa desde o constraint set 7, e o rand-guest check no aeacf31 recusa um ecall com a7 = 7 (zkp-circuits issue #1). Um guest em C ou assembly para a chain 20 usa, portanto, as syscalls 0 a 6.

Compilar, executar, inspecionar

$ 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, a mesma resposta do guest fib em Rust.

build --lang c compila todos os arquivos .c do diretório (em ordem alfabética), mais o start.S e o rt.c embutidos, com:

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

e os vincula com rust-lld -flavor gnu --gc-sections -T <ld>, da toolchain Rust fixada. --gc-sections mantém apenas o que o guest chama.

O runtime de C

-ffreestanding -fno-builtin impedem o clang de reconhecer funções de biblioteca, mas não de emitir chamadas a elas. Uma cópia de struct vira memcpy, um inicializador zero grande vira memset, e uma divisão de 64 bits por um valor em tempo de execução vira uma chamada a uma função auxiliar, porque o RV32IM divide apenas valores de 32 bits. O rt.c define estas oito:

funçãousada para
memcpy, memmove, memset, memcmpcópias, preenchimentos, comparações
__udivdi3, __umoddi3, __divdi3, __moddi3/ e % de 64 bits

São laços de bytes e divisão por deslocamento e subtração. São lentas: em um laço quente, copie e divida em palavras de 32 bits.

Um programa escrito à mão

Você também pode escrever o código de máquina por conta própria: montá-lo, vinculá-lo com o mesmo linker script e empacotar o ELF. Este programa soma duas entradas privadas e publica a soma.

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 com um li a7, n imediatamente antes do respectivo ecall. Assim, o verificador consegue conferir o número.

Monte com o clang fixado, vincule com rust-lld e guest-sdk/guest.ld, e então empacote. Execute de dentro do checkout dos 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 o .text do ELF como o segmento de texto e toda outra seção PROGBITS alocada como um único segmento de dados. .bss fica de fora, porque a RAM começa zerada.

Empacotando o contêiner à mão

O contêiner de imagem é composto por seis palavras de cabeçalho e depois pelas próprias palavras (The RISC-V ISA). Você pode escrever um sem nenhuma toolchain. Este script em Python escreve os mesmos 80 bytes que o pack escreveu acima:

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 o mesmo hc 42986ac1…14bd, e cmp add-hand.bin add.bin não encontra nenhuma diferença. Execute sempre rand-guest check em uma imagem empacotada à mão: o nó recusa qualquer palavra que não decodifique, mas só no momento do deploy.

A carteira também implanta duas formas mais antigas: um arquivo .json { "base_pc", "words" } e um .bin bruto de palavras little-endian carregadas na base 0. O contêiner é a única forma com segmento de dados.

Deploy e chamada

Todos os caminhos acima terminam da mesma maneira. Com uma carteira rand do fullnode v0.3 ou posterior (na chain 20, a chain ativa, v0.6.8 ou posterior: uma carteira v0.6.7 não consegue enviar lá, porque a chain 20 vincula cada prova e cada assinatura ao seu hash de gênese; veja Deploy a Rust program):

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

A taxa de deploy é 0.001 RAND + 0.0001 RAND × words: 0,0024 RAND para as 14 palavras de add.bin e 0,0035 RAND para as 25 de c-fib.bin. Ambos ficam abaixo de 4.096 palavras, portanto cabem no limite de qualquer chain: o padrão de 4.096 e o de 65.535 da chain 20. A chamada é provada localmente no tier 10. Na chain 15 custou 0,002 RAND. Na chain 20 a taxa já não é 0.001 + call_fee(tier): o piso é BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, o rand call imprime gas: <exact> (declaring <limit>, tier <t>) e a taxa antes de provar, e quatro chamadas medidas na chain 18 custaram de 0,0057 a 0,0075 RAND cada (Example programs). Todo deploy e toda chamada na chain 20 também carregam duas provas do lado do bundle, uma autorização de gasto no tier 10 e o bundle no tier 14. Deploy a Rust program descreve o que cada comando faz.

Para que outros possam verificar o que você implantou, publique o código-fonte e o seu commit dos circuits (aeacf31 para as chains 18 a 20). Com o clang 23.1.1 e o Rust 1.98.1, o rand-guest build produz o mesmo hc em qualquer máquina.

Última verificação em relação ao commit c0878c3f do fullnode (chain 21) em 2026-10-12, e 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. Onde o código e esta página divergirem, o código está certo; avise-nos.