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 に渡す入力データ全体に対する正準エンコーディングとコミットメントの設計を解説します。

入力コミットメントにより、「証明されたデータセット」と「主張されたデータセット」の一致を検証可能にします。バイトレベルの正準化により、TypeScript と Rust の間で決定的な一致を保証します。

概要

入力コミットメントは、zkVM ゲストプログラムに渡されるすべての投票データ(コミットメント値と Merkle パス)を単一のハッシュ値に集約するプロトコルです。

このハッシュ値は zkVM のジャーナル(公開出力)にコミットされるため、第三者はジャーナルに記録された入力コミットメントと、公開されている投票データから再計算した値を照合することで、zkVM が実際にどのデータセットを処理したかを独立に検証できます。

flowchart TB
  IN["入力データ<br/>electionId / bulletinRoot / treeSize<br/>/ totalExpected / votes (index 昇順)"]
  SPEC["固定値<br/>domainTag: stark-ballot:input|v1.0<br/>version: 10"]

  IN --> ENC[正準エンコーディング]
  SPEC --> ENC
  ENC --> H[SHA-256]
  H --> IC[入力コミットメント<br/>32 バイト]
  IC --> CMP["第三者照合<br/>再計算値 = journal.inputCommitment"]

入力コミットメントが解決する問題

zkVM の STARK 証明は「ゲストプログラムが正しく実行された」ことを証明しますが、「どの入力に対して実行されたか」は証明のスコープ外です。入力コミットメントがなければ、悪意あるサーバーは以下の攻撃が可能になります:

  1. 投票を除外した入力で zkVM を実行し、有効な STARK 証明を取得する
  2. 公開用の入力データには除外されていない投票を含めて提示する
  3. 第三者は STARK 証明が有効であることを確認できるが、実際に処理された入力は異なる

入力コミットメントをジャーナルに含めることで、第三者は「公開データから再計算した入力コミットメント」と「ジャーナルに記録された入力コミットメント」を照合し、不一致を検出できます。

正準エンコーディング

入力コミットメントの計算には、すべてのフィールドを決定的な順序・エンコーディングで連結する正準化が不可欠です。

バイトレイアウト

input_commitment = SHA-256(
    domain_tag       ← "stark-ballot:input|v1.0" (23 バイト, UTF-8)
    || version       ← u32 リトルエンディアン (4 バイト) = 10
    || election_id   ← UUID v4 バイナリ (16 バイト)
    || bulletin_root ← 32 バイト
    || tree_size     ← u32 リトルエンディアン (4 バイト)
    || total_expected← u32 リトルエンディアン (4 バイト)
    || votes_count   ← u32 リトルエンディアン (4 バイト)
    || [投票データ]  ← インデックス昇順でソートされた各投票
)

各投票のエンコーディング

投票配列の各要素は以下の形式でエンコードされます:

vote_entry =
    index            ← u32 リトルエンディアン (4 バイト)
    || commitment_len← u16 リトルエンディアン (2 バイト) = 32 (固定)
    || commitment    ← 32 バイト
    || path_len      ← u16 リトルエンディアン (2 バイト)
    || path_nodes    ← path_len × 32 バイト

フィールド一覧

フィールドサイズエンコーディング説明
ドメインタグ23 バイトUTF-8 固定文字列"stark-ballot:input|v1.0"
バージョン4 バイトu32 LEv1.0 = 10
選挙 ID16 バイトUUID バイナリ選挙スコープの識別子
掲示板ルート32 バイトハッシュ値最終的な Merkle ルート
ツリーサイズ4 バイトu32 LE掲示板のリーフ数
期待投票数4 バイトu32 LE想定される総投票数
投票数4 バイトu32 LE実際に含まれる投票数
各投票インデックス4 バイトu32 LE掲示板上の位置
コミットメント長2 バイトu16 LE固定値 32
コミットメント32 バイトハッシュ値投票コミットメント
パス長2 バイトu16 LEMerkle パスのノード数
パスノード各 32 バイトハッシュ値包含証明の兄弟ハッシュ

正準化規則

エンコーディングの決定性を保証するために、以下の規則が厳守されます。

ソート規則

投票はインデックスの昇順にソートしてからエンコードする必要があります。

入力データの投票順序は任意であり得ますが、エンコーディング前にインデックスでソートすることで、同じ投票集合から常に同一のバイト列が生成されます。この規則に違反すると、TypeScript と Rust で異なるハッシュ値が計算され、検証が失敗します。

前提となる不変条件:

  • 各投票の index は一意である(重複インデックスは不正入力)

重複インデックスが存在する入力はプロトコル違反であり、正常系の正準化対象ではありません。したがって、正準順序の主キーは index の昇順として定義されます。

実装上は、重複インデックスという異常入力に対して Rust 側で追加の tie-break(commitmentmerklePath)を行いますが、これはあくまで異常系の決定性補助であり、正常系仕様を変更するものではありません。

エンディアン規則

すべての整数フィールドはリトルエンディアンでエンコードされます。

バイト数エンコーディング
u162リトルエンディアン
u324リトルエンディアン

16 進数正規化

コミットメント値やパスノードなどの 16 進数表現は、0x プレフィックスを除去した上でバイト列にデコードされます。16 進数文字列のまま連結するのではなく、常にバイナリ表現を使用します。

TypeScript と Rust の同期

入力コミットメントは TypeScript(サーバー側)と Rust(zkVM ゲスト内)の双方で独立に計算され、結果が一致する必要があります。

flowchart LR
  subgraph TypeScript
    TS_IN[公開入力データ] --> TS_CALC[正準エンコーディング<br/>+ SHA-256]
    TS_CALC --> TS_IC[入力コミットメント A]
  end

  subgraph "Rust (zkVM ゲスト)"
    RS_IN[ゲスト入力] --> RS_CALC[正準エンコーディング<br/>+ SHA-256]
    RS_CALC --> RS_IC[入力コミットメント B]
  end

  TS_IC --> CMP{A = B ?}
  RS_IC --> CMP
  CMP -->|一致| OK[検証成功]
  CMP -->|不一致| NG[検証失敗:<br/>入力データが異なる]

同期が破壊される典型的な原因:

  • ソート順序の不一致
  • エンディアンの不一致
  • ドメインタグの文字列差異
  • バージョン番号の不一致
  • 16 進数正規化規則の差異(大文字/小文字、0x プレフィックスの有無)

検証パイプラインにおける役割

入力コミットメントは、Counted-as-Recorded 段階での検証チェック counted_input_commitment_match として使用されます。

チェック ID検証内容
counted_input_commitment_match公開入力データから再計算した入力コミットメントがジャーナルの値と一致するか

このチェックが失敗する場合、zkVM が処理した入力データと公開されている入力データが異なることを意味し、結果の信頼性が根本的に損なわれます。

注意事項

入力コミットメントには投票者の秘密データ(選択肢や乱数)は含まれません。含まれるのはコミットメント値と Merkle パスのみであり、これらはすべて掲示板上で公開されているデータです。したがって、入力コミットメントの公開は投票の秘密性に影響しません。