Rand Protocol
文檔
RISC-V 程式

RISC-V ISA

RV32IM 在 Rand 上的含義:zkVM 所證明的指令、檢查器所拒絕的內容、八個系統調用、記憶體、程式所附帶的映像容器,以及程式大小上限。

在撰寫 Rand 程式前,請先閱讀本頁。本頁說明你的程式在哪一種機器上運行、工具鏈會拒絕什麼、程式如何與外界通訊,以及你要部署哪一種檔案。後續頁面均以本頁為基礎:部署 Rust 程式、部署 RISC-V 程式,以及兩個轉譯器。

機器

Rand 程式屬 RV32IM:即 32 位元 RISC-V 基本整數指令集,加上乘除擴充。程式在 Rand zkVM 內運行,zkVM 以 Plonky3 批次 STARK 證明每一次運行。驗證者從不查看任何指令,只會按 hc(程式在電路內的 Poseidon2 摘要)核對證明。

組別指令
高位/跳轉LUI AUIPC JAL JALR
分支BEQ BNE BLT BGE BLTU BGEU
載入/儲存LW SW LB LH LBU LHU SB SH
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL MULH MULHU MULHSU DIV DIVU REM REMU
系統僅 ECALL

JALR 按 RV32I 的規定清除目標位址的第 0 位元,而且其編碼必須 funct3 = 0。這兩項規則隨 constraint set 7(chain 16)引入;在此之前,奇數目標無法被取指,運行也就無法被證明。目標為偶數但不是四的倍數,仍然無法被取指。

本機器沒有浮點運算、沒有原子指令(A 擴充)、沒有壓縮指令(C 擴充),亦沒有 64 位元暫存器。C 語言中的 64 位元除法會變成對軟件輔助函式的呼叫;參見 C 路徑。

被拒絕的內容

rand-guest check 會把每個文字字詞交給機器自身的解碼器處理。通過檢查的程式,不會因編碼、系統呼叫編號或佈局方面的原因而在電路中失敗。檢查器會拒絕下列情況:

發現含義
Compressed16 位元的 RVC 編碼;機器只解碼 32 位元字詞
Undecodable機器未實作的操作碼、funct 或移位量
FenceFENCE 或 FENCE.I;機器沒有記憶體排序
Csr任何 CSR 指令;機器沒有 CSR
EbreakEBREAK;不對陷阱建模
Syscall其 a7 由緊接其前的 li 設定、且不對應任何系統呼叫的 ecall
Cap字詞數目超過 --max-words,按載入器的計算方式計算
Layout文字基址沒有字對齊

以下是一個四字詞的程式,含一個 fence、一個 ebreak 和編號為 9 的 ecall,rand-guest check 的報告如下(結束碼 1):

$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000  0x0ff0000f  Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004  0x00100073  Ebreak: EBREAK: traps are not modelled
0x0000100c  0x00000073  Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED

若 ecall 的 a7 為檢查器所無法得知(由 li 以外的指令設定,或在分支之後設定),則計入 unresolved_ecalls,不予拒絕。如果編號有誤,機器在運行時仍會觸發陷阱。

檢查器不會檢查指標值、長度或輸入索引,不會檢查 64 KiB 堆疊與 RAM 的關係,也不會檢查在運行時計算出的位址對齊。未對齊的存取屬於約束違反,因此工具鏈編譯時使用 -unaligned-scalar-mem。rand-guest run 會報告任何運行時陷阱。

系統呼叫

ECALL 從 a7 讀取系統呼叫編號,從 a0 讀取第一個引數,從 a1 讀取第二個引數。回傳值經 a0 傳回。

#系統呼叫作用燃料Rust (guest-sdk)C (guest.h)
0HALT結束運行1halt()rand_halt()
1WRITE_OUTPUT slot word設定公開輸出 slot < 8,每個槽位至多一次;未寫入的槽位為零1write_output(slot, word)rand_write_output
2READ_INPUT idx回傳私有輸入字詞 idx,綁定於加鹽承諾 H_IN1read_input(idx)rand_read_input
3POSEIDON2 ptr n對位於字位址 ptr 的 n ≤ 4096 個字詞進行雜湊,並把 8 個字詞的摘要寫在 ptr..ptr+8 之上;不填充3 + 3·⌈n/4⌉unsafe poseidon2(ptr, n)rand_poseidon2
4KECCAK ptr對 50 個字詞就地進行一次 Keccak-f[1600] 置換192keccak(state)、keccak256(msg)rand_keccak
5SHA256 ptr對 24 個字詞就地進行一次 SHA-256 壓縮64sha256_compress(buf)、sha256(msg)rand_sha256_compress
6READ_PUBLIC idx回傳公開段字詞 idx,綁定於未加鹽的 H_PUB1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr n將長度 n 綁入海綿結構的 POSEIDON2,至少一次置換(constraint set 7)3 + 3·max(⌈n/4⌉, 1)poseidon2_len(buf, n)無

其餘每條指令的成本均為 1 燃料:燃料按每個 cpu 行計一單位,只有雜湊系統呼叫的權重較高(constraint set 8,自 chain 18 起)。一次 Poseidon2 呼叫包括其 ecall 行、兩個回寫行,以及 ⌈n/4⌉ 個吸收行,每行 3 燃料。

POSEIDON2 不作填充。 海綿結構以每個四字詞區塊覆寫其最前的若干通道,因此 [a]、[a, 0]、[a, 0, 0] 與 [a, 0, 0, 0] 會雜湊成同一個摘要。若程式所雜湊的資料長度可變,就必須把長度放進訊息、改為雜湊定長編碼,或呼叫 POSEIDON2_LEN;後者把 n 植入海綿結構的容量部分,使相同字詞得出不同的摘要。在 Rust SDK 中,poseidon2 是作用於原始指標的 unsafe fn,而 poseidon2_len 是作用於切片的安全呼叫;keccak 與 sha256_compress 分別接受 &mut [u32; 50] 和 &mut [u32; 24]。

一項工具鏈缺口。 在電路 aeacf31 中,機器、模擬器和 Rust SDK 都實作了系統呼叫 7,但 rand-guest check 沒有:其系統呼叫清單止於 6,因此會把 a7 = 7 的 ecall 報告為 Syscall 發現,而 rand-guest build 遇到呼叫 poseidon2_len 的 guest 時會以非零狀態退出(circuits issue #1)。guest.h 同樣沒有它的包裝函式。在修復之前,以標準工具建置的 guest 對定長訊息使用 POSEIDON2。

一份證明所公布的內容恰好是:hc、H_IN、H_PUB、層級、所宣告的燃料上限,以及八個輸出字詞。目前在鏈上有兩項限制:

  • 公開段在部署時固定。 在 chain 20 上,rand program deploy --public 設定程式的公開字詞(至多 max_program_public_words,即 32,768 個),而每次呼叫都會按這些字詞檢查。程式本身沒有逐次呼叫的公開字詞。Chain 20 與 chain 19 一樣,會在每次呼叫的公開段中、於這些字詞之後附加八個字詞,即把證明與其交易連結起來的呼叫綁定(在 chain 20 上還包含創世雜湊);guest 讀取自身字詞的索引與以往相同。RPL-2 的 Invoke 會在綁定之後附加所宣告的轉換,程式從 public_len + 8 起讀取(程式)。在沒有這些限制欄位的鏈上(例如 chain 12),公開段為空,READ_PUBLIC 無從讀取。
  • 一次呼叫的私有輸入字詞受密封信封所限。 Chain 20 的 max_call_envelope_bytes 為 65,536,容許 16,071 個字詞;沒有此欄位的鏈則容許 4,295 個。

KECCAK 或 SHA256 呼叫會為證明增加一張表,證明的標頭隨即顯示該呼叫曾進行雜湊。含 keccak 的證明約為 3.3 MB:超出預設 2 MiB 的證明上限,但低於 chain 20 的 4 MiB;而一次呼叫至多可使用 128 次 Keccak-f 置換和 128 次 SHA-256 壓縮(程式)。

記憶體

  • 32 個暫存器;x0 恆為零。
  • RAM 由零開始。每個位址都低於 2³⁰。
  • 預設連結器腳本 guest-sdk/guest.ld 把程式置於 0x1000,其後依次是 .rodata、.data 和 .bss,再於 __stack_top 處設 64 KiB 堆疊。
  • 帶有資料段的程式,需要在其文字段之下為載入器的前導碼預留空間(見下一節)。其自身的 .ld 會把 ORIGIN 提高至 0x10000,evm.ld 和 sbpf.ld 亦然。

映像容器

rand-guest build 和 rand-guest pack 寫出同一種檔案格式,即映像容器。所有數字均為小端序 32 位元字詞:

[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…

載入器把容器轉換成程式 { base_pc, words }:

  1. 為每個非零資料字詞寫入一對 li/sw。零字詞會被略過,因為 RAM 本來就讀為零。
  2. 把該前導碼直接置於 text_base 之下,因此 base_pc = text_base − 4 × 前導碼字詞數。前導碼會直接接續到程式自身的 _start。
  3. hc 與程式 id 是針對整個 words(包括前導碼)計算的。因此 hc 既綁定程式碼,也綁定資料。

不含資料的容器沒有前導碼,其程式和 hc 與同一基址的無標頭平坦二進位檔相同。

fullnode v0.3 及以後版本中的 rand program deploy 憑檔案的第一個字詞辨識容器。0x444e4152 從不會被解碼為指令,因此原始 .bin 不會被誤認為容器。若檔案以該魔數開頭但格式有誤,結果是錯誤,而不是退回其他處理。

兩種身分

名稱說明使用者
hc電路對 (base_pc, len, words) 計算的 Poseidon2 摘要每一個呼叫的證明都以它驗證;rand_getProgram 將它顯示為 code_hash
program idblake3("rand-program" ‖ base_pc ‖ words);使用 --public 部署時,公開字詞亦會一併綁定,標籤為 rand-program-2已部署程式儲存及被呼叫時所用的鍵

rand-guest build 與 rand-guest info 均會列印兩者。hc 具綁定性但不具隱藏性:任何持有候選程式的人都可以確認它。

程式上限

一次部署最多可攜帶該鏈的程式上限(以字計)。

鏈上限
沒有 max_program_words 的創世區塊(chain 12)4,096
有 max_program_words 的創世區塊該值,1..=65535
zkVM 的硬性上限,任何鏈65,535

上限屬創世雜湊的一部分,因此在運行中的鏈上不能更改。rand-guest 按 --max-words(預設為 4,096)檢查。rand program deploy 以 rand_estimateFee 向節點查詢,並在生成任何證明之前拒絕超出上限的程式。

現行鏈 chain 20 提高了上限。其創世雜湊為 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135,max_program_words 為 65,535。它沿用 chain 13(v0.4 鏈)引入的各項限制,惟證明上限除外,chain 17 至 20 將其減半。其他限制如下:

限制chain 20
max_proof_bytes4,194,304(4 MiB);chain 13 至 16 為 8 MiB
max_block_bytes20,971,520(20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,768

**在 chain 20 上,一如 chain 19,字數上限並非起約束作用的限制。**其創世設定了 hardening_v6,據此,當沒有任何呼叫能夠證明該程式時,部署會被拒絕(ProgramUncallable)。每次呼叫以 tier 14 為上限,其 Poseidon2 表容納 2,048 次置換。程式摘要每四個字佔用一次,輸入的 salt 行佔用一次,八個呼叫綁定字佔用兩次,餘下 2,045 次:沒有公開輸入的程式為 8,180 字,附帶公開輸入時則更少(有九個公開字時為 8,168)。在任何鏈上,每個節點的交易池都會拒絕這類部署。

chain 20 上的區塊聚合已關閉:其創世沒有 aggregation 部分。chain 20 自 2026-10-01 06:17 UTC 起在 fullnode v0.6.8 上運行,是由 chain 19 在區塊高度 7,258 的狀態分叉而成的硬分叉(chain 19 停在 7,700);chain 19(同日 03:43 UTC 起運行)本身是 chain 18 在區塊高度 141,935 的重新創世。程式不會跨越切換而延續,因此在較早的鏈上部署的程式,在 chain 20 上並不存在。chain 20 亦是首條具備 RPL-2 程式狀態的鏈(程式)。它透過以下引導節點接入網絡:

/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
映像字數符合 4,096符合 65,535可在 chain 20 部署(可呼叫上限 8,180)
fib(Rust)26是是是
c-fib(C)25是是是
ERC-20,經轉譯(第二階段)11,686否是否
evm.bin,EVM 解釋器18,009否是否
sbpf.bin,sBPF 解釋器8,317否是否
SPL Token,經轉譯65,096否是,尚餘 439否

Tier

證明的 tier t 將運行限制在 2ᵗ − 1 行以內:包括週期數,加上摘要行(每四個程式字一行、每四個輸入字一行,以及公開輸入摘要)。tier 亦設定一個獨立的 Poseidon2 預算。rand-guest run --tier t 會報告兩者:

tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)

tier 是公開的,證明所宣告的 gas 上限亦然。在 chain 20 上,呼叫費用取決於該上限及證明的大小,而非 tier:至少為 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉,按鏈當時的價格計算;價格起始為 100 及 800 個 10⁻⁹ RAND 單位,隨區塊飽和度變動,在 chain 20 上以 10,000 及 80,000 個單位為限(費用)。上限不得超過區塊頭的最高值,即 (2ᵗ − 1) + 2ᵗ⁻²,另加證明所宣告的每個 Keccak 區塊 191 及每個 SHA-256 區塊 63:在沒有哈希表時,tier 10 為 1,279,tier 14 為 20,479。rand-guest run 報告週期數及置換次數,不報告 gas。rand call 會先運行程式,並在生成證明之前印出其耗用的 gas 及所宣告的上限,預設為將實際 gas 向上取整至下一個 2ᵗ⁻² 的倍數:

gas: 5636 (declaring 6144, tier 12)

該行來自 2026-09-30 在 chain 18 上的一次呼叫(匿名投票)。在沒有創世 gas 部分的鏈上,即 chain 15 及更早的鏈,費用按 tier 階梯計算:0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND。證明所需的記憶體隨 tier 急劇上升;參閱驗證者及聚合者硬件。

自 fullnode v0.5.6(2026-09-25)起,一次呼叫最多只可宣告 tier 14:節點在建立驗證密鑰之前,會以 CallTierTooHigh 拒絕更高 tier 的呼叫證明,載有此類證明的區塊亦屬無效。tier 14 的預算為 16,383 行及 2,048 次 Poseidon2 置換,而程式自身的摘要每四個字約耗用一次置換,因此超過 8,180 字的程式無法被呼叫,chain 20 一如 chain 19,會拒絕其部署。以 Rust 或 C 撰寫程式逐步說明這項預算。

最後一次對照 fullnode 提交 c0878c3f(chain 21)核實的日期為 2026-10-12,並已核對 circuits `aeacf31` (`rand-guest/README.md`, `rand-guest/src/check.rs`, `research/docs/01-isa.md`, `guest-sdk/`), fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `ledger/program_state.rs`, `deploy/cut-chain20-genesis.sh`), chain 20's genesis and `rand_getLimits`, read on 2026-10-01, fullnode tag v0.8.0 `c0878c3f` (`docs/zkvm.md` §8a, constraint set 9), `deploy/genesis-chain21.json` and `rand_getLimits` on chain 21, read on 2026-10-12。 如代碼與本頁內容有出入,以代碼為準;請告知我們。