Rand Protocol
文檔
RISC-V 程式

部署 Rust 程式

由 no_std Rust crate 到已部署的保密程式的逐步指引:安裝指定版本的工具鏈、編寫 guest、以 rand-guest 建構並檢查、在模擬器上運行、部署並調用。

本頁帶領讀者把一個小型 Rust 程式從源碼部署為 Rand 上的程式。以下每一條 rand-guest 命令均已實際執行,其輸出為原樣貼上,並非摘要。執行日期為 2026-09-19,所用 circuits 提交為 7ef3220。testnet 現為 chain 21(fullnode v0.8.0,內含 circuits d217252,約束集 9);chain 20 的工具鏈與 chain 18 及 19 的相同,均在 aeacf31 建構(fullnode v0.6.8 與 v0.6.7 固定在同一提交),而兩者的輸出均未重新執行。無論約束集為何,guest 映像的字詞都相同,因此在 aeacf31 建構的程式可部署於 chain 21;只有證明屬於約束集 9,並由 v0.8.0 錢包生成。The RISC-V ISA 說明該機器、系統調用及檔案格式。

1. 安裝工具鏈

hc 取決於確切的編譯器,因此工具鏈已固定版本。

工具版本原因
Rust1.98.1編譯 guest 及 rand-guest 本身
riscv32im-unknown-none-elf 目標適用於 1.98.1guest 的編譯目標
circuits 檢出副本chain 18 至 20 使用 randprotocol/zkp-circuits 的 aeacf31內含 rand-guest、guest-sdk 及連結器腳本
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 有五個子命令:build、check、pack、run 及 info。

提交版本之所以重要,是因為 hc 是源碼與工具鏈的函數:以另一個 guest-sdk 提交重新建構的 guest,可能得出不同的 hc。在 7ef3220 與 aeacf31 之間,rand-guest 目錄沒有變動,而 guest-sdk/src/lib.rs 則有變動(見步驟 2)。

2. 編寫 guest

guest 是依賴 guest-sdk 的 no_std、no_main 二進位 crate。它必須置於 circuits 檢出副本之內:rand-guest build 會從 guest 所在目錄向上尋找 guest-sdk/ 以定位檢出副本,若 guest 在其外則拒絕建構。

此 guest 讀取一個門檻值、一個數量,以及相應數目的私密餘額。若餘額總和達到門檻,它會公布 1 及盈餘;它從不公布餘額本身。

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] 令此 crate 不屬於任何上層工作區,與已提交的 fib guest 的做法相同。

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 導出 read_input、read_public、write_output、poseidon2、keccak、keccak256、sha256_compress、sha256 及 halt,並提供 _start,負責設定堆疊指標並調用 main。沒有分配器,也沒有浮點運算。

在 aeacf31,其中三個函數的簽名與 7ef3220 不同,另有一個新增的導出:

函數於 7ef3220於 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_len不存在fn poseidon2_len(buf: &mut [u32], n: usize) -> [u32; 8],系統調用 7

poseidon2_len 把訊息長度綁定到海綿結構中,是安全的調用方式。rand-guest check 在 aeacf31 仍只列出系統調用 0 至 6,因此調用它的 guest 會建構出一個被 build 拒絕的映像,錯誤為 ecall with a7 = 7, which is no syscall the machine implements(zkp-circuits issue #1)。在此問題修復之前,guest 須對固定長度且帶標籤的訊息使用 unsafe { poseidon2(ptr, n) } 進行雜湊。本頁的 guest 不使用這四個函數中的任何一個。

guest 目錄的規則:

  • 目錄內、任何上層目錄及 $CARGO_HOME 中均不得有 .cargo/config 或 .cargo/config.toml。build 會拒絕建構並指出每個檔案,因為 cargo 會把它併入建構設定。
  • 不得自行加入編譯器旗標。build 已設定全部旗標:連結器腳本、-unaligned-scalar-mem,以及使映像與檢出副本位置無關的路徑重映射。
  • 最多只能有一個 .ld 檔案。build 使用 guest 目錄內唯一的 .ld 檔案;若沒有,則使用 guest-sdk/guest.ld。若有多於一個,須以 --ld 指定。

3. 建構與檢查

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

unaligned-scalar-mem 警告屬預期之內:工具鏈是刻意關閉未對齊存取的。

build 以 cargo +1.98.1 build --release --target riscv32im-unknown-none-elf 編譯,對結果執行 check,並寫出映像容器及 .sha256 固定檔。若 check 拒絕該程式,build 仍會寫出映像,並以非零狀態退出。

旗標預設值含義
--out <path><dir>/image.bin映像的輸出位置
--max-words <n>4096check 所對照的上限;請傳入目標鏈的上限(chain 21 為 65,535)
--ld <path>目錄內唯一的 .ld,否則為 guest-sdk/guest.ld連結器腳本
--lang rust|crust編譯器

4. 在模擬器上運行

以 --input 傳入私密輸入,每個字詞一項。--tier t 亦會報告該次運行是否適合層級 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)

餘額總和為 1,025,因此程式公布 1 及盈餘 25。若餘額為 400 與 250,總和不足,所有輸出均為零。發生陷阱時會印出 trap: <error> 並以 2 退出。

5. 檢視映像

$ 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 是調用的證明所對照驗證的值。程式 id 則是部署及調用時所用的標識。

6. 部署

請使用 fullnode v0.3 或更新版本的 rand 錢包;較舊的錢包無法讀取映像容器。自 chain 14 起,每個捆綁證明均對照其交易的綁定驗證,因此早於 chain 14 建構(b3c594c)的錢包所證明的捆綁會被鏈拒絕。在 chain 21(自 2026-10-12 起的運行中鏈)上,請使用 fullnode v0.8.0 的 rand(fleet 運行 v0.8.0,在 Linux 上建構的 rand 附於其已簽署的 GitHub 發行版)。較舊的錢包無法在 chain 21 上發送:該鏈運行約束集 9,此集改變了每個驗證者金鑰及證明編碼,因此 v0.6.8 或 v0.7.x 錢包的證明無法在該處解碼;並且,與 chain 20 起一樣,創世設定 binding_domain: 1,因此每個證明綁定及每條已簽署訊息均包含創世雜湊。Chain 21 啟動時沒有任何程式:在 chain 20 上部署的內容不會在切換時延續。公開端點 https://rpc.randprotocol.org 已足以同步錢包、部署及調用(設定)。

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

program deploy 會印出程式 id、字詞數目及 hc。隨後它以 rand_estimateFee 向節點查詢部署費用,節點會在任何證明之前拒絕超出鏈上限的程式。接着它證明一個支付該費用的零值自我轉賬捆綁,提交並等待提交確認。該捆綁即每筆交易均帶有的 4 入 4 出隱藏資產捆綁。自 chain 17 起它包含兩個證明,錢包會為每個證明印出一行:層級 10 的花費授權(數秒,約 1.37 MB)及層級 14 的捆綁(在手提電腦上約兩分鐘,約 1.5 MB)。在 chain 15 上,它是一個約 100 秒、約 1.42 MB 的層級 14 證明。An anonymous vote 展示了錢包在 chain 18 上部署時的原樣輸出。

部署費用為 0.001 RAND + 0.0001 RAND × words,chain 21 與以往相同;在 chain 21 上,它被銷毀而非支付予提議者(創世 fees.burn_floor)。Gas 為調用定價,而非為部署定價:

程式字詞數部署費用
threshold400.005 RAND
預設上限的程式(chain 12)4,0960.4106 RAND
chain 21 在無公開段時可部署的最大程式8,1800.819 RAND
65,535 字詞上限的程式(chain 13 至 15)65,5356.5545 RAND

threshold 少於 4,096 個字詞,因此符合預設上限,也符合 chain 21 的 65,535。超過 4,096 個字詞的程式,需要創世設定 max_program_words 至少達到該數值的鏈,例如 chain 21(The RISC-V ISA)。與 chain 19 及 20 一樣,chain 21 還有第二個較低的界限:它拒絕部署任何無法由層級 14 調用容納的程式(ProgramUncallable),即無公開段時超過 8,180 個字詞,有公開段時則更少。rand program deploy --input <word> …(若調用沒有輸入,則用 --check-call)會先在模擬器中運行你所設想的調用,除非結果落在層級 14 或以下,否則拒絕部署。

檢查記錄:

rand program show 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853

其 code_hash 即 rand-guest 所印出的 hc,只是拼寫方式不同。rand-guest 以大端序印出摘要的八個字詞,而錢包、rand program show 及 rand_getProgram 則以小端序印出每個字詞的位元組。因此 a491df99…3665 顯示為 99df91a4…707c:每組八個十六進位數字的位元組順序均已反轉。程式 id 在兩者中的拼寫相同。

7. 調用

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

錢包會取得程式碼,在模擬器中運行該調用,並在證明任何內容之前印出 gas: <exact> (declaring <limit>, tier <t>) 及費用。隨後它封存輸入記錄,在本機證明該次運行,透過捆綁支付費用,並印出收據。自 chain 20 起,這包含三個證明:調用、層級 10 的花費授權及層級 14 的捆綁。

自第 20 條鏈起,費用不再是 0.001 + call_fee(tier)(該公式令這次調用在第 15 條鏈上收費 0.002 RAND)。下限為 BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉:0.001 RAND,加上按 gas 價格計算的聲明上限(創世時為 100 個單位,一個單位為 10⁻⁹ RAND),再加上按字節價格計算的證明及信封字節(創世時為每 KiB 800 個單位)。自第 20 條鏈起,這兩個價格不能升越 10,000 及 80,000 個單位;在第 21 條鏈上,下限會被銷毀,而不是支付予提議者。實際的 gas 仍然保密。錢包預設會把聲明值向上取整至 2^(tier − 2) 的倍數,並按鏈的上限為證明定價(第 21 條鏈為 6,291,456 字節,第 20 條鏈為 4,194,304 字節),因為費用在證明產生之前已經確定,另外還會加上兩級價格的餘裕。在第 18 條鏈上,四次實測調用的費用各為 0.0057 至 0.0075 RAND(Example programs)。這次調用尚未在第 18 至 21 條鏈上進行,因此不提供數字。

鏈上可見的是 tier、聲明的 gas 上限、H_IN 及八個輸出字 [1, 25, 0, 0, 0, 0, 0, 0],餘額則完全不可見。

Programs 介紹收據、輸入信封及 rand open-call。像這樣的 Call 是無狀態的;自第 20 條鏈起,程式也可以透過 Invoke 保存 cell 及 vault(Program state (RPL-2))。

讓他人核對你的 hc

此構建是可重現的。build 會清除編譯器的環境變數(RUSTFLAGS、CARGO_* 覆蓋設定、RUSTC 包裝器等),拒絕 cargo 設定檔,並把檢出目錄的路徑重新映射為 /rand-circuits。同一份源碼配合 Rust 1.98.1,在任何機器上都會得出相同的 hc。要讓他人確認你部署的內容,請公開 guest 的源碼及你的 circuits commit(為第 18 至 20 條鏈構建的 guest 為 aeacf31)。任何人都可以重新構建,並把 hc 與 rand_getProgram 的 code_hash 比對。鏈上只儲存字,不儲存源碼。

疑難排解

訊息原因
… is not inside a circuits checkout (no guest-sdk/ above it)把 guest 目錄移到檢出目錄之內
build 指出 .cargo/config.toml刪除或移走該檔案;它無法被清除
REJECTED 並附有 Cap程式超出 --max-words;把它提高至目標鏈的上限,或縮減程式(opt-level = "s")
REJECTED 並附有 Syscall 及 a7 = 7guest 調用了 poseidon2_len;aeacf31 版本的 rand-guest check 尚未識別 syscall 7(zkp-circuits issue #1)
REJECTED 並附有 Fence、Csr 或 Ebreak構建產生了該機器沒有的指令;參閱被拒絕的內容
run 傳回 trap: …運行時錯誤,例如地址未對齊或未實現的 syscall 編號
run 指該次運行不適合任何 tier該次運行超過 2²⁰ 行或其 Poseidon2 預算,無法被證明

最後一次對照 fullnode 提交 c0878c3f(chain 21)核實的日期為 2026-10-12,並已核對 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。 如代碼與本頁內容有出入,以代碼為準;請告知我們。