STH ダイジェスト
Signed Tree Head (STH) ダイジェストによる分割ビュー攻撃の緩和メカニズムを解説します。
ログ ID、ツリーサイズ、タイムスタンプ、掲示板ルートを束縛するダイジェストにより、サーバーが異なるクライアントに異なるツリー状態を提示する攻撃を検出可能にします。
概要
分割ビュー攻撃(split-view attack)とは、悪意あるサーバーが異なる検証者に対して異なる掲示板の状態を提示する攻撃です。例えば、投票者 A には「全 64 票が含まれたツリー」を見せながら、投票者 B には「特定の票が除外されたツリー」を見せることが考えられます。
STH ダイジェストは、掲示板の状態を(ログ ID、ツリーサイズ、タイムスタンプ、ルートハッシュ)の組として束縛し、独立した第三者ソースとの合意確認を通じてこの攻撃を検出します。
flowchart TD
subgraph "STH ダイジェストの構成"
LID[ログ ID<br/>32 バイト]
TSZ[ツリーサイズ<br/>4 バイト]
TS[タイムスタンプ<br/>8 バイト]
BR[掲示板ルート<br/>32 バイト]
end
LID --> H[SHA-256]
TSZ --> H
TS --> H
BR --> H
H --> STH[STH ダイジェスト<br/>32 バイト]
STH --> J[zkVM ジャーナルに記録]
STH --> TP[第三者ソースに公開]
ダイジェストフォーマット
sth_digest = SHA-256(
log_id ← 32 バイト
|| tree_size ← u32 リトルエンディアン (4 バイト)
|| timestamp ← u64 リトルエンディアン (8 バイト, Unix 時刻ミリ秒)
|| bulletin_root ← 32 バイト
)
SHA-256 への入力は合計 76 バイトです。
各フィールドの仕様
| フィールド | サイズ | エンコーディング | 説明 |
|---|---|---|---|
| ログ ID | 32 バイト | ハッシュ値 | 掲示板インスタンスの識別子 |
| ツリーサイズ | 4 バイト | u32 LE | 掲示板のリーフ数 |
| タイムスタンプ | 8 バイト | u64 LE | Unix 時刻(ミリ秒) |
| 掲示板ルート | 32 バイト | ハッシュ値 | Merkle ツリーのルート |
ログ ID
ログ ID は掲示板インスタンスを一意に識別するための値であり、以下のように生成されます:
log_id = SHA-256("stark-ballot:bulletin-log|v1.0" || seed)
ドメインタグ "stark-ballot:bulletin-log|v1.0" と任意のシード値を連結し、SHA-256 でハッシュします。ログ ID は掲示板のライフタイム中に変化しない固定値です。
ログ ID を STH ダイジェストに含めることで、異なる掲示板インスタンスの STH が偶然に衝突することを防止します。
分割ビュー攻撃と検出メカニズム
攻撃シナリオ
sequenceDiagram
participant A as 投票者 A
participant S as 悪意あるサーバー
participant B as 投票者 B
S->>A: ツリー状態 X<br/>(64 票、ルート R₁)
S->>B: ツリー状態 Y<br/>(63 票、ルート R₂)
Note over A,B: A と B は互いに異なる<br/>ツリー状態を見ている
この攻撃では、サーバーは投票者 B に対して特定の票を除外したツリーを見せています。投票者 B は自身に提示されたツリーに対する包含証明や整合性証明を検証できますが、投票者 A とは異なるツリーを見ていることに気づけません。
第三者合意による検出
検証者は、NEXT_PUBLIC_STH_SOURCES に設定された独立ソースへ問い合わせ、ジャーナル内の STH ダイジェストと照合することで分割ビューを検出できます。
sequenceDiagram
participant V as 検証者
participant S as サーバー
participant T1 as 第三者ソース 1
participant T2 as 第三者ソース 2
V->>S: ジャーナルから STH ダイジェスト D' を取得
V->>T1: STH を問い合わせ → D₁
V->>T2: STH を問い合わせ → D₂
V->>V: D' = D₁ = D₂ ?
alt 全て一致
V->>V: 合意成立 → 検証成功
else 不一致あり
V->>V: 分割ビューの疑い → 検証失敗
end
現行実装は第三者ソースへの照会(fetch)を行うものであり、アプリケーションが第三者ソースへ STH を自動公開する機能は含みません。
合意ロジック
第三者 STH 検証は以下の条件をすべて満たした場合に成功します:
- 十分な一致数: 一致するソースの数が最小要求数(コードフォールバック値: 2)以上
- 全会一致: 応答可能なすべてのソースが一致すること(
matchingSources = comparableSources)
各ソースに対して以下のフィールドが照合されます:
| 照合フィールド | 条件 |
|---|---|
| STH ダイジェスト | 必須一致 |
| 掲示板ルート | 提供されている場合は一致 |
| ツリーサイズ | 提供されている場合は一致 |
zkVM との連携
zkVM ゲストプログラムは、入力として受け取ったログ ID、ツリーサイズ、タイムスタンプ、掲示板ルートから STH ダイジェストを再計算し、ジャーナルにコミットします。
この仕組みにより、STARK 証明が特定のツリー状態に対して生成されたことが保証されます。第三者はジャーナルの STH ダイジェストを独立ソースの値と照合することで、サーバーが証明と異なるツリー状態を提示していないかを確認できます。
検証パイプラインにおける役割
STH ダイジェストの第三者検証は、Recorded-as-Cast 段階のチェックとして実行されます。
| チェック ID | 検証内容 |
|---|---|
recorded_sth_third_party | 独立ソースから取得した STH ダイジェストがジャーナルの値と一致するか |
このチェックが有効化されている場合(STH ソースが設定されている場合)、合意が得られなければ「Verified」ステータスはブロックされます。
設定
第三者 STH 検証は環境変数で制御されます。
| 環境変数 | 説明 | コードフォールバック値 |
|---|---|---|
NEXT_PUBLIC_STH_SOURCES | カンマ区切りの STH ソース URL | 未設定(第三者照合を実行しない) |
NEXT_PUBLIC_STH_MIN_MATCHES | 必要な最小一致ソース数 | 2 |
STH ソースが未設定の場合、recorded_sth_third_party は not_run(未実行)となり、第三者照合は行いません。セキュリティ上は少なくとも 2 つ以上の独立ソースを設定することが推奨されます。
相対パス(例: /api/sth)はリクエスト元のオリジンに対して解決されます。
開発用の .env.local.example では次の値が設定されています:
NEXT_PUBLIC_STH_SOURCES=/api/sthNEXT_PUBLIC_STH_MIN_MATCHES=1
この設定は PoC の動作確認を優先したものです。本番運用では、独立した複数ソースと NEXT_PUBLIC_STH_MIN_MATCHES >= 2 を推奨します。
PoC における制約
本 PoC の開発用テンプレート(.env.local.example)では、STH ソースとして同一サーバー上の API エンドポイント(/api/sth)を使用します。実運用環境では、独立した組織が運営する複数のソースを使用する必要があります。同一サーバー上のソースのみでは、分割ビュー攻撃に対する防御力が限定的です。