Algorithm registries

The named-identifier registries for hashes, AEADs, KEMs, KDFs, and signatures — and the agility rule that makes post-quantum migration additive rather than breaking.

Every cryptographic choice in Label 309 is named by a string identifier drawn from an extensible registry — sha2-256, chacha20-poly1305-stream64k, EdDSA, and so on. A record never carries a raw algorithm number or an implicit assumption; it states which primitive it used, and a verifier looks that identifier up. This is the mechanism behind the algorithm-agile invariant: the registries can grow over time, and a verifier that meets an identifier it does not implement rejects the record with a stable, typed error — it never crashes, and it never silently accepts something it cannot check.

That single rule is what makes the migration to post-quantum algorithms additive. A new identifier is a new row in a table, not a new version of the wire format. Older records keep verifying exactly as before, and older verifiers fail closed against records they were never built to understand.

Two standing rules

Across every registry, two constraints are absolute. Implementations MUST NOT invent novel cryptography — every primitive traces to a named public standard. And all encryption MUST be authenticated: only AEAD constructions are permitted, never an unauthenticated cipher with a bolt-on (or absent) integrity check.

Hash

The content hash is the primary claim of every record, so the hash registry is the most load-bearing. Both registered functions produce a 32-byte digest, and both are mandatory for a conformant implementation to support.

IdentifierAlgorithmDigest
sha2-256SHA-256 (FIPS 180-4)32 B
blake2b-256BLAKE2b-256 (RFC 7693)32 B

A producer MAY hash the same content under both functions for defense in depth; a single hash is sufficient for a valid record.

Merkle commitment

For committing to an ordered list of leaves under a single on-chain root, Label 309 registers one Merkle-commitment identifier. It is the IANA-registered string for the SHA-256 binary Merkle tree, sharing the leaf-prefix (0x00) and internal-node-prefix (0x01) domain separation that prevents leaf/node collisions.

IdentifierAlgorithmRoot
rfc9162-sha256RFC 9162 binary Merkle tree, SHA-25632 B

A single-leaf tree commits to SHA-256(0x00 ‖ leaf), not to the bare leaf — so a one-file proof MUST use a plain hash identifier, never a 1-leaf tree.

AEAD

The AEAD registry governs which content format protects a sealed payload on the wire — the enc.aead field. Exactly one identifier is registered under enc.scheme: 1, and it is a segmented format rather than a single-shot cipher.

IdentifierAlgorithmKey / Nonce / Per-chunk nonce / TagStatus
chacha20-poly1305-stream64kChaCha20-Poly1305, 64 KiB segmented STREAM32 B / 24 B / 12 B / 16 B per chunkMandatory — the wire format
aes-256-gcmAES-256-GCMReserved (future profile)

chacha20-poly1305-stream64k is ChaCha20-Poly1305 (RFC 8439) in the 64 KiB segmented STREAM layout of the age v1 specification: the plaintext is split into 65536-byte chunks, and each chunk is sealed under the content key with a 12-byte per-chunk nonce uint88_be(counter) ‖ final_flag (counter from 0, final_flag 0x01 on the last chunk) and an empty per-chunk AAD, producing a 16-byte tag per chunk. The 24-byte enc.nonce is not a chunk nonce: it is the envelope-unique salt of the content-key HKDF, which is what keeps the counter nonces safe — the content key is single-use, so no two streams ever share a (key, nonce) pair and stateless producers never coordinate nonces across envelopes. The segmented layout lets a verifier authenticate and release a large payload incrementally with bounded memory, and the final flag makes truncation detectable. The on-wire spelling is exactly chacha20-poly1305-stream64k; alternative spellings MUST NOT be produced. The full construction is on Sealed PoE.

chacha20-poly1305-stream64k is the only identifier that may appear as the on-wire content format. A separate construction, chacha20-poly1305 (RFC 8439, 32-byte key / 12-byte nonce / 16-byte tag), is used internally to wrap a per-recipient key inside the sealed construction: a 12-byte all-zero nonce, AAD set to the chosen KEM's info label, producing a 48-byte wrap (32-byte wrapped key + 16-byte tag). It is a building block, not a wire identifier, and a record that names it in enc.aead MUST be rejected. aes-256-gcm is named but inactive; it is reserved for a future encryption profile (enc.scheme: 2) and a v1 verifier rejects any record that selects it.

KEM

The KEM registry covers the key-encapsulation mechanisms used to address a sealed payload to specific recipients. Label 309 registers a classical curve KEM and a post-quantum hybrid that ships active from the first release.

IdentifierAlgorithmPublic key / SecretCiphertext / Shared secret
x25519X25519 ECDH (RFC 7748)32 B / 32 B32 B / 32 B
mlkem768x25519X-Wing hybrid (ML-KEM-768 + X25519)1216 B / 32 B1120 B / 32 B

mlkem768x25519 is the X-Wing construction from draft-connolly-cfrg-xwing-kem-10: it pairs ML-KEM-768 (FIPS 203) with X25519 (RFC 7748) so that an attacker must break both to recover the shared secret. The public key is the ML-KEM-768 encapsulation key concatenated with the X25519 public key (1184 B ‖ 32 B = 1216 B); the secret is a 32-byte seed from which the full key is derived. Each recipient's ciphertext is the ML-KEM-768 ciphertext concatenated with an ephemeral X25519 public key (1088 B ‖ 32 B = 1120 B), and the two shared secrets are combined by the X-Wing SHA3-256 combiner (FIPS 202) into the final 32-byte secret. Label 309 consumes X-Wing as a black-box KEM — encapsulate, decapsulate, the 32-byte shared secret — and relies on no property of the combiner's internal hashing. The identifier is written without internal hyphens to match the established X-Wing spelling.

The hybrid is selected per-record by the encryption header, independently of the content AEAD. Because it is already registered, post-quantum confidentiality is a matter of choosing the identifier — not waiting for a new wire-format version.

A single record names exactly one enc.kem, and every slot uses that KEM's shape; a slot of the wrong shape is ENC_SLOT_INVALID_SHAPE, a wrong-length epk (≠ 32 B) or kem_ct (≠ 1120 B) is KEM_EPK_LENGTH_MISMATCH / KEM_CT_LENGTH_MISMATCH, and an unregistered enc.kem is UNSUPPORTED_KEM_ALG. The encapsulation material must also be distinct within one slots[]: all epk values (for x25519) or all kem_ct values (for mlkem768x25519) MUST differ. A within-record duplicate is rejected with ENC_SLOTS_DUPLICATE_KEM_MATERIAL before any KEM or AEAD primitive runs, because a repeated epk or kem_ct breaks the per-slot key uniqueness the zero-nonce wrap depends on. A verifier also bounds parser resource use before any primitive: an envelope whose slots[] exceeds the reference bound of 1024 slots is ENC_SLOTS_TOO_MANY, and a decoded enc envelope exceeding 65 536 bytes is ENC_ENVELOPE_TOO_LARGE. Both bounds sit far above the ~16 KiB Cardano metadata ceiling that caps any honest record; they are verifier-enforced, deployment-pinned constants — not wire fields — and deployments MAY tighten them.

KDF

The KDF registry names the key-derivation functions. hkdf-sha256 derives keys inside the sealed construction; argon2id stretches a human passphrase against brute force and carries a mandatory parameter floor.

IdentifierAlgorithmParameters
hkdf-sha256HKDF-SHA-256 (RFC 5869)salt (optional), info (optional), output length
argon2idArgon2id (RFC 9106)memory ≥ 65536 KiB, iterations ≥ 3, parallelism ≥ 1

The Argon2id floor is normative: a passphrase-protected payload MUST use at least 64 MiB of memory, at least three iterations, and at least one lane. A producer MAY choose stronger parameters, and they travel with the record so a verifier can reproduce the derivation. Where the platform supports it, producers SHOULD set the parallelism p = 4 — the second recommended profile of RFC 9106 §4 — while a verifier MAY accept any p ≥ 1, subject to deployment ceilings. Those ceilings are an implementation SHOULD, not a MAY: a verifier SHOULD enforce upper bounds against a verifier-side denial-of-service from absurd parameters, reporting ENC_PASSPHRASE_PARAMS_EXCEED_POLICY. The ceiling is hardware-dependent and non-normative, and MUST NOT be conflated with the floor code ENC_PASSPHRASE_ARGON2_PARAMS_TOO_LOW.

Only argon2id is wire-selectable: it is the sole identifier a record may name in enc.passphrase.alg. hkdf-sha256 is an internal building block — it is the fixed extract-and-expand step behind the seed-to-key derivation, the per-slot KEK derivation, the slot-set MAC key, the passphrase-commitment MAC key, and the content-key derivation — and it carries no wire identifier. A record that names hkdf-sha256 in enc.passphrase.alg MUST be rejected: HKDF is built for high-entropy inputs, not for stretching a low-entropy passphrase.

Internal labels are constants, never on the wire

The sealed construction draws its domain separation from a fixed set of label literals — HKDF info tags and SHA-256 prefixes (KEK-salt prefixes, transcript prefixes, and the item-hashes prefix). Each is a constant of enc.scheme: 1, exact ASCII with no terminator or length prefix; none is ever serialised, and none is selectable through any registry. There are eleven:

LabelRole
cardano-poe-kek-v1HKDF info for the per-slot KEK on the x25519 path
cardano-poe-kek-mlkem768x25519-v1HKDF info for the per-slot KEK on the mlkem768x25519 path
cardano-poe-x25519-kek-salt-v1SHA-256 prefix for the x25519 KEK HKDF salt
cardano-poe-xwing-kek-salt-v1SHA-256 prefix for the mlkem768x25519 KEK HKDF salt
cardano-poe-item-hashes-v1SHA-256 prefix for the item-hashes digest hashes_hash
cardano-poe-slots-transcript-v1SHA-256 prefix for the slots-transcript hash slots_hash
cardano-poe-slots-mac-v1HKDF info for the slot-set MAC key
cardano-poe-passphrase-transcript-v1SHA-256 prefix for the passphrase-transcript hash pw_hash
cardano-poe-passphrase-mac-v1HKDF info for the passphrase commitment MAC key
cardano-poe-payload-v1HKDF info for the slots-path content key
cardano-poe-payload-passphrase-v1HKDF info for the passphrase-path content key

Both KEK salts share one labelled-hash shape — SHA-256(label ‖ enc.nonce ‖ <slot KEM material> ‖ pub_R) — under their own per-KEM label. These labels are distinct from the seed-derivation info strings on Keys and from the record-signing domain prefix on Signatures: the set is collision-free and prefix-free, so no per-record sealed label equals — or is a byte-prefix of — a long-term-key-derivation label, and identity-key derivation and per-record key wrapping never collide. A verifier MUST use each literal byte-for-byte; a single divergent byte yields a slots_mac, a commitment, or an AEAD tag the honest producer cannot reproduce. The byte-level construction that consumes each label is on Sealed PoE.

Signature

Label 309 registers one signature algorithm. Authorship signatures are always optional, but when present they are carried as COSE_Sign1 (RFC 9052) using Ed25519.

IdentifierCOSE algAlgorithmWrapper
EdDSA-8Ed25519 (RFC 8032)COSE_Sign1 (RFC 9052)

Verification is strict per RFC 8032 §5.1.7: implementations MUST reject non-canonical signature encodings and small-order points (no cofactor-clearing extension). This matches the conservative acceptance criteria used across Cardano wallets, so a signature that verifies under one conformant implementation verifies under all of them.

Signature support is independent of the content claim. A verifier that does not implement a record's signature algorithm marks that signature slot as unsupported and leaves the timestamp and content claims fully valid — an unknown signature algorithm never invalidates the record itself.

Reserved identifiers

Several identifiers are named but not yet active. They mark the agreed migration path so that future profiles use stable, pre-committed names rather than ad-hoc strings. A conformant producer MUST NOT emit them, and a conformant verifier MUST reject any record that uses one, with the matching typed error.

IdentifierAlgorithmRole
aes-256-gcmAES-256-GCM (NIST SP 800-38D)Content AEAD
ml-kem-768ML-KEM-768 (FIPS 203), standaloneKEM
ml-dsa-65ML-DSA (FIPS 204)Signature
slh-dsa-sha2-128sSLH-DSA (FIPS 205)Signature

ml-kem-768 is the bare post-quantum KEM, distinct from the registered hybrid mlkem768x25519; the hybrid is what Label 309 ships, on the principle that a classical fallback should remain even after the post-quantum half is added.

Algorithm agility and migration

Adding an algorithm is a self-contained, additive operation: cite a public standard for the primitive, add the identifier to the appropriate registry, provide a vetted, well-reviewed implementation of it, and publish a cross-language conformance fixture so independent implementations agree byte-for-byte. The wire-format version does not change, because the schema does not change — only the set of recognized strings grows.

The consequences follow directly from the registry design:

  • Old records stay verifiable. Their identifiers are still in the registry, so every existing record verifies exactly as it did the day it was published.
  • Old verifiers fail closed. A verifier that predates a new identifier rejects records using it with a stable UNSUPPORTED_* error rather than guessing — there is no path to silent acceptance.
  • Post-quantum support is additive. Because mlkem768x25519 is already registered, and because new KEMs and signatures slot into the same mechanism, the post-quantum transition is a registry growth, not a breaking migration.

A wire-format version bump is reserved for genuinely breaking schema changes — a new required field, a removed field, a changed type. Registry growth never qualifies, which is precisely what lets the cryptographic catalogue evolve without ever stranding a published proof.