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

Guide pour les implémenteurs

Comment construire une implémentation conforme de Label 309 : l’architecture en couches recommandée, le contrat d’identité octet pour octet entre les langages et les vecteurs de test de conformité qui définissent l’interopérabilité.

Label 309 est un format de sérialisation (wire format) et un ensemble de constructions cryptographiques, non un produit. Un nombre quelconque d’implémentations indépendantes — en TypeScript, Python, Rust, Go ou un environnement d’exécution mobile natif — peuvent coexister, et un enregistrement produit par l’une DOIT se vérifier sous une autre. Cette page s’adresse à l’équipe qui construit une telle implémentation. Elle décrit l’architecture qui maintient la surface cryptographique auditable, le contrat exact qui rend deux implémentations interopérables et la suite de conformité qui décide — mécaniquement — si vous l’avez respecté.

Deux choses rendent Label 309 interopérable entre les langages. La première est le déterminisme : les constructions sont ancrées à des standards publics (RFC 8949 CBOR canonique, RFC 8032 Ed25519, RFC 7748 X25519, RFC 5869 HKDF, RFC 9106 Argon2id, RFC 9052 COSE), de sorte que les mêmes entrées produisent les mêmes octets partout. La seconde est la suite de conformité : un ensemble de vecteurs de test exacts à l’octet qu’une implémentation reproduit ou ne reproduit pas. La conformité est une propriété que l’on peut vérifier, non une affirmation que l’on fait.

L’architecture en couches

Une implémentation conforme DEVRAIT séparer les primitives cryptographiques de la logique applicative en couches distinctes, chacune ne dépendant que de celle qui se trouve immédiatement en dessous. Les noms ci-dessous désignent des rôles, non des noms de paquet ; choisissez les vôtres.

┌─────────────────────────────────────────────────────────┐
│  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               │
└─────────────────────────────────────────────────────────┘

Les frontières entre couches sont porteuses, non cosmétiques. Chaque couche a une seule mission et une courte liste de choses qu’il lui est interdit de connaître.

Le cœur cryptographique

La couche inférieure ne contient que des primitives : fonctions de hachage, KDF, opérations de signature et de KEM, la couche de contenu AEAD, le CBOR canonique, COSE_Sign1, la construction d’enveloppe et de désenveloppe de la PoE scellée, racines et preuves de Merkle, et les classes d’erreur typées qu’elles lèvent. Elle ne contient aucune logique de domaine, aucun HTTP, aucun accès à une base de données, et aucun import de bibliothèque d’UI ou de framework serveur.

Cette couche DOIT rester exempte de toute dépendance liée à l’application ou au serveur, et DOIT être utilisable en toute sécurité dans le navigateur, pour trois raisons concrètes :

  • Elle s’exécute partout. Calculer l’empreinte d’un fichier, construire une enveloppe et — surtout — le vérificateur autonome s’exécutent dans les navigateurs, dans les workers sans serveur et en ligne de commande aussi aisément que sur un serveur. Une dépendance réservée au serveur (un pilote de base de données, un framework de journalisation lié à un environnement d’exécution, une bibliothèque d’UI) casserait ces cibles et alourdirait tout consommateur qui empaquette le cœur.
  • C’est la surface d’audit. Un relecteur peut lire de bout en bout un paquet composé uniquement de primitives en le confrontant aux RFC. Dès qu’un code applicatif s’y infiltre, la surface qu’un relecteur de sécurité doit garder en tête croît sans limite.
  • C’est ce que les tiers intègrent. Un vérificateur indépendant — quelqu’un qui ne fait confiance à aucun service, seulement à la chaîne — importe cette couche et rien de ce qui est au-dessus. La maintenir petite et portable est ce qui rend pratique le « vérifiez-le vous-même ».

Concrètement, le cœur NE DOIT PAS importer d’ORM ni de pilotes de base de données, de frameworks d’UI, de frameworks de journalisation liés au serveur, ni aucun module applicatif. L’aléa DOIT provenir du CSPRNG de la plateforme (Web Crypto getRandomValues, ou une réexportation équivalente), jamais d’une source réservée à Node, afin que la même source s’exécute sans modification dans un navigateur.

Imposez la frontière en CI, non en revue de code

La règle du zéro dépendance se dégrade dès qu’un import commode s’y glisse. Une implémentation DEVRAIT exécuter une analyse du graphe de dépendances qui parcourt chaque import du cœur et de la bibliothèque du format de sérialisation et fait échouer la build sur tout spécificateur situé hors d’une liste d’autorisation par couche. Les relecteurs oublient ; l’analyseur, non.

La bibliothèque du format de sérialisation

La couche immédiatement supérieure est propriétaire de Label 309 lui-même : le schéma de l’enregistrement, le validateur structurel et l’encodeur et le décodeur CBOR canonique. Elle dépend du cœur cryptographique (pour le hachage, COSE et le codec CBOR) et de rien d’autre qui soit lié à l’application. Sa surface est restreinte et pure :

  • encode — produit les octets CBOR canonique d’un enregistrement validé.
  • decode — l’opération inverse.
  • validate — exécute les contrôles structurels et sémantiques du standard sur un enregistrement décodé et renvoie un résultat typé (voir Vérification).

C’est dans cette couche que vivent, sous forme de code, les règles de L’enregistrement : le jeu fermé de clés, la discipline de réassemblage des chunks, l’invariant items-ou-merkle, les exigences du CBOR canonique. Comme le cœur, elle reste exempte de clients HTTP, de pilotes de base de données et d’imports de framework.

Le SDK et l’application

Le SDK enveloppe les couches inférieures dans des utilitaires ergonomiques — un client de service, des utilitaires de construction et d’ouverture d’enveloppe, et le vérificateur autonome, la fonction qui décode un enregistrement, en contrôle la structure, vérifie d’éventuelles signatures d’enregistrement contre la clé sur la chaîne, et produit un verdict en n’utilisant que des données publiques. Le vérificateur autonome DOIT fonctionner sans aucun accès réseau à un service exploité par l’implémenteur ; sa seule entrée externe est un explorateur de blockchain public que le vérificateur choisit. Le SDK DEVRAIT lui aussi rester utilisable en toute sécurité dans le navigateur.

La couche applicative — UI, routage, persistance, facturation, tâches d’arrière-plan — est un terrain vierge et ne porte aucune obligation d’interopérabilité. Rien dans le standard ne contraint la façon dont vous la construisez, sinon qu’elle se place au-dessus de la surface cryptographique vérifiée plutôt que d’y plonger la main.

Le contrat d’identité octet pour octet

L’interopérabilité est une propriété des octets, non des intentions. Deux implémentations interopèrent si et seulement si les primitives qui n’ont aucune liberté dans leur sortie produisent les mêmes octets à partir des mêmes entrées. C’est le contrat de parité, et c’est le cœur de la conformité.

Le contrat se scinde nettement en deux. Les opérations dont la sortie est entièrement déterminée par leurs entrées DOIVENT être identiques à l’octet entre les implémentations. Les opérations qui consomment de l’aléa ne peuvent pas être égales à l’octet d’un appel à l’autre ; pour celles-ci, le contrat est la consommabilité croisée — une valeur produite par une implémentation DOIT être consommable par toute autre (un texte chiffré scellé dans un langage se déchiffre dans un autre).

Primitives identiques à l’octet

Chacune des opérations ci-dessous est une fonction pure de ses entrées et DOIT émettre une sortie identique à l’octet dans toute implémentation conforme :

PrimitiveAncrée àSortie qui doit coïncider
Seed → paire de clés Ed25519 / X25519HKDF-SHA-256 avec les constantes info enregistréesclés publique et privée dérivées
HKDF-SHA-256RFC 5869matériel de clé en sortie pour une entrée fixe
MAC de l’ensemble de slots HMAC-SHA-256RFC 2104octets de slots_hash et du tag slots_mac pour une CEK et un ensemble de slots fixes
Argon2id (KDF de phrase secrète)RFC 9106clé dérivée pour (m, t, p, salt, len, password) fixes
SHA-256FIPS 180-4empreinte
BLAKE2b-256RFC 7693empreinte
Encodage CBOR canoniqueRFC 8949 §4.2.1octets encodés pour une entrée fixe
Encodage COSE_Sign1RFC 9052octets de la structure pour un en-tête, une charge utile et une signature fixes
Signature / vérification Ed25519RFC 8032 (stricte)signature ; verdict
ECDH X25519RFC 7748secret partagé pour des scalaires fixes
Enveloppe / désenveloppe de PoE scelléePoE scelléeoctets par slot et MAC lorsque les éphémères et la CEK sont injectés
Racine de Merkle + preuves d’inclusionRFC 9162 §2.1.1racine et preuves par feuille sur une liste de feuilles ordonnée

Deux points méritent d’être soulignés. Ed25519 est strict : un vérificateur conforme DOIT appliquer les règles du S canonique et du rejet des points d’ordre faible de RFC 8032 §5.1.7, de sorte que deux implémentations s’accordent non seulement sur les signatures qu’elles acceptent, mais aussi sur celles qu’elles rejettent. Argon2id franchit les frontières entre écosystèmes : des langages différents recourent à des bibliothèques Argon2 différentes, mais toute bibliothèque conforme implémente la RFC 9106 et DOIT produire une sortie identique pour des paramètres identiques — c’est le jeu de paramètres, non la bibliothèque, qui constitue le contrat.

Opérations consommant de l’aléa

La génération de clés, l’enveloppe de PoE scellée sous des éphémères frais par slot et le chiffrement de l’enveloppe puisent tous de l’aléa frais, de sorte que leur sortie diffère à chaque appel et ne peut être ancrée à l’octet. Le contrat pour celles-ci est la consommabilité croisée : la sortie produite par une implémentation DOIT être consommable par toute autre. Un enregistrement scellé dans un langage DOIT se déchiffrer dans un autre ; une paire de clés forgée dans l’un DOIT se vérifier et permettre de chiffrer vers elle dans un autre. Les suites de conformité ancrent ces cas avec des points d’ancrage de test déterministes qui injectent les éphémères — rendant l’enveloppe reproductible — et avec des fixtures d’aller-retour qui chiffrent dans un langage et déchiffrent dans l’autre.

Construire la construction de PoE scellée

La PoE scellée est la partie la plus dense du format de sérialisation, et celle où un seul octet erroné — une clé de map mal ordonnée, une étiquette décalée d’un caractère, un découpage en chunks non canonique — produit une enveloppe qui s’ouvre dans votre propre implémentation mais dans aucune autre. Cette section est la liste de vérification pour la construire : les recettes exactes, les données authentifiées supplémentaires que couvre chaque AEAD, la boucle de déchiffrement par essai, et les garde-fous que tout producteur et tout vérificateur doit imposer. La référence de construction sur PoE scellée est la prose ; ceci est la façon de la câbler pour que la barrière de parité passe au vert. Ancrez exactement ces drafts externes, car leurs entrailles fixent des octets que vous devez reproduire :

  • chacha20-poly1305-stream64k — le format de contenu — est ChaCha20-Poly1305 (RFC 8439) dans la disposition STREAM segmentée de 64 KiB de la spécification age v1. Ancrez exactement la taille de chunk (65536), le nonce par chunk de 12 octets uint88_be(counter) ‖ final_flag, l’AAD par chunk vide et la règle du drapeau final — ils fixent des octets que vous devez reproduire.
  • X-Wing (le KEM mlkem768x25519) est draft-connolly-cfrg-xwing-kem-10. Traitez-le comme un KEM en boîte noire : la construction lie la clé publique du destinataire et le texte chiffré dans l’étape de dérivation de clé elle-même, de sorte qu’elle ne s’appuie sur aucune propriété du hachage interne du combinateur. XWing.Encapsulate DOIT appliquer le contrôle de validité de clé publique de la révision fixée et refuser d’encapsuler vers une clé qui ne le passe pas ; le plancher « jamais en deçà de la sécurité classique de X25519 » est circonscrit aux clés générées valablement, et sauter le contrôle renonce à ce plancher pour ce destinataire. Les vecteurs KEM de conformité ancrent l’encapsulation contre le draft-10, de sorte qu’un décalage de révision du draft remonte immédiatement.

Une CEK, deux chemins de livraison de clé

Un enregistrement scellé chiffre le texte en clair une seule fois sous une unique clé de chiffrement de contenu (CEK), puis livre cette CEK par l’un de deux chemins mutuellement exclusifs, discriminés par la présence des champs — il n’y a pas d’étiquette de mode :

  • chemin slots — la CEK est enveloppée indépendamment vers chaque destinataire sous une clé de chiffrement de clé par slot. enc porte slots (ainsi que kem, slots_mac).
  • chemin passphrase — la CEK est dérivée directement d’une phrase secrète normalisée via Argon2id. enc porte passphrase ; il ne porte ni kem, ni slots, ni slots_mac.

Les deux chemins partagent enc.scheme (toujours 1 ; rejetez toute autre valeur), enc.aead (chacha20-poly1305-stream64k) et enc.nonce (24 octets). Ils diffèrent par l’endroit où réside l’engagement sur la clé : sur la chaîne dans slots_mac pour le chemin slots, dans un en-tête de 32 octets à l’intérieur du blob de texte chiffré pour le chemin passphrase. Les deux lient l’affirmation de hachage de l’élément à leur transcription, et les deux scellent le contenu dans le même STREAM segmenté ; la différence est la livraison de clé et l’engagement, non la couche de contenu.

Enveloppe par slot (chemin slots)

Choisissez un KEM pour tout l’enregistrement — ne mélangez jamais les KEM au sein d’un même slots[]. Pour chacun des N destinataires, dérivez une clé de chiffrement de clé fraîche par slot et enveloppez la même CEK sous elle avec ChaCha20-Poly1305 à un nonce de 12 octets tout à zéro, l’AAD étant fixée au littéral de l’étiquette info de ce KEM (jamais une AAD vide), produisant exactement 48 octets (texte chiffré de la CEK de 32 octets + tag de 16 octets). Le nonce à zéro n’est sûr que parce que la clé de chiffrement de clé est par slot ; voir le garde-fou d’unicité ci-dessous.

x25519 (classique). Paire de clés X25519 éphémères fraîche par slot :

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 (hybride ; X-Wing). Encapsulation X-Wing fraîche par slot :

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

Les deux sels ont une seule forme — SHA-256(label || enc.nonce || <matériel KEM du slot> || pub_R) — portant l’éphémère pub_epk de 32 octets sur le chemin classique et le texte chiffré X-Wing kem_ct de 1120 octets sur le chemin hybride ; || est la concaténation d’octets, et chaque littéral de préfixe de sel est de l’ASCII exact, sans terminateur ni préfixe de longueur. pub_R est la clé wire canonique du destinataire (32 B pour x25519, les 1216 B fixés pour mlkem768x25519). Le slot hybride ne porte aucun epk séparé — l’éphémère X25519 est constitué des 32 derniers octets de kem_ct — et kem_ct est une unique chaîne d’octets CBOR de exactement 1120 octets : seul le corps complet de l’enregistrement est découpé en chunks pour le transport, jamais un champ individuel.

Le sel lie trois valeurs : le matériel KEM du slot (KEK unique par slot), pub_R (déjouant le relais en adjoint confus contre un destinataire différent), et enc.nonce (ancrant la KEK à une seule enveloppe, de sorte qu’un aléa KEM répété ne dégrade que vers une corrélabilité entre enveloppes). Les étiquettes info distinctes offrent une séparation de domaine entre KEM, de sorte qu’aucune KEK dérivée sous un KEM ne puisse égaler une KEK dérivée sous l’autre sur un secret partagé identique. Utilisez chacune des onze étiquettes internes octet pour octet — 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. Aucune n’est jamais sérialisée sur le fil ; ce sont des constantes fixes, non sélectionnables par registre. Un seul octet divergent produit un slots_mac, un engagement ou un tag AEAD que le producteur honnête ne peut reproduire.

Mélangez avant de calculer le MAC. L’ordre d’entrée (« le destinataire principal d’abord ») est une métadonnée privilégiée ; publier les slots dans l’ordre d’entrée la divulgue. Mélangez slots[] avec un CSPRNG en utilisant une permutation de Fisher-Yates non biaisée — un simple tirage d’indice u32 % m penche vers les résidus faibles et doit être échantillonné par rejet jusqu’à un indice uniforme — avant de calculer le MAC de l’ensemble de slots, qui lie l’ordre mélangé tel qu’il apparaît sur le fil.

MAC de l’ensemble de slots : hachez la transcription, puis HMAC sous la CEK

Le MAC de l’ensemble de slots lie l’ensemble de slots complet, plus les champs d’en-tête qui fixent la façon dont les slots sont lus, à la CEK. Construisez-le en deux étapes — hachez une transcription fermée une fois, puis appliquez HMAC à ce hachage :

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

Trois choses font ou défont la parité ici :

  • La transcription est une map fermée sérialisée par canonicalEncode. Son ordre de clés est le tri de RFC 8949 §4.2.1, jamais disposé à la main. Fixer scheme, path, aead, kem et nonce aux côtés des slots signifie qu’un relais qui retourne un quelconque champ d’en-tête — même en laissant valides les formes des slots — change slots_hash et rompt le MAC.
  • La transcription lie l’affirmation de hachage de l’élément. hashes_hash est un SHA-256 étiqueté sur le canonicalEncode de la map hashes complète de l’élément. Comme le destinataire recalcule slots_mac à partir des seuls octets sur la chaîne, une concordance du MAC confirme que l’enveloppe a été scellée pour cette affirmation de hachage exacte — une enveloppe greffée sur un élément avec une map hashes différente échoue à l’étape de concordance sur la chaîne, avant toute récupération de texte chiffré. La valeur slots est directement le tableau mélangé de maps de slot sur le fil : chaque champ de slot est une unique chaîne d’octets (epk 32 B, kem_ct 1120 B), de sorte qu’il n’y a aucun découpage en chunks par champ à canonicaliser.
  • slots_hash est calculé une fois et maintenu constant tout au long de la boucle de déchiffrement par essai. Le contrôle du MAC par slot re-dérive la clé HMAC depuis chaque CEK candidate, mais toujours sur le même slots_hash de 32 octets. Le pré-hachage laisse intact l’engagement à clé dérivée de la CEK : il change le message du HMAC de la transcription complète en son SHA-256, rien de plus.

L’algorithme du MAC, sa dérivation de clé et le schéma de la transcription sont tous fixés par enc.scheme = 1 et identiques pour les deux KEM ; il n’y a aucun identifiant de MAC sur le fil. slots_mac fait exactement 32 octets et est vérifié en temps constant.

Chiffrement du contenu : le STREAM segmenté

Chiffrez le texte en clair une seule fois dans le STREAM segmenté sous une clé de contenu dérivée de la CEK. La clé de contenu est une feuille HKDF distincte de la CEK — salée par enc.nonce, sous un info spécifique au chemin — de sorte que la couche d’enveloppe et la couche de contenu ne dérivent jamais la clé de la même primitive sur les mêmes octets :

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

L’AAD par chunk est vide — et c’est correct, non une omission : la clé de contenu dérive de la CEK, et la CEK est déjà engagée envers l’en-tête complet (y compris hashes_hash) par slots_mac. Retournez un quelconque champ d’en-tête et le destinataire dérive une clé de contenu différente, de sorte que le flux ne s’ouvre pas ; une AAD par chunk relierait le même contexte à chaque chunk sans rien ajouter à la sécurité. Les nonces à compteur sont sûrs parce que la clé de contenu est à usage unique (une CEK fraîche salée par le enc.nonce unique à l’enveloppe), de sorte que deux flux ne partagent jamais une paire (key, nonce).

Construisez le STREAM de sorte que la troncature soit détectable : chaque chunk non final fait exactement 65536 octets de texte en clair, le chunk final porte final_flag = 0x01 et de 0 à 65536 octets (un texte en clair vide est un unique chunk final de longueur zéro — un tag isolé de 16 octets), et un vérificateur DOIT échouer (TAMPERED_CIPHERTEXT) sur un drapeau final manquant, un drapeau final sur un chunk non final, des données après le chunk final, ou un chunk non final trop court. Vérifiez le tag de chaque chunk avant de relâcher son texte en clair, et traitez les octets relâchés comme provisoires jusqu’à ce que le re-contrôle du hachage après déchiffrement passe.

Le texte en clair est l’exacte séquence d’octets du contenu original ; la construction ne préfixe, ne suffixe ni ne chiffre aucun nom de fichier, type MIME, champ de taille ou wrapper de métadonnées. Le blob de texte chiffré publié est constitué des chunks du STREAM (sur le chemin passphrase, précédés de l’en-tête d’engagement de 32 octets ci-dessous). La map enc assemblée et l’URI résultante vont sur la chaîne ; les octets du texte chiffré, non — publiez-les dans un stockage adressé par contenu et mettez l’URI ar:// ou ipfs:// dans le uris[] de l’élément.

Chemin passphrase

Lorsqu’il n’y a aucun destinataire, dérivez la CEK d’une phrase secrète normalisée avec Argon2id. Il n’y a pas de epk, pas d’enveloppe par slot, pas de MAC d’ensemble de slots et pas de boucle de déchiffrement par essai. L’engagement sur la clé que slots_mac fournit sur le chemin slots réside à la place dans un en-tête de 32 octets à l’intérieur du blob de texte chiffré, préfixé avant les chunks du 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_key

Le PASSPHRASE_TRANSCRIPT lie à l’engagement les paramètres de la KDF, les champs d’en-tête et l’affirmation de hachage de l’élément : altérer salt, une quelconque valeur de params, nonce, aead, ou greffer l’enveloppe sur une affirmation de hachage différente, produit un pw_hash différent et fait échouer le contrôle de l’engagement. La valeur "normalization" est une constante fixée par le schéma alimentée dans la transcription pour fixer le profil exact sous lequel la CEK a été dérivée ; elle n’est jamais sérialisée sur le fil (le producteur n’émet que { alg, salt, params }).

Côté vérification, dérivez la CEK candidate, lisez les 32 octets de tête du blob de texte chiffré, recalculez l’engagement et comparez en temps constant avant d’ouvrir le moindre chunk du STREAM. Un blob plus court que 48 octets (engagement de 32 octets + STREAM minimal de 16 octets) est malformé (TAMPERED_CIPHERTEXT). En cas de discordance — phrase secrète erronée, salt / params / en-tête altérés, ou enveloppe greffée — faites remonter le même unique échec générique et ne commencez pas le streaming ; une phrase secrète erronée est indistinguable d’un enregistrement altéré. L’engagement est délibérément hors chaîne : un engagement sur la chaîne serait un oracle de devinette hors ligne gratuit pour chaque enregistrement à phrase secrète, y compris ceux dont le texte chiffré est retenu.

Imposez les planchers de paramètres : longueur de salt de 16 à 64 octets ; m ≥ 65536 KiB (≈ 64 MiB), t ≥ 3, p ≥ 1. Fixez la version d’Argon2 à 0x13 (19) ; aucune autre version n’est admissible sous enc.scheme: 1, et il n’y a aucun champ de version sur le fil. Là où la plateforme le permet, les producteurs DEVRAIENT émettre p = 4 (le second profil recommandé de la RFC 9106 §4) ; les vérificateurs PEUVENT accepter tout p ≥ 1, sous réserve des plafonds de déploiement. Argon2id franchit proprement les frontières entre écosystèmes — c’est le jeu de paramètres, non la bibliothèque, qui est le contrat — de sorte qu’un (m, t, p, salt, len, password) fixe doit produire une sortie identique à l’octet en toute implémentation. Le lien entre une phrase secrète et son enveloppe est l’engagement dans le texte chiffré ci-dessus ; une phrase secrète erronée et un texte chiffré altéré remontent tous deux comme un unique échec générique.

Bornez la phrase secrète brute avant la normalisation et Argon2id : rejetez toute entrée plus longue que les MAX_PASSPHRASE_INPUT_BYTES = 4096 octets UTF-8 de référence, de sorte qu’une phrase secrète pathologique ne puisse provoquer un déni de service pré-KDF. Comme les bornes du chemin slots MAX_SLOTS et de l’enveloppe décodée, c’est une constante fixée par déploiement que vous POUVEZ durcir, non un champ du fil.

Le profil de normalisation est normatif

Deux implémentations DOIVENT dériver une CEK identique à l’octet de la même phrase secrète, et la seule façon de le garantir est une normalisation ancrée. Le profil cardano-poe-pw-norm-v1, appliqué dans l’ordre :

  1. Rejeter les points de code non assignés — une phrase secrète contenant un quelconque point de code non assigné en Unicode 16.0 est rejetée (ENC_PASSPHRASE_UNNORMALIZABLE) avant l’exécution de toute normalisation. Unicode ne garantit la stabilité de la normalisation que sur les points de code assignés, de sorte que ceci ferme une faille de dérive future et est invisible pour les utilisateurs honnêtes.
  2. NFKC — Forme de normalisation KC selon UAX #15 sous Unicode 16.0.
  3. Espaces — définissez l’espace comme tout caractère portant la propriété Unicode White_Space sous Unicode 16.0 ; réduisez chaque séquence maximale à un unique U+0020 SPACE.
  4. Rognage — retirez les espaces de tête et de queue.
  5. Rejeter le vide — si le résultat est la chaîne vide, rejetez (ENC_PASSPHRASE_EMPTY) ; une phrase secrète composée uniquement d’espaces lierait sinon l’enregistrement à une CEK que n’importe quelle partie peut dériver.
  6. Encoder — UTF-8 ; ces octets sont l’entrée mot de passe d’Argon2id.

Fixez Unicode à 16.0 littéralement et ne le laissez pas flotter : l’ensemble de la propriété White_Space, l’ensemble des points de code assignés et les tables de mappage NFKC dépendent tous de la version, de sorte que résoudre le profil contre une version d’Unicode différente peut dériver une CEK différente de la même phrase secrète et échouer à ouvrir un enregistrement honnête. Une révision future qui adopte une version d’Unicode plus récente le fait sous un nouvel identifiant de profil, jamais en réinterprétant cardano-poe-pw-norm-v1.

Déchiffrement par essai : ouvrez chaque slot, intégrez le MAC, échouez de façon générique

Un destinataire détient une clé privée KEM et découvre son slot en essayant d’ouvrir chacun d’eux — les clés publiques des destinataires ne sont pas sur le fil. Avant d’invoquer toute primitive KEM ou AEAD, exécutez les bornes de ressources, puis les garde-fous structurels. Bornez d’abord l’usage des ressources du parseur : rejetez une enveloppe dont la taille décodée dépasse 65536 octets (ENC_ENVELOPE_TOO_LARGE) ou dont les slots[] dépassent MAX_SLOTS = 1024 (ENC_SLOTS_TOO_MANY). Les deux bornes de référence se situent bien au-dessus du plafond de ~16 KiB des métadonnées de transaction Cardano qui contraint tout enregistrement honnête ; ce sont des constantes fixées par déploiement que vous POUVEZ durcir, jamais des champs du fil. Puis les garde-fous structurels : scheme == 1 ; aead, kem enregistrés ; nonce de 24 octets ; slots_mac de 32 octets ; slots non vide ; secret du destinataire de 32 octets ; chaque wrap de 48 octets ; par KEM, chaque epk exactement de 32 octets sans kem_ct (x25519) ou chaque kem_ct exactement de 1120 octets sans epk (mlkem768x25519).

Rejetez ici les encapsulations dupliquées au sein de l’enregistrement, avant toute primitive. Toutes les valeurs epk doivent être distinctes sur le chemin classique, toutes les valeurs kem_ct distinctes sur le chemin hybride ; un doublon lève ENC_SLOTS_DUPLICATE_KEM_MATERIAL. C’est la part vérifiable par le vérificateur de l’invariant d’unicité de la KEK par slot dont dépend l’enveloppe à nonce zéro ; la réutilisation entre enregistrements ou entre clés est une obligation du producteur qu’aucun vérificateur ne peut détecter. Ce rejet se déclenche uniquement sur un epk / kem_ct répété — sceller deux fois vers le même destinataire avec des éphémères par slot frais est légitime et ne le déclenche pas (voir la règle sur les correspondances multiples ci-dessous). unwrap-negative porte le cas du epk dupliqué avec réutilisation de KEK.

Puis exécutez la boucle, en recalculant slots_hash une fois avant elle et en le maintenant constant :

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

Les non-négociables de cette boucle :

  • Ouvrez de façon atomique ; ne relâchez jamais de texte en clair non vérifié. Les deux primitives *_open_or_dummy sont atomiques : en cas d’échec de vérification du tag AEAD, elles ne renvoient aucun texte en clair, et le candidat renvoyé (la CEK enveloppée, ou le texte en clair du contenu) est un leurre fixe ou pseudo-aléatoire indépendant du texte chiffré ayant échoué. C’est ce qui permet à la boucle de porter une candidate_CEK au-delà d’une ouverture d’enveloppe ayant échoué sans jamais exposer d’octets non authentifiés.
  • Intégrez le contrôle du tout-à-zéro dans un bit kem_ok indépendant du secret. Calculez kem_ok = NOT constantTimeEqual(shared, 0^32) pour le chemin x25519, sélectionnez la KEK en temps constant entre la KEK réelle et une KEK leurre dérivée de 0^32 sous le même sel et le même info, et intégrez kem_ok dans l’acceptation (ok = kem_ok AND open_ok AND mac_ok). Ne vous branchez pas hors de la boucle de manière anticipée sur une part invalide — un slot à ECDH invalide ne peut jamais être accepté, et la boucle effectue tout de même un travail identique. (XWing.Decapsulate n’a aucun cas tout-à-zéro, de sorte que kem_ok est fixé à vrai sur le chemin hybride.)
  • Intégrez le contrôle de slots_mac dans la boucle. Un expéditeur malveillant peut fabriquer un slot qui s’ouvre sous la clé du destinataire avec une CEK choisie par l’attaquant (aucune connaissance de la clé privée n’est nécessaire). Accepter le premier succès AEAD comme « le nôtre » laisserait ce slot falsifié éclipser un slot honnête. Exiger que la CEK candidate reproduise aussi slots_mac sur slots_hash déjoue la substitution, la suppression et le réordonnancement de slots. Ne l’omettez jamais.
  • Permettez les correspondances multiples ; ne rejetez qu’un conflit de CEK. Une clé de destinataire PEUT légitimement correspondre à plus d’un slot — sceller la même CEK vers le même destinataire dans plusieurs slots, chacun avec des éphémères frais, est un remplissage valide du nombre de destinataires et ne déclenche pas le rejet du epk/kem_ct dupliqué. Sélectionnez la CEK de la première correspondance et ne rejetez pas du seul fait que plus d’un slot a correspondu. La seule anomalie à rejeter est constituée de deux slots correspondants qui récupèrent des CEK différentes (comparaison en temps constant) : suivez un bit cek_conflict et faites remonter l’unique échec générique s’il est positionné. C’est de la défense en profondeur — sous l’engagement de l’ensemble de slots, une correspondance à CEK distincte est déjà irréalisable — de sorte qu’elle échoue de façon fermée face à une implémentation défectueuse.
  • Itérez tous les slots dans la passe d’une seule clé privée — un nombre constant d’opérations de slot par clé, sans rupture anticipée — de sorte qu’un observateur du temps ne puisse inférer quel slot a correspondu. Pilotez le rejet du tout-à-zéro via kem_ok et un travail leurre plutôt que de sortir de façon anticipée. Un destinataire à plusieurs clés itère clé × slot et PEUT court-circuiter d’une clé à l’autre (ne divulguant que le faible signal « quelle clé a correspondu »), mais doit rester en temps constant sur les slots de chaque clé — et doit re-dériver la moitié pub_R du sel pour chaque clé, puisque les deux KEM lient la clé publique propre du destinataire dans le sel de la KEK. Liez ce sel à l’encodage wire canonique de la clé — exactement la clé publique X25519 de 32 octets, ou exactement les octets de clé publique X-Wing fixés de 1216 octets — jamais un ré-encodage non canonique, sinon les deux côtés dérivent des KEK différentes.
  • Exposez une unique forme d’échec générique aux appelants non fiables. En interne, vous pouvez suivre des résultats typés à des fins de diagnostic local — WRONG_RECIPIENT_KEY (aucun slot ne s’est ouvert), TAMPERED_HEADER (un slot s’est ouvert mais aucune CEK candidate n’a reproduit slots_mac), TAMPERED_CIPHERTEXT (l’AEAD du contenu a échoué après qu’une CEK a été récupérée et le MAC vérifié) — mais un observateur externe NE DOIT PAS les distinguer par la forme de la réponse. Sur le plan du temps, le modèle est délibérément circonscrit : un vérificateur PEUT retourner au contrôle if NOT found avant le déchiffrement du contenu, ce qui sépare un non-destinataire d’un destinataire dont le texte chiffré n’arrive pas à s’ouvrir. Cela ne révèle que destinataire contre non-destinataire, jamais quel slot ni aucun matériel de clé ; un temps uniforme entre ces deux cas n’est pas exigé et une ouverture leurre du contenu NE DOIT PAS être imposée. La garantie de temps constant qui tient est l’invariant entre slots ci-dessus.
  • Recalculez et comparez le hachage du texte en clair après le déchiffrement. La map hashes sur la chaîne s’engage envers le texte en clair, non envers le texte chiffré, de sorte que le destinataire (à la couche applicative) doit recalculer l’empreinte et la comparer : l’entrée sha2-256 doit correspondre, ainsi que blake2b-256 si elle est présente. Une discordance signifie que l’affirmation de hachage de l’enregistrement ne correspond pas aux octets déchiffrés — refusez d’agir sur le texte en clair. Le validateur structurel ne déchiffre jamais.

Bornez la charge utile des deux côtés

Le STREAM segmenté n’impose aucun plafond cryptographique à la charge utile : le compteur de 88 bits par chunk admet 2^88 chunks, et chaque chunk est scellé sous une paire (content_key, nonce) distincte, largement dans la limite d’invocation unique de la RFC 8439, de sorte qu’il n’y a aucun risque de débordement de compteur contre lequel se prémunir. Le maximum qu’un producteur ou un vérificateur impose est donc une politique de déni de service du déploiement, non une constante du fil — imposez-le de façon incrémentale à mesure que le flux est écrit ou lu, et interrompez avant de tamponner une charge utile surdimensionnée. La troncature est détectée structurellement par le drapeau final plutôt que par un plafond de taille. La même posture s’applique aussi bien sur le chemin slots que sur le chemin passphrase.

Fixtures de conformité de la PoE scellée

Le coin de la PoE scellée du corpus est l’endroit où remonte la majorité des bugs entre langages. Faites passer votre implémentation à travers la totalité. Les fixtures positives ancrent l’enveloppe déterministe et la boucle de déchiffrement par essai pour les deux KEM — à destinataire unique et à destinataires multiples, à N mixte, et le pire cas à plusieurs clés privées — plus le cas légitime d’un destinataire qui correspond à deux slots (éphémères frais, même CEK, DOIT se déchiffrer, de sorte qu’une implémentation qui rejette les correspondances multiples échoue ici) et le chemin passphrase (en-tête d’engagement plus les chunks du STREAM dans un seul blob). Un ensemble dédié à la disposition STREAM ancre un texte en clair vide (un unique chunk final de longueur zéro), une charge utile à chunk unique et une charge utile à plusieurs chunks franchissant la frontière des 65536 octets. Des KAT ciblés ancrent les deux sels de KEK (SHA-256(label ‖ enc.nonce ‖ <matériel KEM> ‖ pub_R)), le hashes_hash et sa place dans les deux transcriptions, l’encapsulation X-Wing contre le draft-10, l’extraction HKDF avec sel de longueur zéro (la convention du sel absent de la RFC 5869 §2.2, reflétant la dérivation de la clé de slots_mac), les encodages Bech32 du destinataire et du secret, et l’encodage avec somme de contrôle de la seed d’identité.

Les fixtures négatives ancrent les codes de rejet : un slot fantôme falsifié avant un slot honnête (l’enregistrement DOIT toujours se déchiffrer sous la CEK honnête) ; un retournement d’en-tête (kem/aead/scheme) qui laisse valides les formes des slots ; une greffe de hashes sur un élément à affirmation de hachage différente ; les échecs de l’engagement de phrase secrète (phrase secrète erronée, salt/params altérés, en-tête altéré — tous échouant avant qu’un quelconque chunk s’ouvre) ; les rejets de normalisation de phrase secrète (une entrée à point de code non assigné et une entrée composée uniquement d’espaces) ; le secret partagé X25519 tout-à-zéro ; le slot dupliqué au sein de l’enregistrement ; et les cas d’altération du STREAM (tag de chunk retourné, flux tronqué, données en queue, chunk non final trop court). Deux propriétés n’ont aucun vecteur d’octets et sont affirmées de façon comportementale à la place : le rejet de conflit de CEK (en construire un est exactement la collision d’engagement multi-clé que le standard suppose irréalisable) et la garantie de temps constant entre slots. Reproduisez chaque chaîne d’octets ancrée et émettez le code exact pour chaque cas négatif.

Une propriété de la PoE scellée n’a aucun vecteur d’octets : le rejet de conflit de CEK — deux slots correspondants qui récupèrent des CEK différentes — ne peut être construit comme fixture, parce qu’en construire un est exactement la collision d’engagement multi-clé que le standard suppose irréalisable. Ancrez-le à la place avec un test comportemental au niveau de l’implémentation qui affirme que votre boucle de déchiffrement par essai échoue de façon fermée sur un conflit forcé, de la même façon que la propriété de temps constant entre slots est affirmée de façon comportementale plutôt que comme une chaîne d’octets.

Conformité et vecteurs de test

Les vecteurs de test normatifs sont le contrat d’interopérabilité. Une implémentation est conforme si et seulement si elle reproduit chaque chaîne d’octets ancrée de la suite de conformité à partir des mêmes entrées — et émet le code d’erreur typé correct pour chaque fixture négative. Il n’y a ni crédit partiel ni appel possible : si une comparaison échoue, c’est l’implémentation qui a tort, jamais le vecteur.

Les vecteurs résident dans la suite de conformité du standard, organisés par classe de primitive : fixtures d’enregistrement, enveloppe/désenveloppe de PoE scellée, signatures COSE_Sign1, HKDF, dérivation de seed, Argon2id et CBOR canonique. Chacun ancre des entrées en hexadécimal minuscule et les sorties attendues. Pour les utiliser : alimentez les entrées dans votre implémentation, comparez chaque sortie nommée octet pour octet, et corrigez votre code sur toute discordance.

Trois obligations que toute implémentation doit remplir

Reproduire les vecteurs positifs. Pour chaque fixture d’enregistrement, les deux moitiés de encode(record) == expected_cbor ET l’aller-retour encode(decode(expected_cbor)) == expected_cbor DOIVENT tenir. L’aller-retour se généralise au-delà des fixtures : pour toute entrée bien formée arbitraire, encode(decode(x)) == x. Un décodeur qui perd ou réordonne de l’information, ou un encodeur qui n’est pas canonique, brise cela et échoue à la conformité.

Émettre les bons codes de rejet. Les fixtures négatives apparient un enregistrement délibérément malformé avec le code d’erreur typé exact qu’un validateur structurel DOIT lever. Reproduire les octets des enregistrements valides est la moitié du contrat ; rejeter les invalides avec le code correct est l’autre moitié. Un validateur qui rejette un mauvais enregistrement pour la mauvaise raison — ou qui l’accepte — n’est pas conforme. Les fixtures négatives sont l’unique source de vérité pour la parité de rejet entre langages : la même entrée malformée DOIT lever le même code dans toute implémentation. Le catalogue complet des codes et de leur signification se trouve sur Vérification.

Correspondre aux registres. Les identifiants d’algorithme sont des chaînes nommées tirées des registres sur Registres d’algorithmes. Un identifiant non reconnu DOIT faire remonter le code précis d’algorithme non pris en charge, jamais une acceptation silencieuse ni un plantage.

Corrigez l’implémentation, jamais le vecteur

Les vecteurs sont ancrés aux RFC en amont et aux constructions déterministes de ce standard. Lorsqu’une comparaison échoue, le bug est dans l’implémentation testée. Modifier un vecteur pour faire passer une suite convertit un véritable échec d’interopérabilité en un échec latent qui ne remonte que lorsqu’un enregistrement franchit les implémentations sur la chaîne — le pire moment possible pour le découvrir.

Exécutez la parité à chaque modification

Une implémentation qui livre plus d’un langage — ou qui veut prouver l’interopérabilité avec une autre — DEVRAIT exécuter un unique job d’intégration continue qui construit chaque paquet, exécute la suite de test de chaque langage contre les fixtures partagées, impose l’analyse du graphe de dépendances, et vérifie que le jeu de fixtures est identique des deux côtés. Une fixture ajoutée d’un côté mais pas de l’autre fait échouer la barrière : les deux implémentations ont silencieusement divergé, et la build le détecte avant qu’un enregistrement réel ne le fasse. Les fixtures sont la source canonique ; chaque langage en conserve un miroir identique à l’octet, et la barrière affirme que le miroir est complet et exact.

Conventions de nommage et de sérialisation

Quelques conventions maintiennent une implémentation lisible et le format de sérialisation stable :

  • Les noms de champ sur le fil sont en snake_caseleaf_count, cose_sign1, slots_mac. Cela vaut entre langages : même là où un langage emploie idiomatiquement camelCase pour son API en mémoire, l’enregistrement encodé utilise des clés en snake_case, parce que les clés font partie des octets canoniques que couvre une signature.
  • Les identifiants sont des chaînes de registre, non des énumérations gravées dans le code. Les hachages, AEAD, KEM, KDF et signatures référencent tous des identifiants nommés ; ajouter un algorithme (un KEM post-quantique, par exemple) est une entrée de registre additive, jamais une rupture du format de sérialisation.
  • Les noms de méthode se reflètent sémantiquement entre langages. Une fonction dans un langage a une contrepartie de même nom dans un autre (encode_canonical_cborencodeCanonicalCbor), de sorte qu’un lecteur à l’aise dans l’un ou l’autre peut faire correspondre une surface à l’autre et raisonner sur la parité par inspection.
  • Amorcez d’abord les couches cryptographiques. Mettez en place le cœur cryptographique et la bibliothèque du format de sérialisation contre les vecteurs et amenez la barrière de parité au vert avant d’écrire une ligne de code applicatif. Le vérificateur autonome est la plus petite surface proche de l’application et la prochaine chose à construire ; tout le reste repose sur une couche cryptographique que vous avez déjà prouvée correcte.

Pages connexes

  • L’enregistrement — le format de sérialisation qu’implémentent le validateur et l’encodeur.
  • PoE scellée — la référence de construction derrière les recettes de build d’ici.
  • Registres d’algorithmes — les identifiants nommés qu’une implémentation résout.
  • Vérification — le pipeline de validation, le vérificateur autonome et le catalogue des codes d’erreur.