open-standards/wai/jwp-conformance/README.md
Transaction Science a26124b1c4 Receipts in WAI's binary form (jwp-receipts §2.7) on the receipts track, replacing JSON
jwp-receipts §2.7 defines a binary form for group and object receipts and for a
group's receipts together (magic WJR1): a group is its canonical bytes (§3 step 3),
its link hash computed over them as received; an object is its members in fixed
widths, its leaf_version 1 or 2 followed by its class. Reading yields the JSON
form's members, and every rule of §3 applies to them unchanged. §2.7.4 says what
does not read (malformed, at its §3 step); §5.12 the vectors.

moq-streaming-format §4.1: a receipts-track object carries that form.

wai-rs: jwp_delivery::binary (reader and writer, through from_json), and 20
binary vectors in jwp-conformance; jwp_verify.py reads and writes them from the
text. joule-moq: GroupReceipt/ObjectReceipt to_binary/from_binary, read_receipts,
ReceiptBundle::to_payload/from_payload in the binary form (and read, with the link
hash over the bytes received); the JWP_OBJECT_RECEIPT property carries an object
receipt standing alone. The JSON payload is not read. joule-moq-web's verifier
measures each receipts object, and the same receipts in the JSON form.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 14:43:44 -04:00

10 KiB
Raw Permalink Blame History

jwp-conformance

Conformance for the labelled forms of the JWP delivery receipts (§2.2–§2.4.2, §3, §3.3 and §5.7) and their binary form (§2.7, §5.12), for the WAI_RECEIPT property's readings (moq-streaming-format §5), and for the receipt chain per (signer_pubkey, track_alias) and an object checked by inclusion alone (§3 step 3, §4.4, §5.9 and §6.1): an object's figure, class and origin bound into its Merkle leaf; a group total's class signed under the labelled group payload; the position an object's inclusion proof must walk; the group total read from its objects; a property read as signed only where a signed statement restates it; one chain for each key and track; and an object whose receipt is checked without its envelope reported as inclusion-verified, never as delivered.

Provenance. Generated here by wai_jwp_vectors, from fixed seeds (below). No media.

Files

file what it is
vectors.json The corpus, generated by wai_jwp_vectors gen in wai-rs (feature receipt_claims). wai_jwp_vectors verify requires a byte-identical regeneration and every declared outcome.
jwp_verify.py A second verifier, written from the extension text, that shares no code with wai-rs. It takes BLAKE3, jwp-receipts §3.1 step 3's Ed25519 rule and the claim verifier from ../claim-conformance/claim_verify.py, itself independent of wai-rs.
chain_gap_differential.py Signs genuine chains of up to 90 groups and delivers them shuffled, withheld and released stale, walked back and forward, with rewritten legacy links and forks the key signs, and, on half of them, to a verifier that joins, holds a bound of groups, or both, with receipts lost outright or the chain joined mid-stream; requires the two verifiers to agree on every one.
chain_differential.py Signs receipt chains the corpus does not hold — two keys, two tracks, both payloads, genuine links, replays with and without a rewritten link, forks, gaps, links across tracks and keys, bad signatures, group_id up, equal and down, group_end around the clock, guard and bound on and off — and requires wai_jwp_vectors verdicts and jwp_verify.py verdicts to agree on every one.

Procedures

Each vector names one procedure and declares one outcome.

procedure input outcome
leaf an object receipt its leaf, in the form its label gives (§2.2)
group a group receipt, and optionally the previous one held accept with its link hash and signing payload (§2.3, §3 step 3), or a rejection at a §3 step
object a group receipt, an object receipt and the envelope delivered accept (§3 steps 1–5), or a rejection at a §3 step
total a group receipt and the objects delivered with their envelopes the total's reading — unmetered, unlabelled, weakest (a legacy group whose every non-zero part is labelled: a class name only), or signed (a labelled group's own class, checked against every object or unchecked) — or a rejection
property a WAI_RECEIPT property, with the object, its group and its envelope, or with an energy claim the property's reading — unmetered, unsigned, or signed with the class and the key that signed it (the group's for a leaf, the claim's for a claim) — or a rejection
chain group receipts in the order a verifier receives them, whether §6.1's group_id guard is on for labelled groups (it is always on for legacy ones), and §6.1's freshness bound (now, max_age_s, max_skew_s) or null; optionally recovery (§3 step 3): whether the verifier joins, and max_held, the count of groups it holds live for each chain's missing parents, or null for none; with a count, max_history, the count it holds as history for each chain, inside the gaps its anchors left, and max_total, the count it holds across every chain for each group in order, accept (§3 steps 1–3 against the chain of its key and track, whose head it then becomes, or the head offered again), accept again (a group the chain admitted before, the same bytes, which changes nothing), accept anchor (it anchors a joined or re-anchored chain), accept history (inside a gap an anchor left, linking forward from the group before it or back from the group after it), or a rejection at its §3 step; the verifier holds every group it accepted, so a fork is chain_break and a gap parent_not_held; a group whose signed group_id is below the chain's live edge (the highest of a group that verified and arrived fresh) is exempt from the freshness bound. With holding, a held group's outcome is what became of it: accepted when its parent arrived, or when it filled a gap, parent_not_recovered when a bound was passed, or parent_not_held if still held at the end
inclusion a group receipt and an object receipt, with no envelope accept with the evidence inclusion-verified (§3 steps 1, 2 and 5, step 4 not run; §4.4), or a rejection at a §3 step
binary bytes (hexadecimal) and their form: receipts (a group's receipts, §2.7.3), group or object (one standing alone) read with each receipt's members in the JSON form and, for a group, its link hash over the bytes as received (§2.7.4's reading, §3 step 1's and step 4's reading of the members, and the bytes written again identical), or reject with malformed at its §3 step, or the reason §3 step 1 gives the members

A rejection names its reason (§3.3), and its §3 step where it has one; the group total and a property are not steps of §3.

What the corpus holds

Leaves in both forms, the labelled one with tags 0, 2, 4 and 5. A group in the legacy payload whose objects mix the forms, and a group in the labelled payload chained to it, with their signing payloads, signatures and link hashes. Every way a label, a figure or an origin can be restated, stripped or added, and which of them the legacy leaf leaves unbound. Labels that break their rules in correctly built trees. Objects presented at another object's position — including the duplicated last leaf of an odd level, and a duplicate that would hide a weaker class from the total. Group labels that break their rules, correctly signed; a sign_version other than 2; and group signatures the strict Ed25519 rule refuses (S ≥ L, a key of small order, a key whose y is not reduced). Totals read from complete and incomplete holdings, a total that does not match its parts, and labelled totals whose class, coverage, declared uncertainty or window disagree with what their parts compose to. Properties that agree with, and disagree with, a labelled leaf or an energy claim, including a claim signed by a key other than the publisher's, which reads as signed by that key. Chains (vector v6_per_track_chain and its neighbours): one key's base track and layer track, three chained labelled groups each, offered alone and interleaved; a track's second group without its first; a parent on another track; a track's first group offered again after its second, which changes nothing, a fork from a held group, a gap beside the head, and the head offered again; another key on the same track; the group_id guard off, on, and compared within a track, and always on for a legacy group; a legacy history replayed after one rewritten link, once and in a loop, and a labelled replay; receipts chained per key across tracks; group_end at the clock, with an offset, at the window's edge, stale, ahead of the clock, stale with no bound, a stale group behind the live edge, which fills a gap, a gap of seventy groups, and a stale group whose rewritten link earns it nothing; a previous group of another track or key; and a group whose signature fails, which heads nothing. Recovery (§3 step 3): a parent fetched after its children, which are held and released; a receipt lost with and without holding, the chain re-anchoring past it and regaining the held groups and the lost one as history; a re-anchor that may not move behind its head; a verifier that joins mid-stream, at the chain's first group, and on a legacy chain, with history behind its anchor where the anchor is labelled; a stale group that may not anchor; a gap fetched in ascending order after a re-anchor, each group linking forward from the one before; a joiner's fill in ascending order, held as history outside the live bound and released back from the anchor, within its history bound and past it; a joiner's fetch from the chain's first group, whose null parent is the chain's start, held as history and released back from the anchor, and a second start and a fork once that gap is closed; and the total held across two tracks, passed by one; a group offered again after the head moved on, and a joiner's anchor delivered a second time, which change nothing. Objects checked without their envelopes: a dropped layer object and a labelled one, which verify by inclusion, and a forged group, a rewritten content hash and a moved position, which do not. The binary form (§2.7, §5.12): a live track's group of one object, the mixed and labelled groups with every label tag and their proofs, a group with no object, a legacy group and an object standing alone, each read and written again byte for byte; and the bytes that do not read, each refused malformed at its step, with one whose members §3 step 1 refuses.

Synthetic, and what it attests

Keys are Ed25519 from fixed 32-byte seeds — 0x11 the publisher, 0x22 the meter, 0x33 a relay — and attest nothing. Envelopes, figures, origins and calibration references are invented, and calibration references use a reserved example domain.

Running

cargo run --no-default-features --features receipt_claims --bin wai_jwp_vectors -- verify ../jwp-conformance
python3 jwp_verify.py vectors.json
python3 chain_differential.py ../wai-rs/target/debug/wai_jwp_vectors --n 3000 --seed 1
python3 chain_gap_differential.py ../wai-rs/target/debug/wai_jwp_vectors --n 400 --seed 1

Both run in CI: the Rust verifier on every platform of the conformance matrix, the Python one on the linux runner (.github/workflows/wai-conformance.yml). The Python verifier needs the cryptography package. joule-code's joule-moq pins leaves, roots and signatures from this corpus in its own tests, and its workflow re-runs when the corpus changes.