Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ホストと証明生成

ホストプログラムによる 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

入力構築で行われる主要な処理:

  1. 掲示板の最新 STH スナップショット取得: ルートハッシュ、ツリーサイズ、タイムスタンプを取得
  2. 投票データの変換: 各投票の選択肢を整数に変換(A=0, B=1, C=2, D=3, E=4)
  3. Merkle パスの解決: 各投票について、掲示板から最新の包含証明を取得
  4. 総投票数の設定: ボット投票数 + ユーザー投票数(本 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 プログラムです。以下の処理を行います:

  1. JSON 形式の入力ファイルを読み込み
  2. JSON のバイト配列表現を Rust の固定長配列型へ変換(Vec<u8>[u8; 16/32]
  3. ExecutorEnv に入力をシリアライズして設定
  4. デフォルトプローバーを使用して zkVM ゲストを実行
  5. レシート(STARK 証明 + ジャーナル)を取得
  6. ジャーナルをデコードし、出力ファイルに書き出し

入力 JSON は TypeScript 側のエグゼキューターが事前に正規化して生成します(UUID/ハッシュ文字列をバイト配列へ変換)。

出力ファイル

ホストバイナリは 2 つの JSON ファイルを出力します。

ファイル内容
レシート JSONSTARK 証明(seal)とジャーナルを含む完全なレシートオブジェクト
出力 JSONデコード済みのジャーナル(集計結果、除外情報、各種ハッシュ値)

レシート JSON には image_id フィールドが含まれ、このゲストプログラムの Image ID が記録されます。検証サービスはこのフィールドを使用して、期待される Image ID との照合を行います。

同期モード

同期モードでは、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_MODERUST_LOG をパススルー
エラー処理終了コード非ゼロ、タイムアウト、ファイル不在で失敗

結果の変換

エグゼキューターは出力 JSON のフィールドを TypeScript の命名規則へ正規化し、文字列だけでなくバイト配列形式の値も受理します。ハッシュ系フィールドは 0x 付き 16 進文字列に、election_id は UUID 文字列に変換して ZkVMExecutionResult を構築します。

非同期モード

本番環境では、証明生成を 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: セッションデータ更新

非同期モードの処理フロー

  1. 入力の準備: ディスパッチ Lambda が入力 JSON を S3 にアップロードし、Step Functions 実行を開始
  2. イメージ署名チェック: プローバーコンテナイメージの署名を検証し、承認されたイメージのみ実行を許可
  3. プローバータスク: ECS Fargate タスクが起動し、S3 から入力をダウンロードしてホストバイナリを実行
  4. 出力バンドル: レシート、ジャーナル、公開入力をバンドルし、S3 にアップロード
  5. コールバック: 成功/失敗に応じてコールバック Lambda がセッションデータを更新

公開バンドルの構築

非同期モードでは、ホストバイナリの出力から公開バンドル(bundle.zip)を構築します。

ファイル内容公開
receipt.jsonSTARK レシート(seal + journal)Yes
journal.jsonジャーナルの正準 JSON 表現Yes
public-input.json公開入力データ(コミットメント + Merkle パス)Yes
input.json完全な入力(秘密の選択肢・乱数を含む)No

input.json は秘密データを含むため、公開バンドルには含まれません。公開バンドルの構成については バンドル構造 を参照してください。

非同期モードの特性

項目
タイムアウト15 分(デフォルト、環境変数で変更可能)
リトライS3 アップロードは指数バックオフで 3 回リトライ
エラー処理Step Functions がタスク失敗を検出し、失敗コールバックを実行
ステータス確認クライアントは /api/sessions/:id/status でポーリング

開発モードの動作

RISC0_DEV_MODE=1 を設定すると、RISC Zero は STARK 証明を生成せず、フェイクレシートを返します。

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)。いずれにせよ本番モードの検証基準は満たしません。