
2026/04/02 4:52
**データ構造を誤った方法で署名する**
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
Snowpackは、暗号操作中に異なるデータ型が互いに誤って解釈されることを防ぎ、代替攻撃を抑止するプロトコルレベルのドメインセパレーターを導入します。
各タグ付き構造体について、コンパイラはコンパイル時に一度だけランダム64ビット値を生成し、IDLに埋め込みます。このセパレーターはプロトコルの寿命全体で一定である必要があり、そのユニーク性はコンパイラ/実行時に検証され、重複が見つかるとパニックまたはエラーになります。セパレーターは最終的なシリアライズ形式には含まれません;代わりに署名・検証・暗号化・ハッシュ化・HMACの前にオブジェクトのバイトストリームと連結されます。セパレーターが正規化されたシリアル化に含まれていないため、ある型用に作成された署名は、同じバイトストリームを共有する別の型には失敗します。
Snowpack のエンコーディングは JSON スタイルの位置配列を使用してフィールド順序を保持し、欠損フィールドには
nil を挿入して後方/前方互換性をサポートします。中間配列は厳格な制約下で正規化 MsgPack バイトストリームにシリアライズされます:整数サイズの最小化と 1 ペア以上のキー・バリュー対を持つ辞書を禁止し、決定的な出力を保証します。リストは配列としてエンコードされ、オプション値は単一ペアの辞書に変換されます。タグ付きユニオン(variant)は配列または単一キーの辞書のいずれかを使用し、この形式は既存の MsgPack ライブラリと完全に互換性があります。
このシステムは GitHub 上でオープンソース化されており、現在は Go と TypeScript のターゲットをサポートしています。将来的には他の言語も追加予定です。クロスタイプ代替脆弱性を排除することで、Snowpack は安全なシリアライゼーションに依存する任意のアプリケーションを強化します。その設計は、ドメイン分離と正規化エンコーディングを提供するために他のシリアライゼーション方式にも採用できます。
本文
暗号アルゴリズムに入力する前にデータをどのようにパッケージ化しますか?
(署名、暗号化、MAC あるいはハッシュ)
この質問は数十年もの間、十分な解決策が得られずに残ってきました。
対処すべき重要な課題は少なくとも2つあります:
- 正規化された出力 – エンコーディングは必ず同一の「カノニカル」出力を生成する必要があります。Bitcoin のようなシステムでは、異なるエンコーディングが同じメモリ上のデータにデコードされるケースで問題が発生しています。
- ドメイン分離 – エンコーディング体系は「ドメイン分離」の重要性を確実に扱う必要があります。
問題例
次のような IDL(ここでは protobuf を想定)を考えてみます。
message TreeRoot { int64 timestamp = 1; bytes hash = 2; } message KeyRevoke { int64 timestamp = 1; publicKeyFingerprint hash = 2; }
この2つの構造体はフィールドごとに完全に揃っていますが、意味する内容は全く異なります。
あるノードが
TreeRoot を署名し、その署名をネットワークへ送信したとします。攻撃者は、バイト列ごとに同一になる KeyRevoke メッセージを作成し、そこに TreeRoot の署名を貼り付けることができます。結果として、実際には
KeyRevoke を署名したかのように見せかけることが可能になります。
これは理論上の攻撃ではなく、Bitcoin、Ethereum 上の DEX、TLS、JWT、AWS などで長い歴史を持つ問題です。MAC(HMAC や SHA‑3)、ハッシュ、あるいは暗号化に対しても同じ考えが当てはまります――現代の多くの暗号化手法は認証付きです。一般的に、暗号技術は送信者と受信者がペイロードだけでなく「データ型」についても合意していることを保証すべきです。
既存の解決策では、Solana のローカル名ハッシュや Ethereum でのベストプラクティス、TLS v1.3 の「コンテキスト文字列」など、アドホックな手法が使われています。より体系的なアプローチが求められます。
FOKS のアイデア:IDL 内にドメインセパレータを埋め込む
FOKS は Snowpack と呼ばれる計画を発案しました。これはランダムで不変のドメインセパレータを IDL に直接埋め込みます。
struct TreeRoot @0x92880d38b74de9fb { timestamp @0 : Uint; hash @1 : Blob; }
簡易コンパイラが IDL を対象言語へ変換します。
その言語では、ランタイムライブラリが次のようなメソッドを提供します:
ドメインセパレータ(
@0x92880d38b74de9fb)とオブジェクトのシリアライズ結果を連結し、そのバイト列を署名原語へ渡す。検証側は同じ連結を再構築して署名を確認します。
ドメインセパレータ自体は最終的なシリアリゼーションには現れません。
送信者と受信者は共有プロトコル仕様でこの値に合意するため、余計なバイトを浪費しません。暗号化、HMAC、ハッシュも同様の手順で機能します。
Go(また TypeScript など)では型システムがセキュリティ保証を強制します。
func (t TreeRoot) GetUniqueTypeID() uint64 { return 0x92880d38b74de9fb } func Sign(key Key, obj VerifiableObjecter) ([]byte, error) func Verify(key Key, sig []byte, obj VerifiableObjecter) error
VerifiableObjecter は GetUniqueTypeID() と EncodeToBytes などを要求するインタフェースです。ドメインセパレータを持たない構造体は
GetUniqueTypeID() を実装できず、Sign や Verify に渡すことが型エラーで拒否されます。同様に暗号化・MAC でも同じルールが適用されます。
ランダムに生成されたドメインセパレータはほぼ確実にユニーク(グローバル)であるため、送信者と検証者が扱うデータ型を誤解することはありません。先述の代替攻撃も検証失敗に終わります。
開発者は IDE や CLI などの簡易ツールを使ってランダムドメインセパレータを生成し、プロトコル仕様へ挿入すればよいでしょう。
ランダム生成ロジック
生成ロジックは Rabin フィンガープリントで多項式を乱数化する手法に似ています。
Bob が新しいプロジェクトのために全てのドメインセパレータをランダムに生成した場合、確率的には Bob の検証者が別プロジェクトから来た署名を受け入れることはほぼ起こりません。
Mallory が Bob の公開仕様を見て新規プロジェクトを作り、同じドメインセパレータを意図的に再利用したとしても、彼女が Bob の秘密鍵を持っていなければ Bob で署名が受理されることはありません。
Mallory がランダムに生成しても保証は変わりません。
Snowpack コンパイラとランタイムはプロジェクト内で全ドメインセパレータのユニーク性を確認し、重複があればエラーまたは panic を発生させます。
ドメインセパレータはプロトコルの寿命にわたって固定しておくべきです。フィールド追加・削除時も、残存フィールドの位置が変わらず、退役したフィールドを再利用しない限り問題ありません(protobuf や Cap’n Proto と同様)。
Snowpack IDL:ドメイン分離 + カノニカルエンコーディング
Snowpack の組み込みドメイン分離は革新的です。さらに RPC および暗号関数への入力シリアリゼーションに対して、前方・後方互換性のある簡易かつ効果的な体系を提供します。
エンコーディングフロー
- Go 構造体 → Snowpack – コンパイル時生成
- Snowpack → 途中 JSON‑似オブジェクト – 自己記述配列(例:
)[1234567890, \xdeadbeef] - 途中オブジェクト → バイト列 – MsgPack を使用し、以下の制約付き
- 整数は最小サイズエンコードのみ
- 複数キー–バリュー対を持つ辞書は送信されない(カノニカルキー順序の問題回避)
結果として毎回同一にエンコードされたフラットなバイトストリームが得られます。
プロトコル進化例
struct TreeRoot @0x92880d38b74de9fb { hash @1 : Blob; timestampMsec @2 : Uint; }
中間エンコードは次のようになります:
[ nil, \xdeadbeef, 1234567890123 ]
古いデコーダは欠損フィールドを
nil と解釈し、新しいデコーダは新フィールドに対してゼロ値を受け取ります。プロトコルレベルでの失敗は起こりません。
追加機能
- リスト – 配列ベースで簡単にエンコード
- オプション – 可変要素を持つ配列として表現
- バリアント(タグ付きユニオン) – 単一キー–バリュー対の辞書としてエンコードし、既存 MsgPack ライブラリで安全にデコード可能
これらは FOKS が直面した全てのケースをカバーします。
まとめ
ドメイン分離の欠陥は実際のシステムに度々被害をもたらしてきました。既存の対策はコンテキスト文字列、メソッド名ハッシュ、手作りプレフィックスなどアドホックで、忘れやすく監査しづらいです。
Snowpack は別の道を歩みます:
- ランダムかつ不変の 64‑ビットドメインセパレータが IDL 自体に埋め込まれる
- 型システムが「セパレータ無しオブジェクト」の署名・暗号化・MAC を禁止
このコアアイデアは単一システムを超えるものであり、他のシリアリゼーション方式への採用も歓迎します。現時点では GitHub 上でオープンソース化されており、Go と TypeScript が対象です。今後さらに多くの言語が追加予定です。
クレジット
Jack O’Connor に感謝します。本稿のドラフトへのフィードバックと Snowpack のインスピレーションとなった関連システムの構築に貢献していただきました。