在撰寫 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 |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL 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 會把每個文字字詞交給機器自身的解碼器處理。通過檢查的程式,不會因編碼、系統呼叫編號或佈局方面的原因而在電路中失敗。檢查器會拒絕下列情況:
| 發現 | 含義 |
|---|---|
Compressed | 16 位元的 RVC 編碼;機器只解碼 32 位元字詞 |
Undecodable | 機器未實作的操作碼、funct 或移位量 |
Fence | FENCE 或 FENCE.I;機器沒有記憶體排序 |
Csr | 任何 CSR 指令;機器沒有 CSR |
Ebreak | EBREAK;不對陷阱建模 |
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) |
|---|---|---|---|---|---|
| 0 | HALT | 結束運行 | 1 | halt() | rand_halt() |
| 1 | WRITE_OUTPUT slot word | 設定公開輸出 slot < 8,每個槽位至多一次;未寫入的槽位為零 | 1 | write_output(slot, word) | rand_write_output |
| 2 | READ_INPUT idx | 回傳私有輸入字詞 idx,綁定於加鹽承諾 H_IN | 1 | read_input(idx) | rand_read_input |
| 3 | POSEIDON2 ptr n | 對位於字位址 ptr 的 n ≤ 4096 個字詞進行雜湊,並把 8 個字詞的摘要寫在 ptr..ptr+8 之上;不填充 | 3 + 3·⌈n/4⌉ | unsafe poseidon2(ptr, n) | rand_poseidon2 |
| 4 | KECCAK ptr | 對 50 個字詞就地進行一次 Keccak-f[1600] 置換 | 192 | keccak(state)、keccak256(msg) | rand_keccak |
| 5 | SHA256 ptr | 對 24 個字詞就地進行一次 SHA-256 壓縮 | 64 | sha256_compress(buf)、sha256(msg) | rand_sha256_compress |
| 6 | READ_PUBLIC idx | 回傳公開段字詞 idx,綁定於未加鹽的 H_PUB | 1 | read_public(idx) | rand_read_public |
| 7 | POSEIDON2_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 }:
- 為每個非零資料字詞寫入一對
li/sw。零字詞會被略過,因為 RAM 本來就讀為零。 - 把該前導碼直接置於
text_base之下,因此base_pc = text_base − 4 × 前導碼字詞數。前導碼會直接接續到程式自身的_start。 hc與程式 id 是針對整個words(包括前導碼)計算的。因此hc既綁定程式碼,也綁定資料。
不含資料的容器沒有前導碼,其程式和 hc 與同一基址的無標頭平坦二進位檔相同。
fullnode v0.3 及以後版本中的 rand program deploy 憑檔案的第一個字詞辨識容器。0x444e4152 從不會被解碼為指令,因此原始 .bin 不會被誤認為容器。若檔案以該魔數開頭但格式有誤,結果是錯誤,而不是退回其他處理。
兩種身分
| 名稱 | 說明 | 使用者 |
|---|---|---|
hc | 電路對 (base_pc, len, words) 計算的 Poseidon2 摘要 | 每一個呼叫的證明都以它驗證;rand_getProgram 將它顯示為 code_hash |
| program id | blake3("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_bytes | 4,194,304(4 MiB);chain 13 至 16 為 8 MiB |
max_block_bytes | 20,971,520(20 MiB) |
max_call_envelope_bytes | 65,536 |
max_program_public_words | 32,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 撰寫程式逐步說明這項預算。