Rand Protocol
ドキュメント
RISC-V プログラム

RISC-V ISA

RandにおけるRV32IMの意味を説明します。zkVMが証明する命令、チェッカーが拒否するもの、8つのsyscall、メモリ、プログラムが配布されるイメージコンテナ、そしてプログラムの上限について扱います。

このページは、Rand 向けのプログラムを書く前にお読みください。コードがどのマシンで動くのか、ツールチェーンが何を拒否するのか、プログラムが外部とどのようにやり取りするのか、デプロイするファイルは何か、を説明しています。以降のページはこのページを前提にしています。Rust プログラムのデプロイ、RISC-V プログラムのデプロイ、および 2 つのトランスパイラです。

マシン

Rand のプログラムは RV32IM です。32 ビットの RISC-V 基本整数命令セットに、乗算・除算拡張を加えたものです。プログラムは Rand zkVM 内で実行され、zkVM は各実行を Plonky3 のバッチ STARK で証明します。検証者が命令を目にすることはありません。検証者は、プログラムの回路内 Poseidon2 ダイジェストである hc に対して証明を検査します。

グループ命令
上位即値 / ジャンプ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 でなければなりません。この 2 つの規則は制約セット 7(chain 16)で導入されました。それ以前は、奇数のジャンプ先はフェッチできず、実行を証明できませんでした。偶数でも 4 の倍数ではないジャンプ先は、現在もフェッチできません。

浮動小数点、アトミック命令(A 拡張)、圧縮命令(C 拡張)、64 ビットレジスタはありません。C における 64 ビット除算は、ソフトウェアのヘルパー呼び出しになります。C のパスを参照してください。

拒否されるもの

rand-guest check は、すべてのテキストワードをマシン自身のデコーダに通します。これに合格したプログラムが、エンコーディング、システムコール番号、メモリレイアウトを理由として回路内で失敗することはありません。チェッカーが拒否するものは次のとおりです。

検出項目内容
Compressed16 ビットの RVC エンコーディング。マシンがデコードするのは 32 ビットワードのみです
Undecodableマシンが実装していないオペコード、funct、またはシフト量
FenceFENCE または FENCE.I。マシンにはメモリ順序の仕組みがありません
Csrあらゆる CSR 命令。マシンに CSR はありません
EbreakEBREAK。トラップはモデル化されていません
Syscall直前の li で a7 が設定され、どのシステムコールにも該当しない番号を指す ecall
Cap--max-words を超えるワード数。ローダーが数えるとおりに数えます
Layoutワード境界に揃っていないテキストのベースアドレス

fence、ebreak、ecall 番号 9 を含む 4 ワードのプログラムを、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

チェッカーが a7 を確認できない ecall(li 以外で設定された場合や、分岐の後で設定された場合)は、拒否されず unresolved_ecalls として数えられます。不正な番号に対しては、実行時にマシンが引き続きトラップします。

チェッカーは、ポインタの値、長さ、入力インデックス、RAM に対する 64 KiB のスタック、実行時に計算されるアドレスのアラインメントを検査しません。アラインされていないアクセスは制約違反となるため、ツールチェーンは -unaligned-scalar-mem を付けてコンパイルします。rand-guest run は、実行時のトラップをすべて報告します。

システムコール

ECALL は、システムコール番号を a7 から、第 1 引数を a0 から、第 2 引数を a1 から読み取ります。値は a0 に返ります。

#システムコール効果ガスRust(guest-sdk)C(guest.h)
0HALT実行を終了する1halt()rand_halt()
1WRITE_OUTPUT slot word公開出力 slot < 8 を設定する。スロットごとに 1 回まで。書き込まれていないスロットはゼロ1write_output(slot, word)rand_write_output
2READ_INPUT idxソルト付きコミットメント H_IN に束縛された、秘匿入力のワード idx を返す1read_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 ptr50 ワードに対し、Keccak-f[1600] を 1 回、その場で適用する192keccak(state), keccak256(msg)rand_keccak
5SHA256 ptr24 ワードに対し、SHA-256 圧縮を 1 回、その場で適用する64sha256_compress(buf), sha256(msg)rand_sha256_compress
6READ_PUBLIC idxソルトなしの H_PUB に束縛された、公開セグメントのワード idx を返す1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr n長さ n をスポンジに束縛する POSEIDON2。最低 1 回の置換を行う(制約セット 7)3 + 3·max(⌈n/4⌉, 1)poseidon2_len(buf, n)なし

それ以外の命令はすべて 1 ガスです。ガスは cpu の行ごとに 1 であり、重みが大きいのはハッシュ系のシステムコールだけです(制約セット 8、chain 18 以降)。Poseidon2 の呼び出しは、その ecall の行、2 行の書き戻し行、および 3 ガスずつの ⌈n/4⌉ 行の吸収行で構成されます。

POSEIDON2 はパディングしません。 スポンジは、4 ワードのチャンクごとに最初のレーンを上書きします。そのため、[a]、[a, 0]、[a, 0, 0]、[a, 0, 0, 0] は同じダイジェストにハッシュされます。長さが変わりうるデータをハッシュするプログラムは、メッセージに長さを含めるか、固定長のエンコーディングをハッシュするか、POSEIDON2_LEN を呼び出す必要があります。POSEIDON2_LEN は n をスポンジのキャパシティに埋め込み、同じワードでも異なるダイジェストを生成します。Rust SDK では、poseidon2 は生ポインタを受け取る unsafe fn であり、poseidon2_len はスライスを受け取る安全な呼び出しです。keccak と sha256_compress は、それぞれ &mut [u32; 50] と &mut [u32; 24] を取ります。

ツールチェーンにある 1 つの欠落。 circuits aeacf31 の時点では、マシン、エミュレータ、Rust SDK はシステムコール 7 を実装していますが、rand-guest check は実装していません。そのシステムコール一覧は 6 で終わっているため、a7 = 7 の ecall を Syscall として報告し、poseidon2_len を呼び出すゲストに対して rand-guest build は 0 以外の終了コードで終了します(circuits issue #1)。guest.h にも、そのためのラッパーはありません。これが修正されるまでは、標準のツールで構築するゲストは、固定長メッセージに対して POSEIDON2 を使います。

証明が公開するのは、次のものだけです。hc、H_IN、H_PUB、tier、宣言されたガス上限、および 8 つの出力ワード。現在のチェーンでは、次の 2 つの制限が適用されます。

  • 公開セグメントはデプロイ時に固定されます。 chain 20 では、rand program deploy --public がプログラムの公開ワード(最大 max_program_public_words、32,768)を設定し、すべての呼び出しはそれらに対して検査されます。プログラム固有の、呼び出しごとの公開ワードはありません。chain 20 は chain 19 と同様に、すべての呼び出しのセグメントの末尾に 8 ワードを追加します。これは証明をトランザクションに結び付ける呼び出しバインディングで、chain 20 ではこれにジェネシスハッシュも含まれます。ゲストは、自身のワードを従来と同じインデックスで読み取ります。RPL-2 の Invoke は、バインディングの後に宣言された遷移を追加し、プログラムはそれを public_len + 8 以降から読み取ります(Programs)。chain 12 のように上限フィールドを持たないチェーンでは、公開セグメントは空であり、READ_PUBLIC が読み取るものはありません。
  • 呼び出しの秘匿入力ワードは、封印されたエンベロープによって上限が決まります。 chain 20 の max_call_envelope_bytes は 65,536 で、16,071 ワードまで許容されます。このフィールドを持たないチェーンでは 4,295 ワードです。

KECCAK または SHA256 の呼び出しは、証明にテーブルを 1 つ追加し、その後、証明のヘッダーにはその呼び出しがハッシュしたことが示されます。keccak を含む証明は約 3.3 MB です。これはデフォルトの 2 MiB の証明サイズ上限を超え、chain 20 の 4 MiB の上限を下回ります。また、1 回の呼び出しで使用できるのは、Keccak-f 置換が最大 128 回、SHA-256 圧縮が最大 128 回です(Programs)。

メモリ

  • レジスタは 32 個で、x0 はゼロです。
  • RAM はゼロから始まります。すべてのアドレスは 2³⁰ 未満です。
  • デフォルトのリンカスクリプト guest-sdk/guest.ld は、プログラムを 0x1000 に配置し、続けて .rodata、.data、.bss を置き、その後に __stack_top に 64 KiB のスタックを置きます。
  • データセグメントを持つプログラムには、ローダーのプロローグ(次のセクション)のために、テキストの下に空き領域が必要です。そのプログラム独自の .ld では、evm.ld や sbpf.ld と同様に、ORIGIN を 0x10000 に引き上げます。

イメージコンテナ

rand-guest build と rand-guest pack は、イメージコンテナという 1 つのファイル形式を書き出します。数値はすべてリトルエンディアンの 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 × prologue words となります。プロローグは、そのままプログラム自身の _start へ続きます。
  3. hc とプログラム ID は、プロローグを含む words 全体に対して計算されます。したがって hc は、コードだけでなくデータにも束縛されます。

データを持たないコンテナにはプロローグがなく、同じベースアドレスのヘッダーなしフラットバイナリと同じプログラム、同じ hc になります。

fullnode v0.3 以降の rand program deploy は、先頭のワードによってコンテナを認識します。0x444e4152 は命令としてデコードされることがないため、生の .bin がコンテナと取り違えられることはありません。マジックナンバーで始まっていても不正な形式のファイルは、フォールバックされずにエラーになります。

2 つのアイデンティティ

名前内容使用者
hc回路による (base_pc, len, words) の Poseidon2 ダイジェスト呼び出しごとの証明はすべてこれに対して検証されます。rand_getProgram では code_hash として表示されます
プログラム IDblake3("rand-program" ‖ base_pc ‖ words)。--public 付きのデプロイでは、タグ rand-program-2 のもとで公開ワードも結び付けられますデプロイされたプログラムが保存され、呼び出される際のキー

rand-guest build と rand-guest info は、どちらも両方を出力します。hc はバインディング性を持ちますが、秘匿性は持ちません。候補となるプログラムがあれば、誰でも照合できます。

プログラムの上限

デプロイに含められるのは、チェーンのプログラム上限(ワード数)までです。

チェーン上限
max_program_words のない genesis(チェーン 12)4,096
max_program_words のある genesisその値(1..=65535)
zkVM のハードリミット(どのチェーンでも)65,535

上限は genesis ハッシュの一部であるため、稼働中のチェーンでは変更できません。rand-guest は --max-words(デフォルトは 4,096)に照らして検査します。rand program deploy は rand_estimateFee でノードに問い合わせ、上限を超えるプログラムは、証明を始める前に拒否します。

稼働中のチェーンであるチェーン 20 は、この上限を引き上げています。genesis ハッシュは 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135、max_program_words は 65,535 です。チェーン 13(v0.4 のチェーン)が導入した制限を引き継いでいますが、証明の上限だけは例外で、チェーン 17 から 20 で半分になっています。その他の制限は次のとおりです。

制限チェーン 20
max_proof_bytes4,194,304(4 MiB)。チェーン 13 から 16 では 8 MiB
max_block_bytes20,971,520(20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,768

チェーン 20 でも、チェーン 19 と同様に、ワード数の上限は実際の制約ではありません。 この genesis は hardening_v6 を設定しており、どの呼び出しでもそのプログラムを証明できない場合、デプロイは拒否されます(ProgramUncallable)。呼び出しの上限はティア 14 で、その Poseidon2 テーブルには 2,048 回の置換が収まります。プログラムのダイジェストは 4 ワードにつき 1 回、入力の salt 行が 1 回、呼び出しバインディングの 8 ワードが 2 回を消費するため、残りは 2,045 回です。これは公開入力のないプログラムで 8,180 ワード、公開入力がある場合はそれより少なくなります(公開ワードが 9 個なら 8,168)。このようなデプロイは、どのチェーンでも、すべてのノードのプールが拒否します。

チェーン 20 では、ブロックの集約はオフです。genesis に aggregation セクションがありません。チェーン 20 は、fullnode v0.6.8 で 2026-10-01 06:17 UTC から稼働しています。これはチェーン 19 の高さ 7,258 の状態から切り出したハードフォークです(チェーン 19 は 7,700 で停止しました)。チェーン 19(同日 03:43 UTC から稼働)自体が、高さ 141,935 でのチェーン 18 の re-genesis でした。プログラムはカットをまたいで引き継がれないため、それ以前のチェーンにデプロイされたプログラムは、チェーン 20 には存在しません。チェーン 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 に収まるかチェーン 20 でデプロイできるか(呼び出し可能は 8,180)
fib(Rust)26はいはいはい
c-fib(C)25はいはいはい
ERC-20、変換済み(ステージ 2)11,686いいえはいいいえ
evm.bin、EVM インタプリタ18,009いいえはいいいえ
sbpf.bin、sBPF インタプリタ8,317いいえはいいいえ
SPL Token、変換済み65,096いいえはい(439 の余裕あり)いいえ

ティア

証明のティア t は、実行を 2ᵗ − 1 行に制限します。この行数は、サイクルとダイジェストの行(プログラムの 4 ワードにつき 1 行、入力の 4 ワードにつき 1 行、および公開入力のダイジェスト)の合計です。ティアは、これとは別に Poseidon2 の予算も定めます。rand-guest run --tier t は両方を報告します。

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

ティアは公開されており、証明が宣言するガスリミットも同様です。チェーン 20 では、呼び出し手数料はティアではなく、このリミットと証明のサイズに従います。最低でも 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉ で、価格はチェーンの現在値を使います。価格は 10⁻⁹ RAND を単位として 100 と 800 から始まり、ブロックの充填率に応じて動き、チェーン 20 では 10,000 と 80,000 で止まります(手数料)。リミットは、ヘッダーの上限 (2ᵗ − 1) + 2ᵗ⁻² に、証明が宣言する Keccak ブロックごとに 191、SHA-256 ブロックごとに 63 を加えた値を超えられません。ハッシュテーブルがない場合、ティア 10 で 1,279、ティア 14 で 20,479 です。rand-guest run が報告するのは、ガスではなくサイクルと置換です。rand call は、まずプログラムを実行し、消費するガスと宣言するリミットを表示してから証明します。リミットは、デフォルトでは正確なガスを次の 2ᵗ⁻² の倍数に切り上げた値です。

gas: 5636 (declaring 6144, tier 12)

この行は、2026-09-30 にチェーン 18 で行った呼び出しのものです(匿名投票)。genesis に gas セクションがないチェーン(チェーン 15 以前)では、手数料はティアごとの料金表で、0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND でした。証明に必要なメモリは、ティアとともに急激に増えます。バリデータおよびアグリゲータのハードウェアを参照してください。

fullnode v0.5.6(2026-09-25)以降、呼び出しが宣言できるのは最大でティア 14 までです。それより高いティアの呼び出し証明は、検証鍵を構築する前にノードが CallTierTooHigh で拒否し、そのような証明を含むブロックは無効になります。ティア 14 の予算は 16,383 行と 2,048 回の Poseidon2 置換で、プログラム自身のダイジェストが 4 ワードにつき約 1 回の置換を消費します。そのため 8,180 ワードを超えるプログラムは呼び出せず、チェーン 20 は、チェーン 19 と同様に、そのデプロイを拒否します。予算については、Rust または C でプログラムを書くで順を追って説明しています。

fullnode のコミット c0878c3f(チェーン 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。 コードとこのページが食い違う場合は、コードが正です。お知らせください。