Ceci est une traduction à titre informatif. La version anglaise est normative et prévaut. Lire la version anglaise

Clés

Le modèle de clés de Label 309 : un seed unique de 32 octets, trois paires de clés d’algorithme dérivées de celui-ci par HKDF-SHA-256 avec séparation de domaine, les clés de chiffrement de clé par emplacement qu’un enregistrement scellé dérive par-dessus, et la façon dont les clés publiques des destinataires et les secrets sont encodés.

Label 309 a besoin de trois types de clé asymétrique : une clé Ed25519 qui signe les enregistrements, une clé X25519 qui reçoit les charges utiles scellées classiques et une clé hybride X-Wing (mlkem768x25519) qui reçoit les charges utiles scellées post-quantiques. Le standard ne traite pas ces clés comme trois secrets indépendants à stocker et à manipuler séparément. Il définit exactement un secret — un seed de 32 octets — et une règle déterministe qui le développe en ces trois paires de clés.

Cette page spécifie cette dérivation : le seed, les trois développements HKDF à séparation de domaine qui produisent la clé privée de chaque algorithme, la raison pour laquelle les domaines restent distincts, les clés de chiffrement de clé par emplacement qu’un enregistrement scellé dérive par-dessus, et la façon dont les clés publiques des destinataires et les secrets ainsi obtenus sont encodés en vue de leur échange. Ce qu’une implémentation fait du seed au-delà de cela — où il réside, comment il est déverrouillé, si une même personne en détient plusieurs — sort du cadre de ce document. Label 309 exige seulement que, à partir des mêmes 32 octets, toute implémentation conforme dérive les mêmes clés.

Le seed

Un jeu de clés Label 309 trouve sa racine dans une valeur unique :

PropriétéValeur
Longueur32 octets (256 bits)
OrigineUn RNG cryptographiquement sûr, ou toute valeur de 32 octets que l’utilisateur possède
RôleMatériau de clé d’entrée (IKM) pour les trois développements HKDF ci-dessous

Le seed est une source d’entropie brute, et non une clé au sens propre d’un algorithme particulier. Il ne porte aucune courbe, aucune longueur liée à une primitive, aucune cérémonie d’encodage. Les clés qu’une implémentation utilise réellement sont négociées à chaque dérivation ; le seed survit aux choix d’algorithme effectués au-dessus de lui. Un producteur PEUT générer le seed à neuf à partir du CSPRNG de la plateforme ou importer une valeur de 32 octets existante ; dans les deux cas, elle DOIT se décoder en exactement 32 octets. Aucun motif à faible entropie n’est rejeté au niveau de la couche de dérivation — un seed composé uniquement de zéros est une entrée valide, et c’est précisément ce qui rend ce seed tout à zéro utilisable comme cas de test de conformité reproductible.

Le seed est l’identité tout entière

Toute affirmation de clé publique que Label 309 exprime au sujet d’une partie — la clé qui atteste un enregistrement, les clés qui reçoivent une charge utile scellée — est une fonction déterministe de ces 32 octets. Reproduisez le seed et vous reproduisez les trois paires de clés, octet pour octet.

Encoder le seed pour la sauvegarde

Comme le seed de 32 octets est l’identité, c’est la valeur qu’un utilisateur sauvegarde, exporte et importe — et un blob nu de 32 octets se tronque ou se corrompt facilement en silence. Label 309 en définit un encodage en chaîne avec somme de contrôle, accepté aux côtés de l’hexadécimal brut partout où un seed est pris en entrée.

La forme en chaîne est Bech32 (BIP-173, classique, la limite de 90 caractères étant levée) sous le préfixe lisible par l’humain l309-seed- — le tiret final fait partie du HRP, de sorte que le séparateur Bech32 rend le préfixe visible l309-seed-1…. L’encodage renvoie la forme d’affichage en MAJUSCULES L309-SEED-1… : les secrets s’annoncent clairement, et le rendu en majuscules se distingue visuellement des chaînes de destinataire age1… en minuscules. La forme intégralement en minuscules est un encodage tout aussi valide des mêmes octets.

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

Un analyseur accepte deux représentations et discrimine selon la forme :

  • La chaîne Bech32 dans une seule casse (le mélange de casses est rejeté conformément à BIP-173), avec la somme de contrôle vérifiée et la charge utile décodée d’exactement 32 octets.
  • L’hexadécimal brut — 64 chiffres hexadécimaux, insensible à la casse, tolérant un préfixe 0x ainsi que les espaces environnants ou internes.

Chaque entrée rejetée est associée à un code d’erreur distinct de l’API de construction, de sorte qu’un appelant puisse distinguer une faute de frappe d’un mauvais type de clé :

EntréeCode d’erreur
Une chaîne Bech32 dont la somme de contrôle échoue (un caractère inversé, une troncature)SEED_STRING_BAD_CHECKSUM
Une chaîne Bech32 mélangeant majuscules et minusculesSEED_STRING_MIXED_CASE
Une chaîne Bech32 valide sous un HRP différent (p. ex. un destinataire age1…)SEED_STRING_WRONG_HRP
Une chaîne Bech32 ou hexadécimale se décodant en ≠ 32 octetsSEED_STRING_WRONG_LENGTH
Tout ce qui n’est ni une chaîne Bech32 reconnue ni de l’hexadécimal (vide comprise)SEED_STRING_UNRECOGNIZED

Ces codes décrivent le codec de la chaîne du seed, qui est une commodité de manipulation de clés autour de la dérivation ; ils sont distincts du registre des codes d’erreur de sérialisation qu’émet un validateur structurel (Vérification). L’encodage transporte les 32 octets nus et rien d’autre — pas de version, pas de paramètres de dérivation — car la signification du seed est fixée par les trois chaînes info ci-dessous, et non par la façon dont il a été transporté.

Dériver les trois paires de clés

La clé privée de chaque algorithme est un développement HKDF-SHA-256 indépendant du même seed, conformément à RFC 5869. Les trois développements partagent leur matériau de clé d’entrée et leur sel (absent), et ne diffèrent que par un seul paramètre : la chaîne info qui nomme l’algorithme :

AlgorithmeChaîne infoSortie
Ed25519cardano-poe-ed25519-v1Seed secret Ed25519 de 32 octets
X25519cardano-poe-x25519-v1Seed secret X25519 de 32 octets
mlkem768x25519cardano-poe-mlkem768x25519-v1Seed de clé de décapsulation X-Wing de 32 octets

La dérivation en pseudo-code :

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)

Trois règles rendent ces sorties interopérables entre les implémentations :

  1. Le sel est vide. Le sel HKDF DOIT être la chaîne d’octets de longueur nulle. Conformément à RFC 5869 §2.2, un sel absent est traité comme HashLen octets à zéro — 32 octets à zéro pour SHA-256 — de sorte que toute bibliothèque conforme atteigne la même étape d’extraction.
  2. La sortie fait 32 octets. Chaque développement demande exactement 32 octets (un unique bloc HKDF pour SHA-256).
  3. Les chaînes info sont en ASCII exact. Chaque valeur info DOIT être encodée exactement comme les octets indiqués — aucun espace environnant, aucun terminateur nul, aucune marque d’ordre des octets, aucun saut de ligne final. Les trois chaînes font respectivement 22, 21 et 29 octets.

Les 32 octets de sortie sont le seed secret de l’algorithme, et non son scalaire de courbe développé. RFC 8032 §5.1.5 établit cette distinction pour Ed25519 : le seed secret fait 32 octets, et la bibliothèque de signature le développe en interne (via SHA-512, suivi du clamping) en le scalaire réel et le préfixe de signature. Il en va de même pour X25519, où le clamping est appliqué à l’intérieur de la primitive conformément à RFC 7748 §5. Une implémentation DOIT transmettre la sortie HKDF brute de 32 octets à la primitive et laisser la bibliothèque effectuer le développement et le clamping — elle n’applique ni le clamping ni le développement par avance. Pour X-Wing, la sortie de 32 octets est le seed de clé de décapsulation de X-Wing, à partir duquel la paire de clés complète — y compris la clé publique de 1216 octets — est régénérée de façon déterministe par la génération de clés X-Wing. Dans tous les cas, le seed compact de 32 octets, jamais une clé développée, est la forme canonique à stocker et à transporter.

Pourquoi trois domaines, et non un seul

Le paramètre info de HKDF est son étiquette de séparation de domaine : il lie la sortie développée à un contexte applicatif spécifique, et RFC 5869 §3.1 recommande vivement d’en fournir une lorsque le contexte est disponible. Label 309 fournit une étiquette distincte par algorithme plutôt que de réutiliser un unique développement pour les trois, même si les trois clés privées font, en l’occurrence, toutes 32 octets de large. La raison en est l’isolement :

  • Les défaillances restent confinées. Si deux paires de clés partageaient des octets identiques, une faiblesse propre à un algorithme — un défaut dans la dérivation d’un nonce, un canal auxiliaire sur une multiplication scalaire — pourrait exposer la clé d’un algorithme sans rapport. La séparation de domaine garantit que les trois clés privées sont des fonctions indépendantes du seed, de sorte que la compromission de l’une n’apprend rien à un attaquant sur les autres.
  • La migration reste additive. Chaque chaîne info se termine par -v1. Adopter une courbe différente ou un schéma hybride différent dans une révision future dérive du même seed une clé -v2 fraîche sous une nouvelle étiquette, sans collision avec les clés v1 déjà déployées. Cela reflète l’agilité algorithmique sur laquelle repose le format de sérialisation (wire format) lui-même.

La troisième étiquette, cardano-poe-mlkem768x25519-v1, donne au schéma hybride post-quantique son propre domaine, même si son seed de clé de décapsulation a la même largeur de 32 octets que le secret X25519 classique. Un défaut dans ML-KEM-768, dans X25519 ou dans le combinateur X-Wing ne peut donc pas contaminer en retour la clé de chiffrement classique ni la clé de signature.

Cette étiquette de clé d’identité, cardano-poe-mlkem768x25519-v1, ne porte par ailleurs aucun segment -kek- : elle est distincte de l’étiquette de dérivation de KEK par enregistrement cardano-poe-kek-mlkem768x25519-v1 ci-dessous, de sorte que le développement seed → clé d’identité et l’enveloppement de clé par emplacement d’un enregistrement scellé ne partagent jamais une chaîne info.

Clés de chiffrement de clé par emplacement

Les trois paires de clés dérivées du seed ci-dessus sont des clés d’identité de longue durée. Un enregistrement scellé ajoute une seconde couche de HKDF-SHA-256, cette fois par enregistrement : pour chaque emplacement de destinataire, l’expéditeur dérive une clé de chiffrement de clé (KEK) fraîche de 32 octets qui enveloppe la clé de chiffrement de contenu de l’enregistrement. La dérivation de la KEK fait partie du modèle de clés, c’est pourquoi elle est spécifiée ici ; la façon dont la clé enveloppée voyage ensuite dans l’enveloppe est traitée dans Enregistrement scellé.

Les deux KEM dérivent la KEK avec HKDF-SHA-256 et un info propre au KEM, sous un sel à hachage étiqueté qui lie trois valeurs : le matériau KEM propre à l’emplacement (de sorte que la KEK soit unique par emplacement), la clé publique du destinataire pub_R (de sorte qu’une encapsulation forgée pour un destinataire ne puisse être réacheminée contre un autre) et l’enc.nonce unique par enveloppe (de sorte que la KEK soit ancrée à une unique enveloppe). Le secret partagé est la sortie de l’ECDH propre au KEM (classique) ou de la décapsulation X-Wing (hybride) — la même valeur de 32 octets que l’expéditeur encapsule ou que le destinataire décapsule — et salt et info sont identiques des deux côtés :

; 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)

Les deux sels ont la même forme — SHA-256(label || enc.nonce || <matériau KEM de l’emplacement> || pub_R) — et ne diffèrent que par l’étiquette propre à chaque KEM et par le matériau KEM qu’ils transportent : la clé éphémère pub_epk de 32 octets sur le chemin classique, le texte chiffré X-Wing kem_ct de 1120 octets sur le chemin hybride. Tous deux sont repliés à travers un condensé SHA-256 de longueur fixe, car les entrées hybrides sont surdimensionnées pour un sel brut et une forme uniforme unique maintient les deux chemins alignés. La liaison est calculée à l’extérieur du KEM, sur les propres octets de sérialisation de l’emplacement, de sorte qu’elle traite X-Wing comme un KEM en boîte noire et ne repose sur aucune propriété du hachage interne du combinateur. L’étiquette info distincte par KEM garantit en outre qu’une KEK dérivée sous un KEM ne puisse jamais être égale à une KEK dérivée sous l’autre à partir d’un même secret partagé de 32 octets.

Dans les deux sels, pub_R est l’encodage de sérialisation canonique de la clé du destinataire — exactement la clé publique X25519 de 32 octets pour x25519, exactement la chaîne d’octets de clé publique X-Wing figée de 1216 octets pour mlkem768x25519. Le producteur et le destinataire DOIVENT utiliser cet encodage exact et NE DOIVENT PAS lui substituer un quelconque équivalent non canonique ou réencodé : sinon, les deux côtés fourniraient des sels différents à HKDF et dériveraient des KEK différentes, et l’emplacement ne s’ouvrirait jamais.

Chaque KEK et son préfixe de sel sont des briques de construction internes de enc.scheme: 1 : ils ne portent aucun identifiant de sérialisation et ne sont pas sélectionnables. Les deux étiquettes de préfixe de sel et les deux étiquettes info présentées ici sont quatre des onze littéraux d’étiquette de la construction scellée catalogués sur Registres d’algorithmes ; un vérificateur DOIT utiliser chacun octet pour octet.

Encodages des clés publiques des destinataires

Un expéditeur d’enregistrement scellé a besoin de la clé publique du destinataire sous une forme de chaîne portable, et un destinataire sauvegarde son secret sous la forme correspondante. Label 309 réutilise les encodages Bech32 de destinataires de l’écosystème age, un préfixe lisible par l’humain (HRP) par mécanisme d’encapsulation de clé enregistré.

En Bech32, le 1 est le séparateur entre le HRP et la partie de données, de sorte que le préfixe visible d’une chaîne est son HRP plus ce 1. Le HRP et le préfixe visible sont donc distincts, et la table les maintient dans des colonnes séparées :

KEM (enc.kem)Clé publiqueHRP de clé publiquePréfixe visible de clé publiqueHRP de secretPréfixe visible de secret
x25519Clé publique X25519 de 32 octetsageage1… (62 caractères)AGE-SECRET-KEY-AGE-SECRET-KEY-1…
mlkem768x25519Clé publique X-Wing de 1216 octetsage1pqcage1pqc1… (1960 caractères)AGE-SECRET-KEY-PQ-AGE-SECRET-KEY-PQ-1…

La chaîne de destinataire classique x25519 a le HRP age et la forme standard age1… d’age v1. La clé publique hybride concatène une clé d’encapsulation ML-KEM-768 (1184 octets) avec une clé publique X25519 (32 octets) ; avec ses 1216 octets, sa chaîne de destinataire age1pqc1… fait 1960 caractères.

Le secret qu’une implémentation sauvegarde et importe est, sur les deux chemins, le seed de 32 octets — le seed secret X25519 sous AGE-SECRET-KEY-, et le seed de clé de décapsulation X-Wing (la troisième sortie HKDF ci-dessus, info = "cardano-poe-mlkem768x25519-v1") sous AGE-SECRET-KEY-PQ-. La clé publique hybride de 1216 octets se dérive de ce seed ; c’est le seed compact, jamais la clé développée, qui est le secret canonique à stocker.

BIP-173 plafonne une chaîne Bech32 à 90 caractères, mais ce plafond existe pour les adresses de paiement saisies à la main et ne s’applique pas ici. Une implémentation DOIT encoder et décoder la chaîne age1pqc1… sans imposer la limite de 90 caractères, tout en appliquant les règles de somme de contrôle et de jeu de caractères de Bech32. Le HRP distinct age1pqc empêche le destinataire hybride d’entrer en collision avec un quelconque destinataire classique age — et n’est délibérément pas age1pq, le préfixe plus court qu’un encodage natif amont de ML-KEM-768 + X25519 revendique déjà pour la même primitive, de sorte que les deux encodages de destinataire n’entrent jamais en collision sur la sérialisation. L’encodage classique reste dans des longueurs ordinaires et est traité sans modification.

Ces chaînes ne sont que des commodités de découverte des destinataires. Une clé publique de destinataire n’apparaît jamais sur l’enveloppe de chiffrement d’un enregistrement Label 309 — une entrée enc.slots[] porte le matériau de clé propre à l’emplacement et une valeur wrap, et l’identifiant du KEM apparaît une seule fois dans enc.kem. La façon dont l’enveloppe et les emplacements sont construits est traitée dans Enregistrement scellé.

La clé publique Ed25519 comme kid de signature

La clé publique Ed25519 ne joue aucun rôle de destinataire ; c’est l’identifiant de clé à partir duquel un vérificateur résout une signature. Lorsqu’un producteur signe un enregistrement, les 32 octets bruts de la clé publique Ed25519 constituent le kid (étiquette 4) dans l’en-tête protégé de COSE_Sign1, conformément à RFC 9052. Un vérificateur lit cette valeur de 32 octets directement depuis la signature sur la chaîne et vérifie le corps de l’enregistrement par rapport à elle — la clé publique voyage avec la signature, de sorte qu’aucune recherche séparée n’est requise pour vérifier la paternité. La construction de signature complète, la charge utile signée et les règles de vérification sont spécifiées dans Signatures.

Échange de clés hors bande

Label 309 spécifie comment les clés publiques des destinataires sont encodées, non comment elles sont découvertes. Le standard ne prescrit aucun annuaire, aucun registre et aucun format d’annonce sur la chaîne pour les clés des destinataires. Une partie qui souhaite recevoir une charge utile scellée publie sa chaîne age1… ou age1pqc1… par le canal, quel qu’il soit, auquel les deux parties font déjà confiance — une remise en personne, un enregistrement signé sous sa propre clé Ed25519, un enregistrement à un emplacement web stable ou adressé par contenu — et l’expéditeur est responsable de la provenance de toute clé vers laquelle il chiffre.

C’est une frontière délibérée. La propriété même qui permet de vérifier un enregistrement sans faire confiance à un serveur implique que l’échange de clés ne doit pas réintroduire en douce un intermédiaire de confiance. Un nom placé à côté d’une clé est une attestation de celui qui l’a placé là, jamais une affirmation cryptographique : deux parties utilisant le même identifiant produisent malgré tout des clés aux octets différents, et un vérificateur compare les octets. Associer des noms lisibles par l’humain à des clés est quelque chose qu’une application bâtie sur Label 309 PEUT offrir, mais c’est une fonctionnalité de l’application, externe au protocole.

Pages liées

  • Signatures — comment la clé Ed25519 signe un enregistrement et comment le kid est vérifié.
  • Enregistrement scellé — comment les clés publiques X25519 et X-Wing adressent une charge utile chiffrée à des destinataires spécifiques.
  • Registres d’algorithmes — les identifiants nommés pour les signatures, les KEM, les AEAD et les KDF référencés ici.