| Filename | Latest commit message | Latest commit date |
|---|---|---|
staged-conformance/ holds stage sets and the steps a sink takes with them: image layers over a pinned integer image base (identity, up2 to even and odd sizes) and over a PNG base; video layers per frame and per GOP, with prev runs, over integer motion bases and over a pinned neural-keyframe clip; audio layers over a pinned integer audio base, and a stereo rate ladder over a FLAC base; a splat attribute layer; and one case per refusal and order rule (another base, a wrong producer, a missing layer, conflicts held and after an application, out of order, a fallback base, geometry, truncation, a malformed table, q of 0, a word after the last symbol, a stream that runs out, clamped and bypassed residuals, malformed refines, a capability that is not a refinement, a base not admitted, inclusion-only evidence, a cost limit, a missed deadline, the hold bound). Each case's case.json states what a sink reports at every step and holds at the end; schedule.json holds arrival traces and what a sink presents. wai_staged_vectors gen writes the corpus; verify regenerates it byte for byte, replays every case through StageSink, decodes the integer bases with their pins against stage0.bin and checks their tiers. staged_verify.py is a standard-library reimplementation of the envelope, refines, WIR1, the sink's algorithm, tiers and stage hashes, and the PNG, FLAC and WIV1 keyframe-0 decodes; it checks every layer application in every case. The pure-Python BLAKE3 moves from claim_verify.py to _blake3.py, which both verifiers import. A unit held and decided within one step is reported by its decision alone. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
| .. | ||
| .gitattributes | ||
| _blake3.py | ||
| claim_verify.py | ||
| differential.py | ||
| README.md | ||
| vectors.json | ||
claim-conformance
Conformance for typed receipt classes and the soft binding class
(§2.4.3–§2.4.9, §2.6, §3.1–§3.2): claim receipts of class energy,
lawful-basis, log-inclusion, soft-binding, binding-recovery, measurement and
operating-point, and recovery through a soft binding under the ancestor rule.
Each vector declares one outcome: accept — with the claim's link_hash, which
pins the canonical form, or for a recovery the ancestor it attests — or reject at a
named §3.1 step with a named reason. A verifier that rejects for another reason, or at
another step, does not conform.
Files
| file | what it is |
|---|---|
vectors.json |
The corpus. Every vector declares one outcome, and every reason of §3.1 is exercised at least once. |
claim_verify.py |
A second verifier, written from the extension text, that shares no code with wai-rs. |
differential.py |
Generates inputs the corpus does not hold, runs both verifiers on them, and reports every input on which their verdicts differ. |
Two procedures appear. claim verifies one receipt (§3.1), with optional context the
verifier holds: previous (this signer's previous claim, step 5), object (the
delivery receipt's content_hash and joules_micro), oer (an OER receipt and its
signer keys), declaration (the claim a decision or a recovery record names),
publisher (for a recovery record: the key of a verified delivery receipt for the
declaration's subject — §2.6.3 makes the declaration context only together with it, so
a vector that gives a recovery record one without the other is malformed), and
exact_match_algs (the soft-binding algorithms the verifier has a matcher for; the
corpus's matcher is byte equality), and figure (the receipt that carries the figure a
measurement or operating-point claim names). An object context may also carry the
class its leaf is labelled with (jwp-receipts §2.2), in energy-measurement §2.7's JSON
members, read as that section reads them. A figure context is one of two forms:
claim, an energy claim's wire form, viewed under the moq-jwp:claim-link profile;
or view, a receipt the verifier has already verified, given as {profile, identity, group, figures: [{index, joules_micro, energy_* members}], signer}, so that neither
verifier needs to parse every receipt family a figure profile names. recover runs §3.2 from a declaration, the
publisher key taken from a verified delivery receipt, the observed_value, and the
object in hand's observed_hash (or null). Both procedures report
recovery_signer_mismatch for a declaration the ancestor's publisher did not sign:
recover_signer_mismatch and binding_recovery_declaration_not_publishers are the same
defect, in a recovery performed and in one recorded.
Everything is synthetic. Keys are Ed25519 from fixed seeds (0x11 publisher, 0x22 meter, 0x33 operator, 0x44 sink, 0x55 resolver) and attest nothing; figures, uncertainties and calibration references are invented; algorithm identifiers, registries, endpoints, logs and calibration certificates use reserved example domains; vocabulary terms are illustrative and no verifier here checks them against a vocabulary.
The acquisition class
An energy claim carries its class as energy-measurement §2.7 writes a class in JSON:
energy_provenance, with energy_uncertainty, energy_counter_filtering and
energy_calibration_ref present and null where the class has no such evidence.
These vectors are about the class and its evidence:
- Accepted: an
OnChipCounterfigure with a declared uncertainty (2 % over 1 s) and its filtering stateUnknown, and withOff; aCalibratedInstrumentfigure with a declared uncertainty (0.5 % over 1 s) and a calibration reference; a counter gradedderived, below what it supports; a 1 µJ figure declared at exactly the resolution floor (288,676 ppm); and a counter gradedmeasuredthat names an OER receipt grading the same figuremeasured. - Rejected at step 4: legacy
HwShunt, alone and with evidence beside it (energy_legacy_acquisition); a label in another case (energy_unknown_acquisition); and, asenergy_acquisition_evidence, a counter without its uncertainty or without its filtering state (nullis notUnknown), with a state outsideOn/Off/Unknown, or with a calibration reference; an instrument without its reference, with a reference of white space only (a space and U+3000), or with a filtering state; aModelBasedfigure with an uncertainty; an uncertainty of zero ppm, of a zero window, or of 2³² ppm; and a 1 µJ figure at 288,675 ppm, one below the floor. AnEstimatorgradedsimulatedisenergy_grade_exceeds_acquisition. - Rejected at step 2: an uncertainty without its window (
missing_required_field), with a coverage factor beside it (unknown_member), with its relative part as a string, written as an array, and a filtering state written as a number (field_type). - Rejected at step 3: a
ModelBasedclaim rewritten in transit toOnChipCounterwith an uncertainty and a filtering state: the class and its evidence are signed.
Reports about a figure: measurement and operating-point
A measurement claim publishes how one figure was measured — its bracket's values as
integers, with the liveness facts of each bracket, the build and the power reader — and
an operating-point claim records the operating point it was spent at. Both name the
receipt that seals the figure by a figure profile (energy-measurement §6.1) and restate
the figure with its class. The vectors cover:
- The arithmetic of energy-measurement §6.2: declarations at the least a bracket
supports and one ppm below it; a figure at a half microjoule, which rounds up; brackets
whose products exceed 128 and 200 bits; a calibrated instrument declaring more than the
least; and the declaration a sealing path computes from unrounded samples, which the
rule refuses. Those values are checked by
claim_verify.pywith Python's integers, and the bracket tablewai-rspins inenergy_reportis recomputed by it too. - Liveness (§3.1): a bracket whose rail advanced in too few samples; a frozen rail
refreshed once, which gives a flat idle bracket and an infinite ratio; a reader holding
a stale non-zero value, stepped once by a batch refresh, with its honest facts and with
facts claiming every sample live; a flat idle bracket whose facts claim the rail live
(a repeated sample is not an advance, so a bracket with
sd_uw0 is never live); a refresh published in two steps in each bracket, which passes a rule of two consecutive advances; facts that describe no sequence; and, accepted, aModelBasedfigure whose bracket shows a dead rail, since liveness binds the hardware classes only. - The power reader (§2.4.8 rule 4): a hardware figure's report that names no reader
is refused, with a bracket and without one; accepted, an
OnChipCounterreport with no bracket that names its reader. - Every other rule of §2.4.8 rule 5 and §2.4.9, each with its reason, and two defects at once.
- Step 2: derived quantities are never carried (a bracket with its ratio is
unknown_member); the opening and build-profile enumerations; integers, terms and absolute URIs where the tables require them. - Step 6: against an
energyclaim and against views of a provenance receipt, a delivered object (named by its leaf index and its group), and an energy binding, which no Ed25519 key signs — ameasurementclaim about it isfigure_signer_unknown, anoperating-pointclaim about it is accepted; a report by a key other than the figure's signer; and an operating point recorded against an unmetered or unlabelled figure. Ameasurementclaim checked without the receipt that carries its figure isfigure_not_held, whether a stranger or the figure's own signer signed it: only step 6 shows a report is the figure signer's. - The labelled delivery leaf: an
energyclaim against an object whose leaf carries a class, agreeing, and disagreeing in figure, class or evidence; anOnChipCounterlabel with no filtering state reads asUnknown.
Steps 1 and 2: types, and the order a body is read in
§3.1 step 2 checks exactly the type a member table gives, member by member in the
table's order; whatever a class's rules add to a type is step 4's, with the rule's own
reason. Step-4 vectors pin that boundary from the rules' side: a zero figure
(energy_zero_figure), a zero work count (energy_zero_work), an empty basis
(lawful_basis_purpose_without_basis) and an http endpoint
(soft_binding_endpoint_not_https). Others pin what the text settles on the other
side, each signed again after its edit so the edit is its only defect:
- Step 1: a byte-order mark before the text is
malformed(RFC 8259 lets a parser ignore one); aclasswritten as a number isfield_type, notunknown_class. - Step 2, types: an empty
algis not a term; an endpoint with no scheme is not an absolute URI; a work unit holding U+3000, a White_Space character outside ASCII, is not a term. Each isfield_typeat step 2:algis a term, not a string left whole to step 4's registry form; an endpoint's type is an absolute URI, and onlyhttpsis step 4's; a term's white space is Unicode's White_Space, not ASCII's. - Step 2, order: a lawful-basis body whose
recordnames neither record isfield_type, sincerecordis read first; a declaration whose vocabulary has no scheme and whose purpose has no basis isfield_type, since the table reachesvocabularyfirst and its encoding is checked as it is read; and one whose first purpose's term holds a space and whose second carries an extra member isfield_type, since each purpose is read whole before the next. A reader that checks every purpose's members before the other members, or every encoding after every member, reports these two asmissing_required_fieldandunknown_member.
The OER receipts
The OER receipts in oer contexts are sealed with wai-rs's OER module (its canonical
form and content address), and read and verified by each verifier's own OER verifier
under oer-receipts: the reference verifier
(../oer-conformance/oer2_verify.py) reports the valid ones valid, the one altered after
sealing as receipt_id_mismatch and receipt_hash_mismatch, and as malformed the six
whose figure is not a quantity oer-receipts §4 reads or whose body holds a number §4
forbids: joules_total as a decimal string of 35 significand digits, as the JSON number
0.0120005, as -0.0, as the integer 2⁵³, spelt +0.012000, and as true. Two valid
ones state the claim's figure and grade but are not about its object (an unrelated
object, and the object's hash spelt in uppercase); step 6 rejects both.
Seven vectors state a figure step 6 reads at its edges, as oer-receipts §4 reads a
quantity and compares it exactly: 0.0120005, exactly half-way, which admits 12,000 and
12,001 µJ; the same decimal written 1.20005e-2; the integer 1, one joule; and two
figures written with 34 significand digits, the most a decimal string holds — one exactly
half-way, and one (0.0120004 followed by twenty-six nines) below half-way only in its
last digits, which is 12,000 µJ and not 12,001, although rounding it to 28 significant
digits would make it a tie.
Signatures
Seven vectors test §3.1 step 3 beyond an ordinary bad signature.
- Six satisfy the verification equation — the cofactorless one, and so the
cofactored one — with the key decoded leniently (
yreduced mod p, the sign bit ignored when x = 0) andSreduced mod L. The generator asserts this for each. Step 3 refuses them on two different grounds:- RFC 8032's own rules, which step 3 restates: the identity key written with its sign
bit set, and written with
y= p + 1, each with a signature made without a private key (§5.1.3 decoding refuses both encodings; the point they spell is also of small order, so step 3's choice refuses them too); andSreplaced byS+ L (§5.1.7 requiresS< L). - The choices step 3 fixes: the identity key and a key of order 8, each "signed" with
no private key, and a genuine key signing with
Rthe identity. An RFC 8032 verifier that accepts keys andRof small order accepts these three.
- RFC 8032's own rules, which step 3 restates: the identity key written with its sign
bit set, and written with
- One does not (
signer_key_y_not_reduced): a key of large order written withy= p + j. Nobody can sign for that point, so the vector carries another key's signature, which fails the equation under any decoding (the generator asserts that too). It pins only the reason —bad_signer_key, notsignature_verify_failed— and does not show that a lenient verifier would accept a forgery.
The cryptography package's Ed25519 on its own is more lenient than RFC 8032's
decoding. Called on each vector's key, signature and signing input (cryptography
50.0.1 over OpenSSL 3.6.4, Python 3.14.7, macOS on arm64), it accepted five of the six
that satisfy the equation — both non-canonical spellings of the identity and the three
vectors of the second group — and of the seven refused only S + L and the y = p + j
vector. claim_verify.py therefore makes step 3's decoding, order and range checks
itself, and leaves only the equation to cryptography.
Running
# wai-rs: the canonical fixture, byte-identical regeneration, and every vector
cargo run --no-default-features --features receipt_claims --bin wai_claim_vectors -- verify ../claim-conformance
# the second verifier (needs Python 3 and `cryptography`)
python3 claim_verify.py
# the two verifiers' verdicts on generated inputs the corpus does not hold
cargo build --no-default-features --features receipt_claims --bin wai_claim_vectors
python3 differential.py <target-dir>/debug/wai_claim_vectors [--random N] [--seed S] [--keep FILE]
CI runs the first on the conformance matrix (Linux x86_64, Linux arm64, macOS arm64), and the second
and third on the linux runner, with a fixed seed, in .github/workflows/wai-conformance.yml.
differential.py writes its inputs in the corpus's format with no declared outcome
and runs each verifier's verdicts mode (wai_claim_vectors verdicts <file>,
claim_verify.py verdicts <file>), which prints one verdict per input. Its families:
JSON numbers and strings at the limits of what parsers accept, nesting depth, calendar
dates, URI authorities, acquisition classes with every combination of label,
uncertainty, filtering state and calibration reference around energy-measurement
§2.3–§2.5's rules (calibration references of every White_Space character, and of
U+001C, U+001F, U+200B and U+FEFF, which are not White_Space), OER figures near a
half-microjoule and at the edges of oer-receipts §4's quantities, OER receipts whose sums
and floors sit at §6's tolerances, OER receipts holding numbers §4 forbids in a body
(-0, fractions, exponents, integers beyond I-JSON's range) in members of every kind, a
delivered object's microjoules spelt as the corpus never gives them, context receipts
that do not themselves verify, bodies of every class with one defect from a fixed list
and with every pair of those at two different positions (a member, or a member of a
nested object), where the order §3.1 step 2 reads a body in decides the reason, and, with
--random N, a seeded random sample of dates, endpoints, figures, sums and class
combinations. Separately, each value that makes a whole file unreadable under
oer-receipts §4 — a number beyond the double range, NaN, an unpaired surrogate escape,
a repeated member name, nesting past 64 levels — goes in a file of its own beside
readable twins, and the two verifiers must both refuse the file or give it the same
verdicts. It exits 0 only if the two verdict lists are identical, every file is read or
refused alike, and neither verifier crashed; any difference exits 1, at whatever step.
Both verifiers read a corpus-format file as oer-receipts §4 reads a vector document,
since it holds OER receipts: wai-rs with oer::load, claim_verify.py with the OER
reference's load. A difference where one side reports energy_oer_invalid is marked,
since its cause is in the two OER verifiers rather than the claim rules, but it counts
the same. Those two are compared on their own by ../oer-conformance/fuzz_differential.py.
What each check can and cannot show
The expected outcomes are declared per vector in the generator (wai-rs,
claim_receipt::corpus), not copied from a verifier's output. The signed bytes,
though, are produced by the same module that verifies them, so a canonicalisation
defect would be baked into every signature and link hash and then confirmed. Two
checks close that:
- The canonical fixture. An
OnChipCounterclaim given with members out of order at three levels, whitespace, and\uescapes in the input text. Its canonical bytes, signature and link hash were computed byclaim_verify.py fixture— Python's JSON serialiser with members ordered by UTF-16 code units,cryptography's Ed25519, and BLAKE3 written from its specification — and are pinned as literals inwai-rs(check_canonical_form), whichwai_claim_vectors verifychecks first. Reversing the member order, or changing the signing domain, fails it. - The second verifier.
claim_verify.pyreports every vector's declared outcome and every pinned link hash, including a claim whose canonical form spans three BLAKE3 chunks, and checks that the fixture it computes equals the literals inwai-rs. Its §3.1 step 3 checks other than the equation — RFC 8032 key decoding, the order of the key and ofR, andS< L (whichcryptographyalso checks) — are written from the curve's definition; the equation iscryptography's. Its acquisition-class rule is written from energy-measurement §2.3–§2.5, wherewai-rsadmits the class throughcrate::energy_class.
The corpus shows the two verifiers agree on its vectors and nothing more.
differential.py checks agreement on inputs no vector declares an outcome for: that
the two read the same inputs the same way. It does not show that either reading is the
extension's, and it covers only the families it generates.
The second verifier and the differential were written by the same author as wai-rs,
in a different language with no shared code. They catch disagreements in canonical
form, hashing, signatures and the reading of JSON, dates, hosts, classes and figures;
they are weaker evidence against a misreading of the extension that both verifiers share.
Two of the order vectors above (lawful_basis_vocabulary_before_purposes,
lawful_basis_purposes_in_order) exist because of one: both verifiers once read a
declaration's purposes before its other members, which §3.1 step 2 does not say, and
agreed with each other throughout. It was found by reading the text against the code;
neither check could have shown it.
Regenerating
cargo run --no-default-features --features receipt_claims --bin wai_claim_vectors -- gen ../claim-conformance
Ed25519 signing is deterministic and the seeds are fixed, so the output is
byte-identical on every machine; verify fails if the pinned file differs from it.