Beta Pre-1.0 — the file-naming convention may still change. v1.0 will freeze it. ZDDC home · All specifications

ZDDC - Custody Extension ~1.0
The Conformance Contract
Status: Draft

Status: APPROVED 2026-08-12. What an implementation must do to claim it speaks ZDDC custody v1, what Stage 4 will build, and what v1 deliberately does not do — each deferral with its hook named. Requirement ids [M-C*] here are already taken by canonical form (manifest.md §1), so this document's own requirements are [M-R*] (runner).

Two implementations must satisfy this contract, and the spec exists precisely because they are different worlds:

  • zddc-server (Go) — the inbound door, the mirror walker, the ledger reader;
  • the .html tools (browser, Web Crypto, file://) — the rung-1 producer and the offline verifier.

Per memo constraint 2, both are reference implementations of these documents, never the definition of them. If a third implementation passes the vectors, it interoperates.


1. Levels of conformance

An implementation declares one or more of these. They compose; nothing requires all four.

Level Must do
V — Verifier Everything in envelope.md §6, keys.md §5–§6, and the per-type rules of statements.md; the tri-state result with its as-of date; the currency axis.
P — Producer Emit only canonical payloads ([M-C1]), correct PAE ([M-E6]), the manifest and Merkle construction ([M-M1]–[M-M12]), and the key-file rules ([M-K1]–[M-K6]).
H — Hasher The chunked Merkle construction and inclusion proofs alone ([M-M1]–[M-M5]). The minimum a mirror or an indexer needs.
L — Ledger Monotonic acceptance at the door ([M-S6]), _ledger/ placement ([M-S43]), and currency derivation ([M-S46]–[M-S49]).
  • [M-R1] A verifier (V) MUST NOT be shipped without a hasher (H) — verifying a manifest it cannot recompute is not verification.
  • [M-R2] A producer (P) MUST also be a verifier (V) of its own output. The cheapest way to emit a non-canonical payload is to never read one back.

2. The runner

[M-R3] A conformance runner loads vectors/keys.json and vectors/contexts.json, walks vectors/<area>/{valid,invalid}/, and for each vector dispatches on kind (vectors/README.md §2). It passes when it accepts every valid/ vector and rejects every invalid/ vector with the named reason class, and derives the named verdict for every currency/ vector.

[M-R4] The runner MUST read no clock and no randomness. Every instant it needs is in the vector's context (as_of, now); Ed25519 is deterministic. A runner that consults the wall clock will pass today and fail after the test keys' certified windows close, which is the single most common way a conformance suite rots.

[M-R5] The runner MUST be runnable offline. This is not convenience: keys.md [M-K27] forbids a network fetch during verification, and the only honest way to test that is to run with the network removed. A runner that needs to resolve anything is testing a non-conforming implementation. This holds for every input the suite needs, with no exception — which is why the third-party Ed25519 corpora [M-R12] requires are vendored at corpora/ rather than fetched. Nothing in this suite may reach the network.

[M-R6] Two requirements are not vector-testable and MUST be pinned by the implementation's own tests instead, with the reason recorded (vectors/README.md §5 lists all 35 such ids):

  • [M-E22] — no failure oracle on a network surface (a deployment property);
  • [M-M13] — the 16 MiB ceiling, which the runner MUST synthesize by padding a valid payload past the limit rather than by loading a committed fixture.

[M-R12] Two axes of [M-E10] are likewise not expressible as ZDDC envelope vectors — a small-order A, and a signature satisfying the cofactored equation but not the cofactorless one — because neither can be built from an anchorable test key, so a vector presenting one would be refused at [M-E12] key resolution and would never reach the verification equation. An implementation MUST instead demonstrate [M-E10] by running its Ed25519 verifier over the two corpora vendored at corpora/, matching the reference result on every case:

  • corpora/wycheproof-ed25519_test.json — 151 cases, each of which MUST produce the verdict in its own result field. Cases 63–66, 67–70 and 85 are the S ≥ L and high-bit axes; 42–62 and 151 are the non-canonical-R axis, and reject only under [M-E10] rule 5's encoding comparison.
  • corpora/ed25519-speccheck-cases.json — 12 cases, indexed by array position. The implementation MUST reject cases 4 and 5 (they satisfy the cofactored equation and not the cofactorless one — the only cofactor discriminator in either corpus) and MUST accept cases 0 and 1 (a small-order A with the equation otherwise satisfied). Getting either pair backwards is a conformance failure, not a preference.

corpora/README.md carries the provenance, the pinned SHA-256 of each file, and the per-case tables in full.

The corpora are vendored, not fetched, and [M-R5] applies to this run exactly as it does to the vectors. Two reasons, both structural. First, ZDDC's premise is that artifacts verify offline from a file:// page; a conformance suite that needs the network to run contradicts the contract it is testing, and would leave [M-E10] as the one requirement unverifiable on an air-gapped machine — the machine most likely to be checking custody in the first place. Second, a fixed contract needs fixed inputs: these corpora gain cases upstream, so a suite that fetched the latest would let two runs months apart test different things while both reported "pass". Refreshing the pinned bytes is a reviewed spec change with its own procedure (corpora/README.md), never an automatic update.

A ZDDC implementation that adopts a different Ed25519 library than the two reference implementations MUST run these corpora before claiming conformance — the profile is behaviour, and only the corpora demonstrate it. If its library disagrees with the reference results on any case, [M-E10] narrows to the intersection and D-19 reopens; the implementation does not get to declare the difference immaterial (decisions.md D-19 records the reference run).

[M-R7] Drift is a test failure, not a review item. The committed vectors must equal a fresh gen.js run byte for byte (tests/custody-vectors.spec.js). A hand-edited fixture silently redefines the wire format.

3. Reason classes

The eleven-name closed vocabulary is defined once, in vectors/README.md §4. Two rules govern its use:

  • [M-R8] Classes are for logs, local UI, and this suite. A network surface answers one undifferentiated refusal ([M-E22]) — a reason class on the wire is an oracle, and the shipped peer middleware's bare 401 is the posture to match.
  • [M-R9] Where two classes could plausibly apply to one input (bad_pae vs bad_signature), the vector names the more specific and a runner MAY accept the more general. Nothing else may be widened.

4. What Stage 4 builds

The §10.8 v1 scope, expressed as work:

  1. A shared hashing/canonicalization core, twice. zddc-merkle-v1 and JCS in Go (zddc/internal/custody/) and in shared/ for the tools. shared/hash.js already carries the incremental digest the chunked construction needs; the Merkle layer sits on top of it.
  2. The DSSE profile — PAE, envelope parse/serialize, the numbered verification procedure, in both languages.
  3. The key layer — key files under .zddc.d/keys/ (the shape Stage 1 already ships for the transport key), the party-registry anchor field group (keys.md §3.2), and the length-1/length-2 chain resolver.
  4. The seven statement types — payload builders, validators, and the _ledger/ reader/writer.
  5. The producer UX — first-produce key generation, the signing gesture that shows what is being signed before confirm (memo §6.16), and the verification report with its tri-state × currency result and the anchor as-of date.
  6. The inbound door — monotonic acceptance ([M-S6]), fail-closed refusal of unanchored keys, no auto-anchoring (memo §6.18).
  7. The browser-verifiable .zip affordance — open the .html, point it at the zip, get a per-file table with fingerprints, printable (memo §6.17).
  8. The conformance runner itself, in both implementations, wired into go test ./... and the Playwright suite.

Not Stage 4, though the memo scopes them in v1 and they touch nothing here: audit-access --as <peer> (already independently useful) and the staleness UI vocabulary (already shipped as X-ZDDC-Remote).

5. What v1 does NOT do — deferrals and their hooks

Every deferral from memo §10.8 with the named place a later version plugs into. A hook that is not named is a hook that will be retrofitted badly.

Deferred Hook
Payload encryption (client-side encrypt-before-upload, memo §5b) kem_key/kem_alg in the party anchor ([M-K15]) + the manifest entry's enc field ([M-M8]). A v1 verifier already carries enc through untouched and verifies the ciphertext against root, which is the "a mirror verifies without reading" property. Nothing else in v1 mentions encryption.
RFC 3161 timestamping (memo §6.5) An optional payload field ridden by [M-C7]'s ignore-unknown rule, and — if it must be mandatory for a deployment — [M-C8]'s critical. The receipt is the baseline timestamp and stays so ([M-S17]).
The completeness head / transparency log (memo §6.9) The prev chain ([M-S5]) already gives middle-completeness for free; only the tip is unprotected. A head statement would be another payloadType, which the registry ([M-E4]) is shaped to take — as the 2026-08-16 record-revision addition (its eighth entry, statements.md §11) demonstrated in practice.
Full LTV machinery — evidence-record renewal, re-timestamping (memo §6.8) The hooks are the version identifiers: the algorithm rides the anchor ([M-E8]) and the format version rides payloadType ([M-E4]), so a future algorithm change is a version bump old verifiers refuse rather than mis-verify. The evidentiary export (memo §6.17) is the archive format; it is a view, so it needs no format work now.
Key-hierarchy ceremony (memo §10.8) The record format already expresses key succession ([M-S37]–[M-S42]) and operational-key certification ([M-S29]–[M-S31]). What is deferred is ceremony — HSMs, quorums, offline-root procedure — none of which changes a byte on the wire.
Multi-recipient envelopes Deliberately permanent, not deferred (memo §6.14): to keeps exactly one element forever ([M-S3]). Fan-out is N per-link statements; the join is content-addressing. The array shape stays only because it is already in the grammar.
Equivocation detection Deliberately permanent (memo §10.6, §11.5): bilateral witnessing gives non-repudiation per relationship, never consistency across relationships. The escape hatch is optional milestone head-gossip, strictly additive.
X.509 / eIDAS-qualified / RFC 4998 conformance Deliberately permanent (memo §11.4). The escape hatch is wrapping, never re-architecture: standards-grade signatures travel inside files and survive transmittal untouched, exactly as PE seals do today.

[M-R10] An implementation MUST NOT present a deferral as unsupported-by- accident. The three "permanent" rows above are architectural limits with documented escape hatches; a proposal that requires breaking one is a redesign of the system and must present itself as such (memo §11).

6. Reading order and authority

manifest.md → envelope.md → keys.md → statements.md → this file, with decisions.md as the review agenda and vectors/ as the executable half.

[M-R11] These specs are the contract. The design memo they were transcribed from, docs/design/federation-and-custody.md (cited as "memo §N"), is non-normative background: where the two appear to conflict, the spec wins and the memo is wrong — report it so the memo is corrected, and never code to the memo. (Inverted 2026-09-22 for publication; until then the memo won. An unpublished document cannot bind a third-party implementation.) The three places where the memo conflicted with itself were routed back rather than improvised, and all three are now CLOSED (decisions.md §"The three memo contradictions"): the succession continuity proof (D-10), the anchor-placement reading (D-12), and the stale sha256File justification (corrected on main in 14e1582). Each correction landed in the memo itself, so a reader of either document reaches the same answer.

Rendered from “The Conformance Contract” ZDDC