ホストと証明生成
ホストプログラムによる zkVM 入力構築と STARK 証明生成の仕組みを解説します。
同期モード(ローカルプロセス起動)と非同期モード(ECS Fargate タスク)の 2 つの証明パスがあり、どちらも同一のホストバイナリを使用します。入力構築からレシート出力までのフローを、両モードの差異とともに説明します。
パイプライン全体像
証明生成パイプラインは、入力構築、ホスト実行、出力処理の 3 フェーズで構成されます。
flowchart TD
subgraph "第1フェーズ: 入力構築 (TypeScript)"
SD[セッションデータ] --> IB[入力ビルダー]
IB --> ZI[ZkVMInput]
ZI --> SER[シリアライズ<br/>JSON ファイル]
end
subgraph "第2フェーズ: ホスト実行 (Rust)"
SER --> HOST[ホストバイナリ]
HOST --> ENV[ExecutorEnv 構築]
ENV --> PROVER[デフォルトプローバー]
PROVER --> GUEST[ゲスト実行]
GUEST --> PROOF[STARK 証明生成]
end
subgraph "第3フェーズ: 出力処理"
PROOF --> RCP["レシート JSON<br/>(seal + journal)"]
PROOF --> OUT["出力 JSON<br/>(デコード済みジャーナル)"]
end
入力構築
セッションデータからの抽出
入力ビルダーは、投票セッションに蓄積されたデータから zkVM 入力を構築します。
flowchart LR
subgraph セッションデータ
EID[選挙 ID]
VOTES["投票データ<br/>(選択肢, 乱数, コミットメント)"]
BULL["掲示板<br/>(ルート履歴, 包含証明)"]
LID[ログ ID]
end
subgraph ZkVMInput
EID2[election_id]
BR[bulletin_root]
TS[tree_size]
TE[total_expected]
VWP["votes[]<br/>(VoteWithProof)"]
LID2[log_id]
TSTAMP[timestamp]
end
EID --> EID2
BULL --> BR
BULL --> TS
VOTES --> VWP
LID --> LID2
入力構築で行われる主要な処理:
- 掲示板の最新 STH スナップショット取得: ルートハッシュ、ツリーサイズ、タイムスタンプを取得
- 投票データの変換: 各投票の選択肢を整数に変換(A=0, B=1, C=2, D=3, E=4)
- Merkle パスの解決: 各投票について、掲示板から最新の包含証明を取得
- 総投票数の設定: ボット投票数 + ユーザー投票数(本 PoC では 63 + 1 = 64)
Merkle パスの解決戦略
各投票の Merkle パスは、以下の優先順位で解決されます:
flowchart TD
START[Merkle パス解決] --> P1{掲示板から<br/>包含証明を取得可能?}
P1 -->|Yes| USE1[掲示板の証明を使用]
P1 -->|No| P2{投票データに<br/>事前計算パスが存在?}
P2 -->|Yes| CHK{proofMode = rfc6962<br/>かつ treeSize が一致?}
CHK -->|Yes| USE2[事前計算パスを使用]
CHK -->|No| ERR[エラー]
P2 -->|No| ERR
ホストプログラムの実行
ホストバイナリの役割
ホストバイナリは Rust で記述された CLI プログラムです。以下の処理を行います:
- JSON 形式の入力ファイルを読み込み
- JSON のバイト配列表現を Rust の固定長配列型へ変換(
Vec<u8>→[u8; 16/32]) ExecutorEnvに入力をシリアライズして設定- デフォルトプローバーを使用して zkVM ゲストを実行
- レシート(STARK 証明 + ジャーナル)を取得
- ジャーナルをデコードし、出力ファイルに書き出し
入力 JSON は TypeScript 側のエグゼキューターが事前に正規化して生成します(UUID/ハッシュ文字列をバイト配列へ変換)。
出力ファイル
ホストバイナリは常に 2 つの JSON ファイルを出力し、条件を満たした場合は private bitmap artifact も追加で出力します。
| ファイル | 内容 |
|---|---|
| レシート JSON | { "receipt": ..., "image_id": "0x..." } 形式のラッパー JSON |
| 出力 JSON | デコード済みのジャーナル(集計結果、除外情報、各種ハッシュ値) |
レシート JSON には top-level image_id フィールドも含まれます。検証サービスでの使われ方は 検証サービス を参照してください。
また、ホストはビットマップの整合性を確認し、一致した場合のみ以下の private artifact を出力します。
| ファイル | 内容 |
|---|---|
*-bitmap.json | counted bitmap の厳密 artifact(includedBitmapRoot と対応) |
*-seen-bitmap.json | presented bitmap の厳密 artifact(seenBitmapRoot と対応) |
同期モード
同期モードでは、TypeScript のサーバーサイドプロセスからホストバイナリを直接起動します。
sequenceDiagram participant S as サーバー (TypeScript) participant E as エグゼキューター participant H as ホストバイナリ (Rust) participant FS as ファイルシステム S->>E: executeZkVM(input) E->>FS: 入力 JSON を一時ファイルに書き出し E->>H: 子プロセスとして起動 Note over H: zkVM ゲスト実行<br/>+ STARK 証明生成 H->>FS: レシート JSON + 出力 JSON を書き出し H-->>E: プロセス終了 E->>FS: 出力ファイルを読み取り E->>FS: 一時ファイルを削除 E-->>S: ZkVMExecutionResult
同期モードの特性
| 項目 | 値 |
|---|---|
| 起動方式 | Node.js child_process.exec |
| タイムアウト | 10 分(600 秒) |
| 一時ファイル | リポジトリ直下の .zkvm-temp/ 配下 |
| 環境変数 | RISC0_DEV_MODE と RUST_LOG をパススルー |
| エラー処理 | 終了コード非ゼロ、タイムアウト、ファイル不在で失敗 |
結果の変換
エグゼキューターは出力 JSON のフィールドを TypeScript の命名規則へ正規化し、文字列だけでなくバイト配列形式の値も受理します。ハッシュ系フィールドは 0x 付き 16 進文字列に、election_id は UUID 文字列に変換して ZkVMExecutionResult を構築します。
非同期モード
AWS 環境では、証明生成を ECS Fargate タスクとして非同期に実行します。STARK 証明の生成に数分を要するため、Lambda のタイムアウト制限を回避し、専用のコンピューティングリソースを割り当てます。
sequenceDiagram participant S as サーバー participant SQS as SQS participant SFN as Step Functions participant ECS as ECS Fargate participant S3 as S3 participant CB as コールバック Lambda S->>SQS: ファイナライズリクエスト SQS->>SFN: ディスパッチ Lambda<br/>→ SFN 実行開始 Note over SFN: イメージ署名チェック SFN->>ECS: プローバータスク起動 ECS->>S3: 入力 JSON ダウンロード Note over ECS: ホストバイナリ実行<br/>+ STARK 証明生成 ECS->>S3: レシート・ジャーナル・<br/>バンドルをアップロード ECS-->>SFN: タスク完了 SFN->>CB: 成功コールバック CB->>CB: セッションデータ更新
非同期モードの処理フロー
- 入力の準備: ディスパッチ Lambda が入力 JSON を S3 にアップロードし、Step Functions 実行を開始
- イメージ署名チェック: プローバーコンテナイメージの署名を検証し、承認されたイメージのみ実行を許可
- プローバータスク: ECS Fargate タスクが起動し、S3 から入力をダウンロードしてホストバイナリを実行
- 出力バンドル: レシート、ジャーナル、
public-input.json、election-manifest.json、close-statement.jsonを生成し、整合性検査を通過したものだけをbundle.zipにまとめて S3 にアップロード - コールバック: 成功/失敗に応じてコールバック Lambda がセッションデータを更新
配布対象バンドルの構築
非同期モードでは、ホストバイナリの出力から秘密データを含まない配布対象バンドル(bundle.zip)を構築します。
ここでいう「配布対象」は機密性の分類です。用語の意味と取得経路は バンドル構造 を参照してください。
| ファイル | 内容 | 配布対象 |
|---|---|---|
| receipt.json | STARK レシートのラッパー JSON | Yes |
| journal.json | ジャーナルの正準 JSON 表現 | Yes |
| public-input.json | 秘密データを含まない検証用レコード | Yes |
| election-manifest.json | 選挙設定の公開監査用スナップショット | Yes |
| close-statement.json | 集計締切時点のログ境界を表す公開監査レコード | Yes |
秘密データを含む完全入力は非同期実行時のワーク入力として S3 や一時領域に存在し得ますが、bundle.zip には含まれません。
public-input.json、election-manifest.json、close-statement.json は、journal.json と proof-bound data に対する整合性検査を通過した場合にのみバンドルに含まれます。
public-input.json の項目と inputCommitment の関係は 入力コミットメント、配布経路は バンドル構造 を参照してください。
非同期モードの特性
| 項目 | 値 |
|---|---|
| タイムアウト | 15 分(デフォルト、環境変数で変更可能) |
| リトライ | S3 アップロードは指数バックオフで 3 回リトライ |
| エラー処理 | Step Functions がタスク失敗を検出し、失敗コールバックを実行 |
| ステータス確認 | クライアントは /api/sessions/:id/status でポーリング |
開発モードの動作
RISC0_DEV_MODE=1 を設定すると、RISC Zero は STARK 証明を生成せず、フェイクレシートを返します。
| 項目 | 開発モード (RISC0_DEV_MODE=1) | 本番モード |
|---|---|---|
| 証明の種類 | フェイクレシート | 本物の STARK 証明 |
| 実行時間 | 約 100 ミリ秒 | 約 370 秒(64 票の場合) |
| 安全性 | なし(検証を省略) | 暗号学的に完全 |
| 検証サービス | DevMode として検出 | 完全な STARK 検証を実行 |
開発モードで生成されたレシートは InnerReceipt::Fake 型となります。検証サービスでは通常 DevMode として扱いますが、image_id 不一致などの事前条件違反があれば Failed になります。本番環境でフェイクレシートが混入した場合は検証失敗です。
flowchart TD
ENV{RISC0_DEV_MODE?}
ENV -->|"= 1"| DEV["開発モード<br/>フェイクレシート生成<br/>約 100ms"]
ENV -->|未設定| PROD["本番モード<br/>STARK 証明生成<br/>約 370 秒"]
DEV --> FAKE["InnerReceipt::Fake"]
PROD --> REAL["InnerReceipt::Composite<br/>+ seal データ"]
開発モードは以下の用途に限定されます:
- ローカル開発での高速フィードバック
- E2E テストの高速実行
- UI 開発時のモック
開発モードのレシートは 検証サービス で Fake として検出され、通常は DevMode 扱いになります(image_id 不一致などの事前条件違反時は Failed)。いずれにせよ本番モードの検証基準は満たしません。