実装者向けガイド
Label 309 に準拠した実装の構築方法。推奨される階層型アーキテクチャ、言語をまたいでバイト単位で一致させる契約、そして相互運用性を定義する適合性テストベクターについて説明します。
Label 309 はワイヤーフォーマットと一連の暗号構成であり、製品ではありません。 TypeScript、Python、Rust、Go、あるいはモバイルのネイティブランタイムによる複数の独立した実装が共存でき、ある実装で生成されたレコードは別の実装でも検証できなければなりません(MUST)。 このページは、そうした実装を構築するチームに向けたものです。 暗号面を監査可能な状態に保つアーキテクチャ、二つの実装を相互運用可能にする厳密な契約、そして適合しているかどうかを機械的に判定する適合性スイートについて説明します。
Label 309 が言語をまたいで相互運用可能である理由は二つあります。 一つ目は決定性です。各構成は公開された標準に固定されています (RFC 8949 の canonical CBOR、 RFC 8032 の Ed25519、 RFC 7748 の X25519、 RFC 5869 の HKDF、 RFC 9106 の Argon2id、 RFC 9052 の COSE)。 そのため、同じ入力からはどこでも同じバイト列が得られます。 二つ目は適合性スイートです。実装が再現できるかどうかで判定される、バイト単位で厳密なテストベクターの集合です。 適合性とは検証できる性質であり、主張するだけのものではありません。
階層型アーキテクチャ
適合する実装は、暗号プリミティブとアプリケーションロジックを別々の層に分離し、各層がその直下の層だけに依存する構造をとるべきです(SHOULD)。 以下に挙げる名前はパッケージ名ではなく役割を示すものです。名前は自由に選んでかまいません。
┌─────────────────────────────────────────────────────────┐
│ application │
│ UI, routing, persistence, payments, background jobs │
├─────────────────────────────────────────────────────────┤
│ SDK │
│ service client + standalone verifier + helpers │
├─────────────────────────────────────────────────────────┤
│ wire-format library │
│ schema · structural validator · canonical-CBOR codec │
├─────────────────────────────────────────────────────────┤
│ cryptographic core │
│ hashes · KDFs · signatures · KEM · AEAD · CBOR · COSE │
│ no application or framework dependencies │
└─────────────────────────────────────────────────────────┘この境界は装飾ではなく、構造上不可欠なものです。各層には単一の責務があり、知ってはならない事柄が明確に定められています。
暗号コア
最下層が保持するのはプリミティブだけです。ハッシュ関数、KDF、署名および KEM の演算、AEAD コンテンツ層、canonical CBOR、COSE_Sign1、封緘済み存在証明(Proof of Existence, PoE)のラップ/アンラップ構成、Merkle のルートおよび証明、そしてこれらが送出する型付きエラークラスです。 ドメインロジック、HTTP、データベースアクセス、UI やサーバーフレームワークの import は一切含みません。
この層はアプリケーションまたはサーバーに紐づくものを含んではならず(MUST NOT)、またブラウザでも安全に動作しなければなりません(MUST)。 理由は三つあります。
- あらゆる環境で動作します。 ファイルのハッシュ化、エンベロープの構築、そして何より単独検証者(standalone verifier)は、ブラウザ、サーバーレスワーカー、コマンドラインのいずれでもサーバーと同じように実行できます。サーバー専用の依存(データベースドライバ、ランタイムに紐づいたロギングフレームワーク、UI ライブラリ)があると、これらの動作環境が壊れ、コアをバンドルするすべての利用先が肥大化します。
- 監査対象そのものです。 レビュアーは、プリミティブだけのパッケージを RFC と照らし合わせながら端から端まで読み切れます。アプリケーションコードが混入した瞬間、セキュリティレビュアーが頭の中に収めておかなければならない範囲は際限なく広がります。
- 第三者が組み込むのはこの層です。 どのサービスも信頼せずチェーンだけを信頼する独立した検証者は、この層だけを取り込んで使います。小さく可搬性の高い状態に保つことこそが「自分で検証する」を現実的にしています。
具体的には、コアは ORM やデータベースドライバ、UI フレームワーク、サーバーに紐づくロギングフレームワーク、あるいはアプリケーションモジュールを import してはなりません(MUST NOT)。
乱数はプラットフォームの CSPRNG(Web Crypto の getRandomValues、または同等の再エクスポート)から取得しなければならず(MUST)、Node.js 専用のソースを使ってはなりません。同一のソースがブラウザでも変更なしに動作するためです。
境界はコードレビューではなく CI で強制してください
ゼロ依存のルールは、便利な import が一つ紛れ込んだ瞬間に崩れ始めます。実装は、コアおよびワイヤーフォーマットライブラリ内のすべての import をたどり、層ごとの許可リストに含まれない指定子が見つかればビルドを失敗させる依存グラフの lint を実行すべきです(SHOULD)。レビュアーは見落としますが、linter は見落としません。
ワイヤーフォーマットライブラリ
その一つ上の層が Label 309 そのものを担います。レコードスキーマ、構造バリデーター、canonical CBOR のエンコーダとデコーダです。 暗号コア(ハッシュ化、COSE、CBOR コーデックのため)にのみ依存し、それ以外のアプリケーションに紐づくものには依存しません。 その表面は小さく、純粋です。
- encode — 検証済みレコードから canonical CBOR のバイト列を生成します。
- decode — その逆を行います。
- validate — デコード済みレコードに対して標準が定める構造的・意味的チェックを実行し、型付きの結果を返します(検証を参照)。
レコードの規則がコードとして体現される場所が、この層です。閉じたキー集合、チャンク再結合の規律、items か merkle のいずれか一方という不変条件、canonical CBOR の要件がここに収まります。
コアと同様に、HTTP クライアント、データベースドライバ、フレームワークの import を持たない状態を保ちます。
SDK とアプリケーション
SDK は下位の層を扱いやすいヘルパーへとまとめます。サービスクライアント、エンベロープのビルド・アンロックのヘルパー、そして単独検証者(standalone verifier)です。 この検証者はレコードをデコードし、その構造を確認し、オンチェーンの鍵と照合してレコードの署名を検証し、公開データだけを用いて判定を下す関数です。 単独検証者は、実装者が運用するいかなるサービスへのネットワークアクセスもなしに動作しなければなりません(MUST)。その外部入力は、検証者自身が選んだ公開ブロックチェーンエクスプローラーだけです。 SDK もブラウザで安全に動作する状態に保つべきです(SHOULD)。
アプリケーション層(UI、ルーティング、永続化、課金、バックグラウンドジョブ)はグリーンフィールドであり、相互運用性の義務を負いません。 標準は、その構築方法に何ら制約を課しません。唯一の条件は、すでに正しさが証明された暗号面に踏み込むのではなく、その上に位置することです。
バイト単位で一致させる契約
相互運用性はバイト列の性質であり、意図の問題ではありません。二つの実装が相互運用できるのは、出力に自由度のないプリミティブが同じ入力から同じバイト列を生成する場合に限られます。 これがパリティ契約であり、適合性の核心です。
この契約はきれいに二つに分かれます。出力が入力によって完全に決まる演算は、実装をまたいでバイト単位で一致しなければなりません(MUST)。 乱数を消費する演算は呼び出しごとにバイト列が一致しえません。そうした演算に対する契約は相互消費可能性です。ある実装が生成した値は、他のどの実装でも消費できなければなりません(MUST)(ある言語で封緘された暗号文が別の言語で復号できる、ということです)。
バイト単位で一致するプリミティブ
以下の各演算は入力に対する純粋関数であり、適合するすべての実装でバイト単位で一致する出力を発しなければなりません(MUST)。
| プリミティブ | 準拠先 | 一致しなければならない出力 |
|---|---|---|
| シード → Ed25519 / X25519 鍵ペア | 登録済み info 定数を用いた HKDF-SHA-256 | 導出された公開鍵と秘密鍵 |
| HKDF-SHA-256 | RFC 5869 | 固定入力に対する出力鍵マテリアル |
| HMAC-SHA-256 によるスロット集合 MAC | RFC 2104 | 固定の CEK とスロット集合に対する slots_hash および slots_mac タグのバイト列 |
| Argon2id(パスフレーズ KDF) | RFC 9106 | 固定の (m, t, p, salt, len, password) に対する導出鍵 |
| SHA-256 | FIPS 180-4 | ダイジェスト |
| BLAKE2b-256 | RFC 7693 | ダイジェスト |
| canonical CBOR エンコード | RFC 8949 §4.2.1 | 固定入力に対するエンコード後のバイト列 |
| COSE_Sign1 エンコード | RFC 9052 | 固定のヘッダー、ペイロード、署名に対する構造のバイト列 |
| Ed25519 署名 / 検証 | RFC 8032(厳格モード) | 署名、判定結果 |
| X25519 ECDH | RFC 7748 | 固定のスカラーに対する共有秘密 |
| 封緘済み PoE のラップ/アンラップ | 封緘済み PoE | ephemeral と CEK を注入したときのスロットごとのバイト列および MAC |
| Merkle ルート+包含証明 | RFC 9162 §2.1.1 | 順序付きリーフリストに対するルートおよびリーフごとの証明 |
特に強調すべき点が二つあります。Ed25519 は厳格です。 適合する検証者は RFC 8032 §5.1.7 の canonical-S 規則と低位数点の排除規則を適用しなければなりません(MUST)。これにより、二つの実装は受理する署名だけでなく拒否する署名についても一致します。
Argon2id はエコシステムの境界をまたぎます。 言語が違えば手に取る Argon2 ライブラリも異なりますが、適合するライブラリはいずれも RFC 9106 を実装しており、同一のパラメータから同一の出力を生成しなければなりません(MUST)。契約となるのはライブラリではなくパラメータの集合です。
乱数を消費する演算
鍵生成、スロットごとに新たな ephemeral を用いた封緘済み PoE のラップ、そしてエンベロープ暗号化は、いずれも新鮮な乱数を引き込むため、呼び出しごとに出力が異なり、バイト単位で固定することはできません。 これらに対する契約は相互消費可能性です。ある実装が生成した出力は、他のすべての実装で消費できなければなりません(MUST)。 ある言語で封緘されたレコードは別の言語で復号できなければならず(MUST)、ある実装で鋳造された鍵ペアは別の実装で検証や暗号化の宛先として利用できなければなりません(MUST)。 適合性スイートは、ephemeral を注入してラップを再現可能にする決定的なテストフックと、ある言語で暗号化して別の言語で復号する往復フィクスチャによって、これらを固定します。
封緘済み PoE 構成を組み立てる
封緘済み PoE は、ワイヤーフォーマットのなかで最も密度の高い部分であり、たった 1 バイトの間違い、マップキーの順序ずれ、1 文字ずれたラベル、非正規なチャンク化が、自分の実装では開けるのに他のどの実装でも開けないエンベロープを生んでしまう箇所でもあります。このセクションは組み立てのチェックリストです。厳密なレシピ、各 AEAD が覆う追加認証データ、トライアル復号のループ、そしてあらゆるプロデューサーと検証者が課すべきガードを示します。封緘済み PoE の構成リファレンスが散文だとすれば、こちらはパリティゲートが緑になるよう配線するための手順です。次の外部ドラフトは、再現すべきバイト列を内部で固定するため、厳密にピン留めしてください。
chacha20-poly1305-stream64k— コンテンツフォーマット — は、age v1 仕様の 64 KiB セグメント化 STREAM レイアウトにした ChaCha20-Poly1305(RFC 8439)です。チャンクサイズ(65536)、12 バイトのチャンクごとのノンスuint88_be(counter) ‖ final_flag、空のチャンクごとの AAD、そしてファイナルフラグのルールを厳密にピン留めしてください。これらは再現すべきバイト列を固定します。- X-Wing(
mlkem768x25519の KEM)は draft-connolly-cfrg-xwing-kem-10 です。これをブラックボックスの KEMとして扱ってください。構成は受信者の公開鍵と暗号文を鍵導出ステップそのものに結び付けるため、コンバイナー内部のハッシュのいかなる性質にも依拠しません。XWing.Encapsulateは、固定されたリビジョンの公開鍵有効性チェックを適用しなければならず(MUST)、それに通らない鍵にはカプセル化を拒否しなければなりません。「X25519 のクラシカルなセキュリティを決して下回らない」フロアは、正当に生成された鍵に限って適用されます。このチェックを省くと、その受信者についてフロアを失います。適合性 KEM ベクトルはカプセル化を draft-10 に対して固定するので、ドラフトのリビジョン不一致はただちに表面化します。
1 つの CEK、2 つの鍵配送経路
封緘済みレコードは平文を単一のコンテンツ暗号化鍵(CEK)で一度だけ暗号化し、その CEK を、フィールドの有無で識別される、互いに排他的な 2 つの経路のいずれかで配送します。モードタグはありません。
- slots 経路 — CEK は受信者ごとに、スロットごとの鍵暗号化鍵のもとで独立にラップされます。
encはslots(およびkem、slots_mac)を持ちます。 - passphrase 経路 — CEK は、正規化されたパスフレーズから Argon2id で直接導出されます。
encはpassphraseを持ち、kem、slots、slots_macは持ちません。
両経路は enc.scheme(常に 1。それ以外は拒否)、enc.aead(chacha20-poly1305-stream64k)、enc.nonce(24 バイト)を共有します。異なるのは鍵コミットメントがどこに置かれるかです。slots 経路ではオンチェーンの slots_mac に、passphrase 経路では暗号文ブロブの内側にある 32 バイトのヘッダーにあります。どちらの経路もアイテムのハッシュ主張を自身のトランスクリプトへ束縛し、どちらもコンテンツを同じセグメント化された STREAM で封緘します。異なるのは鍵配送とコミットメントであって、コンテンツ層ではありません。
スロットごとのラップ(slots 経路)
レコード全体で 1 つの KEM を選びます。単一の slots[] の中で KEM を混在させてはいけません。N 人の受信者それぞれについて、新鮮なスロットごとの鍵暗号化鍵を導出し、同じ CEK を 12 バイトの全ゼロノンスの ChaCha20-Poly1305 でラップします。AAD はその KEM の info ラベルリテラルに設定し(空の AAD は不可)、ちょうど 48 バイト(32 バイトの CEK 暗号文 + 16 バイトのタグ)を生成します。全ゼロノンスが安全なのは、鍵暗号化鍵がスロットごとである場合に限られます。下記の一意性ガードを参照してください。
x25519(古典)。 スロットごとに新鮮な X25519 エフェメラル鍵ペア。
priv_epk : randomBytes(32) ; fresh per slot
pub_epk : x25519_publicKey(priv_epk)
shared : x25519_sharedSecret(priv_epk, pub_R) ; reject all-zero result
kek_salt : SHA-256("cardano-poe-x25519-kek-salt-v1" || enc.nonce || pub_epk || pub_R) ; 32 B
KEK : HKDF-SHA-256(ikm = shared, salt = kek_salt,
info = "cardano-poe-kek-v1", L = 32)
wrap : ChaCha20-Poly1305(key = KEK, nonce = zeros(12),
ad = "cardano-poe-kek-v1", plaintext = CEK) ; 48 B
slot : { "epk": pub_epk, "wrap": wrap }mlkem768x25519(ハイブリッド。X-Wing)。 スロットごとに新鮮な X-Wing カプセル化。
enc = XWing.Encapsulate(pub_R) ; named fields — MUST NOT consume positional order
kem_ct = enc.ct ; 1120 B
shared = enc.ss ; 32 B
kek_salt : SHA-256("cardano-poe-xwing-kek-salt-v1" || enc.nonce || kem_ct || pub_R) ; 32 B
KEK : HKDF-SHA-256(ikm = shared, salt = kek_salt,
info = "cardano-poe-kek-mlkem768x25519-v1", L = 32)
wrap : ChaCha20-Poly1305(key = KEK, nonce = zeros(12),
ad = "cardano-poe-kek-mlkem768x25519-v1", plaintext = CEK)
slot : { "kem_ct": kem_ct, "wrap": wrap } ; kem_ct = single 1120-byte byte stringどちらのソルトも一つの形 — SHA-256(label || enc.nonce || <スロットの KEM 素材> || pub_R) — を持ち、古典の経路では 32 バイトのエフェメラル pub_epk、ハイブリッドの経路では 1120 バイトの X-Wing 暗号文 kem_ct を運びます。|| はバイト連結で、各ソルトプレフィックスのリテラルは、終端文字も長さプレフィックスも持たない厳密な ASCII です。pub_R は受信者の正規ワイヤー鍵です(x25519 では 32 B、mlkem768x25519 では固定の 1216 B)。ハイブリッドのスロットは別個の epk を持ちません。X25519 のエフェメラルは kem_ct の末尾 32 バイトです。kem_ct はちょうど 1120 バイトの単一の CBOR バイト文字列です。チャンク化されるのは転送のためのレコード本体全体だけであって、個々のフィールドではありません。
ソルトは 3 つの値を結び付けます。スロットの KEM 素材(KEK をスロット固有にします)、pub_R(別の受信者に対するコンフューズドデピュティ的な中継を阻みます)、そして enc.nonce(KEK を一つのエンベロープに錨で留めるため、KEM 乱数が繰り返されてもエンベロープをまたぐリンク可能性に劣化するだけです)です。異なる info ラベルが KEM をまたぐドメイン分離を与えるため、ある KEM で導出した KEK が、同一の共有秘密上であっても別の KEM で導出したものと等しくなることはありません。11 の内部ラベル、すなわち cardano-poe-kek-v1、cardano-poe-kek-mlkem768x25519-v1、cardano-poe-x25519-kek-salt-v1、cardano-poe-xwing-kek-salt-v1、cardano-poe-item-hashes-v1、cardano-poe-slots-transcript-v1、cardano-poe-slots-mac-v1、cardano-poe-passphrase-transcript-v1、cardano-poe-passphrase-mac-v1、cardano-poe-payload-v1、cardano-poe-payload-passphrase-v1 を、それぞれバイト単位でそのまま使ってください。どれもワイヤー上にシリアライズされることはありません。これらは固定された定数であって、レジストリで選択できるものではありません。1 バイトでも違えば、正直なプロデューサーには再現できない slots_mac、コミットメント、または AEAD タグになります。
MAC を計算する前にシャッフルしてください。 入力順序(「主たる受信者が先頭」)は特権的なメタデータであり、入力順のままスロットを発行するとそれが漏れます。slots[] を CSPRNG で 偏りのない Fisher-Yates の置換でシャッフルしてください。素朴な u32 % m のインデックス抽出は小さい剰余に偏るため、一様なインデックスへ棄却サンプリングしなければなりません。これをスロット集合の MAC を計算する前に行い、その MAC はシャッフル後のワイヤー上の順序を結合します。
スロット集合の MAC:トランスクリプトをハッシュし、CEK で HMAC する
スロット集合の MAC は、スロット集合全体に、スロットの読み方を固定するヘッダーフィールドを加えたものを、CEK へ結合します。2 つのステップで組み立てます。閉じたトランスクリプトを 1 度ハッシュし、そのハッシュを HMAC します。
hashes_hash : SHA-256("cardano-poe-item-hashes-v1" || canonicalEncode(item.hashes)) ; 32 B
SLOTS_TRANSCRIPT = { ; closed 7-key map; keys are a set, not an order
"scheme": 1,
"path": "slots",
"aead": <enc.aead>, ; the content-format identifier
"kem": <enc.kem>, ; "x25519" | "mlkem768x25519"
"nonce": <enc.nonce>, ; bytes(24)
"slots": <slots>, ; the shuffled on-wire slot array
"hashes_hash": hashes_hash ; bytes(32), over this item's hashes
}
slots_hash : SHA-256("cardano-poe-slots-transcript-v1" || canonicalEncode(SLOTS_TRANSCRIPT))
HMAC_KEY : HKDF-SHA-256(ikm = CEK, salt = "", info = "cardano-poe-slots-mac-v1", L = 32)
slots_mac : HMAC-SHA-256(key = HMAC_KEY, msg = slots_hash) ; 32 Bここでパリティの成否を分けるのは 3 点です。
- トランスクリプトは
canonicalEncodeでシリアライズされる閉じたマップです。 そのキー順序は RFC 8949 §4.2.1 のソートであって、決して手で並べたものではありません。scheme、path、aead、kem、nonceをスロットとともに固定することで、ヘッダーフィールドのいずれかを書き換えるリレーは、スロットの形が有効なままであってもslots_hashを変え、MAC を壊します。 - トランスクリプトはアイテムのハッシュ主張を結合します。
hashes_hashは、アイテムの完全なhashesマップのcanonicalEncodeに対するラベル付き SHA-256 です。受信者はオンチェーンのバイト列だけからslots_macを再計算するため、MAC の一致は、エンベロープがこの正確なハッシュ主張のために封緘されたことを確認します。別のhashesマップを持つアイテムに継ぎ接ぎされたエンベロープは、いかなる暗号文の取得よりも前に、オンチェーンの一致ステップで失敗します。slotsの値は、ワイヤー上のスロットマップのシャッフル済み配列をそのまま用います。各スロットフィールドは単一のバイト文字列(epk32 B、kem_ct1120 B)であるため、正規化すべきフィールドごとのチャンク化はありません。 slots_hashはトライアル復号ループ全体を通じて 1 度計算され、一定に保たれます。 スロットごとの MAC チェックは候補 CEK ごとに HMAC を再鍵化しますが、常に同じ 32 バイトのslots_hashに対して行います。事前ハッシュは CEK 鍵付きのコミットメントをそのまま保ちます。HMAC のメッセージを完全なトランスクリプトからその SHA-256 へ変えるだけで、それ以上のことはしません。
MAC アルゴリズム、その鍵導出、トランスクリプトのスキーマは、いずれも enc.scheme = 1 によって固定され、両 KEM で同一です。ワイヤー上の MAC 識別子はありません。slots_mac はちょうど 32 バイトであり、定数時間で検証されます。
コンテンツの暗号化:セグメント化された STREAM
平文を、CEK から導出したコンテンツ鍵のもとで、セグメント化された STREAM で 1 度だけ暗号化します。コンテンツ鍵は CEK の別個の HKDF リーフであり、enc.nonce をソルトとし、経路ごとの info のもとで導出されるため、ラップ層とコンテンツ層が同じバイト列で同じプリミティブを鍵付けすることはありません。
content_key : HKDF-SHA-256(ikm = CEK, salt = enc.nonce, info = "cardano-poe-payload-v1", L = 32)
; STREAM (chacha20-poly1305-stream64k):
CHUNK_SIZE : 65536 plaintext bytes per non-final chunk
chunk nonce : uint88_be(counter) || final_flag ; 12 B; counter from 0, +1 per chunk;
; final_flag = 0x01 on the last chunk, else 0x00
per-chunk AAD : empty
ciphertext : seal(chunk_0) || seal(chunk_1) || ... || seal(chunk_final)
; each chunk sealed with ChaCha20-Poly1305 under content_key; sealed = plaintext + 16 Bチャンクごとの AAD は空です。 これは脱落ではなく正しい設計です。コンテンツ鍵は CEK から導出され、CEK はすでに slots_mac によってヘッダー全体(hashes_hash を含む)にコミットされています。ヘッダーフィールドをどれか一つでも変えれば、受信者は別のコンテンツ鍵を導出するため、ストリームは開かなくなります。チャンクごとの AAD は、安全性を加えることなく同じコンテキストをチャンクごとに再束縛するだけになります。カウンターノンスが安全なのは、コンテンツ鍵が一度しか使われない(エンベロープごとに一意な enc.nonce をソルトとする新鮮な CEK)からであり、2 つのストリームが (key, nonce) の組を共有することはありません。
切り詰めが検知できるように STREAM を構築してください。すべての非ファイナルチャンクはちょうど 65536 バイトの平文、ファイナルチャンクは final_flag = 0x01 と 0〜65536 バイト(空の平文は長さゼロのファイナルチャンク一つ、すなわち単独の 16 バイトタグ)です。検証者は、ファイナルフラグの欠落、非ファイナルチャンクに付いたファイナルフラグ、ファイナルチャンクの後のデータ、あるいは短い非ファイナルチャンクに対して失敗しなければなりません(MUST。TAMPERED_CIPHERTEXT)。各チャンクの平文を放出する前にそのチャンクのタグを検証し、復号後のハッシュ再確認が通るまで、放出されたバイト列を暫定的なものとして扱ってください。
平文はオリジナルのコンテンツバイト列そのものです。この構成はファイル名、MIME タイプ、サイズフィールド、メタデータラッパーを先頭・末尾に付加したり暗号化したりしません。発行される暗号文ブロブは STREAM のチャンクです(passphrase 経路では、後述の 32 バイトのコミットメントヘッダーが前置されます)。組み立てた enc マップと結果の URI はチェーンに載りますが、暗号文のバイト列は載りません。それらをコンテンツアドレス指定のストアへ発行し、その ar:// または ipfs:// URI をアイテムの uris[] に置いてください。
passphrase 経路
受信者がいない場合、正規化されたパスフレーズから Argon2id で CEK を導出します。epk も、スロットごとのラップも、スロット集合の MAC も、トライアル復号ループもありません。slots 経路で slots_mac が提供する鍵コミットメントは、代わりに暗号文ブロブの内側にある 32 バイトのヘッダーに置かれ、STREAM のチャンクの前に前置されます。
passphrase_bytes = utf8(normalize(passphrase)) ; cardano-poe-pw-norm-v1
CEK = argon2id(passphrase_bytes, salt = enc.passphrase.salt,
params = enc.passphrase.params, L = 32)
hashes_hash = SHA-256("cardano-poe-item-hashes-v1" || canonicalEncode(item.hashes))
PASSPHRASE_TRANSCRIPT = { ; closed 6-key map; keys are a set, not an order
"scheme": 1,
"path": "passphrase",
"aead": <enc.aead>,
"nonce": <enc.nonce>, ; bytes(24)
"hashes_hash": hashes_hash, ; bytes(32), over this item's hashes
"passphrase": { ; closed sub-map
"alg": "argon2id",
"salt": enc.passphrase.salt,
"params": { "m": m, "t": t, "p": p },
"normalization": "cardano-poe-pw-norm-v1" ; scheme-fixed constant, NOT on the wire
}
}
pw_hash = SHA-256("cardano-poe-passphrase-transcript-v1" || canonicalEncode(PASSPHRASE_TRANSCRIPT))
PW_MAC_KEY = HKDF-SHA-256(ikm = CEK, salt = "", info = "cardano-poe-passphrase-mac-v1", L = 32)
commitment = HMAC-SHA-256(key = PW_MAC_KEY, msg = pw_hash) ; 32 B
content_key = HKDF-SHA-256(ikm = CEK, salt = enc.nonce, info = "cardano-poe-payload-passphrase-v1", L = 32)
ciphertext blob = commitment || STREAM chunks ; STREAM under content_keyPASSPHRASE_TRANSCRIPT は、KDF パラメータ、ヘッダーフィールド、そしてアイテムのハッシュ主張をコミットメントへ結合します。salt、任意の params 値、nonce、aead を改ざんしたり、エンベロープを別のハッシュ主張に継ぎ接ぎしたりすると、異なる pw_hash が生じ、コミットメントのチェックが失敗します。"normalization" の値は、CEK がどの正規化プロファイルのもとで導出されたかを正確に固定するためにトランスクリプトへ供給されるスキーム固定の定数であり、ワイヤー上にシリアライズされることは決してありません(プロデューサーが発行するのは { alg, salt, params } だけです)。
検証側では、候補 CEK を導出し、暗号文ブロブの先頭 32 バイトを読み、コミットメントを再計算して、いかなる STREAM チャンクを開くよりも前に定数時間で比較します。48 バイト(32 バイトのコミットメント + 16 バイトの最小 STREAM)より短いブロブは不正形式です(TAMPERED_CIPHERTEXT)。不一致の場合 — 誤ったパスフレーズ、改ざんされた salt / params / ヘッダー、あるいは継ぎ接ぎされたエンベロープ — は、同じ単一の汎用的な失敗を表面化させ、ストリーミングを開始しないでください。誤ったパスフレーズは改ざんされたレコードと見分けがつきません。コミットメントは意図的にオフチェーンです。オンチェーンのコミットメントは、暗号文が差し控えられたものも含め、あらゆるパスフレーズレコードに対する無料のオフライン推測オラクルになってしまうからです。
パラメーターの下限を強制してください。salt の長さは 16〜64 バイト、m ≥ 65536 KiB(≈ 64 MiB)、t ≥ 3、p ≥ 1 です。Argon2 のバージョンは 0x13(19)にピン留めしてください。enc.scheme: 1 の下ではそれ以外のバージョンは認められず、ワイヤー上にバージョンフィールドはありません。プラットフォームが対応しているなら、プロデューサーは p = 4(RFC 9106 §4 の 2 番目の推奨プロファイル)を発行すべきです(SHOULD)。検証者は、デプロイの上限に従ったうえで、p ≥ 1 のいずれの値を受け入れてもかまいません(MAY)。Argon2id はエコシステムの境界をきれいにまたぎます。契約はライブラリではなくパラメーター集合であるため、固定された (m, t, p, salt, len, password) はどの実装でもバイト単位で同一の出力を生まなければなりません。パスフレーズとそのエンベロープの間の束縛は、上記の暗号文内コミットメントです。誤ったパスフレーズと改ざんされた暗号文は、どちらも 1 つの汎用的な失敗として表面化します。
正規化と Argon2id の前に、生のパスフレーズを上限で抑えてください。参照上限 MAX_PASSPHRASE_INPUT_BYTES = 4096 UTF-8 バイトより長い入力はすべて拒否し、病的なパスフレーズが KDF 前のサービス拒否を引き起こせないようにします。slots 経路の MAX_SLOTS やデコード後のエンベロープの上限と同様に、これはデプロイ固定の定数であって、より厳しくしてもかまわず(MAY)、ワイヤーフィールドではありません。
パラメータの下限を課してください。salt の長さは 16〜64 バイト、m ≥ 65536 KiB(≈ 64 MiB)、t ≥ 3、p ≥ 1 です。プラットフォームが対応しているなら、プロデューサーは p = 4(RFC 9106 §4 の 2 番目の推奨プロファイル)を発行すべきです(SHOULD)。検証者は、デプロイの上限に従ったうえで、p ≥ 1 のいずれの値を受け入れてもかまいません(MAY)。Argon2id はエコシステムの境界をきれいにまたぎます。契約となるのはライブラリではなくパラメータの集合なので、固定の (m, t, p, salt, len, password) はあらゆる実装でバイト単位に同一の出力を生まなければなりません。標準にはパスフレーズのコミットメント MAC が存在しないため、パスフレーズとそのエンベロープの間の唯一の束縛はコンテンツ AEAD の AAD です。誤ったパスフレーズと改ざんされた暗号文は、いずれも 1 つの汎用的な失敗として表面化します。
正規化と Argon2id の前に、生のパスフレーズを制限してください。参照値 MAX_PASSPHRASE_INPUT_BYTES = 4096 UTF-8 バイトより長い入力はすべて拒否し、病的なパスフレーズが KDF 前のサービス拒否を引き起こせないようにします。slots パスの MAX_SLOTS やデコード後のエンベロープ上限と同様に、これはデプロイ固定の定数であり、より厳しくしてもかまいません(MAY)。ワイヤーフィールドではありません。
正規化プロファイルは規範的である
2 つの実装は、同じパスフレーズから必ずバイト単位に同一の CEK を導出しなければならず、それを保証する唯一の方法はピン留めされた正規化です。プロファイル cardano-poe-pw-norm-v1 を、順序どおりに適用します。
- NFKC — UAX #15 に従う正規化形式 KC を、Unicode 16.0 のもとで適用します。
- 空白 — 空白を、Unicode 16.0 のもとで Unicode の
White_Spaceプロパティを持つすべての文字と定義し、その極大連続を 1 つの U+0020 SPACE へ畳み込みます。 - トリム — 先頭と末尾の空白を取り除きます。
- エンコード — UTF-8。そのバイト列が Argon2id のパスワード入力です。
Unicode を 16.0 に文字どおりピン留めし、浮動させないでください。White_Space プロパティ集合と NFKC のマッピングテーブルはバージョン依存なので、異なる Unicode バージョンに対してプロファイルを解決すると、同じパスフレーズから異なる CEK を導出し、正直なレコードを開けなくなることがあります。新しい Unicode バージョンを採用する将来のリビジョンは、cardano-poe-pw-norm-v1 を再解釈するのではなく、新しいプロファイル識別子のもとで行います。
トライアル復号:すべてのスロットを開き、MAC を織り込み、汎用的に失敗する
受信者は 1 つの KEM 秘密鍵を持ち、各スロットを開こうと試みて自分のスロットを発見します。受信者公開鍵はワイヤー上にありません。いかなる KEM や AEAD のプリミティブを呼ぶ前に、まずリソース上限を、続いて構造ガードを実行します。まずパーサのリソース使用を制限してください。 デコード後のサイズが 65536 バイトを超えるエンベロープは拒否し(ENC_ENVELOPE_TOO_LARGE)、slots[] が MAX_SLOTS = 1024 を超えるものは拒否します(ENC_SLOTS_TOO_MANY)。どちらの参照上限も、正直なレコードを制約する ~16 KiB の Cardano トランザクションメタデータ天井をはるかに上回ります。これらはより厳しくしてもかまわない(MAY)デプロイ固定の定数であり、ワイヤーフィールドではありません。続いて構造ガードです。scheme == 1、aead と kem が登録済み、nonce が 24 バイト、slots_mac が 32 バイト、slots が空でない、受信者秘密が 32 バイト、各 wrap が 48 バイト、そして KEM ごとに、各 epk が 32 バイトで kem_ct なし(x25519)または再構成した各 kem_ct が 1120 バイトで epk なし(mlkem768x25519)。
いかなるプリミティブの前に、レコード内のカプセル化の重複をここで拒否してください。 古典の経路ではすべての epk 値が、ハイブリッドの経路ではすべての再構成済み kem_ct 値が互いに異なっていなければなりません。重複は ENC_SLOTS_DUPLICATE_KEM_MATERIAL を発生させます。これは、全ゼロノンスのラップが依拠するスロットごとの KEK 一意性の不変条件のうち、検証者が確認できる部分です。レコードをまたぐ、あるいは鍵をまたぐ再利用は、いかなる検証者も検知できないプロデューサーの義務です。この拒否は、繰り返された epk / kem_ct に対してのみ発火します。新鮮なスロットごとのエフェメラルを伴って同じ受信者へ二度封緘することは正当であり、これを踏みません(下の複数マッチの規則を参照)。unwrap-negative が、重複 epk と KEK 再利用のケースを含みます。
そのうえでループを実行し、slots_hash をループの前に 1 度だけ再計算して一定に保ちます。
found = false
cek_conflict = false
selected_CEK = 0^32
for slot in slots: ; iterate ALL slots — no early break
; derive KEK per-KEM, as in the wrap recipe. For x25519 the all-zero shared
; secret is rejected via a secret-independent bit, not an early branch:
; kem_ok = NOT constantTimeEqual(shared, 0^32)
; KEK = ct_select(kem_ok, real_KEK, dummy_KEK) ; dummy_KEK from ikm=0^32, same salt/info
; (XWing.Decapsulate has no all-zero case; kem_ok stays true on the hybrid path.)
open_ok, candidate_CEK = ChaCha20-Poly1305_open_or_dummy(KEK, zeros(12), kem_info_label, slot.wrap)
HMAC_KEY = HKDF-SHA-256(candidate_CEK, salt = "", info = "cardano-poe-slots-mac-v1", L = 32)
mac_ok = constantTimeEqual(HMAC-SHA-256(HMAC_KEY, slots_hash), slots_mac)
ok = kem_ok AND open_ok AND mac_ok ; kem_ok folded into acceptance
first = ok AND NOT found ; first matching slot
cek_conflict = cek_conflict OR (ok AND found AND NOT constantTimeEqual(candidate_CEK, selected_CEK))
selected_CEK = ct_select(first, candidate_CEK, selected_CEK) ; constant-time
found = found OR ok
if NOT found: reject (single generic failure)
if cek_conflict: reject (single generic failure)
content_key = HKDF-SHA-256(selected_CEK, salt = enc.nonce, info = "cardano-poe-payload-v1", L = 32)
plaintext = STREAM_open(content_key, ciphertext) ; per-chunk authenticated release
if STREAM_open fails at any chunk: reject (single generic failure) ; TAMPERED_CIPHERTEXTこのループで譲れない点は次のとおりです。
- アトミックに開封し、検証されていない平文を決して渡さないでください。
*_open_or_dummyの両プリミティブはアトミックです。AEAD タグの失敗時には平文を返さず、返す候補(ラップされた CEK、またはコンテンツの平文)は、失敗した暗号文とは独立した固定値または擬似乱数のダミーです。これがあるからこそ、ループは、認証されていないバイト列を一切露出させることなく、ラップの開封失敗を越えてcandidate_CEKを持ち回ることができます。 - 全ゼロのチェックを、秘密に依存しない
kem_okビットへ織り込んでください。x25519の経路ではkem_ok = NOT constantTimeEqual(shared, 0^32)を計算し、KEK を、本物の KEK と、同じソルトと info のもとで0^32から導出したダミー KEK との間で定数時間で選択し、kem_okを受理へ織り込みます(ok = kem_ok AND open_ok AND mac_ok)。無効なシェアで早期に分岐しないでください。ECDH が無効なスロットは決して受理されず、ループは依然として同一の処理を行います。(XWing.Decapsulateには全ゼロのケースがないため、ハイブリッドの経路ではkem_okは真に固定されます。) slots_macのチェックをループに織り込んでください。 悪意ある送信者は、(秘密鍵を知らなくても)受信者の鍵で開けて攻撃者の選んだ CEK を生むスロットを細工できます。最初の AEAD 成功を「自分のもの」として受理すると、その偽造スロットが正直なスロットを覆い隠してしまいます。候補 CEK がslots_hashに対してslots_macも再現することを要求すれば、スロットの差し替え、削除、並べ替えを阻めます。決して省略しないでください。- 複数のマッチを許し、CEK の競合だけを拒否してください。 受信者の鍵が複数のスロットに正当にマッチすることもありえます(MAY)。同じ CEK を同じ受信者へ、それぞれ新鮮なエフェメラルを伴って複数のスロットに封緘することは、正当な受信者数のパディングであり、重複
epk/kem_ctの拒否を踏みません。最初のマッチの CEK を選び、複数のスロットがマッチしたというだけで拒否しないでください。拒否すべき唯一の異常は、異なる CEK を回収する 2 つのマッチするスロットです(定数時間で比較します)。cek_conflictビットを追跡し、それが立っていれば単一の汎用的な失敗を表面化させます。これは多層防御です。スロットセットのコミットメントのもとでは、異なる CEK を生むマッチはすでに実現不可能なので、壊れた実装に対してフェイルクローズします。 - 単一の秘密鍵のパスの中で、すべてのスロットを走査してください — 鍵あたり一定回数のスロット演算で、早期離脱なし — タイミングを観測する者がどのスロットが一致したかを推測できないようにします。全ゼロの拒否は、早期離脱ではなく
kem_okとダミー処理を通じて行います。複数の鍵を持つ受信者は鍵 × スロットを走査し、鍵をまたいで短絡してもかまいません(弱い「どの鍵が一致したか」の信号だけが漏れます)。ただし、いずれか 1 つの鍵のスロットをまたぐ間は定数時間を保たねばならず、両 KEM とも受信者自身の公開鍵をソルトに結び付けるため、鍵ごとにソルトのpub_Rの半分を再導出しなければなりません。そのソルトは、鍵の正規ワイヤーエンコーディング、すなわちちょうど 32 バイトの X25519 公開鍵、またはちょうど固定の 1216 バイトの X-Wing 公開鍵バイト列に結び付けてください。非正規な再エンコードは決して用いないでください。さもなければ両者は異なる KEK を導出します。 - 信頼できない呼び出し側には 1 つの汎用的な失敗の形を表に出してください。 内部では、ローカルな診断のために型付きの結果を追跡してもかまいません。
WRONG_RECIPIENT_KEY(どのスロットも開かない)、TAMPERED_HEADER(スロットは開いたが、どの候補 CEK もslots_macを再現しない)、TAMPERED_CIPHERTEXT(CEK が復元され MAC が検証された後にコンテンツ AEAD が失敗)。ただし、外部の観測者がこれらを応答の形で区別できてはなりません(MUST NOT)。タイミングについては、モデルは意図的にスコープが限定されています。検証者は、コンテンツ復号の前にif NOT foundのチェックで戻ってもかまいません(MAY)。これは、受信者でない者を、スロットは開いたが暗号文の開封に失敗する受信者から分離します。それが明かすのは受信者か否かだけであり、どのスロットも、いかなる鍵材料も決して明かしません。その 2 つのケースの間でタイミングを一様にすることは要求されず、ダミーのコンテンツ開封を義務づけてはなりません(MUST NOT)。成り立つ定数時間の保証は、上記のスロット横断の不変条件です。 - 復号後に平文ハッシュを再計算して比較してください。 オンチェーンの
hashesマップは暗号文ではなく平文にコミットするので、受信者は(アプリケーション層で)ダイジェストを再計算して比較しなければなりません。sha2-256のエントリは一致しなければならず、存在すればblake2b-256も一致しなければなりません。不一致は、レコードのハッシュの主張が復号後のバイト列に一致しないことを意味します。その平文に基づいて行動するのを拒否してください。構造バリデーターは決して復号しません。
両側でペイロードを上限で抑える
セグメント化された STREAM は、暗号的なペイロード上限を課しません。88 ビットのチャンクごとカウンターは 2^88 個のチャンクを許容し、各チャンクは RFC 8439 の単一呼び出し上限に十分収まる、それぞれ異なる (content_key, nonce) の組のもとで封緘されるため、警戒すべきカウンターオーバーフローのリスクはありません。したがってプロデューサーまたは検証者が課す最大値は、ワイヤーの定数ではなくデプロイメントのサービス拒否(DoS)ポリシーです。ストリームの書き込み時または読み取り時に逐次的にそれを課し、過大なペイロードをバッファする前に中止してください。切り詰めは、サイズ上限ではなくファイナルフラグによって構造的に捕捉されます。同じ構えは slots 経路と passphrase 経路の双方に適用されます。
封緘済み PoE の適合性フィクスチャ
封緘済み PoE のあたりは、コーパスのなかで言語をまたいだバグが最も多く現れる箇所です。実装をそのすべてに通してください。肯定フィクスチャは、両 KEM の決定的なラップとトライアル復号ループ(単一・複数受信者、混在 N、そして複数秘密鍵の最悪ケース)に加えて、1 人の受信者が 2 つのスロットにマッチする正当なケース(新鮮なエフェメラル、同じ CEK、必ず復号する(MUST)ので、複数のマッチを拒否する実装はここで不合格になります)、そして passphrase 経路(1 つのブロブの中のコミットメントヘッダーと STREAM チャンク)を固定します。専用の STREAM レイアウトの集合は、空の平文(長さゼロのファイナルチャンク一つ)、単一チャンクのペイロード、そして 65536 バイト境界をまたぐ複数チャンクのペイロードを固定します。的を絞った KAT は、両方の KEK ソルト(SHA-256(label ‖ enc.nonce ‖ <KEM 素材> ‖ pub_R))、hashes_hash と両方のトランスクリプトにおけるその位置、draft-10 に対する X-Wing のカプセル化、長さゼロのソルトでの HKDF 抽出(RFC 5869 §2.2 のソルト不在の慣行であり、slots_mac の鍵導出を映したもの)、Bech32 の受信者・秘密鍵のエンコーディング、そしてチェックサム付きのアイデンティティシードのエンコーディングを固定します。
否定フィクスチャは拒否コードを固定します。正直なスロットの前に置かれた偽造のシャドースロット(レコードは正直な CEK のもとでそれでも復号しなければなりません(MUST))、スロットの形を有効なまま残すヘッダーの書き換え(kem/aead/scheme)、別のハッシュ主張を持つアイテムへの hashes の継ぎ接ぎ、パスフレーズコミットメントの失敗(誤ったパスフレーズ、改ざんされた salt/params、改ざんされたヘッダー — いずれもいかなるチャンクが開くよりも前に失敗します)、パスフレーズ正規化の拒否(未割り当てコードポイントの入力と、空白のみの入力)、全ゼロの X25519 共有秘密、レコード内の重複スロット、そして STREAM の改ざんケース(反転したチャンクタグ、切り詰められたストリーム、末尾の余分なデータ、短い非ファイナルチャンク)です。2 つの性質はバイトベクトルを持たず、代わりにふるまいで表明されます。CEK 競合の拒否(それを構成することは、標準が実現不可能と仮定するマルチキーのコミットメント衝突そのものです)と、スロット横断の定数時間保証です。固定されたすべてのバイト文字列を再現し、すべての否定ケースで厳密なコードを発してください。
封緘済み PoE の一つの性質はバイトベクトルを持ちません。CEK 競合の拒否、すなわち異なる CEK を回収する 2 つのマッチするスロットは、フィクスチャとして構成できません。それを構成することは、標準が実現不可能と仮定するマルチキーのコミットメント衝突そのものだからです。代わりに、トライアル復号ループが強制された競合に対してフェイルクローズすることを表明する実装レベルのふるまいテストで固定してください。これは、スロット横断の定数時間性がバイト文字列ではなくふるまいで表明されるのと同じやり方です。
封緘済み PoE の性質のひとつには、バイトベクトルが存在しません。すなわち CEK 競合の拒否、つまり異なる CEK を回収する 2 つのマッチするスロットは、フィクスチャとして構成できません。それを構成することは、標準が実現不可能と仮定するマルチキーのコミットメント衝突そのものだからです。これは代わりに、トライアル復号ループが、強制された競合に対してフェイルクローズすることを表明する実装レベルの振る舞いテストで固定してください。スロット横断の定数時間の性質が、バイト文字列としてではなく振る舞いとして表明されるのと同じやり方です。
適合性とテストベクター
規範的なテストベクターは、それ自体が相互運用性の契約です。実装が適合していると言えるのは、同じ入力から適合性スイート内のすべての固定バイト列を再現でき、かつすべての否定フィクスチャに対して正しい型付きエラーコードを発する場合に限られます(if and only if)。 部分点も異議申し立ての余地もありません。比較が失敗した場合、誤っているのは実装であって、ベクターが誤っていることはありません。
ベクターは標準の適合性スイートに収められ、プリミティブの種類ごとに整理されています。レコードフィクスチャ、封緘済み PoE のラップ/アンラップ、COSE_Sign1 署名、HKDF、シード導出、Argon2id、canonical CBOR です。 各フィクスチャは入力を小文字の 16 進数で固定し、期待される出力を示します。使い方はこうです。入力を自分の実装に与え、名前付きの各出力をバイト単位で比較し、不一致があればコードを修正します。
すべての実装が満たすべき三つの義務
肯定ベクターを再現すること。 すべてのレコードフィクスチャについて、encode(record) == expected_cbor と、往復の encode(decode(expected_cbor)) == expected_cbor の両方が成り立たなければなりません(MUST)。
この往復はフィクスチャの範囲を超えて一般化されます。任意の整形式の入力に対して encode(decode(x)) == x が成り立ちます。
情報を失ったり並べ替えたりするデコーダ、あるいは canonical でないエンコーダは、これを破り、適合性を満たしません。
正しい拒否コードを発すること。 否定フィクスチャは、意図的に不正な形にしたレコードと、構造バリデーターが発しなければならない(MUST)厳密な型付きエラーコードを対にしています。 有効なレコードのバイト列を再現するのは契約の半分にすぎません。無効なレコードを正しいコードで拒否することがもう半分です。 不正なレコードを誤った理由で拒否する、あるいは受理してしまうバリデーターは適合していません。 否定フィクスチャは、言語をまたいだ拒否のパリティに関する唯一の正規情報源です。同じ不正な入力は、すべての実装で同じコードを引き起こさなければなりません(MUST)。 コードの完全なカタログとその意味は 検証 に記載されています。
レジストリと一致すること。 アルゴリズム識別子は アルゴリズムレジストリ のレジストリから取得した名前付きの文字列です。 認識できない識別子は、黙って受理したりパニックを起こしたりするのではなく、正確な「サポートされていないアルゴリズム」コードを表に出さなければなりません(MUST)。
修正するのは実装であり、ベクターではありません
ベクターは上流の RFC と本標準の決定的な構成に固定されています。比較が失敗したとき、バグはテスト対象の実装にあります。スイートを通すためにベクターを編集すると、実際の相互運用性の失敗が、レコードがチェーン上で実装をまたぐときにしか表面化しない潜在的な失敗へとすり替わってしまいます。発見の時期として最悪のタイミングです。
変更のたびにパリティを実行すること
複数の言語で配布する実装、あるいは別の実装との相互運用性を証明したい実装は、すべてのパッケージをビルドし、各言語のテストスイートを共有フィクスチャに対して実行し、依存グラフの lint を適用し、フィクスチャ集合が両側で同一であることを確認する単一の継続的インテグレーションジョブを実行すべきです(SHOULD)。 一方にだけ追加され、もう一方には追加されていないフィクスチャはゲートを失敗させます。二つの実装が密かに乖離したということであり、実際のレコードが乖離をあぶり出す前にビルドがそれを捕らえます。 フィクスチャが正規情報源です。各言語はバイト単位で一致するミラーを保持し、ゲートはそのミラーが完全かつ正確であることを検証します。
命名とワイヤーの規約
いくつかの規約が、実装を読みやすくし、ワイヤーフォーマットを安定に保ちます。
- ワイヤー上のフィールド名は
snake_caseです(leaf_count、cose_sign1、slots_mac)。これは言語をまたいで成り立ちます。メモリ上の API では慣例的にcamelCaseを使う言語であっても、エンコードされたレコードはsnake_caseのキーを使います。キーは署名が対象とする canonical なバイト列の一部だからです。 - 識別子はレジストリの文字列であり、コードに焼き込まれた列挙型ではありません。ハッシュ、AEAD、KEM、KDF、署名はすべて名前付きの識別子を参照します。アルゴリズムの追加(たとえばポスト量子 KEM)はレジストリへの追記であって、ワイヤーフォーマットの破壊では決してありません。
- 言語をまたいだメソッド名は意味的に対応します。 ある言語の関数には、別の言語で同名の対応物があります(
encode_canonical_cbor↔encodeCanonicalCbor)。どちらかの言語に通じた読者は、一方の表面をもう一方に対応づけ、見ただけでパリティを推論できます。 - まず暗号層を立ち上げてください。 暗号コアとワイヤーフォーマットライブラリをベクターと突き合わせて構築し、アプリケーションコードを一行も書く前にパリティゲートを通します。単独検証者はアプリケーションに最も近い表面の中で最も小さく、次に構築すべきものです。それ以外のすべては、すでに正しさを証明済みの暗号層の上に乗ります。
関連ページ
- レコード — バリデーターとエンコーダが実装するワイヤーフォーマット。
- 封緘済み PoE — ここでの組み立てレシピの背後にある構成リファレンス。
- アルゴリズムレジストリ — 実装が解決する名前付き識別子。
- 検証 — 検証パイプライン、単独検証者、エラーコードのカタログ。