このページは、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 |
| 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 でなければなりません。この 2 つの規則は制約セット 7(chain 16)で導入されました。それ以前は、奇数のジャンプ先はフェッチできず、実行を証明できませんでした。偶数でも 4 の倍数ではないジャンプ先は、現在もフェッチできません。
浮動小数点、アトミック命令(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 | 直前の 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) |
|---|---|---|---|---|---|
| 0 | HALT | 実行を終了する | 1 | halt() | rand_halt() |
| 1 | WRITE_OUTPUT slot word | 公開出力 slot < 8 を設定する。スロットごとに 1 回まで。書き込まれていないスロットはゼロ | 1 | write_output(slot, word) | rand_write_output |
| 2 | READ_INPUT idx | ソルト付きコミットメント H_IN に束縛された、秘匿入力のワード idx を返す | 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] を 1 回、その場で適用する | 192 | keccak(state), keccak256(msg) | rand_keccak |
| 5 | SHA256 ptr | 24 ワードに対し、SHA-256 圧縮を 1 回、その場で適用する | 64 | sha256_compress(buf), sha256(msg) | rand_sha256_compress |
| 6 | READ_PUBLIC idx | ソルトなしの H_PUB に束縛された、公開セグメントのワード idx を返す | 1 | read_public(idx) | rand_read_public |
| 7 | POSEIDON2_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 } に変換します。
- 値がゼロでないデータワードごとに、
li/swのペアを書き出します。RAM はすでにゼロとして読めるため、ゼロのワードは省略されます。 - そのプロローグを
text_baseの直下に配置するため、base_pc = text_base − 4 × prologue wordsとなります。プロローグは、そのままプログラム自身の_startへ続きます。 hcとプログラム ID は、プロローグを含むwords全体に対して計算されます。したがってhcは、コードだけでなくデータにも束縛されます。
データを持たないコンテナにはプロローグがなく、同じベースアドレスのヘッダーなしフラットバイナリと同じプログラム、同じ hc になります。
fullnode v0.3 以降の rand program deploy は、先頭のワードによってコンテナを認識します。0x444e4152 は命令としてデコードされることがないため、生の .bin がコンテナと取り違えられることはありません。マジックナンバーで始まっていても不正な形式のファイルは、フォールバックされずにエラーになります。
2 つのアイデンティティ
| 名前 | 内容 | 使用者 |
|---|---|---|
hc | 回路による (base_pc, len, words) の Poseidon2 ダイジェスト | 呼び出しごとの証明はすべてこれに対して検証されます。rand_getProgram では code_hash として表示されます |
| プログラム ID | blake3("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_bytes | 4,194,304(4 MiB)。チェーン 13 から 16 では 8 MiB |
max_block_bytes | 20,971,520(20 MiB) |
max_call_envelope_bytes | 65,536 |
max_program_public_words | 32,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 でプログラムを書くで順を追って説明しています。