The record

The Label 309 wire format — where the record lives under metadata label 309, its map shape, canonical-CBOR rules, transport chunking, and the CDDL schema.

A Label 309 record is a single CBOR map carried in Cardano transaction metadata under label 309. The map commits one or more content hashes to the chain; the block time of the transaction is the witness that those bytes existed no later than that moment. Everything else the record can carry — storage URIs, an encryption envelope, authorship signatures, a supersedence pointer — is optional metadata about that core claim.

This page defines the on-wire shape: where the record sits, how it is encoded, how oversized values are transported, and the closed schema a structural validator checks against. The cryptographic constructions referenced here (hash algorithms, the sealed envelope, signatures) have their own pages; this one is the wire format.

Where the record lives

A PoE record MUST be placed under transaction metadata label 309, which is reserved as "Proof of Existence record" in the CIP-10 metadata-label registry. Transaction metadata is a map from integer label to value, so a transaction MUST NOT carry more than one PoE record — exactly one record per transaction.

A transaction MAY carry additional metadata under other labels (for example a CIP-20 674 message). A verifier processing PoE MUST ignore every label other than 309.

On the Conway-era ledger, transaction metadata is the metadata field inside the transaction's auxiliary_data. The values admitted under any label are constrained to the ledger's recursive metadatum type — integers, byte strings, text strings, arrays, and maps, with both byte strings and text strings capped at 64 bytes each:

CDDL
metadatum =
    { * metadatum => metadatum }
  / [ * metadatum ]
  / int
  / bstr .size (0..64)
  / tstr .size (0..64)

A transaction carrying any single bstr or tstr longer than 64 bytes is rejected by Cardano nodes at submission, before any verifier sees it. That cap is the reason Label 309 defines a transport-chunking discipline (below); every field the record carries — base or extension — must reduce to a metadatum.

Transport: the whole-body chunk array

A serialised record body routinely exceeds 64 bytes, so it cannot be stored under label 309 as a bare value. The record body is therefore carried as an opaque whole-body chunk array: a single CBOR array of ≤ 64-byte byte strings (bstr .size (1..64)) whose in-order concatenation is the record body. This transport split is the only chunking Label 309 performs — the one genuinely ledger-forced step in the format.

Because the ledger sees only this transport array and never the fields inside the reassembled body, those fields are ordinary CBOR values with no per-field chunk wrappers and no field-level 64-byte cap: a storage URI is a single text string, a COSE_Sign1 is a single byte string, and an X-Wing kem_ct is a single 1120-byte byte string. A field longer than 64 bytes simply rides across the chunk boundaries of the whole-body array like any other stretch of the body.

A producer MUST serialise the record body once to canonical CBOR, split that byte string into chunks of 1 to 64 bytes, and store the resulting definite-length array of definite-length byte strings as the label-309 value. The array form is always required, even for a body of 64 bytes or fewer: such a body is a length-1 array, never a bare map or byte string. Producers SHOULD use the minimal split (every chunk except the last exactly 64 bytes) and SHOULD NOT emit zero-length chunks; over-chunking wastes transaction bytes with no benefit.

A verifier MUST byte-concatenate the array elements in order to reassemble the record body before structural validation, and MUST reject any label-309 value that is not such an array. Chunk boundaries carry no semantic meaning: two transport arrays whose concatenations are byte-identical denote the same record. The carriage-error taxonomy pins the rejection codes — a chunk longer than 64 bytes is CHUNK_TOO_LARGE; an array element that is not a byte string, an indefinite-length array or element, or a non-array label-309 value (bare map, bare byte string, integer) is MALFORMED_CBOR. A zero-length chunk contributes no bytes and is tolerated, never rejected on its own.

The schema describes the reassembled body

Everything below — the record map, the CDDL, the field rules — describes the record body after chunk reassembly. The whole-body chunk array is not part of the schema; it is undone first, then the body is validated.

The record map

The reassembled record body is a CBOR map. Integer-valued fields are CBOR major type 0/1; text fields are major type 3 and MUST be valid UTF-8; byte fields are major type 2; arrays are major type 4; nested maps are major type 5. An optional field that is present MUST NOT carry an empty value.

The top-level shape is:

KeyTypeStatusMeaning
vuintREQUIREDSchema version; this document defines v = 1.
itemsarray of item mapsOPTIONALPer-content commitments — see Content and hashing.
merklearray of commitmentsOPTIONALList commitments binding off-chain leaf lists to one root.
supersedesbytes (32)OPTIONALTransaction hash of a prior record this one replaces.
sigsarray of signature mapsOPTIONALRecord-level authorship signatures — see Signatures.
critarray of text stringsOPTIONALExtension keys that are mandatory to understand.

A conformant record MUST commit to at least one of items (with ≥ 1 entry) or merkle (with ≥ 1 entry). A record carrying neither — or carrying one of them as an empty array — is rejected as an empty record. Apart from this rule, items and merkle are orthogonal: a record may carry either alone or both together.

Label 309 imposes no numeric cap on entry counts. The only ceiling is the live Cardano maximum transaction size, and producers pay per-byte fees that naturally bound record size. A validator MUST NOT reject a record purely because it carries many entries, as long as it fits under the ledger's size limit.

The version field

v is a CBOR unsigned integer, not a semantic-version string. This document defines exactly v = 1. A validator MUST reject a record whose v is outside its supported set with a typed error; it MUST NOT panic, abort, or silently treat the record as a different metadata schema. The v integer bumps only when a change would cause a v1 parser to misinterpret the record — additive, namespaced extensions do not bump it.

Items

Each entry in items is a CBOR map with one required field and two optional ones:

  • hashes — REQUIRED, a non-empty map from hash-algorithm identifier to raw 32-byte digest. At least one entry; duplicate algorithms are impossible because CBOR map keys are unique. See Content and hashing.
  • uris — OPTIONAL, a plural list of discovery URIs (rules below).
  • enc — OPTIONAL, the encryption envelope for a sealed item. See Sealed PoE.

There is no per-item signature slot. Authorship is expressed only at the record level, by a sigs[] entry that covers every item uniformly.

Merkle commitments

Each entry in merkle binds the record to an ordered list of 32-byte leaves via a canonical hash-tree construction, so one 32-byte root on the chain can stand in for an arbitrarily large off-chain leaf list. A commitment is a closed map:

FieldTypeStatusMeaning
algtstrREQUIREDRegistered list-commitment algorithm identifier.
rootbytes (32)REQUIREDCanonical root over the producer's ordered leaf list.
leaf_countuintREQUIREDNumber of committed leaves; binds the root to the list size.
urisURI listOPTIONALContent-addressed URI(s) for the off-chain leaves-list file.

A Merkle root commits to a leaf-list structure, whereas a hashes entry commits to plaintext bytes; the two are verified differently (inclusion proof versus plaintext recomputation), which is why list commitments live at the top level rather than inside an item. The list-commitment registry is disjoint from the content-hash registry — see Algorithm registries.

Supersedes

supersedes is an optional 32-byte Cardano transaction hash pointing at one earlier Label 309 record. It is a service-independent, append-only link: a later record can point at a prior record with no off-chain database or vendor record id.

Supersedence does not remove, revoke, or invalidate the prior record — the chain is append-only, and verifiers MUST continue to treat the earlier record as existent and independently verifiable. The pointer carries no reason or free-text field; any human meaning (correction, replacement, withdrawal) belongs in the new content, not in label 309. A verifier resolving the pointer MUST look it up on the same Cardano network as the containing transaction; the field carries no network discriminator because a transaction hash is unique only within its own network.

Signatures

sigs is an optional array of record-level signature entries. Each entry carries a detached COSE_Sign1 structure over the record body — that is, the full record map with sigs removed — and optionally the signer's public key for the wallet-signing path. A single signature attests to the entire body: every item, every URI, every envelope, the supersedence pointer if present, and any extension keys. Signatures are always optional, and an unrecognised signature algorithm never invalidates the content claim. The signed payload, the domain-separation prefix, signer-key resolution, and strict verification rules are specified on Signatures.

URI rules

When present, uris is a non-empty list; each entry is a single CBOR text string carrying exactly one URI. There is no per-URI length cap and no wrapping shape: the whole-body transport already satisfies the ledger's 64-byte string cap, so a long ipfs://<CIDv1>/<path> URI is one text string like any other. Each URI MUST be absolute, MUST include a scheme and hierarchical part, and MUST NOT contain a fragment identifier — a PoE is a claim about content bytes, not about a sub-component of a document.

The v1 scheme set is closed and content-addressed:

SchemeNotes
ar://Arweave transaction id (43-character base64url). Form ar://<txid>.
ipfs://IPFS CID, CIDv1 preferred. Form ipfs://<cid> or ipfs://<cid>/<path>.

Producers MUST NOT emit any other scheme — https://, http://, file://, data:, and the rest are all rejected. The restriction is deliberate, not temporary: a content-addressed URI binds the fetched bytes to the URI itself through the storage layer's integrity model (an IPFS CID is a multihash of the content; an Arweave transaction id commits to the data under Arweave consensus), so a verifier can confirm "the bytes I fetched are the bytes the producer committed to" without trusting DNS, TLS, gateways, or certificate authorities. An out-of-set scheme makes a record structurally invalid; it never validates as valid.

uris is optional throughout. A hash-only record with uris omitted is a complete claim — content existence is asserted without committing to a retrieval channel. The exact CID profile (accepted multibase prefixes, codecs, and multihashes) is part of the verification rules; see Verification.

Canonical CBOR

Every Label 309 record MUST be encoded as canonical CBOR per RFC 8949 §4.2.1 (Core Deterministic Encoding). Concretely:

  1. Preferred (shortest-form) serialisation for every integer.
  2. Definite-length encoding for all byte strings, text strings, arrays, and maps.
  3. No semantic tags (this document requires none — a bignum tag 2/3 MUST NOT appear).
  4. Map keys sorted in bytewise lexicographic order of their CBOR encoding.
  5. UTF-8 text strings with no byte-order mark.
  6. No duplicate keys in any map.
  7. No floating-point or non-trivial simple values — a record carries only integers, byte strings, text strings, arrays, maps, and (where a schema admits it) true/false/null. Major-type-7 floats (including an integral 1.0), negative zero, and undefined MUST be rejected, not coerced.

Determinism is what makes the format interoperable: two producers expressing the same logical record emit byte-identical bytes, so a signature computed over the body by one implementation verifies under another. A validator MUST reject a non-canonical encoding. Explorers and wallets may surface metadata through a JSON projection, but a conformant verifier MUST validate the original transaction CBOR, never a lossy JSON re-encoding of it.

Forward compatibility

Label 309 v1 reserves a closed set of base keys: v, items, merkle, supersedes, sigs, crit. A record MAY additionally carry extension keys whose names match one of two reserved namespaces:

  • ^x-.+ — the vendor / experimental namespace.
  • ^[a-z]+-.+ — the companion-specification namespace, where the prefix names the registering specification.

A validator MUST decode and preserve extension keys, MUST NOT reject a record solely because they are present, and MUST surface them informationally without claiming to have verified their contents. Extension keys are part of the signed body, so a record-level signature covers them — a relay cannot inject an extension key after the signature was produced. Any unknown top-level key that matches neither pattern (a typo such as supersedess, or a case variant such as Sigs) is rejected as an unknown field. The pattern-based tolerance preserves typo detection on the base set while keeping a stable pool open for future additions.

A producer that requires a verifier to understand a non-base field MUST list that field's name in the top-level crit array. A v1 verifier that encounters a crit entry it does not implement MUST NOT report the record as valid. Each crit entry MUST match the extension-key pattern (base keys are forbidden in crit), MUST name a field actually present in the record, and MUST be unique — so a critical mark is always traceable to a concrete field whose semantics the verifier is obliged to understand. These rules follow the must-understand / must-ignore precedents in RFC 9052 §3.1 (COSE crit) and RFC 7515 §4.1.11 (JWS crit).

Byte budget

The only hard ceiling on record size is the live Cardano maxTxSize protocol parameter — 16 384 bytes at protocol major version 10 on mainnet, subject to ledger parameter updates. Label 309 imposes no schema-level cap below that. Records exceeding the limit are rejected by Cardano nodes at submission, so no verifier ever sees one; a validator MUST NOT invent a Label 309-specific ceiling beneath maxTxSize.

In practice a transaction's non-metadata structure (inputs, outputs, witnesses, fee and validity fields) consumes roughly 245 bytes, leaving on the order of 16 KB for the label-309 record. Producers SHOULD target a few hundred bytes below the limit to absorb fee variance and SHOULD compute the candidate record's size before submission, failing fast if it would not fit. The realistic shapes that fit are generous: well over a hundred single-hash items, dozens of record-level signatures, or many classical recipient slots all sit comfortably within one transaction — and a single Merkle root commits to an unbounded off-chain leaf list at a fixed on-chain cost of 32 bytes.

CDDL schema

The following CDDL is the structural schema for the reassembled record body — the canonical-CBOR bytes obtained after concatenating the ≤ 64-byte chunk array stored under label 309. The reassembled body is plain deterministic CBOR: it is not itself a ledger metadatum, and its fields are not subject to the 64-byte string cap, which the whole-body transport wrapper alone satisfies. The wrapper is not modelled here.

The block describes the permissive superset of well-formed shapes; cross-field invariants (the items-or-merkle rule, the slotspassphrase exclusivity of the encryption envelope, registry membership of algorithm identifiers, the per-KEM slot-shape rules) are enforced by a typed validation pass over the decoded structure, not by the CDDL itself.

CDDL
; An extension value is any CBOR value the canonical (deterministic) encoding
; profile admits. Floats and semantic tags are excluded by that profile (they
; are rejected as MALFORMED_CBOR on decode), so the exclusion is not repeated
; here; the reassembled body carries no field-level 64-byte cap.
extension-value =
    { * extension-value => extension-value }
  / [ * extension-value ]
  / int
  / bstr
  / tstr
  / bool
  / null

; A conformant record MUST carry at least one of `items` (>= 1 entry) or
; `merkle` (>= 1 entry); a record with both absent (or both empty) is rejected
; as SCHEMA_EMPTY_RECORD by the typed pass, not at the CDDL layer.
poe-record = {
  poe-common,
  ? "items": [ 1* item-entry ],
  ? "crit":  [ 1* tstr ],
  * extension-key => extension-value
}

poe-common = (
  "v": 1,
  ? "merkle": [ 1* merkle-commit ],
  ? "supersedes": bytes32,
  ? "sigs": [ 1* sig-entry ],
)

extension-key = tstr .regexp "^x-.+"
              / tstr .regexp "^[a-z]+-.+"

item-entry = {
  "hashes": hash-map,
  ? "uris": [ 1* uri ],
  ? "enc": enc,
}

; A non-empty CBOR map keyed by a content-hash algorithm identifier with the
; 32-byte digest as value. Map-key uniqueness makes duplicate algorithms
; structurally impossible.
hash-map = { + content-hash-alg => bytes32 }

; A list commitment binds the record to an ordered leaf list. `leaf_count`
; binds the on-chain commitment to the off-chain list size.
merkle-commit = {
  "alg":        merkle-commit-alg,
  "root":       bytes32,
  "leaf_count": uint32,
  ? "uris":     [ 1* uri ],
}

; `enc` is a choice between the scheme-1 envelope shape and a bounded opaque
; envelope (the degrade-to-opaque rule for an unsupported scheme/kem/aead). The
; typed pass enforces the slots/passphrase exclusivity and the per-KEM
; slot-shape rules over a supported envelope.
enc = enc-scheme-1 / enc-opaque

; `scheme: 1` is not a version counter for the `enc` map alone: it names the
; ENTIRE sealed cryptographic suite — the canonicalEncode rules, the slot
; schema, the HKDF and HMAC hashes, the wrap AEAD, the segmented-STREAM content
; format, the transcript schemas, the in-ciphertext passphrase commitment, the
; pinned X-Wing revision, every domain-separation label, and the Argon2id and
; passphrase-normalization profiles. Changing any one of them requires a new
; `scheme` value; see Sealed PoE for the construction it pins.
enc-scheme-1 = {
  "scheme": 1,
  "aead":   aead-alg,
  "nonce":  bstr,
  ? "kem":        kem-alg,
  ? "slots":      [ 1* slot ],
  ? "slots_mac":  bytes32,
  ? "passphrase": passphrase-block,
}

; The opaque reading of an envelope under an unsupported identifier: `scheme`
; is the only structurally required key, and every other entry is any key/value
; pair the canonical profile admits, subject to the generic decode bounds.
enc-opaque = {
  "scheme": uint,
  * tstr => extension-value
}

slot = classical-slot / hybrid-slot

; enc.kem = "x25519": the per-slot X25519 ephemeral public key + wrapped CEK.
classical-slot = {
  "epk":  bytes32,
  "wrap": bytes48,
}

; enc.kem = "mlkem768x25519": the 1120-byte X-Wing ciphertext plus the wrapped
; CEK. There is NO `epk` — the X25519 ephemeral is the trailing 32 bytes of the
; X-Wing ciphertext inside `kem_ct`.
hybrid-slot = {
  "kem_ct": bstr .size 1120,
  "wrap":   bytes48,
}

passphrase-block = {
  "alg":    kdf-alg,
  "salt":   bstr .size (16..64),
  "params": { "m": uint32, "t": uint32, "p": uint32 },
}

; A signature entry is a closed map. `cose_sign1` is REQUIRED and carries the
; CBOR-encoded COSE_Sign1 as a single byte string; `cose_key` is OPTIONAL and
; carries the CBOR-encoded COSE_Key for the wallet-signing path as a single
; byte string.
sig-entry = {
  "cose_sign1":  bstr,
  ? "cose_key":  bstr,
}

; A uri is one absolute URI in a single text string. The URI shape rules
; (absolute, no fragment, closed scheme set {ar://, ipfs://}) are enforced in
; the typed pass; the rule carries no length cap.
uri = tstr

bytes32 = bstr .size 32
bytes48 = bstr .size 48

; uint32 is the pinned range of every numeric field: an unsigned integer
; representable in 4 bytes (0 .. 2^32-1), handled as an exact integer.
uint32 = uint .size 4

; Algorithm-identifier strings are open `tstr`: the registries are
; authoritative for accepted values, and the typed pass emits the precise
; unsupported-algorithm code for any unrecognised identifier.
content-hash-alg   = tstr  ; e.g. "sha2-256", "blake2b-256"
merkle-commit-alg  = tstr  ; e.g. "rfc9162-sha256"
aead-alg           = tstr
kem-alg            = tstr
kdf-alg            = tstr
  • Content and hashing — the hashes map, what a digest commits to, and exact-bytes semantics.
  • Algorithm registries — the named identifiers for hashes, list commitments, AEADs, KEMs, and KDFs.
  • Signatures — the record-level sigs construction and verification.
  • Sealed PoE — the enc envelope and recipient key slots.
  • Verification — the validation pipeline, the CID profile, and the error catalogue.