Rand Protocol
文檔
RISC-V 程式

Solana 轉譯器

sbpf2rv 可將 Solana sBPF 程式轉譯為 Rand 的 RISC-V ISA:本文說明其定位、信任模型與 ELF 防護、哪些情況會觸發 trap 與哪些會被拒絕、實測數據,並附完整示範,逐步轉譯真實的 SPL Token 程式,其結果與 sBPF 直譯器逐字相符。

sbpf2rv 會讀取已編譯的 Solana 程式(即 cargo build-sbf 產生的 sBPF ELF,副檔名為 .so),並將其轉換為 Rand 程式。它輸出 C 代碼,再由 rand-guest build 將該 C 代碼編譯為 RV32IM 映像。對於同一個 ELF 和同一條指令,該映像所發佈的八個輸出字,與 Rand 的 sBPF 直譯器 guest(sbpf.bin)所發佈的相同。本頁以真實的 SPL Token 程式展示完整流程。

簡介

輸入一個 sBPF v1 ELF
輸出一個 C shim crate;rand-guest build 會將其製成映像容器
運行位置鏈下,在開發者的機器上;鏈上只會看到映像
必須吻合的對象sbpf.bin 在相同兩條輸入磁帶上的八個輸出字
代碼庫circuits 代碼庫中的 sbpf2rv/
固定版本的工具鏈Rust 1.98.1、clang 23.1.1、cc crate 1.4.6

轉譯後的程式沿用直譯器的 ABI。它讀取相同的輸入磁帶:公開磁帶載有 ELF,私密磁帶載有序列化後的指令(先是帳戶,再是指令數據)。它在程式外圍運行同一套 sbpf-core 測試框架:解碼輸入、載入 ELF、檢查帳戶區域,並對輸入和輸出計算哈希。被替換成原生代碼的,只有 sBPF 的執行本身。因此,它的輸出與直譯器逐字相同。

信任模型

轉譯後程式的證明由三項要素約束。

  1. hc 約束轉譯後的代碼。 C 代碼已編譯進映像,因此映像的 hc 涵蓋這些代碼。
  2. ELF 守衛將 hc 約束於單一程式。 shim 從磁帶上的 ELF 載入程式,若不加檢查,同一個映像便可在相同的 hc 下運行由調用者任選的另一個 ELF。因此,生成的 program.c 末尾附有一個摘要,對應其轉譯來源的已載入程式:文本、唯讀數據、它們的地址以及入口點。在運行任何內容之前,shim 會以同樣方式(Poseidon2)對磁帶上已載入的程式計算哈希並作比較。任何其他程式都會以 BadElf、狀態 2 中止,並停留在前狀態(pre-state)。所以,只要證明能針對此 hc 通過驗證,它就是該程式的一次運行。
  3. H_PUB 約束 ELF 的位元組。 ELF 經由公開磁帶傳送,證明的公開輸入摘要逐字涵蓋它。

hc 是否忠實轉譯自某個 ELF,可透過重新構建來檢驗:對該 ELF 運行固定版本的 sbpf2rv,以固定版本的工具鏈構建,再比較 hc。正因如此,構建過程是可重現的。

鏈上不會記錄源 ELF 或其哈希,只會儲存映像及其程式 id。希望公開 ELF 的部署者,可將其與程式一併發佈。

守衛約束的是已載入的程式,而非檔案本身。兩個 ELF 若只在載入器不會檢視之處有所不同(標頭填充、符號表),便會載入為同一個程式,並在兩邊運行得完全一致。ELF 中只要有一個 .rodata 位元組被改動,即使轉賬從不讀取該位元組,轉譯結果也會拒絕它,而直譯器則會運行它。

哪些會觸發陷阱,哪些會被拒絕

轉譯時不會拒絕任何東西。 格式錯誤的 ELF 會以載入器自身的訊息拒絕,但格式正確的 ELF 中,沒有任何一條指令會被拒絕。掃描器會把程式可能遇到的情況以警告報告,而轉譯後的程式會在運行時,於直譯器會觸發陷阱的同一位置觸發陷阱:

警告含義運行時
UnknownSyscall運行時未實現的系統調用哈希觸發 UnknownSyscall 陷阱
Cpi跨程式調用(sol_invoke_signed*)同樣的 UnknownSyscall 陷阱;CPI 尚未實現
RegisterOutOfRange寄存器 r11..r15,或高於 r10 的 callx 寄存器觸發 BadInsn 陷阱
UnknownOpcode不屬於任何 v1 類別的操作碼,包括僅見於 v2 的 sdiv/srem 系列和 hor64觸發 BadInsn 陷阱
JumpOutOfText跳轉、調用或順序執行落在函數文本之外觸發 BadJump 陷阱
BadCallImmSrcsrc 為 2..=10 的 call imm(僅見於手工構建的 ELF)觸發 BadInsn 陷阱

若轉譯器拒絕從未執行到的代碼,就會拒絕直譯器現時能夠運行的程式。SPL Token 便是這樣的程式:它調用了 sol_set_return_data 和 sol_get_sysvar,而 Transfer 路徑從未執行到這兩者。

有三項差異是可接受的。每一項都只會令轉譯結果在直譯器未必中止之處中止;轉譯結果絕不會完成一次直譯器會中止的運行。

  1. 掃描集合之外的 callx 會觸發 BadJump,而直譯器可能會在該地址運行真實的代碼。
  2. 預算檢查可能順延一個基本塊。 為使 SPL Token 符合 65,535 字的上限,其 3,546 個基本塊中有 1,505 個略過自身的指令數上限檢查。在跨越上限的那一個基本塊內,中止的類別可能不同,但狀態和八個字相同。
  3. 只有源程式會運行。 其他 ELF 會被拒絕(見上文的守衛)。

Ed25519 和 secp256k1。 sol_ed25519_verify 和 sol_secp256k1_recover 的軟件實現存在於 sbpf-rt/,但並未鏈接。直譯器對兩者都會觸發陷阱,因此為求一致,轉譯結果也會觸發陷阱。每次調用分別約需 14.3M 和 15.7M 個週期,遠高於最大等級(2²⁰)。它們需要協處理器表。

數據

以下是在 SPL Token(guests-compiled/sbpf/programs/spl_token.so,直譯器自身的測試固件)上、並已啟用 ELF 守衛的實測結果。

映像程式字數上限hc
SPL Token,已轉譯65,09665,535(尚餘 439)8ca905ae…758b
sbpf.bin,直譯器8,31765,535059a7086…f476
向量狀態sBPF 指令數轉譯後週期數sbpf.bin 週期數等級(兩者)
Transfer 2501143765,851694,49820
Transfer 超出餘額0133748,836679,81420
MintTo 2501120708,648634,42320
MintTo 由錯誤的授權者發出0134696,694627,24120
Burn 2501131710,245637,03820
Burn 超出餘額0121695,434624,78120
Transfer 只帶兩個帳戶069645,015569,65620
帳戶過多(測試框架拒絕)20303,933304,61620

Transfer 的週期花在哪裏:

階段轉譯後sbpf.bin
讀取兩條磁帶302,881302,880
載入 ELF177,012173,859
檢查帳戶區域103,829103,824
將 sBPF 堆疊和堆積清零49,18149,181
輸出和輸入哈希、公開字、膠合代碼51,09142,723
ELF 守衛72,949—
sBPF 程式本身8,90822,031
合計765,851694,498

就每條 sBPF 指令而言,轉譯後程式自身的執行成本低約 2.4 至 2.5 倍(約 62 個週期,對比 154 個)。但每次運行約 98% 是固定的測試框架開銷,而守衛另加 73k 個週期。因此,對 SPL Token 而言,轉譯後的映像反而貴 69k 至 75k 個週期,兩者同樣停留在等級 20。

轉譯只對運算密集的程式有利。 以每條指令節省約 90 個週期計算,一次執行 10,000 條 sBPF 指令的調用可節省約 0.9M 個週期。SPL Token 只執行 120 至 143 條。

逐步示範:SPL Token 的轉譯與比對

所有命令均在 circuits 檢出目錄的根目錄下、於 7ef3220 版本運行,工具鏈取自部署 Rust 程式,clang 23.1.1 取自部署 RISC-V 程式。冗長的輸出在標示 … 之處截去。

0. 構建工具

(cd sbpf2rv && cargo +1.98.1 build --release)
(cd rand-guest && cargo +1.98.1 build --release)

1. 轉譯

--out 必須是 circuits 檢出目錄內的一個目錄。

$ sbpf2rv/target/release/sbpf2rv guests-compiled/sbpf/programs/spl_token.so --out sbpf2rv/target/spl-token --name spl-token
entry pc 225: 30 function(s), 3546 block(s), 12061 instruction(s), 30 callx target(s), 0 refusal(s), 4 warning(s)
  fn 225: 2065 block(s)
  …                                                (30 functions in all)
  warning: UnknownSyscall { pc: 7803, hash: 2720453611 }
  warning: UnknownSyscall { pc: 9530, hash: 2720453611 }
  warning: UnknownSyscall { pc: 8698, hash: 2720453611 }
  warning: UnknownSyscall { pc: 11547, hash: 331461893 }
wrote sbpf2rv/target/spl-token/program.c (521757 bytes of C) and the spl-token shim crate: rand-guest build sbpf2rv/target/spl-token

0 refusal(s)。四項警告都是 Transfer 從未執行到的真實系統調用:哈希 2720453611 是 sol_set_return_data(三個調用點),331461893 是 sol_get_sysvar。

2. 構建映像

映像超過預設的 4,096 字上限,因此須傳入 --max-words 65535。

$ rand-guest/target/release/rand-guest build sbpf2rv/target/spl-token --max-words 65535
…
65096 words against a cap of 65535 (fits); 2 ecall(s) with a non-static a7
OK
wrote sbpf2rv/target/spl-token/image.bin and its .sha256 (65096 words, hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b, program id 65d234ce9ca4d082f487c038abfa76e67f41a0d91e10b9ac388f1e1541d59184)

$ rand-guest/target/release/rand-guest info sbpf2rv/target/spl-token/image.bin
form: image container
text 64819 words at 0x10000; data 102 words (101 non-zero) at 0x4f4cc; prologue 277 words; program 65096 words from base_pc 0xfbac
hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b
program id 65d234ce9ca4d082f487c038abfa76e67f41a0d91e10b9ac388f1e1541d59184
65096 words against a cap of 4096: does not fit

只要以相同的工具鏈運行這兩個步驟,任何人都會得到這個 hc。

3. 兩邊運行並比對

250 的 Transfer 需要兩份字列表,由 tests/parity.rs 根據直譯器自身的調用編碼構建:27,151 個公開字(經字編碼的 ELF)和 10,458 個私密字(先是帳戶,再是 Transfer 的判別值和金額)。rand-guest run 以 --public 和 --input 接收它們。以下只列出每份列表的開頭幾個字:

$ rand-guest run sbpf2rv/target/spl-token/image.bin \
    --public 108600 1179403647 65794 0 0 17235971 1 2088 …   (27 151 words)
    --input  41825 4 0 65791 0 50529027 50529027 50529027 …  (10 458 words)
out[0] = 1
out[1] = 2892832079
out[2] = 376091303
out[3] = 1311040261
out[4] = 2015764099
out[5] = 3682593600
out[6] = 3311553006
out[7] = 141115266
cycles 765851
tier 20

同樣的兩份列表,送入直譯器 guest:

$ rand-guest run guests-compiled/bin/sbpf.bin \
    --public 108600 1179403647 65794 0 0 17235971 1 2088 …   (27 151 words)
    --input  41825 4 0 65791 0 50529027 50529027 50529027 …  (10 458 words)
out[0] = 1
out[1] = 2892832079
out[2] = 376091303
out[3] = 1311040261
out[4] = 2015764099
out[5] = 3682593600
out[6] = 3311553006
out[7] = 141115266
cycles 694498
tier 20

八個字完全相同。out[0] = 1 是狀態(成功)。out[1..8] 是直譯器的 ABI 對輸入和輸出所定義的摘要。

4. 以一條命令檢查全部八個向量

parity 測試會構建轉譯後的映像,在兩個映像上通過 rand-guest run 運行每個向量,並在八個字出現任何差異時失敗:

$ cd sbpf2rv && cargo +1.98.1 test --test parity the_spl_token -- --nocapture
…
translated SPL Token: 65096 program words (cap 65 535), hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b
transfer 250: Ok(0), status 1; cycles translated 765851, interpreter guest 694498
transfer too much: Ok(1), status 0; cycles translated 748836, interpreter guest 679814
MintTo 250: Ok(0), status 1; cycles translated 708648, interpreter guest 634423
MintTo 250 signed by someone else: Ok(4), status 0; cycles translated 696694, interpreter guest 627241
Burn 250: Ok(0), status 1; cycles translated 710245, interpreter guest 637038
Burn 1 000 001 (balance 1 000 000): Ok(1), status 0; cycles translated 695434, interpreter guest 624781
transfer with two accounts: Ok(47244640256), status 0; cycles translated 645015, interpreter guest 569656
an account count above MAX_ACCOUNTS: Err(BadElf), status 2; cycles translated 303933, interpreter guest 304616
…
test the_spl_token_transfer_translates_and_matches_the_interpreter_word_for_word ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 27.84s

5. 部署

rand program deploy sbpf2rv/target/spl-token/image.bin --public spl_token.so

映像從公開帶讀取 ELF,因此 ELF 是程式的公開輸入,於部署時固定。--public 接受 .so 檔案本身;錢包會把它編碼成與步驟 4 相同的 27,151 個字。

這需要一條創世時把 max_program_words 設為至少 65,096、並把 max_program_public_words 設為至少 27,151 的鏈。Chain 12 把一次部署的上限定為 4,096 個字,且不接受公開輸入,因此錢包在該鏈上尚未進行任何證明便拒絕了此映像。Chain 13 即 v0.4 鏈,把兩項上限設為 65,535 和 32,768,其後每條鏈都沿用這兩個數值。現行的 chain 20(創世 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135)同樣沿用 chain 13 的區塊與呼叫信封上限,並把證明上限減半至 4 MiB(4,194,304 位元組,自 chain 17 起;chain 13 至 16 設為 8 MiB)。大小上限已不是唯一的關卡。Chain 20 的創世啟用了 hardening_v6,在此設定下,若部署的程式字數超出一次呼叫在 tier-14 呼叫上限下所能容納的數量,該部署即屬無效(ProgramUncallable):在沒有公開輸入時最多 8,180 個字,附帶公開輸入時則更少。此映像為 65,096 個字,另附 27,151 個字的公開輸入,因此 chain 20 會拒絕這次部署,也會拒絕相應的呼叫:每個 SPL 向量都在 tier 20 運行,高於 tier-14 呼叫上限(限制)。Chain 20 未啟用區塊聚合(其創世沒有 aggregation 一節);其引導節點列於運行驗證者。部署費用把公開字與程式碼字一併計算:0.001 + 0.0001 × (65,096 + 27,151) = 9.2257 RAND,即下文 chain 13 部署所支付的費用。

此次部署於 2026-09-19 在 chain 13 上進行,當時 chain 13 為現行鏈(正式 FRI 配置,創世 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3),由創世錢包 shielded-1 發出。其後每條鏈(14 至 19)都是不帶 chain 13 程式的全新鏈,此映像至今未在其中任何一條上重新部署,因此下文的交易與高度屬於 chain 13,已無法在區塊瀏覽器上查到。程式 id 僅是 base_pc、程式字與公開字的哈希,所以相同的映像與 ELF 在任何接納該部署的鏈上都會得到相同的 id。以下是錢包的輸出,進度行已省略(…):

$ rand program deploy <image.bin> --public spl_token.so
program id: 740236918310f8e52bb0c1ef49b2b0e0c018762e289666660685b0694c8dd00a (65096 words, hc ae05a98c03f5623c68e87dde320f0429538b093efeffa5db1a2fb4098b7516ad, public input 27151 words, digest ec57b10ed6f3a3d5fe87ec072f691322ad747377ba2f0a1788bcfe3e7334d67e)
…
proved in 94.3s: tier 14, 1418406 bytes
submitted deploy 626fc9387ecc1f6b39f44184807303ac2843029d8fb8e28338bf186445927530
…
程式 id740236918310f8e52bb0c1ef49b2b0e0c018762e289666660685b0694c8dd00a
映像65,096 個字;在 rand-guest 的寫法中為 hc 8ca905ae…,在錢包的寫法中為 ae05a98c…
公開輸入27,151 個字(即 ELF),摘要 ec57b10ed6f3a3d5fe87ec072f691322ad747377ba2f0a1788bcfe3e7334d67e
費用 bundle(chain 13 的 2 入 2 出 bundle)tier 14,1,418,406 位元組,證明耗時 94.3 秒
部署交易(chain 13)626fc9387ecc1f6b39f44184807303ac2843029d8fb8e28338bf186445927530
錨定高度(chain 13)400
費用9.2257 RAND

自 chain 14 起,支付費用的 bundle 是每筆交易都附帶的那個 4 入 4 出隱藏資產 bundle,針對該交易的綁定進行證明。它仍為 tier 14:在 chain 14 和 15 上,以正式配置計約 100 秒、約 1.42 MB。自 chain 17 起,bundle 還附帶一個 tier-10 的授權證明(拆分授權),因此在 chain 18 和 19 上,一筆交易的費用 bundle 約為 1.49 MB 的 bundle 證明加約 1.37 MB 的授權證明。

程式 id 綁定 ELF,因此與 rand-guest info 為裸映像印出的 id 不同。請使用 deploy 印出的 id 進行呼叫。

限制

  • 尚未證明任何對此程式的呼叫,它沒有部署在 chain 20 上,而且 chain 20 同時拒絕部署與呼叫。 上述部署是在 chain 13 上進行的。在 chain 20 的 hardening_v6 下,部署的字數若超出 tier-14 呼叫所能容納的數量即屬無效(ProgramUncallable;最多 8,180 個字),而此映像有 65,096 個字。輸入方面則可容納:ELF 是部署的公開輸入,由 rand call 自行取得,而 10,458 個私有字也在 16,071 的輸入上限之內,chain 20 保留此上限。但每個 SPL 向量都是 tier 20,而自 fullnode v0.5.6(2026-09-25)起,節點會在建立驗證金鑰之前,拒絕任何高於 tier 14 的呼叫證明(CallTierTooHigh)。同一項檢查也限制一次呼叫可宣告的哈希表:2¹³ 行 SHA-256(128 次壓縮)和 2¹² 行 Keccak(128 次置換)。tier-20 的呼叫只能在鏈下證明,而目前尚不存在 tier-20 的證明(見下一點)。
  • 尚未產生任何 tier 20 的證明。 一次 tier-20 的證明在一部 48 GB 的筆記型電腦上於約 31 GB 時被終止,在一部 64 GB 的 droplet 上則於 10 分 41 秒後在 65.1 GB 時被 OOM 終止。根據實測的 tier-16/18 縮放比例推算(每增加 2 個 tier,記憶體約 ×3.9,時間約 ×4.1),tier 20 需要約 330 GB 和約 3.6 小時,超過 DigitalOcean 最大的記憶體型 droplet(m-32vcpu-256gb,256 GB)。證明者以單執行緒運行:一部 16 vCPU 的機器只有一個核心處於 99% 的使用率。參見證明記憶體。
  • 不支援 CPI。 跨程式調用會觸發 trap。
  • 僅支援 sBPF v1。 僅 v2 才有的運算碼會觸發 trap。
  • Ed25519 和 secp256k1 系統呼叫會觸發 trap,直至它們有協處理器表為止。
  • 鏈上只把 ELF 記錄為公開輸入。 帶 --public 的部署會儲存 ELF 的字及其摘要,並由 rand_getProgramPublic 提供。鏈上不記錄映像是如何由它建構出來的:請公開建構輸入,以便他人重新建構 hc。

最後一次對照 fullnode 提交 c0878c3f(chain 21)核實的日期為 2026-10-12,並已核對 circuits `7ef3220` (`sbpf2rv/README.md`, `docs/superpowers/specs/2026-09-18-sbpf-to-rv32-translator-design.md`); steps 1, 2 and the parity test re-run on 2026-09-19。 如代碼與本頁內容有出入,以代碼為準;請告知我們。