Jedes Programm, das RV32IM-Maschinencode ist, kann deployt werden, unabhängig davon, womit es erzeugt wurde. Diese Seite zeigt zwei Wege: C über rand-guest build --lang c und ein Programm, das Sie in Assembler schreiben und selbst packen. Beide enden im selben Image-Container, den Ein Rust-Programm deployen erzeugt, und beide werden mit rand program deploy deployt. Richten Sie zuerst den circuits-Checkout und rand-guest ein, wie in Schritt 1 jener Seite. Für die Chains 18 bis 20 liegt der Checkout auf Commit aeacf31, den fullnode v0.6.7 und v0.6.8 beide festlegen. Die folgenden Ausgaben entstanden am 2026-09-19 auf 7ef3220 und wurden nicht erneut ausgeführt. Das Verzeichnis rand-guest, einschließlich guest.h und rt.c, ist in beiden Commits identisch.
C über clang
Was Sie brauchen
| Werkzeug | Version | Bezug |
|---|---|---|
| clang | 23.1.1, mit RISC-V-Backend | brew install llvm unter macOS; Apples System-clang hat kein RISC-V-Backend. Oder setzen Sie $CLANG auf eine 23.1.1-Binärdatei |
rust-lld | aus Rust 1.98.1 | rustup +1.98.1 component add llvm-tools |
rand-guest weist jedes clang zurück, dessen Version nicht 23.1.1 ist. RAND_GUEST_CLANG_UNPINNED=1 baut mit einem anderen, mit einer Warnung, dass hc nicht mit veröffentlichten Images übereinstimmt.
Den Guest schreiben
Ein C-Guest-Verzeichnis enthält nur .c-Dateien (und optional eine .ld). rand-guest liefert guest.h, _start und eine kleine Laufzeit mit. Fügen Sie weder eine eigene guest.h noch eine start.S hinzu: build --lang c weist ein Verzeichnis zurück, das eine von beiden enthält.
Dies ist die eingecheckte Datei guests-compiled/c-fib/fib.c:
#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 deklariert rand_read_input, rand_read_public, rand_write_output, rand_poseidon2, rand_keccak, rand_sha256_compress und rand_halt. Es hat keinen Wrapper für Syscall 7, die längengebundene POSEIDON2_LEN, die die Maschine seit Constraint-Set 7 ausführt, und rand-guest check bei aeacf31 weist ein ecall mit a7 = 7 zurück (zkp-circuits Issue #1). Ein C- oder Assembler-Guest für Chain 20 verwendet daher die Syscalls 0 bis 6.
Bauen, ausführen, untersuchen
$ 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, dieselbe Antwort wie beim Rust-Guest fib.
build --lang c kompiliert jede .c-Datei im Verzeichnis (in sortierter Reihenfolge) sowie die mitgelieferten start.S und rt.c mit:
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
und linkt sie mit rust-lld -flavor gnu --gc-sections -T <ld> aus der festgelegten Rust-Toolchain. --gc-sections behält nur, was der Guest aufruft.
Die C-Laufzeit
-ffreestanding -fno-builtin hindern clang daran, Bibliotheksfunktionen zu erkennen, aber nicht daran, Aufrufe dorthin zu erzeugen. Eine Struct-Kopie wird zu memcpy, ein großer Null-Initialisierer wird zu memset, und eine 64-Bit-Division durch einen Laufzeitwert wird zu einem Hilfsaufruf, da RV32IM nur 32-Bit-Werte dividiert. rt.c definiert diese acht:
| Funktion | Verwendung |
|---|---|
memcpy, memmove, memset, memcmp | Kopieren, Füllen, Vergleichen |
__udivdi3, __umoddi3, __divdi3, __moddi3 | 64-Bit-/ und % |
Sie sind Byte-Schleifen und Shift-Subtract-Division. Sie sind langsam: Kopieren und dividieren Sie in einer heißen Schleife in 32-Bit-Wörtern.
Ein handgeschriebenes Programm
Sie können den Maschinencode auch selbst schreiben: assemblieren, mit demselben Linker-Skript linken und die ELF-Datei packen. Dieses Programm addiert zwei private Eingaben und veröffentlicht die Summe.
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
Setzen Sie jede Syscall-Nummer mit einem li a7, n unmittelbar vor dem zugehörigen ecall. Der Checker kann die Nummer dann verifizieren.
Assemblieren Sie mit dem festgelegten clang, linken Sie mit rust-lld und guest-sdk/guest.ld, und packen Sie dann. Führen Sie dies innerhalb des circuits-Checkouts aus:
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 übernimmt das .text der ELF-Datei als Text-Segment und jede andere allozierte PROGBITS-Sektion als ein Daten-Segment. .bss bleibt außen vor, weil der RAM bei null beginnt.
Den Container von Hand packen
Der Image-Container besteht aus sechs Header-Wörtern und danach den Wörtern selbst (Die RISC-V ISA). Sie können einen ganz ohne Toolchain schreiben. Dieses Python-Skript schreibt dieselben 80 Bytes, die pack oben geschrieben hat:
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 gibt dasselbe hc 42986ac1…14bd aus, und cmp add-hand.bin add.bin findet keinen Unterschied. Führen Sie rand-guest check immer auf einem von Hand gepackten Image aus: Der Node weist jedes Wort zurück, das sich nicht dekodieren lässt, aber erst beim Deploy.
Die Wallet deployt außerdem zwei ältere Formen: eine .json-Datei { "base_pc", "words" } und eine rohe .bin aus Little-Endian-Wörtern, die an der Basis 0 geladen wird. Nur der Container hat ein Daten-Segment.
Deployen und aufrufen
Jeder der obigen Wege endet gleich. Mit einer rand-Wallet aus fullnode v0.3 oder neuer (auf Chain 20, der Live-Chain, v0.6.8 oder neuer: Eine v0.6.7-Wallet kann dort nicht senden, weil Chain 20 jeden Beweis und jede Signatur an ihren Genesis-Hash bindet; siehe Ein Rust-Programm deployen):
rand program deploy add.bin
rand call <program id> --input 40 --input 2
Die Deploy-Gebühr beträgt 0.001 RAND + 0.0001 RAND × words: 0.0024 RAND für die 14 Wörter von add.bin und 0.0035 RAND für die 25 von c-fib.bin. Beide liegen unter 4,096 Wörtern und passen daher in das Limit jeder Chain: das Standardlimit von 4,096 und das von Chain 20 mit 65,535. Der Aufruf wird lokal auf Tier 10 bewiesen. Auf Chain 15 kostete er 0.002 RAND. Auf Chain 20 ist die Gebühr nicht mehr 0.001 + call_fee(tier): Die Untergrenze ist BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉, rand call gibt vor dem Beweisen gas: <exact> (declaring <limit>, tier <t>) und die Gebühr aus, und vier auf Chain 18 gemessene Aufrufe kosteten je 0.0057 bis 0.0075 RAND (Beispielprogramme). Jedes Deploy und jeder Aufruf auf Chain 20 trägt außerdem zwei bundle-seitige Beweise: eine Tier-10-Ausgabeautorisierung und das Tier-14-Bundle. Ein Rust-Programm deployen beschreibt, was jeder Befehl tut.
Damit andere prüfen können, was Sie deployt haben, veröffentlichen Sie den Quellcode und Ihren circuits-Commit (aeacf31 für die Chains 18 bis 20). Mit clang 23.1.1 und Rust 1.98.1 liefert rand-guest build auf jeder Maschine dasselbe hc.