Rand Protocol
Dokumentasi
Program RISC-V

Menerapkan program Rust

Langkah demi langkah daripada crate Rust no_std hingga program sulit yang telah diterapkan - pasang rantaian alat yang ditetapkan, tulis guest, bina dan semaknya dengan rand-guest, jalankan pada emulator, terapkan dan panggil.

Halaman ini membawa sebuah program Rust kecil daripada kod sumber hingga menjadi program yang digunakan (deploy) pada Rand. Setiap perintah rand-guest di bawah telah dijalankan, dan outputnya ditampal, bukan diringkaskan. Pelaksanaan dibuat pada 2026-09-19 pada commit circuits 7ef3220. Testnet kini ialah chain 20, yang rantai alatnya (toolchain), seperti chain 18 dan 19, dibina pada aeacf31 (fullnode v0.6.8 menetapkan commit yang sama dengan v0.6.7), dan output belum dijalankan semula di sana. RISC-V ISA menerangkan mesin, syscall dan format fail.

1. Pasang rantai alat

hc bergantung pada pengkompil yang tepat, jadi rantai alat itu ditetapkan pada versi tertentu.

alatversisebab
Rust1.98.1mengkompil guest dan rand-guest itu sendiri
sasaran riscv32im-unknown-none-elfuntuk 1.98.1sasaran guest
checkout circuitsrandprotocol/zkp-circuits pada aeacf31 untuk chain 18 hingga 20mengandungi rand-guest, guest-sdk dan skrip penaut (linker)
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 mempunyai lima subperintah: build, check, pack, run dan info.

Commit itu penting kerana hc ialah fungsi bagi kod sumber dan rantai alat: guest yang dibina semula dengan commit guest-sdk yang lain boleh menghasilkan hc yang berbeza. Antara 7ef3220 dan aeacf31, direktori rand-guest tidak berubah, manakala guest-sdk/src/lib.rs berubah (langkah 2).

2. Tulis guest

Guest ialah crate binari no_std, no_main yang bergantung pada guest-sdk. Ia mesti berada di dalam checkout circuits: rand-guest build mencari checkout itu dengan mencari guest-sdk/ di atas direktori guest, dan menolak guest yang berada di luarnya.

Guest ini membaca satu ambang, satu kiraan dan sebanyak itu baki peribadi. Ia menerbitkan 1 dan lebihan jika baki tersebut memenuhi ambang. Ia tidak pernah menerbitkan baki tersebut.

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 mengekalkan crate itu di luar sebarang ruang kerja (workspace) induk, seperti guest fib yang telah 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 mengeksport read_input, read_public, write_output, poseidon2, keccak, keccak256, sha256_compress, sha256 dan halt, serta menyediakan _start, yang menetapkan penuding tindanan (stack pointer) dan memanggil main. Tiada pengalokasi dan tiada titik terapung.

Pada aeacf31, tiga daripada tandatangan tersebut berbeza daripada 7ef3220, dan terdapat satu eksport baharu:

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_lentiadafn poseidon2_len(buf: &mut [u32], n: usize) -> [u32; 8], syscall 7

poseidon2_len mengikat panjang mesej ke dalam span (sponge) dan ialah panggilan yang selamat. rand-guest check pada aeacf31 masih hanya menyenaraikan syscall 0 hingga 6, jadi guest yang memanggilnya dibina menjadi imej yang ditolak oleh build dengan ecall with a7 = 7, which is no syscall the machine implements (isu zkp-circuits #1). Sehingga isu itu dibaiki, guest melakukan cincangan dengan unsafe { poseidon2(ptr, n) } ke atas mesej bertag berpanjang tetap. Guest pada halaman ini tidak menggunakan keempat-empatnya.

Peraturan untuk direktori guest:

  • Tiada .cargo/config atau .cargo/config.toml di dalamnya, di mana-mana direktori induk, atau dalam $CARGO_HOME. build menolak dan menamakan setiap fail, kerana cargo akan menggabungkannya ke dalam binaan.
  • Tiada bendera pengkompil anda sendiri. build menetapkan semuanya: skrip penaut, -unaligned-scalar-mem, dan pemetaan semula laluan yang menjadikan imej bebas daripada lokasi checkout.
  • Satu fail .ld sahaja, paling banyak. build menggunakan satu-satunya .ld dalam direktori guest, atau guest-sdk/guest.ld jika tiada. Jika lebih daripada satu, namakannya dengan --ld.

3. Bina dan semak

$ 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)

Amaran unaligned-scalar-mem adalah dijangka: rantai alat itu sengaja mematikan capaian tidak sejajar.

build mengkompil dengan cargo +1.98.1 build --release --target riscv32im-unknown-none-elf, menjalankan check ke atas hasilnya, dan menulis bekas imej serta pin .sha256. Jika check menolak program itu, build tetap menulis imej dan keluar dengan kod bukan sifar.

benderalalaimaksud
--out <path><dir>/image.binlokasi imej disimpan
--max-words <n>4096had yang diukur oleh check; berikan had chain sasaran (65,535 pada chain 20)
--ld <path>satu-satunya .ld dalam direktori, jika tiada guest-sdk/guest.ldskrip penaut
--lang rust|crustpengkompil

4. Jalankan pada emulator

Berikan input peribadi dengan --input, satu perkataan (word) bagi setiap satu. --tier t turut melaporkan sama ada larian itu muat dalam tier 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)

Baki itu berjumlah 1,025, jadi program menerbitkan 1 dan lebihan 25. Dengan baki 400 dan 250, jumlahnya tidak mencukupi, dan setiap output ialah sifar. Satu trap mencetak trap: <error> dan keluar dengan kod 2.

5. Periksa imej

$ 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 ialah apa yang digunakan untuk mengesahkan bukti sesuatu panggilan. ID program ialah apa yang anda gunakan (deploy) dan panggil.

6. Deploy

Gunakan wallet rand daripada fullnode v0.3 atau lebih baharu; wallet yang lebih lama tidak membaca bekas imej. Sejak chain 14, setiap bukti bundle disahkan terhadap pengikatan (binding) transaksinya, jadi wallet yang lebih lama daripada binaan chain 14 (b3c594c) menjana bukti bundle yang ditolak oleh chain. Pada chain 20, chain yang aktif, gunakan rand daripada fullnode v0.6.8 atau lebih baharu (fleet menjalankan v0.6.8, dan rand yang dibina pada Linux dilampirkan pada keluaran GitHub-nya). Wallet v0.6.7 tidak boleh menghantar pada chain 20: genesis chain 20 menetapkan binding_domain: 1, jadi setiap pengikatan bukti dan setiap mesej bertandatangan merangkumi cincang genesis, manakala wallet v0.6.7 hanya mengikat id chain. Titik akhir awam https://rpc.randprotocol.org mencukupi untuk menyegerakkan wallet, melakukan deploy dan membuat panggilan (persediaan).

rand fee deploy 40               # the floor for a 40-word program
rand program deploy threshold.bin

program deploy mencetak ID program, bilangan perkataan dan hc. Kemudian ia meminta nod untuk yuran deploy dengan rand_estimateFee, yang menolak program yang melebihi had chain sebelum sebarang pembuktian. Seterusnya ia membuktikan bundle pindahan kendiri bernilai sifar yang membayar yuran itu, menghantarnya, dan menunggu commit. Bundle itu ialah bundle aset tersembunyi 4-masuk-4-keluar yang dibawa oleh setiap transaksi. Sejak chain 17 ia terdiri daripada dua bukti, dan wallet mencetak satu baris bagi setiap satu: pengesahan perbelanjaan tier-10 (beberapa saat, kira-kira 1.37 MB) dan bundle tier-14 (kira-kira dua minit pada komputer riba, kira-kira 1.5 MB). Pada chain 15 ia hanyalah satu bukti tier-14 sekitar 100 s dan sekitar 1.42 MB. Undian tanpa nama menunjukkan deploy pada chain 18 seperti yang dicetak oleh wallet.

Yuran deploy ialah 0.001 RAND + 0.0001 RAND × words, pada chain 20 seperti sebelumnya. Gas menetapkan harga panggilan, bukan deploy:

programwordsyuran deploy
threshold400.005 RAND
program pada had lalai (chain 12)4,0960.4106 RAND
program terbesar yang di-deploy oleh chain 20 tanpa segmen awam8,1800.819 RAND
program pada had 65,535 perkataan (chain 13 hingga 15)65,5356.5545 RAND

threshold kurang daripada 4,096 perkataan, jadi ia muat dalam had lalai dan 65,535 milik chain 20. Program yang melebihi 4,096 perkataan memerlukan chain yang genesis-nya menetapkan max_program_words sekurang-kurangnya setinggi itu, seperti chain 20 (RISC-V ISA). Chain 20, seperti chain 19, mempunyai had bawah yang kedua: ia menolak deploy program yang tiada panggilan tier-14 mampu memuatkannya (ProgramUncallable), iaitu lebih daripada 8,180 perkataan tanpa segmen awam dan kurang jika ada satu. rand program deploy --input <word> … (atau --check-call untuk panggilan tanpa input) menjalankan panggilan yang anda maksudkan melalui emulator terlebih dahulu dan menolak deploy melainkan ia berada pada atau di bawah tier 14.

Semak rekod:

rand program show 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853

code_hash-nya ialah hc yang dicetak oleh rand-guest, dieja secara berbeza. rand-guest mencetak setiap lapan perkataan digest dalam big-endian, manakala wallet, rand program show dan rand_getProgram mencetak bait setiap perkataan dalam little-endian. Jadi a491df99…3665 muncul sebagai 99df91a4…707c: setiap kumpulan lapan digit heks dibalikkan susunan baitnya. ID program dieja sama oleh kedua-duanya.

7. Panggil

rand call 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853 \
    --input 1000 --input 4 --input 400 --input 250 --input 300 --input 75

Wallet mengambil kod, menjalankan panggilan dalam emulator, dan mencetak gas: <exact> (declaring <limit>, tier <t>) serta yuran sebelum membuktikan apa-apa. Kemudian ia mengunci transkrip input, membuktikan larian itu secara setempat, membayar yuran melalui bundle, dan mencetak resit. Pada chain 20 itu ialah tiga bukti: panggilan, pengesahan perbelanjaan tier-10 dan bundle tier-14.

Yuran pada chain 20 tidak lagi 0.001 + call_fee(tier), yang menjadikan panggilan ini 0.002 RAND pada chain 15. Paras minimumnya ialah BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉: 0.001 RAND, ditambah had yang diisytiharkan pada harga gas (100 unit pada genesis, satu unit ialah 10⁻⁹ RAND), ditambah bait bukti dan sampul pada harga bait (800 unit setiap KiB pada genesis). Pada chain 20, kedua-dua harga tidak boleh naik melebihi 10,000 dan 80,000 unit. Gas yang tepat kekal peribadi. Secara lalai, dompet mengisytiharkannya dibundarkan ke atas kepada gandaan 2^(tier − 2), meletakkan harga bukti pada had 4,194,304 bait bagi chain tersebut kerana yuran ditetapkan sebelum bukti wujud, dan menambah ruang lebihan sebanyak dua langkah harga. Empat panggilan yang diukur pada chain 18 masing-masing berharga 0.0057 hingga 0.0075 RAND (Example programs). Panggilan ini belum dibuat pada chain 18 hingga 20, maka tiada angka diberikan untuknya.

Chain melihat tier, had gas yang diisytiharkan, H_IN dan lapan perkataan output [1, 25, 0, 0, 0, 0, 0, 0]. Chain tidak pernah melihat baki.

Programs merangkumi resit, sampul input dan rand open-call. Sesuatu Call seperti ini tidak berkeadaan; pada chain 20, program juga boleh menyimpan sel dan peti simpanan melalui Invoke (Program state (RPL-2)).

Biarkan orang lain menyemak hc anda

Binaan ini boleh dihasilkan semula. build membersihkan persekitaran pengkompil (RUSTFLAGS, penggantian CARGO_*, pembungkus RUSTC dan lain-lain), menolak fail konfigurasi cargo, dan memetakan semula laluan checkout ke /rand-circuits. Sumber yang sama dengan Rust 1.98.1 menghasilkan hc yang sama pada mana-mana mesin. Untuk membolehkan orang lain mengesahkan apa yang anda deploy, terbitkan sumber guest dan commit circuits anda (aeacf31 bagi guest yang dibina untuk chain 18 hingga 20). Sesiapa sahaja kemudian boleh membina semula dan membandingkan hc dengan code_hash daripada rand_getProgram. Chain hanya menyimpan perkataan, bukan sumbernya.

Penyelesaian masalah

mesejpunca
… is not inside a circuits checkout (no guest-sdk/ above it)alihkan direktori guest ke dalam checkout
build menamakan .cargo/config.tomlpadam atau alihkannya; fail itu tidak boleh dibersihkan
REJECTED dengan Capprogram melebihi --max-words; naikkannya kepada had chain sasaran, atau kecilkan program (opt-level = "s")
REJECTED dengan Syscall dan a7 = 7guest memanggil poseidon2_len; rand-guest check pada aeacf31 belum mengenali syscall 7 (zkp-circuits issue #1)
REJECTED dengan Fence, Csr atau Ebreakbinaan menghasilkan arahan yang tidak ada pada mesin; lihat what is refused
trap: … daripada runralat semasa larian, seperti alamat tidak sejajar atau nombor syscall yang belum dilaksanakan
run menyatakan larian tidak muat dalam mana-mana tierlarian melebihi 2²⁰ baris atau bajet Poseidon2nya; ia tidak boleh dibuktikan

Disahkan kali terakhir terhadap commit fullnode c0878c3f (rantai 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 kod dan halaman ini tidak selari, kodlah yang betul; sila maklumkan kepada kami.