Rand Protocol
Dokumentasi
Program RISC-V

Men-deploy program Rust

Langkah demi langkah dari crate Rust no_std hingga program rahasia yang telah di-deploy - pasang toolchain yang dipatok versinya, tulis guest, build dan periksa dengan rand-guest, jalankan di emulator, deploy, lalu panggil.

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.

alatversialasan
Rust1.98.1mengompilasi guest dan rand-guest itu sendiri
target riscv32im-unknown-none-elfuntuk 1.98.1target guest
checkout circuitsrandprotocol/zkp-circuits pada aeacf31 untuk chain 18 hingga 20memuat 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:

fungsipada 7ef3220pada aeacf31
keccakfn keccak(ptr: *mut u32)fn keccak(state: &mut [u32; 50])
sha256_compressfn sha256_compress(ptr: *mut u32)fn sha256_compress(buf: &mut [u32; 24])
poseidon2fn poseidon2(ptr: *mut u32, n: usize)unsafe fn poseidon2(ptr: *mut u32, n: usize)
poseidon2_lentidak adafn 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/config atau .cargo/config.toml di dalamnya, di direktori induk mana pun, maupun di $CARGO_HOME. build menolak dan menyebutkan setiap berkas tersebut, karena cargo akan menggabungkannya ke dalam proses build.
  • Tidak ada flag kompiler tambahan dari Anda. build mengatur semuanya: linker script, -unaligned-scalar-mem, dan pemetaan ulang path yang membuat image tidak bergantung pada lokasi checkout.
  • Paling banyak satu berkas .ld. build menggunakan satu-satunya berkas .ld di direktori guest, atau guest-sdk/guest.ld jika 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.

flagbawaanarti
--out <path><dir>/image.binlokasi penyimpanan image
--max-words <n>4096batas 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.ldlinker script
--lang rust|crustkompiler

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:

programwordsbiaya deploy
threshold400.005 RAND
program pada batas bawaan (chain 12)4,0960.4106 RAND
program terbesar yang di-deploy chain 20 tanpa segmen publik8,1800.819 RAND
program pada batas 65,535 word (chain 13 hingga 15)65,5356.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

messagecause
… is not inside a circuits checkout (no guest-sdk/ above it)pindahkan direktori guest ke dalam checkout
build menyebut .cargo/config.tomlhapus atau pindahkan berkas tersebut; berkas itu tidak dapat dibersihkan
REJECTED dengan Capprogram melebihi --max-words; naikkan ke batas chain tujuan, atau perkecil program (opt-level = "s")
REJECTED dengan Syscall dan a7 = 7guest memanggil poseidon2_len; rand-guest check pada aeacf31 belum mengenal syscall 7 (zkp-circuits issue #1)
REJECTED dengan Fence, Csr atau Ebreakbuild menghasilkan instruksi yang tidak dimiliki mesin; lihat what is refused
trap: … dari runkesalahan saat run, seperti alamat yang tidak selaras atau nomor syscall yang belum diimplementasikan
run menyatakan run tidak muat pada tier mana punrun melebihi 2²⁰ baris atau anggaran Poseidon2-nya; run tidak dapat dibuktikan

Terakhir diverifikasi terhadap commit fullnode c0878c3f (chain 21) pada 2026-10-12, dan circuits `7ef3220` (`rand-guest/README.md`, `guest-sdk/`, `guests-compiled/fib`); every `rand-guest` output on this page was produced on 2026-09-19, circuits `aeacf31` (`guest-sdk/src/lib.rs`, `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. Jika kode dan halaman ini tidak sesuai, kodelah yang benar; beri tahu kami.