Schlüssel
Das Schlüsselmodell von Label 309. Ein 32-Byte-Seed, daraus drei Algorithmus-Schlüsselpaare, abgeleitet per domänengetrennter HKDF-SHA-256, die Slot-Schlüsselverschlüsselungsschlüssel, die ein versiegelter PoE darauf aufsetzt, sowie die Kodierung der öffentlichen Empfängerschlüssel und Geheimnisse.
Label 309 benötigt drei Arten asymmetrischer Schlüssel: einen Ed25519-Schlüssel zum Signieren von Datensätzen, einen X25519-Schlüssel für den Empfang klassisch versiegelter Nutzdaten und einen X-Wing-Schlüssel (mlkem768x25519) für den Empfang post-quanten-versiegelter Nutzdaten. Der Standard behandelt diese nicht als drei unabhängige Geheimnisse, die separat gespeichert und verwaltet werden müssen. Er definiert genau ein Geheimnis, nämlich einen 32-Byte-Seed, sowie eine deterministische Regel, die daraus alle drei Schlüsselpaare ableitet.
Diese Seite spezifiziert diese Ableitung: den Seed, die drei domänengetrennten HKDF-Expansionen, die den privaten Schlüssel für jeden Algorithmus erzeugen, die Gründe für die Trennung der Domänen, die Slot-Schlüsselverschlüsselungsschlüssel, die ein versiegelter PoE darauf aufsetzt, sowie die Kodierung der resultierenden öffentlichen Empfängerschlüssel und Geheimnisse für den Austausch. Was eine Implementierung darüber hinaus mit dem Seed unternimmt, also wo er gespeichert und wie er entsperrt wird oder ob eine Person mehrere davon besitzt, liegt außerhalb des Geltungsbereichs. Label 309 setzt lediglich voraus, dass jede konforme Implementierung ausgehend von denselben 32 Bytes dieselben Schlüssel ableitet.
Der Seed
Ein Label-309-Schlüsselsatz ist in einem einzigen Wert verwurzelt:
| Eigenschaft | Wert |
|---|---|
| Länge | 32 Bytes (256 Bit) |
| Quelle | Ein kryptografisch sicherer Zufallsgenerator (CSPRNG) oder ein beliebiger, dem Nutzer gehörender 32-Byte-Wert |
| Rolle | Eingabe-Schlüsselmaterial (IKM) für die drei nachfolgenden HKDF-Expansionen |
Der Seed ist eine reine Entropiequelle, kein Schlüssel im Sinne eines bestimmten Algorithmus. Er trägt keine Kurve, keine an ein Primitiv gebundene Länge, keine Kodierungsvorschrift. Die von einer Implementierung tatsächlich verwendeten Schlüssel werden je Ableitung festgelegt; der Seed überdauert die dabei getroffenen Algorithmusentscheidungen. Ein Erzeuger KANN den Seed frisch vom plattformseitigen CSPRNG erzeugen oder einen bestehenden 32-Byte-Wert importieren; in beiden Fällen MUSS er sich auf genau 32 Bytes dekodieren lassen. Auf der Ableitungsschicht wird kein Muster geringer Entropie abgelehnt: Ein Seed aus lauter Nullbytes ist eine gültige Eingabe, und genau das macht ihn als reproduzierbares Konformitäts-Fixture nutzbar.
Der Seed ist die gesamte Identität
Jede Aussage über den öffentlichen Schlüssel einer Partei, die Label 309 trifft, also der Schlüssel, der für einen Datensatz bürgt, und die Schlüssel, die versiegelte Nutzdaten empfangen, ist eine deterministische Funktion dieser 32 Bytes. Reproduzieren Sie den Seed, und Sie reproduzieren alle drei Schlüsselpaare Byte für Byte.
Den Seed für die Sicherung kodieren
Da der 32-Byte-Seed die Identität ist, ist er der Wert, den eine Nutzerin sichert, exportiert und importiert – und ein nackter 32-Byte-Blob lässt sich leicht unbemerkt abschneiden oder beschädigen. Label 309 definiert dafür eine Stringkodierung mit Prüfsumme, die überall dort, wo ein Seed als Eingabe entgegengenommen wird, neben rohem Hex akzeptiert wird.
Die Stringform ist Bech32 (BIP-173,
klassisch, mit aufgehobener 90-Zeichen-Längengrenze) unter dem menschenlesbaren
Präfix l309-seed- – der abschließende Bindestrich ist Teil des HRP, sodass der
Bech32-Trenner das sichtbare Präfix l309-seed-1… ergibt. Das Kodieren liefert die
GROSSGESCHRIEBENE Anzeigeform L309-SEED-1…: Geheimnisse sind laut, und die
Großschreibung hebt sich optisch von den kleingeschriebenen age1…-Empfängerstrings
ab. Die durchgängig kleingeschriebene Form ist eine gleichermaßen gültige Kodierung
derselben Bytes.
seed (32 bytes) 0000…0000 -> L309-SEED-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQLFUN82Ein Parser akzeptiert zwei Darstellungen und unterscheidet sie anhand der Form:
- Den Bech32-String in einheitlicher Schreibung (gemischte Schreibung wird gemäß BIP-173 abgelehnt), mit verifizierter Prüfsumme und einer dekodierten Nutzlast von exakt 32 Bytes.
- Rohes Hex – 64 Hexziffern, ohne Unterscheidung von Groß- und Kleinschreibung,
ein
0x-Präfix sowie umgebende oder eingestreute Leerzeichen werden toleriert.
Jede abgelehnte Eingabe wird auf einen eigenen Fehlercode der Konstruktions-API abgebildet, sodass eine aufrufende Stelle einen Tippfehler von einem falschen Schlüsseltyp unterscheiden kann:
| Eingabe | Fehlercode |
|---|---|
| Ein Bech32-String mit fehlschlagender Prüfsumme (ein vertauschtes Zeichen, eine Abschneidung) | SEED_STRING_BAD_CHECKSUM |
| Ein Bech32-String mit gemischter Groß- und Kleinschreibung | SEED_STRING_MIXED_CASE |
Ein gültiger Bech32-String unter einem anderen HRP (z. B. ein age1…-Empfänger) | SEED_STRING_WRONG_HRP |
| Ein Bech32- oder Hex-String, der zu ≠ 32 Bytes dekodiert | SEED_STRING_WRONG_LENGTH |
| Alles, was weder ein erkannter Bech32-String noch Hex ist (auch leer) | SEED_STRING_UNRECOGNIZED |
Diese Codes beschreiben den Seed-String-Codec, eine Bequemlichkeit der
Schlüsselhandhabung rund um die Ableitung; sie sind verschieden von der
Wire-Fehlercode-Registry, die ein struktureller Validator ausgibt
(Verifizierung). Die Kodierung trägt die nackten 32 Bytes und
sonst nichts – keine Version, keine Ableitungsparameter –, denn die Bedeutung des
Seeds ist durch die drei info-Strings weiter unten festgelegt, nicht dadurch, wie
er transportiert wurde.
Ableitung der drei Schlüsselpaare
Der private Schlüssel jedes Algorithmus ist eine eigenständige HKDF-SHA-256-Expansion desselben Seeds, gemäß RFC 5869. Die drei Expansionen teilen sich ihr Eingabe-Schlüsselmaterial und ihr (nicht vorhandenes) Salt und unterscheiden sich ausschließlich in einem Parameter, nämlich dem info-String, der den jeweiligen Algorithmus benennt:
| Algorithmus | info-String | Ausgabe |
|---|---|---|
| Ed25519 | cardano-poe-ed25519-v1 | 32-Byte-Secret-Seed für Ed25519 |
| X25519 | cardano-poe-x25519-v1 | 32-Byte-Secret-Seed für X25519 |
mlkem768x25519 | cardano-poe-mlkem768x25519-v1 | 32-Byte-Seed für den X-Wing-Decapsulation-Key |
Die Ableitung in Pseudocode:
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)Drei Regeln gewährleisten die Interoperabilität dieser Ausgaben über verschiedene Implementierungen hinweg:
- Das Salt ist leer. Das HKDF-Salt MUSS die Byte-Zeichenkette der Länge
null sein. Gemäß RFC 5869 §2.2
wird ein fehlendes Salt als
HashLenNullbytes behandelt, also 32 Nullbytes für SHA-256, sodass jede konforme Bibliothek denselben Extract-Schritt erreicht. - Die Ausgabe umfasst 32 Bytes. Jede Expansion fordert genau 32 Bytes an (ein einziger HKDF-Block für SHA-256).
- Die
info-Strings sind exaktes ASCII. Jederinfo-Wert MUSS als genau die angezeigten Bytes kodiert werden: ohne umgebende Leerzeichen, ohne abschließendes Nullzeichen, ohne Byte-Order-Mark, ohne abschließenden Zeilenumbruch. Die drei Strings umfassen 22, 21 bzw. 29 Bytes.
Die 32 Ausgabebytes sind der Secret-Seed des Algorithmus, nicht sein entfalteter Kurven-Skalar. RFC 8032 §5.1.5 zieht diese Unterscheidung für Ed25519: Der Secret-Seed umfasst 32 Bytes, und die Signaturbibliothek entfaltet ihn intern (über SHA-512 und anschließendes Clamping) zum eigentlichen Skalar und Signaturpräfix. Dasselbe gilt für X25519, wo das Clamping innerhalb des Primitivs gemäß RFC 7748 §5 angewendet wird. Eine Implementierung MUSS die rohe 32-Byte-HKDF-Ausgabe an das Primitiv übergeben und der Bibliothek die Entfaltung und das Clamping überlassen; sie clampt oder entfaltet nicht vorab. Bei X-Wing ist die 32-Byte-Ausgabe der X-Wing-Decapsulation-Key-Seed, aus dem das vollständige Schlüsselpaar, einschließlich des 1216 Byte großen öffentlichen Schlüssels, deterministisch durch die X-Wing-Schlüsselgenerierung neu erzeugt wird. In jedem Fall ist der kompakte 32-Byte-Seed, niemals ein entfalteter Schlüssel, die kanonische Form für Speicherung und Übertragung.
Warum drei Domänen statt einer
Der info-Parameter von HKDF ist sein Domänentrennungs-Tag: Er bindet die expandierte Ausgabe an einen spezifischen Anwendungskontext, und RFC 5869 §3.1 empfiehlt ausdrücklich, einen solchen anzugeben, sofern Kontext verfügbar ist. Label 309 verwendet einen eigenen Tag pro Algorithmus, statt eine Expansion für alle drei wiederzuverwenden, obwohl alle drei privaten Schlüssel zufällig dieselbe Breite von 32 Bytes aufweisen. Der Grund ist Isolation:
- Schwachstellen bleiben eingegrenzt. Würden zwei Schlüsselpaare identische Bytes teilen, könnte eine algorithmenspezifische Schwäche, etwa ein Fehler in der Nonce-Ableitung oder ein Seitenkanal bei einer Skalarmultiplikation, den Schlüssel eines unbeteiligten Algorithmus preisgeben. Die Domänentrennung garantiert, dass die drei privaten Schlüssel unabhängige Funktionen des Seeds sind, sodass die Kompromittierung eines Schlüssels einem Angreifer nichts über die anderen verrät.
- Migration bleibt additiv. Jeder
info-String endet auf-v1. Wer in einer künftigen Revision eine andere Kurve oder ein anderes Hybridverfahren einführt, leitet aus demselben Seed unter einem neuen Tag einen frischen-v2-Schlüssel ab, ohne Kollision mit bereits ausgerollten v1-Schlüsseln. Das entspricht der algorithmischen Flexibilität, auf die sich das Wire-Format selbst stützt.
Der dritte Tag, cardano-poe-mlkem768x25519-v1, gibt dem Post-Quanten-Hybrid eine eigene Domäne, obwohl der Seed seines Decapsulation-Keys dieselbe Breite von 32 Bytes wie der klassische X25519-Geheimschlüssel aufweist. Eine Schwäche in ML-KEM-768, in X25519 oder im X-Wing-Combiner kann daher weder den klassischen Verschlüsselungsschlüssel noch den Signaturschlüssel mitkontaminieren.
Dieser Identitätsschlüssel-Tag, cardano-poe-mlkem768x25519-v1, trägt zudem kein -kek--Segment: Er unterscheidet sich vom datensatzspezifischen KEK-Ableitungs-Label cardano-poe-kek-mlkem768x25519-v1 weiter unten, sodass die Seed-zu-Identitätsschlüssel-Expansion und die Slot-Schlüsselumhüllung eines versiegelten PoE nie denselben info-String teilen.
Slot-Schlüsselverschlüsselungsschlüssel
Die drei oben aus dem Seed abgeleiteten Schlüsselpaare sind langlebige Identitätsschlüssel. Ein versiegelter PoE fügt eine zweite, datensatzspezifische Schicht aus HKDF-SHA-256 hinzu: Für jeden Empfänger-Slot leitet der Absender einen frischen 32-Byte-Schlüsselverschlüsselungsschlüssel (Key-Encryption Key, KEK) ab, der den Inhalts-Verschlüsselungsschlüssel des Datensatzes umhüllt. Die KEK-Ableitung gehört zum Schlüsselmodell und wird daher hier spezifiziert; wie der umhüllte Schlüssel anschließend im Umschlag mitreist, behandelt Versiegelte PoE.
Beide KEMs leiten den KEK mit HKDF-SHA-256 und einem KEM-spezifischen info ab, unter einem Salt aus markiertem Hash, das drei Werte bindet: das KEM-Material des Slots selbst (sodass der KEK slot-eindeutig ist), den öffentlichen Empfängerschlüssel pub_R (sodass eine für einen Empfänger erstellte Kapselung nicht gegen einen anderen weitergereicht werden kann) und die umschlageindeutige enc.nonce (sodass der KEK an genau einen Umschlag verankert ist). Das gemeinsame Geheimnis ist die Ausgabe des KEM-eigenen ECDH (klassisch) bzw. der X-Wing-Entkapselung (hybrid), derselbe 32-Byte-Wert, ob der Absender kapselt oder der Empfänger entkapselt, und salt und info sind auf beiden Seiten identisch:
; x25519 (classical) — salt is a labelled SHA-256 over the ephemeral and recipient keys
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 (hybrid) — same labelled-salt shape under the hybrid's own label
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)Die beiden Salts haben dieselbe Form – SHA-256(label || enc.nonce || <Slot-KEM-Material> || pub_R) – und unterscheiden sich nur im KEM-eigenen Label und im mitgeführten KEM-Material: dem 32 Byte langen flüchtigen pub_epk auf dem klassischen Pfad, dem 1120 Byte langen X-Wing-Geheimtext kem_ct auf dem hybriden Pfad. Beide werden durch einen SHA-256-Digest fester Länge gefaltet, weil die hybriden Eingaben für ein rohes Salt überlang sind und eine einheitliche Form die beiden Pfade angeglichen hält. Die Bindung wird außerhalb des KEM berechnet, über die eigenen Wire-Bytes des Slots, sodass sie X-Wing als Black-Box-KEM behandelt und sich auf keine Eigenschaft des internen Hashens im Kombinierer stützt. Das KEM-distinkte info-Label garantiert zusätzlich, dass ein unter einem KEM abgeleiteter KEK bei identischem 32-Byte-Geheimnis nie einem unter dem anderen KEM abgeleiteten KEK gleichen kann.
In beiden Salts ist pub_R die kanonische Wire-Kodierung des Empfängerschlüssels, nämlich genau der 32-Byte-X25519-Public-Key bei x25519 und genau die festgelegte 1216-Byte-X-Wing-Public-Key-Bytefolge bei mlkem768x25519. Erzeuger und Empfänger MÜSSEN genau diese Kodierung verwenden und DÜRFEN kein nicht-kanonisches oder neu kodiertes Äquivalent einsetzen: Andernfalls speisten beide Seiten unterschiedliche Salts in HKDF ein und leiteten unterschiedliche KEKs ab, und der Slot ließe sich nie öffnen.
Jeder KEK und sein Salt-Präfix sind interne Bausteine von enc.scheme: 1: Sie tragen keinen Übertragungsbezeichner und sind nicht wählbar. Die beiden Salt-Präfix-Labels und die beiden info-Labels hier sind vier der elf Label-Literale der versiegelten Konstruktion, die unter Algorithmus-Registries katalogisiert sind; ein Verifizierer MUSS jedes Byte für Byte verwenden.
Kodierungen öffentlicher Empfängerschlüssel
Wer einen versiegelten Existenznachweis (engl. Proof of Existence, PoE) versendet, benötigt den öffentlichen Schlüssel des Empfängers in einer portablen Stringform, und ein Empfänger sichert sein Geheimnis in der passenden Form. Label 309 übernimmt die Bech32-Empfängerkodierungen des age-Ökosystems, ein menschenlesbares Präfix (HRP) pro registriertem Schlüsselkapselungsmechanismus (Key-Encapsulation Mechanism, KEM).
In Bech32 ist die 1 der Trenner zwischen dem HRP und dem Datenteil, sodass das menschlich sichtbare Präfix eines Strings sein HRP plus diese 1 ist. Das HRP und das sichtbare Präfix sind daher verschieden, und die Tabelle hält sie in getrennten Spalten:
KEM (enc.kem) | Öffentlicher Schlüssel | HRP des Public Keys | Sichtbares Präfix des Public Keys | HRP des Geheimnisses | Sichtbares Präfix des Geheimnisses |
|---|---|---|---|---|---|
x25519 | 32-Byte-X25519-Public-Key | age | age1… (62 Zeichen) | AGE-SECRET-KEY- | AGE-SECRET-KEY-1… |
mlkem768x25519 | 1216-Byte-X-Wing-Public-Key | age1pqc | age1pqc1… (1960 Zeichen) | AGE-SECRET-KEY-PQ- | AGE-SECRET-KEY-PQ-1… |
Der klassische x25519-Empfänger-String hat das HRP age und die standardmäßige age-v1-Form age1…. Der hybride öffentliche Schlüssel verkettet einen ML-KEM-768-Kapselungsschlüssel (1184 Bytes) mit einem öffentlichen X25519-Schlüssel (32 Bytes); mit 1216 Bytes umfasst sein age1pqc1…-Empfänger-String 1960 Zeichen.
Das Geheimnis, das eine Implementierung sichert und importiert, ist auf beiden Pfaden der 32-Byte-Seed: der X25519-Secret-Seed unter AGE-SECRET-KEY- und der X-Wing-Decapsulation-Key-Seed (die dritte HKDF-Ausgabe oben, info = "cardano-poe-mlkem768x25519-v1") unter AGE-SECRET-KEY-PQ-. Der 1216 Byte große hybride öffentliche Schlüssel leitet sich aus diesem Seed ab; der kompakte Seed, niemals der entfaltete Schlüssel, ist das kanonische Geheimnis, das gespeichert wird.
BIP-173 begrenzt einen Bech32-String auf 90 Zeichen, doch diese Grenze existiert für von Menschen eingetippte Zahlungsadressen und gilt hier nicht. Eine Implementierung MUSS den age1pqc1…-String kodieren und dekodieren, ohne die 90-Zeichen-Grenze durchzusetzen, und dabei weiterhin die Bech32-Prüfsummen- und Zeichensatzregeln anwenden. Die eigene HRP age1pqc verhindert, dass der hybride Empfänger mit einem klassischen age-Empfänger kollidiert, und ist bewusst nicht age1pq, das kürzere Präfix, das eine vorgelagerte native ML-KEM-768-+-X25519-Kodierung für dasselbe Primitiv bereits beansprucht, sodass die beiden Empfängerkodierungen auf der Übertragungsstrecke nie kollidieren. Die klassische Kodierung bleibt im Rahmen üblicher Längen und wird unverändert behandelt.
Diese Strings dienen ausschließlich der Empfängererkennung. Ein öffentlicher Empfängerschlüssel erscheint niemals im Verschlüsselungsumschlag eines Label-309-Datensatzes. Ein enc.slots[]-Eintrag trägt das Schlüsselmaterial pro Slot und einen wrap-Wert, und die KEM-Kennung erscheint genau einmal unter enc.kem. Wie Umschlag und Slots aufgebaut werden, behandelt Versiegelte PoE.
Der öffentliche Ed25519-Schlüssel als Signatur-kid
Der öffentliche Ed25519-Schlüssel spielt keine Empfängerrolle; er ist der Schlüsselbezeichner (Key Identifier), gegen den ein Verifizierer eine Signatur auflöst. Wenn ein Erzeuger einen Datensatz signiert, ist der rohe 32-Byte-Ed25519-Public-Key der kid (Label 4) im geschützten Header des COSE_Sign1, gemäß RFC 9052. Ein Verifizierer liest diesen 32-Byte-Wert direkt aus der On-Chain-Signatur und prüft den Datensatzrumpf dagegen. Der öffentliche Schlüssel wird zusammen mit der Signatur übermittelt, sodass zur Prüfung der Urheberschaft keine separate Abfrage erforderlich ist. Die vollständige Signaturkonstruktion, die signierten Nutzdaten und die Verifizierungsregeln sind unter Signaturen spezifiziert.
Schlüsselaustausch außerhalb des Bandes
Label 309 spezifiziert, wie öffentliche Empfängerschlüssel kodiert werden, nicht wie sie gefunden werden. Der Standard schreibt für Empfängerschlüssel weder ein Verzeichnis noch eine Registry noch ein On-Chain-Ankündigungsformat vor. Wer einen versiegelten Existenznachweis empfangen möchte, veröffentlicht seinen age1…- oder age1pqc1…-String über einen Kanal, dem beide Seiten ohnehin bereits vertrauen: eine persönliche Übergabe, einen unter dem eigenen Ed25519-Schlüssel signierten Datensatz, einen Datensatz an einem stabilen Web- oder inhaltsadressierten Ort. Der Absender trägt die Verantwortung für die Herkunft jedes Schlüssels, an den er verschlüsselt.
Dies ist eine bewusste Grenze. Dieselbe Eigenschaft, die es erlaubt, einen Datensatz ohne Vertrauen in einen Server zu verifizieren, bedeutet, dass der Schlüsselaustausch keinen vertrauenswürdigen Mittler durch die Hintertür wieder hereinholen darf. Ein Name, der neben einem Schlüssel platziert wird, ist eine Bekräftigung dessen, der ihn dort platziert hat, niemals eine kryptografische Aussage: Zwei Parteien, die denselben Handle verwenden, erzeugen dennoch Schlüssel mit unterschiedlichen Bytes, und ein Verifizierer vergleicht die Bytes. Menschenlesbare Namen auf Schlüssel abzubilden ist etwas, das eine auf Label 309 aufbauende Anwendung anbieten KANN, doch es handelt sich um ein Anwendungsmerkmal, das außerhalb des Protokolls liegt.
Verwandte Seiten
- Signaturen: wie der Ed25519-Schlüssel einen Datensatz signiert
und wie der
kidverifiziert wird. - Versiegelte PoE: wie die öffentlichen X25519- und X-Wing-Schlüssel verschlüsselte Nutzdaten an bestimmte Empfänger adressieren.
- Algorithmus-Registries: die benannten Kennungen für Signaturen, KEMs, AEADs und KDFs, auf die hier Bezug genommen wird.
Algorithmus-Register
Die benannten Bezeichner-Register für Hashes, AEADs, KEMs, KDFs und Signaturen sowie die Agilitätsregel, durch die der Umstieg auf Post-Quanten-Verfahren additiv erfolgt und nichts Bestehendes bricht.
Signaturen
Das optionale `sigs`-Array auf Datensatzebene, ein abgetrenntes COSE_Sign1 über den gesamten Datensatzkörper, sein domänengetrennter signierter Inhalt, die beiden Wege zur Übermittlung des Signaturschlüssels und die strenge Ed25519-Verifizierung.