Halaman ini membawa sebuah program Rust kecil dari kode sumber hingga menjadi program yang ter-deploy di Rand. Setiap perintah rand-guest di bawah ini telah dijalankan, dan keluarannya ditempelkan apa adanya, bukan diringkas. Proses dijalankan pada 2026-09-19 pada commit circuits 7ef3220. Testnet kini berada di chain 20, yang toolchain-nya, seperti pada chain 18 dan 19, dibangun pada aeacf31 (fullnode v0.6.8 mengunci commit yang sama dengan v0.6.7), dan keluarannya belum dijalankan ulang di sana. RISC-V ISA menjelaskan mesinnya, syscall, dan format berkasnya.
1. Pasang toolchain
hc bergantung pada kompiler yang persis sama, sehingga toolchain dikunci pada versi tertentu.
| alat | versi | alasan |
|---|---|---|
| Rust | 1.98.1 | mengompilasi guest dan rand-guest itu sendiri |
target riscv32im-unknown-none-elf | untuk 1.98.1 | target guest |
| checkout circuits | randprotocol/zkp-circuits pada aeacf31 untuk chain 18 hingga 20 | memuat rand-guest, guest-sdk, dan linker script |
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 memiliki lima subperintah: build, check, pack, run, dan info.
Commit ini penting karena hc adalah fungsi dari kode sumber dan toolchain: guest yang dibangun ulang terhadap commit guest-sdk yang lain dapat menghasilkan hc yang berbeda. Antara 7ef3220 dan aeacf31, direktori rand-guest tidak berubah, sedangkan guest-sdk/src/lib.rs berubah (langkah 2).
2. Tulis guest
Guest adalah crate biner no_std, no_main yang bergantung pada guest-sdk. Crate ini harus berada di dalam checkout circuits: rand-guest build menemukan checkout dengan mencari guest-sdk/ di atas direktori guest, dan menolak guest yang berada di luarnya.
Guest ini membaca sebuah ambang batas, sebuah jumlah, dan sebanyak jumlah itu saldo privat. Guest ini memublikasikan 1 beserta selisih lebihnya jika saldo-saldo tersebut mencukupi ambang batas. Guest ini tidak pernah memublikasikan saldonya.
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]
[workspace] yang kosong menjaga crate ini tetap berada di luar workspace induk mana pun, seperti pada guest fib yang sudah di-commit.
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 mengekspor read_input, read_public, write_output, poseidon2, keccak, keccak256, sha256_compress, sha256, dan halt, serta menyediakan _start, yang mengatur stack pointer dan memanggil main. Tidak ada alokator dan tidak ada bilangan floating point.
Pada aeacf31, tiga dari signature tersebut berbeda dari 7ef3220, dan ada satu ekspor baru:
| fungsi | pada 7ef3220 | pada 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 | tidak ada | fn poseidon2_len(buf: &mut [u32], n: usize) -> [u32; 8], syscall 7 |
poseidon2_len mengikat panjang pesan ke dalam sponge dan merupakan panggilan yang aman. rand-guest check pada aeacf31 masih hanya mencantumkan syscall 0 hingga 6, sehingga guest yang memanggilnya akan menghasilkan image yang ditolak oleh build dengan pesan ecall with a7 = 7, which is no syscall the machine implements (zkp-circuits issue #1). Sampai hal ini diperbaiki, guest melakukan hashing dengan unsafe { poseidon2(ptr, n) } pada pesan bertag dengan panjang tetap. Guest pada halaman ini tidak menggunakan satu pun dari keempatnya.
Aturan untuk direktori guest:
- Tidak ada
.cargo/configatau.cargo/config.tomldi dalamnya, di direktori induk mana pun, maupun di$CARGO_HOME.buildmenolak dan menyebutkan setiap berkas tersebut, karena cargo akan menggabungkannya ke dalam proses build. - Tidak ada flag kompiler tambahan dari Anda.
buildmengatur semuanya: linker script,-unaligned-scalar-mem, dan pemetaan ulang path yang membuat image tidak bergantung pada lokasi checkout. - Paling banyak satu berkas
.ld.buildmenggunakan satu-satunya berkas.lddi direktori guest, atauguest-sdk/guest.ldjika tidak ada. Jika ada lebih dari satu, tentukan dengan--ld.
3. Build dan periksa
$ 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)
Peringatan unaligned-scalar-mem memang diharapkan: toolchain sengaja menonaktifkan akses tak sejajar.
build mengompilasi dengan cargo +1.98.1 build --release --target riscv32im-unknown-none-elf, menjalankan check pada hasilnya, lalu menulis container image beserta pin .sha256. Jika check menolak program, build tetap menulis image dan keluar dengan kode bukan nol.
| flag | bawaan | arti |
|---|---|---|
--out <path> | <dir>/image.bin | lokasi penyimpanan image |
--max-words <n> | 4096 | batas yang menjadi acuan check; berikan batas milik chain tujuan (65,535 pada chain 20) |
--ld <path> | satu-satunya .ld di direktori, jika tidak ada maka guest-sdk/guest.ld | linker script |
--lang rust|c | rust | kompiler |
4. Jalankan pada emulator
Berikan input privat dengan --input, satu word untuk setiap input. --tier t juga melaporkan apakah eksekusi muat dalam tingkat t.
$ 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)
Saldo-saldo tersebut berjumlah 1,025, sehingga program memublikasikan 1 dan selisih lebih sebesar 25. Dengan saldo 400 dan 250, jumlahnya kurang, dan setiap output bernilai nol. Sebuah trap mencetak trap: <error> dan keluar dengan kode 2.
5. Periksa image
$ 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
hc adalah nilai yang menjadi acuan verifikasi bukti sebuah panggilan. ID program adalah yang Anda deploy dan panggil.
6. Deploy
Gunakan wallet rand dari fullnode v0.3 atau yang lebih baru; wallet yang lebih lama tidak dapat membaca container image. Sejak chain 14, setiap bukti bundel diverifikasi terhadap binding transaksinya, sehingga wallet yang lebih lama daripada build chain 14 (b3c594c) membuktikan bundel yang ditolak oleh chain. Pada chain 20, chain yang aktif, gunakan rand dari fullnode v0.6.8 atau yang lebih baru (fleet menjalankan v0.6.8, dan rand yang dibangun di Linux dilampirkan pada rilis GitHub-nya). Wallet v0.6.7 tidak dapat mengirim di chain 20: genesis chain 20 menetapkan binding_domain: 1, sehingga setiap binding bukti dan setiap pesan yang ditandatangani menyertakan hash genesis, sedangkan wallet v0.6.7 hanya mengikat chain id. Endpoint publik https://rpc.randprotocol.org sudah cukup untuk menyinkronkan wallet, melakukan deploy, dan memanggil (penyiapan).
rand fee deploy 40 # the floor for a 40-word program
rand program deploy threshold.bin
program deploy mencetak ID program, jumlah word, dan hc. Kemudian perintah ini menanyakan biaya deploy kepada node dengan rand_estimateFee, yang menolak program yang melampaui batas chain sebelum proses pembuktian apa pun. Setelah itu, perintah ini membuktikan bundel transfer-diri bernilai nol yang membayar biaya tersebut, mengirimkannya, dan menunggu commit. Bundel tersebut adalah satu bundel aset tersembunyi 4-in-4-out yang dibawa setiap transaksi. Sejak chain 17, bundel ini terdiri dari dua bukti, dan wallet mencetak satu baris untuk masing-masing: otorisasi pengeluaran tingkat 10 (hitungan detik, sekitar 1.37 MB) dan bundel tingkat 14 (sekitar dua menit pada laptop, sekitar 1.5 MB). Pada chain 15, ini adalah satu bukti tingkat 14 sekitar 100 detik dan sekitar 1.42 MB. Pemungutan suara anonim menunjukkan deploy pada chain 18 sebagaimana dicetak oleh wallet.
Biaya deploy adalah 0.001 RAND + 0.0001 RAND × words, pada chain 20 sama seperti sebelumnya. Gas menentukan harga panggilan, bukan deploy:
| program | words | biaya deploy |
|---|---|---|
threshold | 40 | 0.005 RAND |
| program pada batas bawaan (chain 12) | 4,096 | 0.4106 RAND |
| program terbesar yang di-deploy chain 20 tanpa segmen publik | 8,180 | 0.819 RAND |
| program pada batas 65,535 word (chain 13 hingga 15) | 65,535 | 6.5545 RAND |
threshold berukuran kurang dari 4,096 word, sehingga muat dalam batas bawaan maupun 65,535 milik chain 20. Program yang melebihi 4,096 word membutuhkan chain yang genesis-nya menetapkan max_program_words setidaknya sebesar itu, seperti chain 20 (RISC-V ISA). Chain 20, seperti chain 19, memiliki batas kedua yang lebih rendah: chain menolak men-deploy program yang tidak dapat dimuat oleh panggilan tingkat 14 mana pun (ProgramUncallable), yaitu lebih dari 8,180 word tanpa segmen publik dan lebih sedikit jika ada segmen publik. rand program deploy --input <word> … (atau --check-call untuk panggilan tanpa input) menjalankan panggilan yang Anda maksudkan melalui emulator terlebih dahulu dan menolak men-deploy kecuali hasilnya berada pada atau di bawah tingkat 14.
Periksa catatannya:
rand program show 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853
code_hash-nya adalah hc yang dicetak rand-guest, dengan penulisan yang berbeda. rand-guest mencetak masing-masing dari delapan word digest dalam big-endian, sedangkan wallet, rand program show, dan rand_getProgram mencetak byte setiap word dalam little-endian. Jadi a491df99…3665 muncul sebagai 99df91a4…707c: setiap kelompok delapan digit heksadesimal dibalik urutan byte-nya. ID program dituliskan sama oleh keduanya.
7. Panggil
rand call 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853 \
--input 1000 --input 4 --input 400 --input 250 --input 300 --input 75
Wallet mengambil kode, menjalankan panggilan pada emulator, lalu mencetak gas: <exact> (declaring <limit>, tier <t>) dan biayanya sebelum membuktikan apa pun. Setelah itu, wallet menyegel transkrip input, membuktikan eksekusi secara lokal, membayar biaya melalui sebuah bundel, dan mencetak tanda terima. Pada chain 20, itu berarti tiga bukti: panggilan, otorisasi pengeluaran tingkat 10, dan bundel tingkat 14.
Biaya pada chain 20 tidak lagi 0.001 + call_fee(tier), yang membuat panggilan ini berbiaya 0,002 RAND pada chain 15. Batas bawahnya adalah BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉: 0,001 RAND, ditambah batas gas yang dideklarasikan pada harga gas (100 unit pada genesis, satu unit adalah 10⁻⁹ RAND), ditambah byte bukti dan amplop pada harga byte (800 unit per KiB pada genesis). Pada chain 20, kedua harga tersebut tidak dapat naik melampaui 10.000 dan 80.000 unit. Gas yang sebenarnya tetap privat. Secara bawaan, wallet mendeklarasikannya dengan pembulatan ke atas ke kelipatan 2^(tier − 2), menetapkan harga bukti pada batas 4.194.304 byte milik chain karena biaya ditetapkan sebelum bukti ada, dan menambahkan ruang cadangan sebesar dua langkah harga. Empat panggilan terukur pada chain 18 masing-masing berbiaya 0,0057 hingga 0,0075 RAND (Example programs). Panggilan ini belum pernah dilakukan pada chain 18 hingga 20, sehingga tidak ada angka yang diberikan untuknya.
Chain melihat tier, batas gas yang dideklarasikan, H_IN, dan delapan kata keluaran [1, 25, 0, 0, 0, 0, 0, 0]. Chain tidak pernah melihat saldo.
Programs membahas tanda terima, amplop masukan, dan rand open-call. Sebuah Call seperti ini tidak menyimpan status; pada chain 20, sebuah program juga dapat menyimpan cell dan vault melalui Invoke (Program state (RPL-2)).
Biarkan pihak lain memeriksa hc Anda
Build ini dapat direproduksi. build membersihkan lingkungan kompiler (RUSTFLAGS, penimpaan CARGO_*, pembungkus RUSTC, dan lainnya), menolak berkas konfigurasi cargo, dan memetakan ulang path checkout ke /rand-circuits. Source yang sama dengan Rust 1.98.1 menghasilkan hc yang sama pada mesin mana pun. Agar pihak lain dapat memastikan apa yang Anda deploy, publikasikan source guest dan commit circuits Anda (aeacf31 untuk guest yang dibangun bagi chain 18 hingga 20). Siapa pun kemudian dapat membangun ulang dan membandingkan hc dengan code_hash dari rand_getProgram. Chain hanya menyimpan kata-katanya, bukan source-nya.
Pemecahan masalah
| message | cause |
|---|---|
… is not inside a circuits checkout (no guest-sdk/ above it) | pindahkan direktori guest ke dalam checkout |
build menyebut .cargo/config.toml | hapus atau pindahkan berkas tersebut; berkas itu tidak dapat dibersihkan |
REJECTED dengan Cap | program melebihi --max-words; naikkan ke batas chain tujuan, atau perkecil program (opt-level = "s") |
REJECTED dengan Syscall dan a7 = 7 | guest memanggil poseidon2_len; rand-guest check pada aeacf31 belum mengenal syscall 7 (zkp-circuits issue #1) |
REJECTED dengan Fence, Csr atau Ebreak | build menghasilkan instruksi yang tidak dimiliki mesin; lihat what is refused |
trap: … dari run | kesalahan saat run, seperti alamat yang tidak selaras atau nomor syscall yang belum diimplementasikan |
run menyatakan run tidak muat pada tier mana pun | run melebihi 2²⁰ baris atau anggaran Poseidon2-nya; run tidak dapat dibuktikan |