任何屬 RV32IM 機器碼的程式均可部署,無論其由何種工具產生。本頁介紹兩條路徑:透過 rand-guest build --lang c 編譯 C 程式,以及以彙編語言自行撰寫並自行打包的程式。兩者最終都會得到與 部署 Rust 程式 所產生的相同映像容器,並同樣以 rand program deploy 部署。請先按該頁的第 1 步設置 circuits 檢出目錄及 rand-guest。對於鏈 18 至 20,該檢出目錄位於提交 aeacf31,fullnode v0.6.7 與 v0.6.8 均固定使用此提交;fullnode v0.8.0(鏈 21)則固定使用 d217252(約束集 9),在任一提交下構建的映像由相同的字組成,因此可憑 v0.8.0 錢包部署到鏈 21。以下輸出於 2026-09-19 在 7ef3220 產生,其後並未重新運行。rand-guest 目錄(包括 guest.h 與 rt.c)在兩個提交中均相同。
透過 clang 編譯 C
所需工具
| 工具 | 版本 | 方式 |
|---|---|---|
| clang | 23.1.1,附 RISC-V 後端 | 在 macOS 上執行 brew install llvm;Apple 的系統 clang 不含 RISC-V 後端。或將 $CLANG 設為 23.1.1 版的二進位檔 |
rust-lld | 來自 Rust 1.98.1 | rustup +1.98.1 component add llvm-tools |
rand-guest 會拒絕任何版本並非 23.1.1 的 clang。RAND_GUEST_CLANG_UNPINNED=1 可讓它以其他版本構建,但會發出警告,指出 hc 將與已發布的映像不一致。
撰寫 guest
C guest 目錄只包含 .c 檔案(以及可選的 .ld)。rand-guest 會提供 guest.h、_start 及一個小型運行時。請勿自行加入 guest.h 或 start.S:只要目錄中有其中任何一個,build --lang c 便會拒絕處理。
以下為已提交的 guests-compiled/c-fib/fib.c:
#include "guest.h"
static uint32_t fib(uint32_t n) {
uint32_t a = 0, b = 1;
for (uint32_t i = 0; i < n; i++) {
uint32_t c = a + b;
a = b;
b = c;
}
return a;
}
void main(void) {
rand_write_output(0, fib(rand_read_input(0)));
rand_halt();
}
guest.h 聲明了 rand_read_input、rand_read_public、rand_write_output、rand_poseidon2、rand_keccak、rand_sha256_compress 及 rand_halt。它沒有為系統調用 7 提供封裝;該調用即長度受限的 POSEIDON2_LEN,機器自約束集 7 起已運行此調用,而 aeacf31 的 rand-guest check 會拒絕 a7 = 7 的 ecall(zkp-circuits issue #1)。因此,供鏈 20 或 21 使用的 C 或彙編 guest 只能使用系統調用 0 至 6(系統調用 8,即 MODQ,屬於約束集 9,僅供多重簽名授權 guest 使用)。
構建、運行、檢視
$ rand-guest build --lang c guests-compiled/c-fib --out c-fib.bin
25 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote c-fib.bin and its .sha256 (25 words, hc 76e723cb4c218f9a7f657f39b9f95ee2353d8fbeb601aaea394afa29928f3aee, program id c213cf5c94679cab4585ed1e81b61736a7d2fe5605dc7884e4acec4144665999)
$ rand-guest run c-fib.bin --input 20 --tier 10
out[0] = 6765
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 116
tier 10
tier 10: fits (cycles 126 of 1023, Poseidon2 permutations 10 of 128)
fib(20) = 6765,與 Rust fib guest 的結果相同。
build --lang c 會編譯目錄中的每個 .c 檔案(按排序順序),連同內置的 start.S 與 rt.c,編譯選項如下:
clang --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax
-nostdlib -ffreestanding -fno-builtin -ffunction-sections -fdata-sections -Os
-ffile-prefix-map=<checkout>=/rand-circuits -c
然後以固定版本 Rust 工具鏈中的 rust-lld -flavor gnu --gc-sections -T <ld> 進行鏈接。--gc-sections 只保留 guest 實際調用的部分。
C 運行時
-ffreestanding -fno-builtin 可阻止 clang 識別庫函數,卻無法阻止它生成對這些函數的調用。結構體複製會變成 memcpy,大型零初始化會變成 memset,而以運行時數值進行的 64 位除法則會變成輔助函數調用,因為 RV32IM 只能對 32 位數值作除法。rt.c 定義了以下八個函數:
| 函數 | 用途 |
|---|---|
memcpy、memmove、memset、memcmp | 複製、填充、比較 |
__udivdi3、__umoddi3、__divdi3、__moddi3 | 64 位 / 與 % |
它們是逐字節循環與移位減法除法,速度較慢:在熱點循環中,請以 32 位字為單位進行複製與除法。
手寫程式
你亦可自行撰寫機器碼:將其彙編,以同一個鏈接腳本鏈接,再打包 ELF。此程式將兩個私有輸入相加,並公開其總和。
add.S:
# Reads private inputs 0 and 1, writes their sum to output slot 0, halts.
.section .text._start
.globl _start
_start:
li a7, 2 # READ_INPUT
li a0, 0
ecall # a0 = input 0
mv t0, a0
li a7, 2 # READ_INPUT
li a0, 1
ecall # a0 = input 1
add t1, t0, a0
li a7, 1 # WRITE_OUTPUT slot, word
li a0, 0
mv a1, t1
ecall
li a7, 0 # HALT
ecall
請在每次 ecall 之前,緊接以 li a7, n 設定其系統調用編號,檢查器便能驗證該編號。
以固定版本的 clang 彙編,以 rust-lld 及 guest-sdk/guest.ld 鏈接,然後打包。請在 circuits 檢出目錄內運行:
CLANG=/opt/homebrew/opt/llvm/bin/clang
LLD=$(rustc +1.98.1 --print sysroot)/lib/rustlib/$(rustc +1.98.1 -vV | sed -n 's/host: //p')/bin/rust-lld
$CLANG --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax -c add.S -o add.o
$LLD -flavor gnu -T guest-sdk/guest.ld add.o -o add.elf
$ rand-guest pack add.elf --out add.bin
wrote add.bin (80 bytes) and add.bin.sha256
$ rand-guest check add.bin
14 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
$ rand-guest run add.bin --input 40 --input 2
out[0] = 42
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 14
tier 10
$ rand-guest info add.bin
form: image container
text 14 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 14 words from base_pc 0x1000
hc 42986ac1f93396d7de75f6757149523e7f1e9c5999d748af9b1915ada8ee14bd
program id 3b34c9d16adbd81a4849148675a914d10e5da0bd5b6aa64967e5f8a5fc75d941
14 words against a cap of 4096: fits
pack 會將 ELF 的 .text 作為文本段,並將其餘每個已分配的 PROGBITS 節合為一個數據段。.bss 不計在內,因為 RAM 由零開始。
手動打包容器
映像容器由六個標頭字組及隨後的字組本身構成(RISC-V ISA)。你無需任何工具鏈即可寫出一個。以下 Python 腳本寫出的 80 字節,與上文 pack 所寫出的相同:
import struct
text = [
0x00200893, # li a7, 2 READ_INPUT
0x00000513, # li a0, 0
0x00000073, # ecall a0 = input 0
0x00050293, # mv t0, a0
0x00200893, # li a7, 2 READ_INPUT
0x00100513, # li a0, 1
0x00000073, # ecall a0 = input 1
0x00a28333, # add t1, t0, a0
0x00100893, # li a7, 1 WRITE_OUTPUT
0x00000513, # li a0, 0 slot 0
0x00030593, # mv a1, t1 word
0x00000073, # ecall
0x00000893, # li a7, 0 HALT
0x00000073, # ecall
]
# magic ("RAND") version text_base n_text data_base n_data
header = [0x444E4152, 1, 0x1000, len(text), 0, 0]
with open("add-hand.bin", "wb") as f:
f.write(struct.pack(f"<{len(header) + len(text)}I", *header, *text))
rand-guest info add-hand.bin 會印出相同的 hc 42986ac1…14bd,而 cmp add-hand.bin add.bin 找不到任何差異。對手動打包的映像,務必運行 rand-guest check:節點會拒絕任何無法解碼的字組,但只會在部署時才拒絕。
錢包亦支援部署兩種較舊的形式:.json 檔案 { "base_pc", "words" },以及以小端序字組組成、載入於基址 0 的原始 .bin。只有容器這種形式含有數據段。
部署與調用
以上每條路徑的結尾都相同。使用 fullnode v0.3 或更新版本的 rand 錢包(在自 2026-10-12 起運行的鏈 21 上,則須使用 v0.8.0 的 rand:舊版錢包無法在該鏈上發送交易,因為鏈 21 運行約束集 9,舊版錢包無法產生其證明;並且與鏈 20 一樣,會將每個證明與簽名綁定至其創世哈希;參見部署 Rust 程式):
rand program deploy add.bin
rand call <program id> --input 40 --input 2
部署費用為 0.001 RAND + 0.0001 RAND × words:add.bin 的 14 個字組為 0.0024 RAND,c-fib.bin 的 25 個字組為 0.0035 RAND。兩者均少於 4,096 個字組,因此符合任何鏈的上限:預設的 4,096,以及鏈 21 的 65,535。調用在本地以層級 10 生成證明。在鏈 15 上,其費用為 0.002 RAND。自鏈 20 起,費用不再是 0.001 + call_fee(tier):下限為 BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉,rand call 會在生成證明之前印出 gas: <exact> (declaring <limit>, tier <t>) 及費用,而在鏈 18 上實測的四次調用,每次費用為 0.0057 至 0.0075 RAND(示例程式)。自鏈 20 起,每次部署與調用亦附帶兩個捆綁包端證明,即一個層級 10 的花費授權證明與層級 14 的捆綁包證明。部署 Rust 程式說明了各命令的作用。
如要讓他人核實你所部署的內容,請公開源代碼及你的 circuits 提交(鏈 18 至 20 為 aeacf31)。使用 clang 23.1.1 與 Rust 1.98.1 時,rand-guest build 在任何機器上都會得出相同的 hc。