このページでは、プログラムをオンチェーンに置いて呼び出すまでの流れを説明します。プログラムとは何か、どう作るか、呼び出しが何を公開し何を秘匿するか、レシートの読み戻し方、そして直面する制限です。すべてのプログラムは公開コードであり、すべての呼び出しは証明です。Example programsには完全なアプリケーションがあり、randprotocol/example-programsには chain 20 にデプロイ済みのプログラム(RPL-2 のものを含みます。後述)のソースがあります。
プログラムとは
プログラムとは、Rand zkVM 向けの RV32IM マシンコードです。形式は { base_pc: u32, words: Vec<u32> } で、語数はチェーンのプログラム上限以下とします(chain 20 では 65,535 語で、chain 13 以降のすべてのチェーンと同じです。chain 12 のように max_program_words のない genesis では 4,096 語です)。base_pc は 4 の倍数で、すべての語が命令としてデコードできなければなりません(デコードできない語は、実行時ではなくデプロイ時に拒否されます)。chain 20 は chain 19 と同様に、どの呼び出しも保持できないプログラムのデプロイも拒否します。公開セグメントがなく 8,180 語を超えるもの、あるいは公開セグメントがあってそれより少ない語数のものです(後述)。プログラムは内容アドレス方式で、不変です。
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
呼び出しとは、ダイジェストが hc のプログラムが、ある秘匿入力に対して実行され、宣言されたガス上限を超えて消費せず、8 個の出力語を公開したことを示す STARK 証明です。ノードは証明を検証してレシートを記録し、それ以外には何も起こりません。出力はデータであり、効果ではありません。呼び出しによって RAND が移動することはなく、価値を動かすのは呼び出しの手数料を支払うバンドルにおける呼び出し元です。プログラムの出力が何を意味するかは、プログラムと呼び出し元の取り決めで決まります。chain 20 では Invoke が例外で、その証明は宣言された状態遷移を保証し、台帳がそれを適用します(Program state (RPL-2))。
ゲストにできること:
| グループ | 命令 |
|---|---|
| 上位即値 / ジャンプ | 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。FENCE、CSR、EBREAK、A 拡張、C 拡張は使えません |
システムコールは a7 の番号で指定します。
| # | システムコール | 効果 |
|---|---|---|
| 0 | HALT | 実行を終了します |
| 1 | WRITE_OUTPUT slot word | 公開出力 slot < 8 を確定します。各スロットへの書き込みは 1 回までで、書き込まれなかったスロットは 0 です |
| 2 | READ_INPUT idx | ソルト付きコミットメント H_IN に束縛された秘匿入力語 idx を返します |
| 3 | POSEIDON2 ptr n | n ≤ 4096 語を Poseidon2 スポンジでその場でハッシュします |
| 4 | KECCAK ptr | Keccak-f[1600] の置換を 1 回その場で実行します。証明に keccak テーブルが含まれるようになります |
| 5 | SHA256 ptr | SHA-256 の圧縮を 1 回その場で実行します。証明に sha256 テーブルが含まれるようになります |
| 6 | READ_PUBLIC idx | 公開セグメントの語 idx を返します。chain 20 では公開セグメントはデプロイ時に固定されます(rand program deploy --public)。max_program_public_words のない genesis では空で、何も読み出せません |
| 7 | POSEIDON2_LEN ptr n | メッセージ長をスポンジに束縛した POSEIDON2 で、[a] と [a, 0] は異なるハッシュになります(制約セット 7、chain 16 以降)。circuits aeacf31 の rand-guest check は、これをいまも拒否します(次節) |
メモリは、x0 がゼロである 32 個のレジスタ、ゼロから始まる RAM、2³⁰ 未満のすべてのアドレス、そしてコンパイル済みゲスト用の 64 KiB のスタックです。rand-guest で作ったプログラムは、読み取り専用データと初期化済みデータをイメージコンテナに持ち、プログラム自身のコードが走る前に、プログラムの一部であるローダのプロローグがそのデータを書き込みます(The RISC-V ISA)。ノートコミットメント、ナリファイア、Merkle の検査はシステムコールではなく、POSEIDON2 上のライブラリルーチンです。
chain 18(制約セット 8)以降、すべての実行はガスで計量されます。通常の命令とシステムコール 0、1、2、6 は 1 ガスずつ、n 語に対する POSEIDON2 は 3 + 3 × ⌈n / 4⌉、KECCAK の置換 1 回は 192、SHA256 の圧縮 1 回は 64 です。一覧表と呼び出しが宣言する内容は Example programs にあります。
プログラムの作り方
方法は 3 つあり、いずれも RV32IM の語を生成します。
組み込みアセンブラ。 ウォレットには手書きでアセンブルしたゲストが 5 つ付属しており、それらを { "base_pc", "words" } 形式の JSON ファイルにアセンブルします。
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
rand-guest によるコンパイル済みプログラム。 circuits リポジトリの rand-guest は、no_std の Rust クレート(riscv32im-unknown-none-elf、Rust 1.98.1)または C ディレクトリ(clang 23.1.1)をイメージコンテナにビルドし、すべての語をマシンのデコーダで検査して、hc とプログラム id を出力します。ビルドはハーメティックなので、同じソースからは、どのマシンでも同じ hc が得られます。chain 18 から 20 では、circuits のコミット aeacf31(ブランチ feat/v067-circuits)でツールチェーンをビルドしてください。これは fullnode v0.6.7 と v0.6.8 がどちらも固定しているコミットです。このコミットの rand-guest check が列挙するのはシステムコール 0 から 6 のみなので、guest_sdk::poseidon2_len(システムコール 7)を呼ぶゲストは、マシンでは実行できるにもかかわらず、ビルド時に拒否されます(zkp-circuits issue #1)。手順は Deploy a Rust program と Deploy a RISC-V program を参照してください。
変換されたプログラム。 sbpf2rv は Solana の sBPF ELF を、evm2rv は EVM のランタイムバイトコードを、それぞれ変換して、出力が Rand のインタープリタ・ゲストと語単位で一致するプログラムにします。詳しくは The Solana transpiler と The Ethereum transpiler を参照してください。
ウォレット自体にコンパイラはありません。rand program deploy が受け付けるのは、.json、リトルエンディアンの語からなる生の .bin、または rand-guest が書き出すイメージコンテナです(fullnode v0.3 以降)。
デプロイ
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
testnet は chain 20 で、そこへのデプロイ、呼び出し、invoke には fullnode v0.6.8 以降の rand ウォレットが必要です。古いウォレットでは送信自体ができません。chain 20 の genesis は binding_domain: 1 を設定しているため、すべての証明バインディングと署名済みメッセージに genesis ハッシュが含まれますが、v0.6.7 のウォレットのバインディングが持つのは chain id だけだからです。公開エンドポイント https://rpc.randprotocol.org は、ウォレットの同期、デプロイ、呼び出しに対応しています(setup)。
ウォレットはまず、プログラム id、語数、hc を表示し、rand_estimateFee でノートに手数料を問い合わせます。この呼び出しは、証明を生成する前に、チェーンの上限を超えるプログラムを拒否します。次に、ゼロ価値の自己宛て転送バンドル(すべてのトランザクションが持つ、4 入力 4 出力の秘匿アセットバンドルで、手数料はその RAND スロットから支払われます)を構築します。このバンドルは、自身のノートから 0.001 RAND + 100,000 units × words を支払います(公開セグメントの語もコード語として数えます)。そこに Action::Deploy { base_pc, words, public } を付け、トランザクションのバインディングに対してバンドルを証明し、送信します。chain 17 以降は証明が 2 つになり、ウォレットはそれぞれについて 1 行ずつ表示します。tier-10 の支出承認(数秒、約 1.37 MB)と、tier-14 のバンドル(ノート PC で約 2 分、約 1.5 MB)です。chain 17 より前はバンドル証明のみで、chain 15 では約 100 秒、約 1.42 MB でした。デプロイ手数料にガス項やバイト項はありません。ガスが課金対象とするのは呼び出しだけです。ノードはすべての語がデコードできることを検査し、hc を計算してレコードを保存し、バックグラウンドで検証鍵の構築を始めます。コードは公開されますが、誰が支払ったかは公開されません。rand_getProgram には deployer フィールドがありません。同じ語列を 2 回デプロイしても冪等です。
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
呼び出し
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
ウォレットはノートからコードと公開セグメントを取得し、それらが id にハッシュされることを検査します。呼び出しをエミュレータで実行し、証明を生成する前にガスと手数料を表示します。
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
続いて、入力トランスクリプトを封印し(後述)、チェーンの FRI プロファイルのもとで、入力を秘匿ベクトルとして呼び出しをローカルで証明し、手数料を支払うバンドルを証明して送信し、待機して、レシートを表示します。chain 20 では、呼び出しの証明は自身のトランザクションに束縛され(genesis ハッシュを含むバインディング)、バンドルは 2 つの証明なので、呼び出しには 3 つの証明が伴います。呼び出し、tier-10 の支出承認、tier-14 のバンドルです。上の 2 行は、2026-09-30 の chain 18 における匿名投票の呼び出しのものです(An anonymous vote)。
手数料。 チェーン20での下限は BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉ です。BUNDLE_BASE は 0.001 RAND、バイト数は呼び出しの証明と入力エンベロープの合計です。価格の初期値はそれぞれ 100 と 800 ユニット(1ユニットは 10⁻⁹ RAND)で、ブロックの充填率に応じて動きます。チェーン20ではこれらの上限を 10,000 と 80,000 ユニットとし、バイト価格の負荷に数えるのはバイト価格を支払うバイト(呼び出しの証明とエンベロープ)だけです(byte_load: "paying")。チェーン18と19では価格に上限がなく、すべてのトランザクションのバイトがバイト価格を動かしていました。台帳の固定スケジュール 0.001 RAND + call_fee(tier)(チェーン15のルール)は、ここには適用されません。ウォレットは証明が存在する前に手数料を確定させるため、証明のサイズをチェーンの上限である 4 MiB として見積もり、さらに価格2段階分の余裕を加えます。チェーン18で実測した4件の呼び出しは、1件あたり 0.0057〜0.0075 RAND でした(計算の詳細)。rand fee call <tier> --gas <limit> --bytes <B> は、下限そのものをノードに問い合わせます。private_payment は4つのプライベート残高を読み取り、その合計がしきい値以上であれば超過分を公開します。出力は [1, 0, 25, 0, 0, 0, 0, 0] です。チェーンが目にするのは証明、tier、そしてこの8ワードだけで、残高そのものは決して目にしません。
フラグ:--tier T は tier を強制します。--gas-limit <N|max> は、既定値の代わりに正確なガスリミットまたは tier の上限を宣言します(既定値は、正確なガスを 2^(tier − 2) の倍数に切り上げたものです)。--fee <RAND> は手数料を設定します。--expect-public <FILE> は、プログラムの公開セグメントがそのファイルのワードと一致しない限り、証明の前に拒否します。--auditor <rand1…> は、指定した監査人向けにもトランスクリプトを封印します。--print-call-key は呼び出しごとの鍵を表示します。--no-envelope はトランスクリプトを公開しません。--cuda は GPU バックエンドを選択します(実機では未検証で、エンベロープの封印とは両立しません)。チェーン20は呼び出しでの --cuda を完全に拒否します。トランザクションに結び付けられた呼び出しの証明は、CPU バックエンドで生成されるためです。
レシート
レシートはコミットされた呼び出しごとに保存され、ノードが同期するとき、またはチェーンを検証するときに再計算されます。
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in は、プライベート入力に対する証明のソルト付きコミットメントであり、それ単体では何も開示しません。h_pub はプログラムのデプロイ時の公開セグメントのダイジェスト、すなわちプログラムの public_digest です。公開セグメントなしでデプロイされたプログラムでは null になります。effect フィールドはなく、宣言されたガスリミットもレシートには含まれません。エクスプローラーは、call に対する GET /transactions/:hash で同じオブジェクトを receipt として表示し、GET /programs/:id でプログラムの呼び出しを一覧し、GET /transactions?kind=call&program=<id> で絞り込みます。
入力エンベロープ:呼び出しを後から開示する
既定では、rand call は呼び出しの (salt, inputs) トランスクリプトを呼び出し入力エンベロープに封印して公開します。これにより、2つ目の証明を作らなくても、後から入力を示すことができます。
| 部分 | バイト | 内容 |
|---|---|---|
body | 12 + 16 + 4·n + 16 | 呼び出しごとの新しい鍵 K_call で暗号化した salt ‖ inputs |
to_sender | 60 | 呼び出し元の送信用ビューイングキーでラップした K_call |
kem_ct | 1,088 または 0 | 監査人宛ての ML-KEM-768 カプセル化。監査人がいなければ空 |
to_auditor | 60 または 0 | そのカプセル化の秘密値でラップした K_call |
body の関連データは H_IN であるため、別の呼び出しに移し替えたエンベロープは誰にも開けません。チェーンが確認するのはサイズだけ(max_call_envelope_bytes 以下。チェーン20では 65,536 バイト、このフィールドのないジェネシスでは 18,432 バイト)で、rand_getCallEnvelope(tx) でそのまま提供します。エンベロープを開けられる鍵は3つだけで、それ以外にはありません。呼び出し元のビューイングキー(そのウォレットが行ったすべての呼び出し)、呼び出しごとの鍵(その1件の呼び出し)、監査人のビューイングキー(その1件の呼び出し)です。
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call はトランスクリプトを開き、input_digest(salt, inputs) == H_IN を確認し、復元した入力でエミュレータ上のプログラムを再実行して、その出力をレシートの出力と比較します。どちらかの確認に失敗するとゼロ以外の終了コードで終了するため、スクリプトが警告を「はい」と取り違えることはありません。--no-envelope は元に戻せません。ソルトが失われると、呼び出し元を含め誰もその呼び出しを開けなくなります。
v0.6.8 以降、再実行は証明が作られた公開セグメントの上で行われます。すなわち、プログラムのデプロイ時の公開ワードと、hardening_v6 の下ではその後ろに続く8ワードの呼び出しバインディングです(fullnode issue #116)。v0.6.7 では、open-call が動作するのは公開セグメントなしでデプロイされたプログラムに限られていました。公開セグメントを持つプログラムでは、入力を表示したあと re-running the program on these inputs failed: PublicIndex(0) を出力して停止し、どちらの確認も実行しないままゼロ以外の終了コードで終了していました。
プログラムの状態 (RPL-2)
チェーン20は、ジェネシスの program_state セクションによって RPL-2 が有効になった最初のチェーンです。プログラムは、これまで持たなかった3つのものを得ます(fullnode docs/program-state.md、v0.6.8 の crates/randprotocol-core/src/ledger/program_state.rs)。
| セル | プログラムごとの、8ワードのキーから8ワードの値へのマップ。存在しないセルは8個のゼロとして読まれ、8個のゼロを書き込むとセルは削除されます。ステートルートにコミットされます |
| ボールト | アセットごとの公開残高(0 は RAND)。価値は invoke のバンドルを通じて入り、チェーンが計算するノートとして出ていきます |
| プログラム自身のトークン | rand token create --program <id> で登録されたトークン(ミント権限は Program(id)、初期供給なし)は、そのプログラムの invoke によってのみミントとバーンが行われます |
セルとボールトの残高は、トークンの供給量と同様に公開です。そのどれもノートではありません。
Invoke(Action 33)は、トランジションも宣言する呼び出しであり、その呼び出しの証明は、プログラムがまさにそのトランジションを受理することを示します。
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
入ってくるものは、バンドルがバーンするものです。burn_r はボールトに預け入れられる RAND であり、burn_asset の burn_a は、預け入れられる(inflow は deposit)か、破棄される(inflow は burn、プログラム自身のトークンのみ)トークンです。出ていくものは、支払いごとにチェーンが計算する1つのノートで、支払い(pays)、ミント(mints)の順に並び、金額と受取人の pk はミントと同様に公開です。台帳がトランジションを適用するのは、プログラムが存在し、形式が正当で、reads のすべてのセルが宣言された値を今も保持し、ボールト(このトランザクション自身の預け入れの後)がすべての支払いを賄え、すべてのミントがプログラム自身のトークンのものであり、証明が検証に通る場合に限られます。一致しなくなった読み取りは StaleRead として拒否されます。ウォレットは再読み取りして証明をやり直します。
invoke と call の違い。
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| 証明が作られる公開セグメント | public ‖ call_binding | public ‖ call_binding ‖ context(ワード列で表したトランジション) |
| 成功時にチェーンが行うこと | レシートを保存する。出力はデータ | レシートを保存し、トランジションを適用する。セルを書き込み、ボールトを動かし、支払いノートを追加する |
| 価値 | バンドル自身の転送のみ | バンドルのバーンを通じてボールトへ預け入れ、支払いとミントはボールトから出す |
| 状態への依存 | なし | 証明は依存しない(セグメントはトランザクションから構築される)が、読み取りの確認は、受理時とプールで依存する |
| 手数料 | call の下限 | call の下限に、作成されたセルごとの cell_fee を加えたもの |
プログラムはコンテキストのワード i を READ_PUBLIC(public_len + 8 + i) で読みます。ワード0はバージョン(1)、ワード1〜4は reads、writes、pays、mints の数、続いて burn_r、inflow の種別、burn_asset、burn_a、その後に各 read と write がキーと値(それぞれ8ワード)で並び、最後に各支払いがアセットと金額で続きます。受取人はコンテキストに含まれません。金額を決めるのはプログラムで、誰に支払うかはバインディングが固定します。プログラムは、提示されるすべてのコンテキストワードに制約をかけなければならず、パニックする経路があってはなりません。guest-sdk のパニックハンドラは停止し、停止した実行は証明可能な実行だからです。
チェーン20の上限(rand_getLimits.program_state):
| 上限 | チェーン20 |
|---|---|
cell_fee | 作成されたセル(何も保存されていない場所に非ゼロで書き込まれたセル)ごとに 10,000,000 ユニット、すなわち 0.01 RAND。セルの書き換えや削除は無料 |
max_reads | 8 セル |
max_writes | 8 セル |
max_payouts | 4 ノート(pays と mints の合計) |
実際には、セグメントの規則のほうが厳しいのが普通です。セグメント全体が、同じプログラムへの呼び出しが証明される公開テーブルに収まらなければならず、公開入力のないプログラムでは 127 ワードです。そのため、コンテキストに使えるのは 119 ワードで、たとえば reads 3、writes 3、payouts 4 となります。
コマンドと RPC(ウォレット v0.6.8):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke は、証明の前に reads の各エントリをライブのセルと照合し、読み取りが古くなっていた場合は、証明前でも取り込み時でも終了コード 3 で終了します。スクリプトは再読み取りしてやり直せます。"kind": "invoke" を指定した rand_estimateFee は手数料を見積もります。トランジションファイルの形式は、fullnode の docs/cli.md にあります。チェーン20にデプロイされた RPL-2 の完全なプログラム(amm、stableswap、orderbook、stablecoin、lending、perp、crowdfund、stoploss)と、RPL-1 のプログラム(sealed-auction、ballot、eligibility、private-join、credit-score)は、randprotocol/example-programs にあります。
公開されるものと隠されるもの
| 公開 | 非公開 |
|---|---|
プログラム id とそのコード、ティア、宣言されたガス上限、8 つの出力、H_IN、バンドルの手数料と公開フィールド、エンベロープのサイズ、呼び出しが Keccak と SHA-256 のどちらを使ったか、およびおおよその使用頻度(proof ヘッダーが各ハッシュテーブルの高さを宣言しています) | プライベート入力、レジスタ、メモリ、分岐、実際のサイクル数と正確なガス、H_IN のソルト、呼び出した人物とその支払い手段 |
同じ実行の 2 つの証明は、異なるバイト列になります。chain 15 以前ではこれが十分ではありませんでした。プライベートなワードが 31 個以下の呼び出しでは、隠すには小さすぎるテーブルが証明され、入力を証明から復元できたのです。prover は v0.6 以降、これらのテーブルを 128 行を下限としており、制約セット 7(chain 16 以降)は、なお漏れていたテーブルごとの合計値を秘匿します。hc は binding ですが hiding ではありません。候補となるプログラムを列挙できる人であれば、どれがデプロイされたかを確認できます。
まだ実現できないこと
fullnode の記述のとおりに示します。
- プログラム間の呼び出しはできず、RPL-2 の外に状態を持つこともできません。
Callは 1 回の実行に対する 1 つの証明であり、呼び出し元がフィードバックした内容を除き、呼び出しから呼び出しへ引き継がれるものはありません。chain 20 ではInvokeがプログラム自身のセルと vault を読み書きします(上記)。chain 20 より前のチェーンにはプログラムの状態がなく、invoke が他のプログラムのセルに触れることはできません。 - vault の外でプログラムが主導する転送はできません。 エフェクト種別 1、すなわちプログラムが支払うアカウントへの転送は、アカウントとともに削除されました。また
Callの出力はステートメントです。chain 20 ではInvokeがプログラム自身の vault から最大 4 つの note を支払い、金額と受取人は公開されます。 - ウォレットにコンパイラはありません。 プログラムは、組み込みのアセンブル済みゲスト、またはcircuits リポジトリの
rand-guestでビルドしたイメージです。 - 公開入力はデプロイ時に固定され、呼び出しごとには指定できません。 制約セット 6 で
READ_PUBLICとH_PUBが追加されました。chain 20 ではデプロイ時に最大 32,768 個の公開ワードを持たせることができ、プログラム id がそれらを束縛し、すべての呼び出しの証明はそれらのダイジェストに対して検証されます。呼び出しごとの公開ワードはありません。 - ハッシュのシステムコールは大きな証明を生みます。 keccak を含む証明は約 3.2 MB で、デフォルトの 2 MiB の証明上限を完全に超えます。sha256 を含む証明は約 400 KB 増えます。chain 20 の上限は 4,194,304 バイト(4 MiB。chain 17 から 19 と同じで、chain 13 から 16 では 8 MiB でした)で、両方を収容できます。chain 18 では、Keccak テーブルを持つティア 12 の呼び出しが 3.35 MB、SHA-256 テーブルを持つティア 14 の呼び出しが 1.91 MB でした(Example programs)。また、1 回の呼び出しは 128 回の Keccak-f 置換と 128 回の SHA-256 圧縮に制限されており(後述)、証明の 1 KiB ごとにバイト単価が課されます。
- EVM と Solana のプログラムは chain 20 ではデプロイできず、その呼び出しは呼び出しティアの上限を超えます。 インタープリタのゲスト(
evm.binは 18,009 ワード、sbpf.binは 8,317 ワード)と変換済みイメージ(ERC-20 は 11,686 ワード、SPL Token は 65,096 ワード)は、デフォルトの 4,096 ワードの上限を超え、chain 13 が設定して以降すべてのチェーンが維持している 65,535 ワードの上限を下回ります。変換済みの 2 つのイメージは、2026-09-19 に chain 13 でデプロイされ、SPL Token は ELF を公開入力としていました。chain 14 と 15 は新規に切り直されたため、どちらのチェーンでもイメージは再デプロイされていません。chain 20 は chain 19 と同様に、この 4 つのいずれのデプロイも拒否します。hardening_v6ルールの下では、ティア 14 の呼び出しでは収容できないプログラム(公開セグメントがなければ 8,180 ワード超、あればそれより少ないワード数)はデプロイ時に拒否され(ProgramUncallable)、誰も呼び出せないプログラムのためにデプロイ手数料が支払われることはありません。ERC-20 の呼び出しはティア 16 または 18 で証明されます。ティア 16 のapproveは 2026-09-19 に chain 13 で、3,412,405 バイトの本番用証明とともに呼び出されました(Ethereum transpiler)。chain 20 はティア 14 を超える呼び出しをすべて拒否するため(次の項目)、現時点では chain 20 で受理される ERC-20 の呼び出しはありません。SPL Token の呼び出しはティア 20 で、約 330 GB が必要です。SPL の呼び出しの証明は存在しません(proving memory)。EVM のブロックコンテキスト opcode はトラップし、オンチェーンの EVM 状態はありません。 - 呼び出しはティア 14 以下で証明されます。 fullnode v0.5.6(2026-09-25)以降、ティア 14 を超える呼び出しの証明は拒否され(
CallTierTooHigh)、128 回を超える Keccak-f 置換または 128 回を超える SHA-256 圧縮を含む証明も同様に拒否されます。chain 13 のティア 16 の ERC-20approveは、chain 15、18、19 で拒否されたであろうのと同様に chain 20 でも拒否され、ティア 18 の ERC-20 やティア 20 の SPL Token の呼び出しもすべて拒否されます。これらはオフチェーンでは引き続き証明できます(the budget)。 - GPU による証明は、実際のハードウェアではまだ一度も動作していません。
--cudaには--features cudaを付けたビルドとコミット済みの PTX が必要ですが、フリートにはどちらもありません。chain 20 では、ビルドにかかわらず、呼び出しの証明は CPU バックエンドで行われます。 hcは hiding ではありません。 上記のとおりであり、witness リクエストによって、ウォレットがどのリーフを使うかはノードに伝わります。