10. Conformance
Draft 0.1. Reference: spec/verifier/verify-offline.js, spec/verifier/run-vectors.sh, spec/verifier/vectors/*.json.
10.1 What conformance means
- A verifier conforms to this draft when, run against every vector in
spec/verifier/vectors/, it produces the vector's expected verdict and, where the vector carries expected values, those exact values. - An issuer conforms when every receipt it publishes verifies under the reference verifier: ids re-derive from the chapter 02 hash domains, leaves follow the four-field grammar, signatures verify under their context prefixes, and inclusion proofs verify against a signed head.
- A log conforms when its heads verify under chapter 05 and its inclusion proofs verify under the reference verifier without any call back to the log operator.
Conformance says nothing about the truth of the claims inside a Record, the identity of any witness, or acceptance by any receiver. The trademark and conformance-claim policy is pending (LICENSE-PENDING.md).
10.2 Vector structure
Each vector is one JSON file:
| field | type | meaning |
|---|---|---|
id | string | v<nn>-<slug>; the file name without .json |
chapter | string | the chapter the vector exercises |
title | string | the normative sentence being tested |
check | enum | which algorithm the verifier runs (section 10.3) |
input | object | the check's inputs; shapes per check kind |
expect | object | verdict: "pass" or "fail", plus expected values (canonical, id, sha256, roots, empty_root, snapshot_sha256) where the check produces values |
A vector whose expect.verdict is fail passes when the verifier correctly refuses. Signature vectors were generated once with ephemeral keys; only public halves are embedded, and the vectors do not depend on any live key or endpoint.
10.3 Check kinds
| check | input | what the verifier does |
|---|---|---|
canonicalize | value | chapter 02 section 2.3 canonical form; compares to expect.canonical |
leaf_payload | leaf {receipt_id, prefix, sha256, issued_at} | builds the four-field leaf with normalization; refuses non-loggable prefixes |
receipt_id | kind and the raw inputs for that kind (payload, course, attestation, worksample, note, wall) | applies the per-prefix hash domain and derives <prefix>_ + 12 hex |
kb_binding | key_id, public_jwk, established_at | derives did:key from x, builds the frozen kb_ payload, derives id and sha256 |
family_sig | leaf_payload, sig, public_jwk | Ed25519 under eformogi-receipt-sig-v1: |
countersig | leaf_payload, org_countersig, public_jwk | Ed25519 under eformogi-witness-countersig-v1: |
merkle_root | leaf_payloads[] | leaf hashes and RFC 6962 roots over prefixes of the list |
inclusion | leaf_hash, leaf_index, tree_size, audit_path, root_hash | the RFC 9162 fold |
sth | sth, public_jwk | whitelist the seven fields, canonicalize, Ed25519 under eformogi-sth-v1: |
proof | proof, public_jwk, sha256? | the four-step algorithm of chapter 06 |
vc_proof | credential, public_jwk | eddsa-jcs-2022 DataIntegrityProof |
10.4 The vectors
| id | chapter | exercises | expected |
|---|---|---|---|
| v01-canonical-json | 02 | sorted keys, arrays in order, unicode, nesting | pass with exact string |
| v02-leaf-payload-exact-bytes | 02 | id and sha lowercased, ISO ms timestamp, sorted keys | pass with exact string |
| v03-leaf-payload-rejects-ecr | 02 | ecr_ is never loggable | fail |
| v04-cr-derivation | 02 | cr_ hash domain with credits as posted string | pass, cr_0ad5487e3ca4 |
| v05-att-derivation-ws-target | 02, 03 | att_ hash domain with lowercased email as email_hash_input | pass, att_5d9b296a66cb |
| v06-ws-derivation-v1 | 02 | ws_ v1 domain; issued_at outside | pass, ws_53071e72cd4a |
| v07-wn-derivation | 02 | wn_ domain with author: kid fixed | pass, wn_e45fc1d88617 |
| v08-tr-derivation | 02 | tr_ whole-payload domain (gold-demo shape) | pass, tr_d83e84e54c94 |
| v09-kb-binding-frozen | 04 | did:key derivation and the frozen kb_ payload | pass, kb_b5ed898815ac |
| v10-family-sig-valid | 04 | family signature under its context | pass |
| v11-family-sig-context-stripped | 04 | a context-free signature must not verify | fail |
| v12-org-countersig-valid | 03 | organization countersignature under its context | pass |
| v13-merkle-roots-frozen | 05 | roots for 1, 2, 3, 7, 8, 16 leaves and the empty root | pass with exact hashes |
| v14-inclusion-valid | 05 | leaf 3 of 7 folds to the root | pass |
| v15-inclusion-wrong-index | 05 | same path, wrong index | fail |
| v16-sth-valid | 05 | seven-field signed tree head | pass |
| v17-sth-tampered-tree-size | 05 | tampered tree_size | fail |
| v18-proof-four-steps-valid | 06 | artifact hash, leaf hash, inclusion, STH signature | pass |
| v19-proof-tampered-path | 06 | one flipped audit-path element | fail |
| v20-vc-eddsa-jcs-2022 | 07 | DataIntegrityProof over a JCS-coerced credential | pass |
| v21-proof-with-family-signature | 06 | a real proof carrying issuer_signature: leaf hash, inclusion, tree head, family signature, did:key derivation | pass |
| v22-proof-family-signature-tampered | 06 | the same proof with one character of the family signature flipped | fail |
v21 and v22 are frozen from the live production proof for tr_8e59bb9641e8 plus the issuer_signature block /api/verify-receipt already serves publicly, so the offline path is pinned against a real receipt rather than a fixture. Neither vector asserts anything about key_established_at: that ordering is advisory (chapter 06, step 4) and never enters a verdict.
Chapters 01, 08, and 09 have no vectors: they specify structure, plugin interfaces, and controls rather than byte-level algorithms. Frozen values in v09 and v13 are identical to the values pinned by tests/test-family-signing.js and tests/test-merkle-log.js, so the trust gate and this suite cannot drift apart silently.
10.5 Running
sh spec/verifier/run-vectors.sh # all vectors; non-zero exit on any mismatch
sh spec/verifier/run-vectors.sh -v # print every step
node spec/verifier/verify-offline.js spec/verifier/vectors/v14-inclusion-valid.json
Output at the time of this draft: vectors: 22/22 matched expected verdicts.
10.6 Adding a vector
- State the normative sentence it tests and name the chapter.
- Generate expected values from the reference implementation in
shared/, never by hand, and never from the verifier under test. - If the vector needs a key, use an ephemeral key and embed only the public half.
- Prefer one positive and one negative vector per mechanism; a suite that cannot fail proves nothing.
- Never embed a real family's data. Fixtures use the public demo student (Maya Chen) or synthetic values.
10.7 What this does not prove
Twenty vectors pin the algorithms. They do not exercise the online endpoints, rate limits, revocation states, tier corroboration, or any legal control; those are covered by the trust gate (npm run test:trust, the trust gate at the time of this draft) and by the operational history in TRUST_REPORT.md.
10.8 The Witness Transcript name
Witness Transcript is a mark of Eformogi, Inc. An implementation whose records pass this conformance suite, in the version of the specification it claims, may call the resulting artifact a Witness Transcript and may use the name in its own prose. An implementation MUST NOT call a record a Witness Transcript unless it passes the suite. The mark MUST NOT be used in the name of a company or a product. This is a trademark policy conditioned on a technical suite, not a certification program: passing it is still not "certified", "official", or "accredited", and nobody, including Eformogi, may describe an implementation that way. The full trademark text is in LICENSING.md.