これは参考用の翻訳です。正式な基準は英語版であり、内容が食い違う場合は英語版が優先されます。 英語版を読む

Label 309 の鍵モデル。32 バイトのシード 1 つ、ドメイン分離された HKDF-SHA-256 によってそこから導出される 3 つのアルゴリズム鍵ペア、封緘済み PoE がその上で導出するスロットごとの鍵暗号化鍵、そして受信者公開鍵と秘密鍵のエンコーディング方法を規定します。

Label 309 には 3 種類の非対称鍵が必要です。レコードに署名する Ed25519 鍵、古典的な封緘済みペイロードを受信する X25519 鍵、そしてポスト量子の封緘済みペイロードを受信する X-Wingmlkem768x25519)ハイブリッド鍵です。この標準は、これらを別々に保管・管理する 3 つの独立した秘密とは捉えません。定義するのはただ 1 つの秘密、すなわち 32 バイトのシードと、そこからすべての鍵ペアを展開する決定論的なルールです。

このページでは、その導出を規定します。シードそのもの、各アルゴリズムの秘密鍵を生成する 3 つのドメイン分離された HKDF 展開、ドメインを分けて扱う理由、封緘済み PoE がそれらの上で導出するスロットごとの鍵暗号化鍵、そして導出された受信者公開鍵と秘密鍵を交換用にエンコードする方法です。シードをこれ以外にどう扱うか、すなわちどこに保管し、どのようにアンロックし、1 人の人間が複数のシードを保有するかどうかは、対象範囲外とします。Label 309 が関心を持つのは、同じ 32 バイトを与えれば、適合するすべての実装が同一の鍵を導出するという 1 点だけです。

シード

Label 309 の鍵セットは、単一の値を起点とします。

プロパティ
長さ32 バイト(256 ビット)
生成元暗号学的に安全な乱数生成器(CSPRNG)、またはユーザーが所有する任意の 32 バイト値
役割以下の 3 つの HKDF 展開への入力鍵材料(IKM)

シードは、いずれかのアルゴリズムの意味での鍵ではなく、むき出しのエントロピー源です。曲線も、プリミティブに紐づいた長さも、エンコーディングの手続きも持ちません。実装が実際に使う鍵は導出のたびに決まるものであり、シードはその上で行われるアルゴリズムの選択より長く残り続けます。生成者は、プラットフォームの CSPRNG から新たにシードを生成してもよく(MAY)、既存の 32 バイト値をインポートしてもかまいません。いずれの場合も、デコード結果はちょうど 32 バイトでなければなりません(MUST)。導出層では低エントロピーのパターンを拒否しません。全ゼロのシードも有効な入力であり、だからこそ全ゼロシードを再現可能な適合性フィクスチャとして使えます。

シードがアイデンティティのすべて

Label 309 がある主体について表現する公開鍵の事実、すなわちレコードを保証する鍵や封緘済みペイロードを受信する鍵は、いずれもこの 32 バイトから決まる決定論的な関数です。シードを再現すれば、3 つの鍵ペアすべてをバイト単位でそのまま再現できます。

バックアップ用のシードのエンコーディング

32 バイトのシードがアイデンティティそのものであるため、これはユーザーがバックアップし、エクスポートし、インポートする値です。そして、むき出しの 32 バイトのブロブは、気づかぬうちに切り詰められたり破損したりしやすいものです。Label 309 はそのためにチェックサム付きの文字列エンコーディングを定義し、シードを入力として受け取るあらゆる箇所で、生の 16 進数と並んでこれを受理します。

文字列形式は、ヒューマンリーダブルプレフィックス(HRP)l309-seed- のもとでの Bech32(BIP-173 クラシック、ただし 90 文字の長さ制限は解除)です。末尾のハイフンは HRP の一部なので、Bech32 の区切り文字を加えると、見える形のプレフィックスは l309-seed-1… になります。エンコードは大文字の表示形式 L309-SEED-1… を返します。 秘密は声高であるべきであり、また大文字の表記は、小文字の age1… 受信者文字列と視覚的にはっきり区別できるからです。すべて小文字の形式も、同じバイト列の等しく有効なエンコーディングです。

seed (32 bytes)  0000…0000  ->  L309-SEED-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQLFUN82

パーサーはふたつの表現を受理し、その形によって処理を振り分けます。

  • Bech32 文字列は単一の大小文字で(大小文字の混在は BIP-173 に従って拒否されます)、チェックサムが検証され、デコードされたペイロードがちょうど 32 バイトであること。
  • 生の 16 進数 — 64 桁の 16 進数で、大小文字を区別せず、0x プレフィックスや前後・内部の空白を許容します。

拒否される入力はそれぞれ、構成 API の別個のエラーコードに対応づけられるため、呼び出し側はタイプミスと誤った鍵の種類を区別できます。

入力エラーコード
チェックサムが不合格となる Bech32 文字列(文字の入れ替わり、切り詰め)SEED_STRING_BAD_CHECKSUM
大文字と小文字が混在する Bech32 文字列SEED_STRING_MIXED_CASE
別の HRP のもとでの有効な Bech32 文字列(例:age1… 受信者)SEED_STRING_WRONG_HRP
32 バイト以外にデコードされる Bech32 文字列または 16 進数文字列SEED_STRING_WRONG_LENGTH
認識可能な Bech32 文字列でも 16 進数でもないもの(空文字列を含む)SEED_STRING_UNRECOGNIZED

これらのコードは、導出のまわりにある鍵取り扱いの利便機能であるシード文字列コーデックを表すものであり、構造バリデーターが発するワイヤーのエラーコードレジストリ(検証)とは別物です。このエンコーディングが運ぶのはむき出しの 32 バイトだけであって、それ以外は何も — バージョンも、導出パラメーターも — 運びません。シードの意味は、それがどう運ばれたかではなく、以下の 3 つの info 文字列によって固定されているからです。

3 つの鍵ペアの導出

各アルゴリズムの秘密鍵は、RFC 5869 に従い、同一のシードを独立して HKDF-SHA-256 で展開したものです。3 つの展開は入力鍵材料と(省略された)ソルトを共有し、異なるのはアルゴリズム名を示す info 文字列という 1 つのパラメーターだけです。

アルゴリズムinfo 文字列出力
Ed25519cardano-poe-ed25519-v132 バイトの Ed25519 秘密シード
X25519cardano-poe-x25519-v132 バイトの X25519 秘密シード
mlkem768x25519cardano-poe-mlkem768x25519-v132 バイトの X-Wing 脱カプセル化鍵シード

擬似コードで表した導出は次のとおりです。

ed25519_priv        = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-ed25519-v1",        length = 32)
x25519_priv         = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-x25519-v1",         length = 32)
mlkem768x25519_priv = HKDF-SHA-256(ikm = seed, salt = "", info = "cardano-poe-mlkem768x25519-v1", length = 32)

これらの出力を実装間で相互運用可能にするルールが 3 つあります。

  1. ソルトは空です。 HKDF のソルトはゼロ長のバイト文字列でなければなりません(MUST)。RFC 5869 §2.2 によれば、ソルトが省略された場合は HashLen 個のゼロバイト、SHA-256 なら 32 個のゼロバイトとして扱われるため、適合するすべてのライブラリが同じ抽出ステップにたどり着きます。
  2. 出力は 32 バイトです。 各展開はちょうど 32 バイト、すなわち SHA-256 における HKDF 1 ブロック分を要求します。
  3. info 文字列は厳密な ASCII です。info の値は、示したバイト列そのものとしてエンコードしなければなりません(MUST)。前後の空白、ゼロ終端文字、バイトオーダーマーク、末尾の改行はいずれも付けません。3 つの文字列の長さは、それぞれ 22、21、29 バイトです。

この 32 バイトの出力は、展開された曲線スカラーではなく、アルゴリズムの秘密シードです。RFC 8032 §5.1.5 は Ed25519 についてこの区別を明示しています。秘密シードは 32 バイトであり、署名ライブラリが内部で SHA-512 とクランピングを経て、実際のスカラーと署名プレフィックスへと展開します。X25519 も同様で、クランピングは RFC 7748 §5 に従ってプリミティブの内部で適用されます。実装は、生の 32 バイトの HKDF 出力をそのままプリミティブに渡し、展開とクランピングはライブラリに任せなければなりません(MUST)。事前にクランピングや展開を行ってはなりません。X-Wing の場合、32 バイトの出力は X-Wing の脱カプセル化鍵シードであり、これをもとに X-Wing の鍵生成によって、1216 バイトの公開鍵を含む鍵ペア全体が決定論的に再生成されます。いずれの場合も、保管・転送の正規形式は、展開後の鍵ではなく、コンパクトな 32 バイトのシードです。

なぜ 1 つではなく 3 つのドメインなのか

HKDF の info パラメーターはドメイン分離タグです。展開後の出力を特定のアプリケーションコンテキストに結び付ける役割を持ち、RFC 5869 §3.1 はコンテキストが利用できる場合にこれを与えることを強く推奨しています。Label 309 では、3 つの秘密鍵がたまたますべて 32 バイト幅であっても、1 つの展開を 3 つで使い回さず、アルゴリズムごとに別々のタグを使います。理由は分離にあります。

  • 障害を局所化できます。 2 つの鍵ペアが同じバイト列を共有していると、あるアルゴリズム固有の弱点、たとえばノンス導出の欠陥やスカラー乗算のサイドチャネルが、無関係なアルゴリズムの鍵まで露出させてしまう恐れがあります。ドメイン分離によって 3 つの秘密鍵はシードの独立した関数であることが保証されるため、1 つが危殆化しても、攻撃者は他の鍵について何も得られません。
  • 移行を加算的に行えます。info 文字列は -v1 で終わります。将来の改訂で別の曲線や別のハイブリッドを採用する場合は、同じシードから、新しいタグのもとで新たな -v2 鍵を導出すればよく、すでに配備された v1 鍵と衝突することはありません。これは、ワイヤーフォーマット自体が依拠するアルゴリズム俊敏性(アルゴリズムを差し替え可能)と同じ考え方です。

3 つ目のタグ cardano-poe-mlkem768x25519-v1 は、その脱カプセル化鍵シードが古典的な X25519 の秘密と同じ 32 バイト幅であっても、ポスト量子ハイブリッドに独自のドメインを与えます。そのため、ML-KEM-768、X25519、あるいは X-Wing のコンバイナーに欠陥があっても、古典的な暗号鍵や署名鍵にまで影響が及ぶことはありません。

このアイデンティティ鍵のタグ cardano-poe-mlkem768x25519-v1 には、-kek- の区切りも含まれていません。後述するレコードごとの KEK 導出ラベル cardano-poe-kek-mlkem768x25519-v1 とは別物であり、そのおかげでシード → アイデンティティ鍵の展開と、封緘済み PoE のスロットごとの鍵ラップとが、info 文字列を共有することは決してありません。

スロットごとの鍵暗号化鍵

上記の 3 つのシード由来鍵ペアは、長期にわたるアイデンティティ鍵です。封緘済み PoE は、その上に レコードごとの HKDF-SHA-256 の層をもう 1 つ重ねます。受信者スロットごとに、送信者は新たな 32 バイトの鍵暗号化鍵(KEK)を導出し、それでレコードのコンテンツ暗号化鍵をラップします。KEK の導出は鍵モデルの一部なので、ここで規定します。ラップされた鍵がそのあとエンベロープにどう載るかは 封緘済み PoE で扱います。

どちらの KEM も KEK を HKDF-SHA-256 と KEM 固有の info で、ラベル付きハッシュのソルトのもとで導出します。このソルトは 3 つの値を結び付けます。スロット自身の KEM 素材(KEK をスロット固有にします)、受信者公開鍵 pub_R(ある受信者向けに作られたカプセル化が別の受信者に対して中継されるのを防ぎます)、そしてエンベロープごとに一意な enc.nonce(KEK を 1 つのエンベロープに固定します)です。共有秘密は、KEM 自身の ECDH(古典)または X-Wing の脱カプセル化(ハイブリッド)の出力であり、送信者がカプセル化しても受信者が脱カプセル化しても同じ 32 バイトの値になります。saltinfo は両者で同一です。

; x25519(古典)— ソルトはエフェメラル公開鍵と受信者公開鍵に対するラベル付き SHA-256
kek_salt = SHA-256("cardano-poe-x25519-kek-salt-v1" || enc.nonce || pub_epk || pub_R)  ; 32 bytes
KEK      = HKDF-SHA-256(ikm  = shared,                  ; the X25519 ECDH shared secret
                        salt = kek_salt,
                        info = "cardano-poe-kek-v1",
                        L    = 32)

; mlkem768x25519(ハイブリッド)— ハイブリッド自身のラベルのもとで同じラベル付きソルトの形
kek_salt = SHA-256("cardano-poe-xwing-kek-salt-v1" || enc.nonce || kem_ct || pub_R)    ; 32 bytes
KEK      = HKDF-SHA-256(ikm  = shared,                  ; the X-Wing shared secret
                        salt = kek_salt,
                        info = "cardano-poe-kek-mlkem768x25519-v1",
                        L    = 32)

2 つのソルトは同じ形 — SHA-256(label || enc.nonce || <スロットの KEM 素材> || pub_R) — を持ち、異なるのは KEM ごとのラベルと、運ぶ KEM 素材だけです。すなわち古典のパスでは 32 バイトのエフェメラル pub_epk、ハイブリッドのパスでは 1120 バイトの X-Wing 暗号文 kem_ct です。どちらも固定長の SHA-256 ダイジェストにまとめられます。ハイブリッドの入力は生のソルトとしてはオーバーサイズであり、また一つの統一された形にしておくことで 2 つのパスの足並みが揃うからです。この結び付けは KEM の外側で、スロット自身のワイヤーバイト列に対して計算されるため、X-Wing をブラックボックスの KEM として扱い、コンバイナー内部のハッシュのいかなる性質にも依拠しません。さらに、KEM ごとに異なる info ラベルは、同一の 32 バイト共有秘密のもとであっても、ある KEM で導出した KEK が別の KEM で導出した KEK と等しくなることが決してないことを保証します。

どちらのソルトでも pub_R は受信者鍵の正規ワイヤーエンコーディングであり、x25519 ではちょうど 32 バイトの X25519 公開鍵、mlkem768x25519 ではちょうど固定の 1216 バイトの X-Wing 公開鍵バイト列です。送信者と受信者はこの正確なエンコーディングを使わなければならず(MUST)、非正規な、あるいは再エンコードされた等価物で代用してはなりません(MUST NOT)。さもなければ両者は異なるソルトを HKDF に渡して異なる KEK を導出してしまい、スロットは決して開きません。

各 KEK とそのソルトプレフィックスは、enc.scheme: 1内部構成要素です。ワイヤー上の識別子を持たず、選択もできません。ここで挙げた 2 つのソルトプレフィックスラベルと 2 つの info ラベルは、アルゴリズムレジストリ で目録化されている封緘構成の 11 のラベルリテラルのうちの 4 つです。検証者はそれぞれをバイト単位でそのまま使わなければなりません(MUST)。

受信者公開鍵のエンコーディング

封緘済み PoE の送信者は、受信者の公開鍵を、持ち運びできる文字列形式で必要とし、受信者は対応する秘密鍵を、それと同じ形式でバックアップします。Label 309 は、登録済みの鍵カプセル化メカニズムごとに 1 つずつのヒューマンリーダブルプレフィックス(HRP)を割り当て、age エコシステムの Bech32 受信者エンコーディングを再利用します。

Bech32 では 1 が HRP とデータ部の区切りなので、文字列の人間が 目にする プレフィックスは、その HRP その 1 を合わせたものです。したがって HRP と見える形のプレフィックスは別物であり、表ではそれらを別の列に分けています。

KEM(enc.kem公開鍵公開鍵 HRP公開鍵の見える形のプレフィックス秘密鍵 HRP秘密鍵の見える形のプレフィックス
x2551932 バイトの X25519 公開鍵ageage1…(62 文字)AGE-SECRET-KEY-AGE-SECRET-KEY-1…
mlkem768x255191216 バイトの X-Wing 公開鍵age1pqcage1pqc1…(1960 文字)AGE-SECRET-KEY-PQ-AGE-SECRET-KEY-PQ-1…

古典の x25519 受信者文字列は HRP が age で、標準的な age v1 の形 age1… です。ハイブリッド公開鍵は、ML-KEM-768 のカプセル化鍵(1184 バイト)と X25519 公開鍵(32 バイト)を連結したものです。1216 バイトなので、その age1pqc1… 受信者文字列は 1960 文字になります。

実装がバックアップしインポートする秘密鍵は、いずれのパスでも 32 バイトのシードです。X25519 の秘密シードは AGE-SECRET-KEY- のもとで、X-Wing の脱カプセル化鍵シード(上記 3 つ目の HKDF 出力、info = "cardano-poe-mlkem768x25519-v1")は AGE-SECRET-KEY-PQ- のもとでエンコードされます。1216 バイトのハイブリッド公開鍵はそのシードから導出されます。保管すべき正規の秘密は、展開後の鍵ではなく、コンパクトなシードです。

BIP-173 は Bech32 文字列を 90 文字に制限していますが、これは人間が手で入力する支払いアドレスのための制限であり、ここには適用されません。実装は、Bech32 のチェックサムと文字セットのルールは引き続き適用しつつ、90 文字制限を課さずに age1pqc1… 文字列をエンコード・デコードしなければなりません(MUST)。HRP を age1pqc と分けておくことで、ハイブリッドの受信者が古典的な age の受信者と衝突しなくなります。これは意図的に age1pq を避けています。age1pq は、同じプリミティブに対する上流のネイティブな ML-KEM-768 + X25519 エンコーディングがすでに使っている短いプレフィックスであり、こうして 2 つの受信者エンコーディングがワイヤー上で衝突しないようにしています。古典的なエンコーディングは通常の長さに収まり、これまでどおり変更なく処理されます。

これらの文字列は、あくまで受信者を見つけるための便宜的なものです。受信者公開鍵が Label 309 レコードの暗号化エンベロープにそのまま現れることはありません。enc.slots[] の各エントリがスロットごとの鍵材料と wrap 値を持ち、KEM 識別子は enc.kem に 1 度だけ現れます。エンベロープとスロットをどう構築するかは 封緘済み PoE で扱います。

署名の kid としての Ed25519 公開鍵

Ed25519 公開鍵は受信者としての役割を持ちません。これは、検証者が署名を照合する際に参照する鍵識別子です。生成者がレコードに署名すると、生の 32 バイトの Ed25519 公開鍵が、RFC 9052 に従って COSE_Sign1 の保護対象ヘッダー内の kid(ラベル 4)になります。検証者はこの 32 バイト値をチェーン上の署名から直接読み取り、それに対してレコード本体を検証します。公開鍵は署名とともに運ばれるため、著作者性の検証に別途の参照を要しません。署名の構成全体、署名対象のペイロード、そして検証ルールは 署名 で規定します。

アウトオブバンドの鍵交換

Label 309 が規定するのは、受信者公開鍵をどうエンコードするかであって、どう見つけるかではありません。この標準は、受信者鍵のためのディレクトリも、レジストリも、チェーン上の公告フォーマットも、一切定めません。封緘済みペイロードを受け取りたい主体は、双方がすでに信頼しているチャネルを通じて、自分の age1… または age1pqc1… 文字列を公開します。対面での手渡し、自身の Ed25519 鍵で署名したレコード、安定した Web 上やコンテンツアドレス指定の場所に置いたレコードなどです。そして、暗号化の宛先とする鍵の出所について責任を負うのは送信者です。

これは意図して引いた境界です。サーバーを信頼せずにレコードを検証できるというこの標準の特性は、裏を返せば、鍵交換の場面で信頼される仲介者をこっそり呼び戻してはならないということでもあります。鍵の隣に置かれた名前は、それを置いた者による証言であって、暗号学的な主張ではありません。同じハンドル名を使う 2 者でも、生成される鍵のバイト列は異なり、検証者が比較するのはそのバイト列です。人間が読める名前を鍵に対応づけることは、Label 309 の上に構築するアプリケーションが提供してもよい(MAY)機能ですが、それはアプリケーションの機能であり、プロトコルの外にあります。

関連ページ

  • 署名 — Ed25519 鍵でレコードに署名する方法と、kid を検証する方法。
  • 封緘済み PoE — X25519 と X-Wing の公開鍵で、暗号化ペイロードを特定の受信者に宛てる方法。
  • アルゴリズムレジストリ — ここで参照した署名、KEM、AEAD、KDF の名前付き識別子。