這四個程式各是一個機密調用的完整應用:一道問題、一個介面、一個 Rust guest、主機端工具,以及在線上 testnet 的一次部署與一次調用。它們比教程更長,而且各自倚重機器的不同部分。以 Rust 或 C 編寫程式是它們共用模型的指南。若 read_input、公開段或八個輸出槽對你而言尚屬陌生,請先閱讀該文。
此處一切均先在 chain 18 上以 fullnode v0.6.7 運行,時間為 2026-09-30,頁面上的每一項輸出均由那些運行結果直接貼上。程式不會跨越 chain 切換而延續,因此在 2026-10-01,這四個程式原封不動地重新部署到 chain 19,其後再部署到 chain 20(fullnode v0.6.8),每次均重複了調用。程式 id 是程式碼與公開段的哈希,而非 chain 的哈希,因此所有 id 均相同;記錄中的交易哈希屬於 chain 18,下表連結的則是 chain 20 的交易。在 chain 20 上,程式亦可在多次調用之間保存狀態(RPL-2);這四個程式並不使用,而程式狀態與 example-programs 倉庫涵蓋了使用狀態的程式。
四個程式
| 程式 | 一次調用所證明的內容 | 倚重 | 字數 | 層級 |
|---|---|---|---|---|
| 匿名投票 | 調用者是已公布名單中的成員而不透露是哪一位,且此前未曾投票 | Keccak-256、公開段 | 427 | 12 |
| 負債證明 | 客戶的私密餘額合計不超過已公布的儲備,且各客戶可核對所屬的根 | SHA-256、在程式內構建的樹 | 994 | 14 |
| 鏈下賬本 | 一批轉賬使賬本由一個狀態進入下一個狀態,每名發送者均已獲授權,且沒有憑空產生任何價值 | Poseidon2、在多次調用之間攜帶的狀態 | 773 | 14 |
| 私密信貸決定 | 一個已公布的模型批准或拒絕由認證方擔保的私密申請人數據 | 整數運算、公開段中的模型 | 462 | 12 |
四個程式在 chain 20 上均在線運行,直至其於 2026-10-12 退役。Chain 21(fullnode v0.8.0)一如每次切換,起始時並無任何程式,這四個程式尚未在該處重新部署;重新部署時,下列 id 不會改變,因為 id 是程式碼與公開段的哈希,而非 chain 的哈希。表中每次調用均以相同輸入重複該程式頁面所示的 chain 18 調用:
| 程式 | 程式 id | 於 chain 20 部署的交易 | 於 chain 20 的首次調用 |
|---|---|---|---|
| 匿名投票 | ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 | f6ae5528…0f82 | 3a304e7a…427b |
| 負債證明 | fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0 | a8bc281b…17d9 | 6116c15f…d62a |
| 鏈下賬本 | c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab | 72cc95f2…3d51 | 9c9998bb…66eb |
| 私密信貸決定 | 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c | 413e6658…e42b | d3ceb306…124a |
瀏覽器 randscan.org/programs 一頁列出每個程式及其調用,位於 randscan.org/programs。testnet chain 會不時被替換,程式亦不會延續:下次切換之後,這些 id 將不復存在,程式需要重新部署。
設置
**工具鏈。**guest 以電路倉庫中的 rand-guest 構建,所用的提交是 fullnode v0.6.7 與 v0.6.8 均內置的那一個:
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits
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"
該提交之所以重要,有兩個原因。hc 是源碼與工具鏈的函數,因此在 guest-sdk 的另一個提交上重新構建的 guest 可能得出不同的 hc。此外,SDK 的簽名亦有變動:在 aeacf31,keccak 與 sha256_compress 接受數組引用,而較舊的提交接受的是原始指針。
**源碼。**四個 guest、其主機腳本與示範數據合為一個下載包。請在 checkout 之內解壓,因為 rand-guest build 會從 guest 所在目錄向上查找 guest-sdk:
mkdir -p my-guests
curl -sSf https://randprotocol.org/examples/rand-example-programs.tar.gz | tar -xz --strip-components=1 -C my-guests
ls my-guests
# allowlist ledger ledger-host score solvency
這些文件亦可在 https://randprotocol.org/examples/programs/ 下逐個瀏覽,例如 allowlist/src/main.rs。
**錢包。**部署與調用需要 fullnode v0.6.8 的 rand 錢包(在 fullnode checkout 中於標籤 v0.6.8 下執行 cargo build --release -p randprotocol-client,或使用 v0.6.8 發佈版本所附的 rand)、一條密鑰,以及 RAND。在 chain 21 上,錢包為 fullnode v0.8.0(標籤 v0.8.0)的 rand:約束集 9 改變了每一個 verifier 密鑰,因此 v0.6.8 與 v0.7.x 的錢包均無法在該處部署或調用。較舊的錢包同樣無法在 chain 20 上交易:chain 20 將每個證明與每條已簽名訊息綁定至其創世區塊,而 v0.6.7 錢包並不如此。
**RPC。**公共端點 https://rpc.randprotocol.org 足以應付這些頁面上的一切:讀取程式與回執、同步錢包、部署與調用。錢包首次同步須讀取每個區塊頭與每個含 note 的區塊,在 2026-09-30 chain 18 的高度下為 645 個請求,經該端點約需三分鐘。該端點容許每個地址每分鐘 1,200 次讀取與 6 次發送。你自己的節點(運行節點)則沒有此類限制。
export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json
在 chain 18 上,匿名投票頁面所計的其中一票 fdbcc960…fc2b,在一部手提電腦上生成證明,並僅經公共端點發送。在 chain 19 與 chain 20 上,每次的全部十三筆交易(四次部署與九次調用)均以此方式完成。
chain 18 為程式帶來的改變
**調用須聲明 gas 上限,並為此付費。**約束集 8 對每次運行計量。一般的週期為 1 gas。哈希系統調用則是例外:
| 指令 | gas |
|---|---|
任何 RV32IM 指令、HALT、WRITE_OUTPUT、READ_INPUT、READ_PUBLIC | 1 |
對 n 個字的 POSEIDON2 | 3 + 3 × ⌈n / 4⌉ |
SHA256,一次壓縮 | 64 |
KECCAK,一次置換 | 192 |
證明帶有一個新的公開值,即聲明上限,而電路強制要求該次運行的 gas 不高於它。確切的 gas 保持私密。rand call 會先在模擬器中運行程式,並印出兩者:
gas: 5636 (declaring 6144, tier 12)
錢包預設會將確切的 gas 向上取整至 2^(tier − 2) 的倍數,即層級 12 為 1,024、層級 14 為 4,096,因此聲明上限對該次運行幾乎沒有透露甚麼。--gas-limit max 聲明該層級的上限,除層級之外不透露任何信息。--gas-limit N 則聲明一個確切數字。
費用取決於聲明上限與位元組數。
floor = 0.001 RAND + gas price × declared limit + byte price × ⌈bytes / 1024⌉
價格起初為每 gas 100 單位、每 KiB 800 單位(一單位為 10⁻⁹ RAND),並每個區塊向目標略作移動。rand_getLimits 提供當前價格。錢包會加上兩個價格步幅的餘裕,即 1.125² 倍,因為調用在定價後兩三個區塊才會落地。
有一個細節決定了大部分費用。自 chain 18 起,調用的證明綁定至其自身交易,因此錢包必須在證明存在之前確定費用,並按鏈上 4 MiB 的上限而非其實際大小為證明定價。對匿名投票而言,即 (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6,191,437 單位,即 0.0062 RAND,其中 gas 約佔八分之一。這些頁面上的四次調用,每次費用介乎 0.0057 至 0.0075 RAND。--fee 可覆蓋預設值。
**每個 bundle 含兩個證明。**Chain 18 採用拆分授權:支付費用的 bundle 除層級 14 的 bundle 證明外,還附帶一個小型的層級 10 授權證明。錢包會生成兩者,並各印出一行。因此一次調用共有三個證明:調用、授權與 bundle。在 Apple Silicon 手提電腦上,此處的四次調用每次耗時三至五分半鐘,記憶體峰值為 6.1 至 6.6 GB。其中 bundle 證明每次約佔兩分鐘。層級 12 的調用證明增加半分鐘,層級 14 的則增加兩分鐘。
調用必須符合的上限。
| 限制 | chain 18 至 20 |
|---|---|
| 層級 | 至多 14:16,383 個週期,包括摘要行 |
| Poseidon2 置換 | 層級 14 為 2,048,包括程式、輸入與公開摘要 |
| Keccak 置換 | 128 |
| SHA-256 壓縮 | 128 |
| 調用證明 | 4,194,304 位元組(4 MiB) |
| 程式 | 無公開段時為 8,180 個字,有公開段時則少數個字:chain 21 與 chain 18 至 20 一樣,拒絕任何層級 14 調用均無法容納的部署(ProgramUncallable),不論 max_program_words(65,535)允許多少 |
| 公開段 | 32,768 個字,於部署時固定 |
| 附封存記錄的私密輸入 | 16,071 個字 |
rand-guest run --tier 14 會在部署前檢查頭兩項。哈希表會令證明變大。賬本的層級 14 證明沒有哈希表,為 1.50 MB。SHA-256 表使負債證明增至 1.91 MB,而 Keccak 表使投票的證明增至 3.35 MB,已佔 4 MiB 上限的大部分。
四個程式共通的慣例
- 槽位 0 是狀態,而 0 絕不是有效的狀態值。 每一種結果(包括失敗)都有一個由 1 起算的編號,而失敗會乾淨地停機,以便可以被證明。全為零的收據不會被誤認為一個結果。
- 槽位 7 不作使用。 發生 panic 的 Rust guest 會在該處寫入
0xdeadbeef(3,735,928,559)。讀取者應先檢查槽位 7。 - 槽位 2 至 6 承載一個 160 位元的數值:nullifier、根、連結或承諾。完整的 256 位元雜湊值佔八個字,會令狀態無處容身。截短為五個字後,仍保留 80 位元的抗碰撞強度,而各頁面會說明這一強度所保護的是甚麼。
- 位元組順序依從雜湊。 Keccak 的數值是小端序的字,SHA-256 的數值是大端序的字,因為各個系統呼叫正是這樣讀取其區塊。主機腳本與 guest 若在這一點上不一致,產生的摘要便永遠不會吻合。
- 隨實例而異的內容置於公開段;隨每次呼叫而異的內容為私有輸入。 投票的根、儲備數字和模型的權重在部署時已固定,並綁定於程式 id。呼叫本身不附帶任何公開字。
- 輸入字在命令列上以十進位表示。 各頁面以
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)把見證檔案轉為旗標。
親自呼叫一次
在源碼已解壓、工具鏈已建置,並備有已注資的錢包之後(v0.6.8 於 chain 20,這些運行即在其上進行;v0.8.0 於 chain 21),以示範成員之一的身分投票,只需四條命令:
cd my-guests/allowlist
python3 allowlist.py witness 10 1 demo-secrets.txt 6 1 > witness.txt # member 6, choice 1
rand-guest build . --max-words 65535 # only needed to run it in the emulator first
rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
呼叫不需要映像檔:rand call 會從節點取得程式的代碼和公開段,核對它們的雜湊值與 id 相符,然後在其上進行證明。
讀取結果不需要錢包,也不需要節點。任何收據都只需向公開端點發出一次請求:
$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"rand_getReceipt","params":["8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"]}'
{"id":1,"jsonrpc":"2.0","result":{"h_in":"ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6","h_pub":"2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510","height":98335,"index":0,"outputs":[1,2,392974773,873526743,1921224601,3950868247,2248722827,0],"program":"ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970","tier":12,"tx":"8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"}}
瀏覽器的 API 可一次請求取得某個程式的全部呼叫:https://randscan.org/api/v1/programs/<program id> 會返回 call_count 和 recent_calls(Explorer API)。
它們都不做的事
Rand 上的程式是對一項計算的證明,而鏈本身不會依據其任何輸出採取行動。這些程式沒有一個會轉移 RAND 或代幣、持有餘額,或記住先前的呼叫。投票由讀取收據的人點算。負債要與儲備比對,而儲備需要它們自己的憑證。帳本的餘額是鏈下樹中的數字。信貸決定是貸款人自行選擇是否遵從的一個位元。每一頁的結尾都列出其證明留給使用者自行處理的事項。