Skip to Content
Test Vectors

Test Vectors

The canonical conformance corpus ships inside ahl-core 0.5.0, under test_data/. It is generated from committed constants and re-verified from disk on every test run; nothing in it is hand-edited. tests/vectors.rs reads only the files on disk, exactly as a foreign implementation would.

The corpus tracks AHL Internet-Draft draft-zatona-ahl-00 revision 0.4 for everything the I-D covers — including its own §7 for the Evidence Receipt container — and implements the revision as a clean break, not a superset: no material of an earlier revision is verified.

Layout

PathContents
adaptor/The test adaptor profile document ahl-test-log-v1.md, content-addressed and pinned by every manifest version of the main corpus
profiles/ahl-test-atl-leaf-v1.md, this corpus’s own ATL-shaped profile, pinned by the ATL-bound corpus; and ahl-adaptor-atl-v1.md, the released ATL binding, shipped verbatim and pinned by no vector here
vectors/statements/57 anchored envelopes, entry index 0 through 56, plus 5 malformed statements under malformed/ naming the rule each violates
vectors/merkle/7 tree vectors: the log tree (entry-index order, never sorted), the record-sorted batch tree, wide-outputs, input-set and two disposition trees, and an authenticated range proof
vectors/checkpoints/checkpoints.json — 27 signed checkpoints with 27 witness cosignatures
vectors/closure/6 closure scenarios
vectors/witness/Signed witness refusal evidence carrying two conflicting checkpoints
receipts/88 Evidence Receipts, plus index.json
vectors/atl/The ATL-shaped toy corpus: 7 anchored envelopes and a log tree of 3 checkpoints
receipts/atl/13 receipts over that corpus, with an index.json of their own
keys/Committed test key seeds — published constants, never to be reused

The main corpus

57 anchored entries and 27 checkpoints, carrying interlocking scenarios rather than isolated fixtures. Among them: a retroactive correction and its propagation; correction supersession, whose closure seeds are the original and every superseded replacement but never its own replacement; a retraction after two corrections, which seeds exactly its own record — the opposite case, and the reason the two rules are not one rule; non-retroactive scope evaluated as an interval intersection over parsed instants, never as string comparison; a challenge (a retraction under a key that is not the dataset authority); competing triggers with genuine, non-verifying and mixed signature sets; a key statement that retires its own signing key; cross-dataset record naming; continued history backed by a consistency proof; a stale manifest binding; void governance material; and one statement id anchored three times under three different signature sets.

The corpus carries two governance-key rotations, one per side: manifest v2 (entry 25) rotates the witness set, manifest v4 (entry 55) replaces the log key. Each has a rotation_proofs[] element proving the rotating manifest’s anchoring under the state it retires, and the corpus publishes one checkpoint per rotation deliberately signed and cosigned under the outgoing governance state.

Receipts

receipts/index.json records, for every vector, the Protocol §7.7 result a conformant verifier must reach, the required assertion whose finding produces a non-verified one, the normative rule that must fire, and the trust policy those outcomes assume.

SetVectorsverifiedinvalidunverifiable
receipts/8824595
receipts/atl/1367—

Every claim type in the registry has at least one positive and at least one negative: statement-anchored, record-ingested, record-derived, trigger-declared, trigger-effective, disposition-declared, disposition-effective, propagation-complete, governance-state.

Findings, not just results, are pinned. A non-verified vector names which of the twelve required assertions produced its outcome — versions, resource-limits, structure, adaptor-profile, anchoring, governance, checkpoint-authentication, witnesses, envelope-validity, cross-field, claim-material, content-binding — so a foreign implementation must fail for the right reason and not merely fail. Eleven vectors additionally record the number of informative items they must report: void entries the run inspected, which belong to no assertion, carry no result value, and never enter the reduction.

The trust policy in index.json carries only the two verifier-local budgets — max_decoded_bytes 8 388 608 and max_work_units 100 000. The §7.8 fixed limits, nesting depth 4 and 64 embedded receipts, are constants of the crate and not policy: a verifier that could lower either would report invalid over a receipt another verifier verifies.

Some outcomes cannot be carried by a portable vector at all, because what decides them is the verifier’s own configuration — a policy holding no adaptor profile, another corpus’s genesis anchor, no trusted witness key, no dataset key, or a tightened budget. Those are exercised in tests/vectors.rs instead, each tested both alone (result unverifiable, the independent assertions still verified) and beside a byte-decidable defect (result invalid, the gap reported next to it).

The ATL-shaped set

A second toy log under vectors/atl/, bound to a second adaptor profile, exists to show that the three serializations an ATL binding changes are dispatched end to end rather than assumed: the log leaf, SHA-256(0x00 || SHA-256(JCS(envelope)) || METADATA_HASH); the checkpoint signing bytes, a fixed 98-byte blob rather than canonical JSON, with the checkpoint time rendered to exactly nine fractional digits because the blob binds it; and checkpoint.raw, the binary framing, which must parse to the same values as the JSON members.

That corpus pins ahl-test-atl-leaf-v1, its own profile with its own document, and deliberately not the released ahl-adaptor-atl-v1. A profile’s identity is its bytes, so only the released artifact may carry that id; a toy log pinning it would assert a conformance claim these checkpoints — signed by a toy key over a toy tree — cannot make. The released artifact is shipped beside it, byte for byte, at profiles/ahl-adaptor-atl-v1.md — 110 320 bytes, sha256:80a7defdd934242fb4986b2868f4553f0a245995c96ffe543baff59f6bb805aa — and is exposed as ahl_core::ATL_PROFILE_DOCUMENT with its digest as ahl_core::ATL_PROFILE_DIGEST, so a client pinning the real profile takes {id, digest} from the crate rather than fetching a document to learn its own digest. A unit test recomputes one from the other. Shipping an artifact is not configuring a policy: this corpus’s policy holds the test profile alone, so a receipt pinning ahl-adaptor-atl-v1 against it is unverifiable — a gap in the verifier’s configuration, not a defect of the receipt.

The determinism gate

cargo run --bin gen_vectors

The generator has no wall-clock read and no randomness: every timestamp is a fixed constant, every key comes from a committed seed, every record is committed content. Two consecutive runs must leave test_data/ byte-identical — if they do not, that is a bug, and the corpus determinism check runs in CI.

Before writing anything the generator verifies its own output and aborts on any mismatch: statement-id and entry-id uniqueness, every envelope signature — including that the deliberately non-verifying fixtures really do not verify — manifest lineage and key-snapshot semantics, the challenge’s authority status, every checkpoint signature and witness cosignature, every inclusion proof, every range proof (including that it rejects substitution), every consistency proof between published checkpoints (including that a proof for the wrong pair of sizes is rejected), the witness refusal evidence, and an independent recomputation of every revocation closure. It then runs every receipt vector through verify_receipt_report and requires each positive one to reach verified and each negative one to reach the §7.7 result of the rejection it names, by the specific rule it names. A vector that cannot be self-verified never reaches the repository.

cargo test cargo clippy --all-targets --all-features -- -D warnings cargo fmt --all -- --check cargo llvm-cov --all-features --fail-under-lines 90

Canonicalization, and the pinned dependency

AHL is a sibling of ATL , and their Merkle semantics must not drift apart. ahl-core depends on atl-core at the exact registry version =0.23.2 — identical to git tag v0.23.2, which the crate formerly pinned by rev 79ac9c085857. The = is the point: a caret requirement would let a later patch supply the node hashing every committed root depends on.

JCS canonicalization (RFC 8785), node hashing, root computation and proof generation all come from there, and inclusion proofs are verified normatively through atl_core::core::merkle::verify_inclusion rather than a local reimplementation. Consistency proofs come from the same place, so continued_history evidence is the RFC 9162 construction the ATL family already implements and not a second one. Range proofs are the one construction atl-core has no primitive for, so the combining recursion is local — but every hash it computes is still atl-core’s, and a width-1 range is cross-checked through verify_inclusion so the two constructions cannot diverge. AHL therefore ships no JCS vector corpus of its own.

What the corpus does not cover

The crate names its own gaps rather than leaving them to be discovered. Its verifier implements only the two canonicalization procedures the I-D itself defines, jcs and exact-bytes; a dataset declaring any other identifier makes that dataset’s content-binding finding unverifiable, never invalid, and no vector exercises an unimplemented identifier end to end, because the identifier comes from the manifest and every receipt over that dataset would be built on it. A receipt pinning an adaptor profile id this build does not implement is refused as a limitation of the build, named as such. And no vector carries a witness key sourced local-policy; that branch is covered in tests/vectors.rs, since what decides it is the verifier’s configuration rather than anything a portable vector can carry.

The corpus tracks a draft, so it is expected to change with it: the intended stable contract is the shape of the corpus, not yet its digests.

Last updated on