Diese Seite führt ein kleines Rust-Programm vom Quellcode bis zu einem auf Rand bereitgestellten Programm. Jeder rand-guest-Befehl unten wurde ausgeführt, und seine Ausgabe ist eingefügt, nicht zusammengefasst. Die Läufe fanden am 2026-09-19 beim Circuits-Commit 7ef3220 statt. Das testnet ist inzwischen chain 20. Dessen Toolchain ist, wie die der chains 18 und 19, bei aeacf31 gebaut (fullnode v0.6.8 pinnt denselben Commit wie v0.6.7), und die Ausgaben wurden dort nicht erneut erzeugt. Die RISC-V ISA erklärt die Maschine, die Syscalls und das Dateiformat.
1. Die Toolchain installieren
hc hängt vom exakten Compiler ab, deshalb ist die Toolchain gepinnt.
| Werkzeug | Version | Zweck |
|---|---|---|
| Rust | 1.98.1 | kompiliert das Gastprogramm und rand-guest selbst |
das Target riscv32im-unknown-none-elf | für 1.98.1 | das Target des Gastprogramms |
| ein Circuits-Checkout | randprotocol/zkp-circuits bei aeacf31 für chains 18 bis 20 | enthält rand-guest, guest-sdk und das Linker-Skript |
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits, what fullnode v0.6.7 and v0.6.8 pin
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"
rand-guest hat fünf Unterbefehle: build, check, pack, run und info.
Der Commit ist wichtig, weil hc eine Funktion aus Quellcode und Toolchain ist: Ein Gastprogramm, das gegen einen anderen Commit von guest-sdk neu gebaut wird, kann ein anderes hc erhalten. Zwischen 7ef3220 und aeacf31 blieb das Verzeichnis rand-guest unverändert, guest-sdk/src/lib.rs hingegen nicht (Schritt 2).
2. Das Gastprogramm schreiben
Ein Gastprogramm ist ein no_std-, no_main-Binary-Crate, das von guest-sdk abhängt. Es muss innerhalb des Circuits-Checkouts liegen: rand-guest build findet den Checkout, indem es oberhalb des Verzeichnisses des Gastprogramms nach guest-sdk/ sucht, und lehnt ein Gastprogramm außerhalb eines Checkouts ab.
Dieses Gastprogramm liest einen Schwellenwert, eine Anzahl und so viele private Salden. Es veröffentlicht 1 und den Überschuss, wenn die Salden den Schwellenwert decken. Die Salden veröffentlicht es nie.
circuits/
guest-sdk/
rand-guest/
my-guests/
threshold/
Cargo.toml
src/main.rs
my-guests/threshold/Cargo.toml:
[package]
name = "threshold"
version = "0.1.0"
edition = "2021"
[dependencies]
guest-sdk = { path = "../../guest-sdk" }
[profile.release]
opt-level = 3
lto = true
panic = "abort"
codegen-units = 1
[workspace]
Das leere [workspace] hält das Crate aus jedem übergeordneten Workspace heraus, so wie es das eingecheckte Gastprogramm fib tut.
my-guests/threshold/src/main.rs:
#![no_std]
#![no_main]
use guest_sdk::{halt, read_input, write_output};
/// Input 0 is a threshold, input 1 a count n, inputs 2..2+n are private balances.
/// Output 0 is 1 if the balances sum to at least the threshold, else 0.
/// Output 1 is the surplus when they do. The balances themselves are never published.
#[no_mangle]
pub extern "C" fn main() -> ! {
let threshold = read_input(0);
let n = read_input(1);
let mut sum: u32 = 0;
for i in 0..n {
sum = sum.wrapping_add(read_input(2 + i));
}
if sum >= threshold {
write_output(0, 1);
write_output(1, sum - threshold);
}
halt();
}
guest-sdk exportiert read_input, read_public, write_output, poseidon2, keccak, keccak256, sha256_compress, sha256 und halt und stellt _start bereit, das den Stack-Pointer setzt und main aufruft. Es gibt keinen Allocator und keine Gleitkommaarithmetik.
Bei aeacf31 unterscheiden sich drei dieser Signaturen von 7ef3220, und es gibt einen neuen Export:
| Funktion | bei 7ef3220 | bei aeacf31 |
|---|---|---|
keccak | fn keccak(ptr: *mut u32) | fn keccak(state: &mut [u32; 50]) |
sha256_compress | fn sha256_compress(ptr: *mut u32) | fn sha256_compress(buf: &mut [u32; 24]) |
poseidon2 | fn poseidon2(ptr: *mut u32, n: usize) | unsafe fn poseidon2(ptr: *mut u32, n: usize) |
poseidon2_len | nicht vorhanden | fn poseidon2_len(buf: &mut [u32], n: usize) -> [u32; 8], Syscall 7 |
poseidon2_len bindet die Nachrichtenlänge in den Sponge ein und ist der sichere Aufruf. rand-guest check bei aeacf31 listet weiterhin nur die Syscalls 0 bis 6, sodass ein Gastprogramm, das ihn aufruft, zu einem Image gebaut wird, das build mit ecall with a7 = 7, which is no syscall the machine implements ablehnt (zkp-circuits issue #1). Bis das behoben ist, hasht ein Gastprogramm mit unsafe { poseidon2(ptr, n) } über Nachrichten fester Länge mit Tag. Das Gastprogramm auf dieser Seite verwendet keine der vier Funktionen.
Regeln für das Verzeichnis des Gastprogramms:
- Keine
.cargo/configoder.cargo/config.tomldarin, in keinem übergeordneten Verzeichnis und nicht in$CARGO_HOME.buildverweigert den Dienst und nennt jede Datei, weil cargo sie in den Build einmischen würde. - Keine eigenen Compiler-Flags.
buildsetzt sie alle: das Linker-Skript,-unaligned-scalar-memund eine Pfad-Umschreibung, die das Image unabhängig davon macht, wo der Checkout liegt. - Höchstens eine
.ld-Datei.buildverwendet die eine.ldim Verzeichnis des Gastprogramms oderguest-sdk/guest.ld, falls keine vorhanden ist. Bei mehr als einer benennen Sie sie mit--ld.
3. Bauen und prüfen
$ rand-guest build my-guests/threshold --out threshold.bin
Compiling guest-sdk v0.1.0 (…/circuits/guest-sdk)
warning: unstable feature specified for `-Ctarget-feature`: `unaligned-scalar-mem`
|
= note: this feature is not stably supported; its behavior can change in the future
warning: `guest-sdk` (lib) generated 1 warning
Compiling threshold v0.1.0 (…/circuits/my-guests/threshold)
warning: `threshold` (bin "threshold") generated 1 warning (1 duplicate)
Finished `release` profile [optimized] target(s) in 0.15s
40 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote threshold.bin and its .sha256 (40 words, hc a491df99f289d81e2b6e09849a850b14e36e2bd6edc5f5a6de46c3d67c703665, program id 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853)
Die Warnung zu unaligned-scalar-mem ist erwartet: Die Toolchain schaltet unausgerichtete Zugriffe absichtlich ab.
build kompiliert mit cargo +1.98.1 build --release --target riscv32im-unknown-none-elf, führt check auf dem Ergebnis aus und schreibt den Image-Container sowie einen .sha256-Pin. Lehnt check das Programm ab, schreibt build das Image trotzdem und beendet sich mit einem Wert ungleich null.
| Flag | Standard | Bedeutung |
|---|---|---|
--out <path> | <dir>/image.bin | wohin das Image geschrieben wird |
--max-words <n> | 4096 | die Obergrenze, gegen die check misst; geben Sie die Obergrenze der Ziel-chain an (65.535 auf chain 20) |
--ld <path> | die eine .ld im Verzeichnis, sonst guest-sdk/guest.ld | das Linker-Skript |
--lang rust|c | rust | der Compiler |
4. Im Emulator ausführen
Übergeben Sie private Eingaben mit --input, je ein Wort. --tier t meldet zusätzlich, ob der Lauf in Tier t passt.
$ rand-guest run threshold.bin --input 1000 --input 4 --input 400 --input 250 --input 300 --input 75 --tier 10
out[0] = 1
out[1] = 25
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 52
tier 10
tier 10: fits (cycles 66 of 1023, Poseidon2 permutations 14 of 128)
Die Salden summieren sich zu 1.025, also veröffentlicht das Programm 1 und einen Überschuss von 25. Bei den Salden 400 und 250 reicht die Summe nicht, und jede Ausgabe ist null. Ein Trap gibt trap: <error> aus und beendet sich mit 2.
5. Das Image untersuchen
$ rand-guest info threshold.bin
form: image container
text 40 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 40 words from base_pc 0x1000
hc a491df99f289d81e2b6e09849a850b14e36e2bd6edc5f5a6de46c3d67c703665
program id 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853
40 words against a cap of 4096: fits
Gegen hc wird der Beweis eines Aufrufs verifiziert. Die Programm-ID ist das, unter dem Sie bereitstellen und aufrufen.
6. Bereitstellen
Verwenden Sie die rand-Wallet aus fullnode v0.3 oder neuer; ältere Wallets lesen den Image-Container nicht. Seit chain 14 wird jeder Bundle-Beweis gegen das Binding seiner Transaktion verifiziert, sodass eine Wallet, die älter als der chain-14-Build (b3c594c) ist, ein Bundle beweist, das die chain ablehnt. Verwenden Sie auf chain 20, der aktiven chain, das rand aus fullnode v0.6.8 oder neuer (die Flotte läuft mit v0.6.8, und ein unter Linux gebautes rand hängt an dessen GitHub-Release). Eine v0.6.7-Wallet kann auf chain 20 nicht senden: Der Genesis von chain 20 setzt binding_domain: 1, sodass jedes Proof-Binding und jede signierte Nachricht den Genesis-Hash einschließt, während eine v0.6.7-Wallet nur die chain-ID bindet. Der öffentliche Endpunkt https://rpc.randprotocol.org genügt, um die Wallet zu synchronisieren, bereitzustellen und aufzurufen (Einrichtung).
rand fee deploy 40 # the floor for a 40-word program
rand program deploy threshold.bin
program deploy gibt die Programm-ID, die Wortanzahl und hc aus. Dann fragt es den Node mit rand_estimateFee nach der Deploy-Gebühr, was ein Programm über der Obergrenze der chain vor jedem Beweisen ablehnt. Anschließend beweist es ein Selbstüberweisungs-Bundle ohne Wert, das die Gebühr zahlt, übermittelt es und wartet auf den Commit. Das Bundle ist das eine 4-in-4-out-Bundle mit verborgenem Asset, das jede Transaktion trägt. Seit chain 17 besteht es aus zwei Beweisen, und die Wallet gibt für jeden eine Zeile aus: eine tier-10-Spend-Autorisierung (Sekunden, etwa 1,37 MB) und das tier-14-Bundle (etwa zwei Minuten auf einem Laptop, etwa 1,5 MB). Auf chain 15 war es ein einziger tier-14-Beweis von etwa 100 s und etwa 1,42 MB. Eine anonyme Abstimmung zeigt ein Deploy auf chain 18, wie die Wallet es ausgab.
Die Deploy-Gebühr beträgt 0.001 RAND + 0.0001 RAND × words, auf chain 20 wie zuvor. Gas bepreist Aufrufe, nicht Deploys:
| Programm | Wörter | Deploy-Gebühr |
|---|---|---|
threshold | 40 | 0,005 RAND |
| ein Programm an der Standardobergrenze (chain 12) | 4.096 | 0,4106 RAND |
| das größte Programm, das chain 20 ohne öffentliches Segment bereitstellt | 8.180 | 0,819 RAND |
| ein Programm an der Obergrenze von 65.535 Wörtern (chains 13 bis 15) | 65.535 | 6,5545 RAND |
threshold hat weniger als 4.096 Wörter und passt damit in die Standardobergrenze und in die 65.535 von chain 20. Ein Programm mit mehr als 4.096 Wörtern braucht eine chain, deren Genesis max_program_words mindestens so hoch setzt, etwa chain 20 (Die RISC-V ISA). Chain 20 hat, wie chain 19, eine zweite, niedrigere Grenze: Sie lehnt es ab, ein Programm bereitzustellen, das kein tier-14-Aufruf fassen könnte (ProgramUncallable); das sind mehr als 8.180 Wörter ohne öffentliches Segment und weniger mit einem. rand program deploy --input <word> … (oder --check-call für einen Aufruf ohne Eingaben) lässt den Aufruf, den Sie im Sinn haben, zuerst durch den Emulator laufen und verweigert das Deploy, wenn er nicht bei Tier 14 oder darunter landet.
Prüfen Sie den Eintrag:
rand program show 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853
Sein code_hash ist das hc, das rand-guest ausgegeben hat, nur anders geschrieben. rand-guest gibt jedes der acht Wörter des Digests Big-Endian aus, während die Wallet, rand program show und rand_getProgram die Bytes jedes Worts Little-Endian ausgeben. So erscheint a491df99…3665 als 99df91a4…707c: Jede Gruppe aus acht Hex-Ziffern ist byteweise umgekehrt. Die Programm-ID wird von beiden gleich geschrieben.
7. Aufrufen
rand call 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853 \
--input 1000 --input 4 --input 400 --input 250 --input 300 --input 75
Die Wallet holt den Code, führt den Aufruf im Emulator aus und gibt gas: <exact> (declaring <limit>, tier <t>) sowie die Gebühr aus, bevor sie irgendetwas beweist. Dann versiegelt sie das Eingabe-Transkript, beweist den Lauf lokal, zahlt die Gebühr über ein Bundle und gibt die Quittung aus. Auf chain 20 sind das drei Beweise: der Aufruf, die tier-10-Spend-Autorisierung und das tier-14-Bundle.
Die Gebühr auf Chain 20 ist nicht mehr 0.001 + call_fee(tier), was diesen Aufruf auf Chain 15 zu 0.002 RAND machte. Die Untergrenze lautet BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉: 0.001 RAND, zuzüglich des deklarierten Limits zum Gaspreis (100 Einheiten im Genesis, eine Einheit entspricht 10⁻⁹ RAND), zuzüglich der Proof- und Envelope-Bytes zum Byte-Preis (800 Einheiten pro KiB im Genesis). Auf Chain 20 können die beiden Preise nicht über 10,000 bzw. 80,000 Einheiten steigen. Das exakte Gas bleibt privat. Standardmäßig deklariert die Wallet es aufgerundet auf ein Vielfaches von 2^(tier − 2), setzt den Proof mit der Obergrenze der Chain von 4,194,304 Bytes an, weil die Gebühr festgelegt wird, bevor der Proof existiert, und fügt zwei Preisstufen als Reserve hinzu. Vier gemessene Aufrufe auf Chain 18 kosteten jeweils 0.0057 bis 0.0075 RAND (Example programs). Dieser Aufruf wurde auf den Chains 18 bis 20 nicht ausgeführt, daher wird dafür keine Zahl angegeben.
Die Chain sieht den Tier, das deklarierte Gas-Limit, H_IN und die acht Ausgabewörter [1, 25, 0, 0, 0, 0, 0, 0]. Die Kontostände sieht sie nie.
Programs behandelt Receipts, Input-Envelopes und rand open-call. Ein Call wie dieser ist zustandslos; auf Chain 20 kann ein Programm über einen Invoke außerdem Zellen und einen Vault führen (Program state (RPL-2)).
Andere Ihren hc prüfen lassen
Der Build ist reproduzierbar. build bereinigt die Umgebung des Compilers (RUSTFLAGS, CARGO_*-Überschreibungen, RUSTC-Wrapper und mehr), lehnt Cargo-Konfigurationsdateien ab und bildet den Pfad des Checkouts auf /rand-circuits ab. Derselbe Quellcode ergibt mit Rust 1.98.1 auf jeder Maschine denselben hc. Damit andere nachvollziehen können, was Sie deployt haben, veröffentlichen Sie den Quellcode des Guests und Ihren Circuits-Commit (aeacf31 für einen Guest, der für die Chains 18 bis 20 gebaut wurde). Jeder kann dann neu bauen und hc mit dem code_hash von rand_getProgram vergleichen. Die Chain speichert nur die Wörter, nicht den Quellcode.
Fehlerbehebung
| message | cause |
|---|---|
… is not inside a circuits checkout (no guest-sdk/ above it) | Verschieben Sie das Guest-Verzeichnis in den Checkout |
build names a .cargo/config.toml | Löschen oder verschieben Sie sie; sie lässt sich nicht bereinigen |
REJECTED with Cap | Das Programm überschreitet --max-words; erhöhen Sie den Wert auf die Obergrenze der Ziel-Chain oder verkleinern Sie das Programm (opt-level = "s") |
REJECTED with Syscall and a7 = 7 | Der Guest ruft poseidon2_len auf; rand-guest check bei aeacf31 kennt Syscall 7 noch nicht (zkp-circuits issue #1) |
REJECTED with Fence, Csr or Ebreak | Der Build hat eine Instruktion erzeugt, die die Maschine nicht kennt; siehe what is refused |
trap: … from run | Ein Laufzeitfehler, etwa eine nicht ausgerichtete Adresse oder eine nicht implementierte Syscall-Nummer |
run says the run fits no tier | Der Lauf überschreitet 2²⁰ Zeilen oder sein Poseidon2-Budget; er kann nicht bewiesen werden |