Verification

The three Label 309 verifier roles, the verdict states, finality depth, and the typed error catalogue — how anyone reaches the same answer from public infrastructure alone.

Label 309 is verified, never asserted. A publisher anchors a content hash on Cardano under label 309; from that point on the claim stands on its own bytes, and anyone holding the transaction reference can check it. This page defines how that check runs: the three verifier roles, what each one does and does not touch, the verdict states they emit, the confirmation depth below which a verdict stays provisional, and the typed error catalogue that makes two independent implementations agree on the same failure for the same input.

The defining property is service independence. A conformant verifier reaches its verdict using only the public chain, a verifier-chosen Cardano explorer or gateway, and — for content and sealed claims — content-addressed storage gateways the verifier also chooses. It never contacts the publisher. The standard names no specific provider; the gateway is an input the operator supplies.

Three roles, each a strict extension

Verification is layered. Each role does everything the role above it does, then adds one capability. A lower role is a complete, useful verifier on its own — it simply proves less.

RoleAddsTouches
Structural validatorschema + domain conformance over the record bytesnothing — a pure function
Public verifierchain resolution, on-chain inclusion, signature checksa Cardano explorer + content gateways
Recipient verifiertrial-decrypt of a sealed payload, plaintext-hash recomputethe verifier's own private key

Structural validator — a pure function over the bytes

The structural validator is a single function from a byte string to a result. It performs no I/O, no cryptographic signature checks, and no decryption. It never sees a network, a transaction, or a key. Given the same input it returns the same output, every time, anywhere — which is what lets it run pre-submission inside a publisher's tooling, inside a third-party indexer, or inside an archival tool confirming long-term well-formedness, all without a server.

Its pipeline is fixed:

1. resource bounds   — a local, non-normative guard on input size; never a
                       Label 309 conformance error.
2. canonical decode  — decode with a canonical-CBOR decoder (RFC 8949 §4.2.1):
                       definite lengths, sorted map keys, no duplicate keys,
                       valid UTF-8. Any malformed or non-canonical input →
                       a single MALFORMED_CBOR.
3. schema parse      — type, length, and the chunk/length bounds; a strict
                       object mode that rejects unknown fields.
4. domain rules      — cross-field constraints the schema cannot express:
                       registry membership, the items-or-merkle rule, COSE
                       structural shape, URI reconstruction, envelope shape.
5. result            — { valid, record } with optional warnings/info, or a
                       sorted list of typed issues.

The validator is profile-agnostic: it parses the full v1 schema regardless of which subset a downstream verifier intends to act on. Errors fail the record; warnings and info entries are surfaced but leave it valid. Crucially, it confirms the shape of a COSE_Sign1 — four-element array, detached (null) payload, a well-formed protected header — but never verifies the signature, and it never rejects a record merely because the signature algorithm is one it does not recognise (that is tagged SIGNATURE_UNSUPPORTED, severity info, and the record stays valid). Verifying the signature is the public verifier's job. See The record for the schema this pass enforces.

Public verifier — chain, inclusion, and signatures

The public verifier layers the chain on top of the structural validator. Given a Cardano transaction reference, it:

  1. Resolves a verifier-chosen explorer. The explorer chain is an input; the verifier tries them in order, fetching the raw on-chain transaction CBOR — never the explorer's metadata-JSON projection. The JSON view collapses CBOR major types into a JSON union and discards map-key order, definite-length framing, and the bytes-vs-text discriminator, so a verifier that re-encoded from it could not reproduce the byte-exact signing input and every signature on a conforming record would fail. A single provider's negative answer is not chain-authoritative — the transaction may be on chain and merely unknown to that provider — so the verifier consults every remaining provider before emitting TX_NOT_FOUND; if every provider is unreachable it emits PROVIDER_UNAVAILABLE.
  2. Binds the fetched bytes to the transaction reference. Before reading anything out of a fetched transaction, the verifier recomputes blake2b-256 over the fetched transaction-body bytes — by ledger definition, the transaction id — and rejects the response on any mismatch with the requested hash. It then recomputes blake2b-256 over the fetched auxiliary-data bytes and rejects on any mismatch with the now-verified body's auxiliary_data_hash. Both digests are computed over the bytes exactly as fetched, never a re-encoding. A response that fails either check carries provably wrong bytes and is discarded; if no provider yields a response that survives the binding, the report carries TX_INTEGRITY_MISMATCH. After this step every byte of the record and the surrounding transaction is cryptographically committed to the caller-supplied hash — no explorer can substitute, amend, or truncate the record without producing a blake2b-256 second preimage.
  3. Unwraps the auxiliary data. The Conway-era ledger admits three auxiliary-data encodings, all valid: a bare untagged metadata map, a two-element [ metadata, scripts ] array, and a tag-259 map with the metadata under key 0. The verifier MUST accept all three and dispatch purely on the top-level CBOR type and tag — a tag-259 value is the keyed-map form, an untagged array is the two-element form, and an untagged map is always the metadata map itself. It MUST NOT inspect a map's keys to guess the shape; any other top-level shape, or any tag other than 259, is MALFORMED_CBOR. If the bound transaction carries no label-309 metadata, the verifier emits METADATA_NOT_FOUND — a record-attributable outcome, because the bound transaction itself proves the absence: no provider could have stripped the metadata without failing the binding.
  4. Reassembles the whole-body chunk array. The label-309 value is the canonical-CBOR record body split into an array of ≤ 64-byte byte strings. The verifier byte-concatenates the elements in order — returning the raw bytes, no re-encode pass — so the canonical-CBOR check can still catch a non-conformant on-chain encoding.
  5. Structurally validates the reassembled body (the role above). A validator rejection short-circuits the report with verdict failed.
  6. Checks confirmation depth (below). A transaction below the threshold halts here with verdict pending.
  7. Verifies every record-level signature under strict Ed25519. It does not decrypt. Signature resolution, the domain-separated payload, and the strict verification rules are specified on Signatures.
  8. Fetches and hash-checks content, holding the record to account only for bytes it can attribute to a URI's own content address (below). It does not decrypt.

Why raw CBOR, not JSON

A signature is computed over the byte-exact canonical CBOR of the record body. A JSON projection of metadata is lossy by construction — it cannot round-trip back to those bytes. Re-encoding from JSON breaks every signature on a conforming record. The raw transaction CBOR is the only authoritative input to any cryptographic check; a JSON view is for human display, after verification has already passed.

Recipient verifier — decrypt and recompute

The recipient verifier is a public verifier that additionally holds a private key. For a sealed item addressed to it, it trial-decrypts the on-chain key slots with its key, recovers the content key on success, decrypts the ciphertext, and then recomputes the plaintext hashes against the on-chain commitment — closing the loop between the encrypted bytes and the content-existence claim. Because every sealed item carries at least one content-hash entry, that recomputation always has something concrete to compare against. The sealed envelope, the key slots, and the unwrap construction are specified on Sealed PoE.

Before the recipient touches any KEM or AEAD primitive, it re-applies the same envelope shape and resource checks the structural validator already ran, rejecting a structurally invalid or oversized envelope first. Two of those pre-primitive guards are deployment-pinned resource bounds, not wire fields: the reference bounds are MAX_SLOTS = 1024 slots and 65536 bytes for the decoded enc envelope, both far above the ~16 KiB Cardano transaction-metadata ceiling that bounds any honest record, so a record exceeding either is malformed and is rejected with ENC_SLOTS_TOO_MANY or ENC_ENVELOPE_TOO_LARGE before a single primitive runs. Deployments MAY tighten them.

One shape check is load-bearing for security: the encapsulation material MUST be distinct within one slots[] — all epk values for x25519, or all kem_ct values for mlkem768x25519. A within-record duplicate is rejected with ENC_SLOTS_DUPLICATE_KEM_MATERIAL before any KEM or AEAD primitive runs, because a repeated epk/kem_ct would break the per-slot key uniqueness the zero-nonce wrap relies on. Cross-record or cross-key key reuse is a producer obligation and is not verifier-detectable; only the within-record duplicate is. All three checks are structural — the validator enforces them on every record (the duplicate-material, slot-count, and decoded-envelope codes are Part-A codes); the recipient simply runs them again as defence-in-depth.

The unwrap itself is two cryptographic steps the recipient reproduces from the envelope, never from any side input. First it recomputes the slots transcript hash slots_hash once, before the loop, and holds it constant across every slot: slots_hash = SHA-256("cardano-poe-slots-transcript-v1" || canonicalEncode(SLOTS_TRANSCRIPT)), where the transcript pins the header fields, the on-wire slot set (each slot field a single byte string — there is no per-field chunking to normalise), and the item's hash claim via hashes_hash. Binding hashes_hash is what lets the recipient confirm the envelope was sealed for this exact hash claim from on-chain bytes alone, before any ciphertext fetch — an envelope spliced onto an item with a different hashes map fails the MAC. It iterates all slots with no early break; a slot is accepted only when the content key it yields also reproduces the on-wire slots_mac over that constant slots_hash. Acceptance also folds in a secret-independent validity bit: on the classical path a slot crafted to drive the X25519 shared secret to the all-zero value sets that bit false (a constant-time compare against 0^32), the KEK is constant-time-selected to a dummy derived from 0^32 so the loop performs identical work, and the bit gates the slot's acceptance — an invalid-ECDH slot can never open regardless of its wrap or MAC. Both the wrap open and the later content open are atomic: on an AEAD tag failure they return no plaintext, and the candidate they hand back (the wrapped key, or the content plaintext) is a fixed or pseudorandom dummy that is independent of the failed ciphertext — no unverified plaintext is ever released.

A recipient key MAY legitimately match more than one slot: a producer may seal the same content key to the same recipient in several slots, each with fresh per-slot KEM material, to pad the recipient count — a valid privacy technique, and distinct from the duplicate-encapsulation-material rejection, which fires only on an identical epk/kem_ct. The verifier selects the first match's key and MUST NOT reject merely because several slots matched. The one anomaly it MUST reject is two matching slots that recover different content keys (compared in constant time): the loop carries a cek_conflict bit and surfaces the single generic failure if any later match yields a key differing from the selected one. This is defence-in-depth — under the slot-set commitment a distinct-key match is already infeasible, so the check simply fails closed.

Then, on the recovered content-encryption key, it derives the content key (an HKDF leaf of that key salted by enc.nonce) and opens the segmented STREAM ciphertext chunk by chunk, verifying each chunk's tag before releasing that chunk's plaintext. The per-chunk AAD is empty: all header context is already bound to the key transitively through slots_mac, so flipping any header field changes what the recipient derives and the stream fails to open. Truncation is caught by the final flag — a missing final chunk, data after it, a final flag on a non-final chunk, or a short non-final chunk all fail as TAMPERED_CIPHERTEXT. The segmented format imposes no cryptographic payload ceiling (the 88-bit chunk counter admits 2^88 chunks); a practical maximum is a deployment denial-of-service policy, enforced incrementally as the stream is read. On the passphrase path the recipient first reads the leading 32-byte commitment header and compares it in constant time before opening any chunk.

The recipient path is where the error catalogue earns its precision: it distinguishes the case where no slot accepted this key (wrong recipient) from the case where a slot accepted the key but the slot set or the ciphertext was tampered. Those are different security claims and carry different codes (below). An untrusted caller, however, receives exactly one generic failure shape regardless of cause — no slot opened, the slot set was tampered, or the content tag failed — and the response never reveals which case occurred, nor which slot matched; the typed codes are an internal diagnostic for a trusted local caller only.

Timing follows one explicit model. The verifier MAY return at the no-match check — before content decryption — so a non-recipient and a recipient take measurably different time. That difference reveals only recipient-versus-non-recipient, never which slot matched and no key material. Uniform timing between a non-recipient and a recipient whose ciphertext fails to open is NOT required, and a dummy content open MUST NOT be mandated — it would impose content-decryption cost on every passer-by. The constant-time guarantee that does hold is the across-slots one: within a single private key's pass the loop runs over all slots with constant-time compares, so nothing leaks which slot, if any, that key unwraps.

Finality: confirmation depth

Cardano settlement is probabilistic. A transaction one block deep can still be orphaned by a short reorg; a transaction many blocks deep has settled with overwhelming likelihood. A verifier that called a one-block-deep record valid would let an attacker re-anchor a contradictory record on a competing fork and collect a "valid" verdict on both — silently breaking the append-only assumption the whole proof rests on.

So a verifier reports the record as pending, not failed, while it sits below a confirmation-depth threshold. The RECOMMENDED general-purpose threshold is ≥ 15 blocks (roughly five minutes). The threshold is verifier policy, not a wire constant: deployments handling high-value or evidentiary records SHOULD raise it toward hard finality, and a verifier MUST surface the threshold it used so consumers can layer a stricter policy on top. A pending record is well-formed and on-chain; it simply has not settled deeply enough yet, and may resolve to valid on a later retry.

Confirmation depth, block height, and block time are explorer-asserted facts the verifier never fabricates. The transaction-reference binding makes the transaction content trustless, but the chain facts about it are not derivable from the bytes — transaction CBOR carries no timestamp or height. Depth is computed as (resolving explorer's tip height) − (height of the including block) + 1, so a transaction in the tip block has depth exactly 1; block time is the POSIX timestamp (integer seconds, UTC) of the including block's slot, taken from the explorer's time field, never recomputed by the verifier. Because these facts rest on the explorer's word, a verifier SHOULD resolve them from at least two independent explorers and surface any divergence; deployments for which block time is load-bearing — legal notarisation, deadline disputes — MUST cross-check it. The report carries the resolved depth alongside the threshold it was compared against, and block_time (with block_slot when available).

Verdict states

A verifier concludes in one of four machine verdicts, each paired one-to-one with a process exit code, so a caller — a CI gate, a monitor, a script — can tell a record-attributable failure apart from a transient operational one without parsing the structured report. The governing line is attribution: condemning a record requires evidence the record's own bytes — or bytes attributably bound to its references — actually provide. No provider misbehaviour can manufacture a failed.

VerdictExitMeaning
valid0every check the verifier ran returned ok; no error-severity issue is present.
failed1a record-attributable failure: the structural validator rejected the bytes, a signature did not verify, an attributable hash mismatched, the bound transaction carries no label-309 metadata (METADATA_NOT_FOUND), or a deny-host rule fired.
unverifiable2no record-attributable error, but a required check could not run or could not be attributed — the transaction did not resolve, no provider response survived the binding (TX_INTEGRITY_MISMATCH), or committed content/ciphertext could not be obtained or attributed. The same record may verify valid on retry or under a different gateway.
pending3structurally well-formed and on-chain, but below the confirmation-depth threshold (INSUFFICIENT_CONFIRMATIONS); may settle. No result from a pending record may be presented as final.

Verifier-host runtime failures that are not record-attributable use exit codes 4 and above and correspond to no verdict.

A valid verdict MUST NOT be reported when any error-severity issue is present; a record MAY be valid with a non-empty warnings and/or info list. Neither layer may "soften" an error into a warning to make a record pass. Each verdict is reserved for its own case: pending is never substituted for valid or failed, and a provider-attributable failure is unverifiable, never failed.

A commitment floor governs availability: when content checking ran but availability failures left no content commitment of the record actually verified, the verdict is unverifiable, never valid — a valid verdict means at least one content commitment was checked. Integrity outcomes are untouched by the floor: attributable bytes that fail a commitment produce failed regardless of what else was available.

Content-address binding and attribution

Step 8 (and, for the recipient verifier, decryption) fetches bytes from content-addressed storage gateways the verifier chose — and gateways are untrusted. The verdict line above turns on a single question the verifier MUST be able to answer about every fetched byte stream: can these bytes be attributed to the URI itself? Both schemes are content-addressed, so they can:

  • ipfs:// — recompute the CID over the fetched content (the multihash directly for a raw-codec CID; block-by-block digests along the resolved path for a DAG-form CID).
  • ar:// — validate the signed Arweave transaction and recompute the chunk Merkle tree against its data_root; for an ANS-104 data item, recompute the deep-hash, verify the owner signature, and check that the SHA-256 of the signature equals the id in the URI.

Bytes that satisfy the record's own digests need no binding check — the record's commitment is at least as strong as the storage layer's. Where the binding is applied, attribution decides what a mismatch means:

  • Attributable bytes — binding verified, or supplied out-of-band by the caller — are held against the record when they fail a commitment: URI_INTEGRITY_MISMATCH for item content (a hard integrity failure regardless of what any sibling URI holds), the MERKLE_* family for a leaves-list, TAMPERED_CIPHERTEXT for an attributable ciphertext blob. Verdict failed.
  • Unattributable bytes — binding not verified, or verified and failed — indict the serving provider, never the record: URI_PROVIDER_INTEGRITY_MISMATCH (warning). The verifier continues with the remaining URIs and gateways; a claim left with no attributable bytes ends in an availability outcome (CONTENT_UNAVAILABLE, MERKLE_LEAVES_UNAVAILABLE, CIPHERTEXT_UNAVAILABLE), verdict unverifiable — exactly as if nothing had been fetched.

This is the storage-side counterpart of the transaction-reference binding: a misbehaving gateway can degrade only availability, never the verdict. URI_INTEGRITY_MISMATCH and URI_PROVIDER_INTEGRITY_MISMATCH are therefore distinct codes — the first condemns the record, the second condemns a provider — and a verifier that conflated them would let a hostile gateway forge a failed. The post-decryption plaintext-hash recheck needs no attribution qualifier: ciphertext that opens under the authenticated envelope is attributed by the AEAD itself, so a plaintext-hash mismatch there (URI_INTEGRITY_MISMATCH) is always record-attributable and forces failed.

The typed error catalogue

Every failure mode resolves to a code from a single closed catalogue. Codes are SCREAMING_SNAKE_CASE, and a conformant implementation MUST emit exactly those strings — never a parser's internal lowercase code, never a free-form message. Two implementations in two languages reaching the same input emit the same code; the full normative list is pinned byte-exact by the conformance test suite, and the catalogue is locked (codes are added only by amendment).

Severity model

Every issue carries one of three severities, and the distinction is load-bearing:

  • error — invalidates the verdict. A valid result cannot coexist with any error.
  • warning — a non-fatal run-time anomaly (a single gateway failed, a leaves-list was only partially available) that does not block valid.
  • info — a deliberate non-check: an aspect the verifier chose not to evaluate (a field outside its profile, an unrecognised optional algorithm). An info entry is not a softened error and is never used as one.

One code stands apart: INSUFFICIENT_CONFIRMATIONS maps to the pending verdict rather than to a severity, because the record is well-formed and only awaiting settlement.

Error families

The catalogue groups into families. A representative — not exhaustive — set of codes:

FamilySeverityRepresentative codes
Malformed / non-canonical CBORerrorMALFORMED_CBOR, CHUNK_TOO_LARGE
SchemaerrorSCHEMA_TYPE_MISMATCH, SCHEMA_MISSING_REQUIRED, SCHEMA_UNKNOWN_FIELD, SCHEMA_EMPTY_RECORD
Unsupported algorithmerrorUNSUPPORTED_HASH_ALG, UNSUPPORTED_AEAD_ALG, UNSUPPORTED_KEM_ALG, UNSUPPORTED_MERKLE_COMMIT_ALG
Explorer / metadataerror / pendingTX_NOT_FOUND, PROVIDER_UNAVAILABLE, TX_INTEGRITY_MISMATCH (→ unverifiable), METADATA_NOT_FOUND (→ failed); INSUFFICIENT_CONFIRMATIONS (→ pending)
Signatureerror / infoMALFORMED_SIG_COSE_SIGN1, SIGNER_KEY_UNRESOLVED, SIGNATURE_INVALID, WALLET_ADDRESS_MISMATCH; SIGNATURE_UNSUPPORTED (info)
Encryption / KEM / wraperrorKEM_EPK_LENGTH_MISMATCH, KEM_CT_LENGTH_MISMATCH, WRAP_LENGTH_MISMATCH, ENC_SLOTS_DUPLICATE_KEM_MATERIAL, ENC_SLOTS_TOO_MANY, ENC_ENVELOPE_TOO_LARGE, ENC_REQUIRES_CONTENT_HASH
Decryption outcomeerrorWRONG_DECRYPTION_INPUT_SHAPE, WRONG_RECIPIENT_KEY, TAMPERED_HEADER, TAMPERED_CIPHERTEXT, KDF_DERIVATION_FAILED
URI / contenterror / warningINVALID_URI, URI_TARGET_FORBIDDEN, URI_INTEGRITY_MISMATCH, CONTENT_UNAVAILABLE (→ unverifiable), CIPHERTEXT_UNAVAILABLE (→ unverifiable); URI_PROVIDER_INTEGRITY_MISMATCH, URI_FETCH_FAILED (warnings)
Merkle list-commitmenterror / warning / infoMERKLE_ROOT_MISMATCH, SCHEMA_MERKLE_LEAF_COUNT_MISMATCH; MERKLE_LEAVES_UNAVAILABLE (dual); MERKLE_UNSUPPORTED (dual)
Service independenceerrorSERVICE_INDEPENDENCE_VIOLATION

The network/policy codes (TX_NOT_FOUND, PROVIDER_UNAVAILABLE, CONTENT_UNAVAILABLE, CIPHERTEXT_UNAVAILABLE) — and TX_INTEGRITY_MISMATCH, whose mismatch is provable against the providers rather than the record — are error-severity in the issue list (they block a valid verdict) but not record-attributable, so they map to unverifiable, never failed. URI_PROVIDER_INTEGRITY_MISMATCH is the per-fetch warning under the same principle. A few codes carry dual severityENC_UNSUPPORTED, MERKLE_UNSUPPORTED, OUT_OF_PROFILE_SKIPPED, and MERKLE_LEAVES_UNAVAILABLE — reading info/warning by default and promoting to error in a strict context (the recipient role, the merkle-only escalation, strict end-to-end mode, or the commitment floor).

The recipient path is the most diagnostically precise family. For a trusted local caller, a failed decrypt resolves to one of three internal codes: WRONG_RECIPIENT_KEY means no slot accepted the supplied key (no content key was ever recovered); TAMPERED_HEADER means a key was recovered but the candidate content key did not reproduce slots_mac over slots_hash (a slot, a header field, or slots_mac itself was altered); TAMPERED_CIPHERTEXT means the slot set was intact but the content AEAD tag failed after the key was recovered. The three are structurally distinguishable to that local caller, and the boundary between them leaks no key material. To an untrusted external caller all three collapse to the single generic failure described above, indistinguishable by response shape. Under the timing model, a permitted early no-match return separates WRONG_RECIPIENT_KEY (a non-recipient) from the two recipient-side tamper outcomes, which carry no further distinction from one another — so the diagnostic precision never becomes a slot- or key-selective oracle.

How codes map to the four verdicts

A code emitted by the structural validator means the record bytes do not conform. The public verifier short-circuits the report with verdict failed — exit 1 — and the validator's issue list, without running any further chain or crypto work. A code emitted only after structural validation passes — a signature that did not verify, an attributable hash that did not match, METADATA_NOT_FOUND on the bound transaction — is likewise failed: each is record-attributable, evidence the record's own bytes provide.

A transient or provider-attributable failure is a different verdict: content that could not be fetched or attributed, an explorer that was unreachable, or a provider that served bytes failing the transaction-reference binding all map to unverifiable — exit 2 — because none is the record's fault and the same record may verify valid on retry. The distinction the exit code preserves is therefore three-way among the failing states: record-attributable failed (1), operational or provider-attributable unverifiable (2), and below-threshold pending (3).

A few codes carry context-dependent severity. A verifier reading a record richer than its declared profile (for example a hash-only verifier encountering a sealed item) reports the extra field as info in a display context and still validates the hash claim, or as error in a strict end-to-end audit context. Likewise an unrecognised optional signature algorithm is info — the content-existence claim does not depend on it — so a public hash-only proof remains valid even when an advisory signature is unverifiable.

Service independence is not optional

Verification never reaches back to the publisher. Every outbound call a conformant verifier makes is directed at infrastructure the operator chose — a Cardano explorer for the transaction, and content-addressed storage gateways for any ar:// or ipfs:// bytes. This is structurally enforced, not a code-comment promise:

  • Every network call routes through a single egress wrapper that records url, method, status, byte count, and purpose for every call — success, failure, and retry alike — into a mandatory audit trail on the report. A verifier that cannot produce that trail cannot prove its independence.
  • That wrapper accepts a deploy-supplied deny-host list and hard-fails any call to a matching host with SERVICE_INDEPENDENCE_VIOLATION. The list is an operator input — a conformance suite populates it with the implementer's own domains — not a wire constant of Label 309.
  • A conformance harness runs the verifier against frozen fixture transactions in a network where the operator's own domains resolve to nowhere, and asserts the verifier still returns valid. The assertion is made at the OS network layer, not by grepping source — a verifier reaching a forbidden host by hardcoded IP would pass a source scan but fail this test.

The verifier needs a reachable Cardano explorer and nothing implementer-specific. Content gateways, a recipient private key, and an off-chain leaves-list are optional inputs that unlock the content, recipient, and Merkle checks respectively. None of them is a service the standard names, and none of them is the publisher.

  • The record — the wire format the structural validator checks against: label 309, the map shape, chunk reassembly, and the CDDL schema.
  • Signatures — the record-level COSE_Sign1 construction, the domain-separated payload, and the strict Ed25519 verification rules.
  • Sealed PoE — the encryption envelope, recipient key slots, and the unwrap the recipient verifier performs.