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: chunking and reassembly

Because no metadata byte- or text-string may exceed 64 bytes, any logical value whose content can be longer is carried as an array of ≤ 64-byte strings and reassembled by concatenation before use. There are two parallel shapes:

  • Byte-string chunks — a non-empty CBOR array of byte strings (bstr .size (1..64)), used for signature blobs and for the record body itself.
  • Text-string chunks — a non-empty CBOR array of text strings (tstr .size (1..64)), used for storage URIs.

The whole record body is serialised once to canonical CBOR, then that single byte string is split into a byte-string chunk array and stored under label 309. A verifier MUST byte-concatenate the array elements in order to reassemble the record body before structural validation. Chunk boundaries carry no semantic meaning: a producer may split a value mid-codepoint, and consumers validate only the reassembled value.

The chunk-array wrapper is always required, even for values that fit in a single 64-byte chunk: such a value is encoded as a length-1 array, never as a bare string. Producers SHOULD NOT over-chunk — splitting an 8-byte URI into three fragments wastes bytes with no benefit — but MUST split anything longer than 64 bytes.

A structural validator enforces the per-chunk range [1, 64] bytes (a zero-length chunk is rejected on the same footing as an over-long one), the non-empty-array invariant, and the major-type discriminator (byte-string arrays carry major type 2; URI arrays carry major type 3). For URI chunks, a multi-byte UTF-8 codepoint MUST NOT be split across a chunk boundary, so the reconstructed concatenation is always valid UTF-8.

The schema describes the reassembled body

Everything below — the record map, the CDDL, the field rules — describes the record body after chunk reassembly. The chunk-array transport wrapper 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 itself a chunked text array that reconstructs to a single URI. Reconstructed URIs 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 version 9. 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 chunk-array transport 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) are enforced by a typed validation pass over the decoded structure, not by the CDDL itself.

CDDL
; Every field a Label 309 record carries — base or extension — MUST be a
; `metadatum`: the record sits inside transaction metadata under label 309,
; and the ledger admits only the recursive metadatum type (no floats, no
; tags, bstr/tstr <= 64 bytes).
metadatum =
    { * metadatum => metadatum }
  / [ * metadatum ]
  / int
  / bstr .size (0..64)
  / tstr .size (0..64)

; 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 an empty record by the typed pass, not at the CDDL layer.
poe-record = {
  poe-common,
  ? "items": [ 1* item-entry ],
  ? "crit":  [ 1* tstr ],
  * extension-key => metadatum
}

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-chunk-array ],
  ? "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": uint,
  ? "uris":     [ 1* uri-chunk-array ],
}

; `enc` is modelled as a permissive superset admitting both key-delivery
; paths; the typed pass enforces the slots/passphrase exclusivity and the
; per-KEM slot-shape rules.
enc = {
  enc-common,
  ? "kem":        kem-alg,
  ? "slots":      [ 1* slot ],
  ? "slots_mac":  bytes32,
  ? "passphrase": passphrase-block,
}

enc-common = (
  "scheme": 1,
  "aead":   aead-alg,
  "nonce":  bstr,
)

slot = classical-slot / hybrid-slot

classical-slot = {
  "epk":  bytes32,
  "wrap": bytes48,
}

hybrid-slot = {
  "kem_ct": [ 1* bstr .size (1..64) ],
  "wrap":   bytes48,
}

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

; A signature entry is a closed map. `cose_sign1` is REQUIRED and carries the
; chunked CBOR-encoded COSE_Sign1; `cose_key` is OPTIONAL and carries the
; chunked CBOR-encoded COSE_Key for the wallet-signing path.
sig-entry = {
  "cose_sign1":  bytes-chunk-array,
  ? "cose_key":  bytes-chunk-array,
}

bytes-chunk-array = [ 1* bytes-chunk ]
bytes-chunk = bstr .size (1..64)

; A uri-chunk-array reconstructs to one absolute URI string; producers MUST
; NOT split a multi-byte UTF-8 codepoint across chunks.
uri-chunk-array = [ 1* uri-chunk ]
uri-chunk = tstr .size (1..64)

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

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