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.
| Role | Adds | Touches |
|---|---|---|
| Structural validator | schema + domain conformance over the record bytes | nothing — a pure function |
| Public verifier | chain resolution, on-chain inclusion, signature checks | a Cardano explorer + content gateways |
| Recipient verifier | trial-decrypt of a sealed payload, plaintext-hash recompute | the 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:
- 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 emitsPROVIDER_UNAVAILABLE. - Binds the fetched bytes to the transaction reference. Before reading
anything out of a fetched transaction, the verifier recomputes
blake2b-256over the fetched transaction-body bytes — by ledger definition, the transaction id — and rejects the response on any mismatch with the requested hash. It then recomputesblake2b-256over the fetched auxiliary-data bytes and rejects on any mismatch with the now-verified body'sauxiliary_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 carriesTX_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. - 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 key0. 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, isMALFORMED_CBOR. If the bound transaction carries no label-309 metadata, the verifier emitsMETADATA_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. - 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.
- Structurally validates the reassembled body (the role above). A validator
rejection short-circuits the report with verdict
failed. - Checks confirmation depth (below). A transaction below the threshold halts
here with verdict
pending. - 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.
- 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.
| Verdict | Exit | Meaning |
|---|---|---|
| valid | 0 | every check the verifier ran returned ok; no error-severity issue is present. |
| failed | 1 | a 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. |
| unverifiable | 2 | no 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. |
| pending | 3 | structurally 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 itsdata_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_MISMATCHfor item content (a hard integrity failure regardless of what any sibling URI holds), theMERKLE_*family for a leaves-list,TAMPERED_CIPHERTEXTfor an attributable ciphertext blob. Verdictfailed. - 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), verdictunverifiable— 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
validresult cannot coexist with anyerror. - 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:
| Family | Severity | Representative codes |
|---|---|---|
| Malformed / non-canonical CBOR | error | MALFORMED_CBOR, CHUNK_TOO_LARGE |
| Schema | error | SCHEMA_TYPE_MISMATCH, SCHEMA_MISSING_REQUIRED, SCHEMA_UNKNOWN_FIELD, SCHEMA_EMPTY_RECORD |
| Unsupported algorithm | error | UNSUPPORTED_HASH_ALG, UNSUPPORTED_AEAD_ALG, UNSUPPORTED_KEM_ALG, UNSUPPORTED_MERKLE_COMMIT_ALG |
| Explorer / metadata | error / pending | TX_NOT_FOUND, PROVIDER_UNAVAILABLE, TX_INTEGRITY_MISMATCH (→ unverifiable), METADATA_NOT_FOUND (→ failed); INSUFFICIENT_CONFIRMATIONS (→ pending) |
| Signature | error / info | MALFORMED_SIG_COSE_SIGN1, SIGNER_KEY_UNRESOLVED, SIGNATURE_INVALID, WALLET_ADDRESS_MISMATCH; SIGNATURE_UNSUPPORTED (info) |
| Encryption / KEM / wrap | error | KEM_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 outcome | error | WRONG_DECRYPTION_INPUT_SHAPE, WRONG_RECIPIENT_KEY, TAMPERED_HEADER, TAMPERED_CIPHERTEXT, KDF_DERIVATION_FAILED |
| URI / content | error / warning | INVALID_URI, URI_TARGET_FORBIDDEN, URI_INTEGRITY_MISMATCH, CONTENT_UNAVAILABLE (→ unverifiable), CIPHERTEXT_UNAVAILABLE (→ unverifiable); URI_PROVIDER_INTEGRITY_MISMATCH, URI_FETCH_FAILED (warnings) |
| Merkle list-commitment | error / warning / info | MERKLE_ROOT_MISMATCH, SCHEMA_MERKLE_LEAF_COUNT_MISMATCH; MERKLE_LEAVES_UNAVAILABLE (dual); MERKLE_UNSUPPORTED (dual) |
| Service independence | error | SERVICE_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 severity — ENC_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.
Related pages
- 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.