シナリオ一覧
改ざんシナリオ S0〜S5 の定義と、実装上どこを改変するかを整理します。ここでは「zkVM 入力」「主張集計(claimed tally)」「ジャーナル統計(missing/invalid/excluded)」の関係を中心に説明します。
実装上の共通前提
- 1 回の finalize で選択されるシナリオは 1 つ(S0〜S5)
totalExpectedは 64(ユーザー 1 + ボット 63)- 掲示板(CT Merkle)は追記専用で、シナリオ適用で既存エントリは削除しない
tamperModeはnone/input/claimの 3 種- 本章は実 API 経路(
/api/finalize→finalize-session→finalize-sync|async)を基準に説明する - finalize 実行モードは
FINALIZE_ASYNC_MODEで切り替わる(false: 同期,true: 非同期) .env.local.exampleの既定値はFINALIZE_ASYNC_MODE=false(同期)- AWS 運用では通常
FINALIZE_ASYNC_MODE=true(非同期)を使う NEXT_PUBLIC_USE_MOCK_API=trueの mock API fixture は本章と異なるチェック結果を返すことがある/api/verifyのcounted_*チェックは STARK 状態でゲートされる(verificationStatus=not_runではnot_run、runningではpending)- 本章の「主な失敗点」は、STARK 検証が解決済み(
success/failed)でcounted_*が評価可能な局面を前提とする
tamperMode により、zkVM 入力へ反映されるかどうかが決まります。
flowchart TD
A[シナリオ選択] --> B{tamperMode}
B -->|none / claim| C[元の votes を zkVM 入力へ]
B -->|input| D[modifiedVotes を zkVM 入力へ]
C --> E[zkVM 実行]
D --> E
S0: 正常(改ざんなし)
改ざんを適用しない基準シナリオです。
| 項目 | 値 |
|---|---|
| tamperMode | none |
| zkVM 入力票数 | 64 |
| claimed と verified | 一致 |
excludedCount | 0 |
S1: ユーザー票の除外
ユーザー票(インデックス 0)を modifiedVotes から削除し、63 票を zkVM に渡します。
| 項目 | 値 |
|---|---|
| tamperMode | input |
| zkVM 入力票数 | 63 |
| claimed と verified | 一致(どちらも 63 票入力ベース) |
| ジャーナル統計 | missingIndices=1, invalidIndices=0, excludedCount=1 |
ポイント:
- 掲示板上のユーザー票エントリは残る
- 検出は主に完全性チェック(
excludedCount > 0) - ビットマップ証明が利用可能なら
counted_my_vote_includedでも検出可能
S2: ユーザー票に関する主張集計の改ざん
ユーザー票に対する「主張集計(表示する tally)」のみ改ざんします。zkVM には元の 64 票を渡します。
| 項目 | 値 |
|---|---|
| tamperMode | claim |
| zkVM 入力票数 | 64(元データ) |
| claimed と verified | 不一致(ユーザー選択肢が -1、別候補が +1) |
excludedCount | 0(通常) |
inputCommitment | zkVM 入力由来のため通常は一致 |
ポイント:
- 「票の中身を zkVM 入力で差し替える」実装ではない
- レシートや STARK 証明は通常どおり有効
- 検出の主因は
counted_tally_consistentの失敗
S3: ボット票の除外
現行実装ではボット票インデックス 1(targetBotId 初期値)を削除し、63 票を zkVM に渡します。
| 項目 | 値 |
|---|---|
| tamperMode | input |
| zkVM 入力票数 | 63 |
| claimed と verified | 一致(どちらも 63 票入力ベース) |
| ジャーナル統計 | missingIndices=1, invalidIndices=0, excludedCount=1 |
S1 との違い:
- S1: ユーザー自身の未集計をビットマップで直接示せる
- S3: ユーザー票は含まれるが、集計全体の完全性違反で検出される
S4: ボット票に関する主張集計の改ざん
1 票のボット票に関する「主張集計」だけを改ざんします。zkVM 入力は元の 64 票のままです。
| 項目 | 値 |
|---|---|
| tamperMode | claim |
| zkVM 入力票数 | 64(元データ) |
| claimed と verified | 不一致(対象ボットの元候補が -1、別候補が +1) |
excludedCount | 0(通常) |
inputCommitment | zkVM 入力由来のため通常は一致 |
ポイント:
- S2 と同様に、改ざん対象は
tally.counts側 - 検出の主因は
counted_tally_consistentの失敗
S5: ランダムエラー注入
64 票からランダムに 1 票を選び、50% で「除外」または「再集計(別候補化)」を行います。
実装上の重要点:
tamperModeは常にinput- そのため zkVM 入力は常に
modifiedVotesが使われる - 除外パスでは
missingIndicesが増え、再集計パスでは不正票化によりinvalidIndicesが増えるため、いずれもexcludedCount > 0になる - 再集計パスでは
counted_tally_consistentも失敗し得る(claimed は再集計後、verified は不正票除外後)
| 分岐 | zkVM 入力 | 代表的な統計 |
|---|---|---|
| 除外パス | 63 票 | missingIndices=1, invalidIndices=0, excludedCount=1 |
| 再集計パス | 64 票 | missingIndices=0, invalidIndices=1, excludedCount=1 |
再集計パスでも excludedCount が 0 にならないのは、zkVM 側で不正票として除外されるためです。
シナリオ一覧表
| シナリオ | 類型 | tamperMode | zkVM 入力 | 主な失敗点(STARK 解決後) |
|---|---|---|---|---|
| S0 | 正常 | none | 元の 64 票 | なし |
| S1 | 除外 | input | 63 票(ユーザー除外) | excludedCount > 0 |
| S2 | 主張改ざん | claim | 元の 64 票 | claimed ≠ verified |
| S3 | 除外 | input | 63 票(ボット除外) | excludedCount > 0 |
| S4 | 主張改ざん | claim | 元の 64 票 | claimed ≠ verified |
| S5 | ランダム(実装上 input) | input | 63 または 64 票 | excludedCount > 0(再集計では claimed ≠ verified も発生) |
ジャーナル統計オーバーライド(同期 finalize のみ)
同期 finalize(ローカル実行)では、tamperMode=input の場合(S1/S3/S5)に zkVM 実行後の統計を次で上書きします。
missingIndices = max(existingMissing, ignoredCount)invalidIndices = max(existingInvalid, recountedCount)excludedCount = missingIndices + invalidIndicescountedIndices = totalExpected - excludedCount
一方、FINALIZE_ASYNC_MODE=true の非同期 finalize ではこのオーバーライドは適用せず、コールバックで復元した zkVM ジャーナル値をそのまま用います。
tamperMode=claim(S2/S4)ではこのオーバーライドは行いません。
集計フローへの挿入点
flowchart TB
A[セッション votes 読み込み] --> B[シナリオ適用]
B --> C{tamperMode}
C -- input --> D[modifiedVotes で zkVM 入力生成]
C -- claim --> E[元の votes で zkVM 入力生成]
D --> F[zkVM 実行]
E --> F
F --> G[finalizationResult 保存]
B --> H[claimedCounts 計算]
H --> G