open-standards/wai/claim-conformance/differential.py
Transaction Science 5ee4c7e28b
Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 1m39s
standards workspaces build + test / aac-rs (pull_request) Successful in 32s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 1m8s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 37s
standards workspaces build + test / map-rs (pull_request) Successful in 40s
standards workspaces build + test / mesh-rs (pull_request) Successful in 35s
standards workspaces build + test / openpay (pull_request) Successful in 2m53s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 24s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Failing after 42s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 26s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 50s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m48s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m17s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m6s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m48s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m34s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 22s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 2m0s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 2m20s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 26s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 1m58s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 44s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 44s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 2m5s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m53s
wai formal verification (integer core) / kani (pull_request) Successful in 4m4s
standards workspaces build + test / macOS energy backend (jcp-energy) (pull_request) Successful in 1m50s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 54s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 22s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Failing after 1m11s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 26s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 2m29s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 22s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 58s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 2m35s
wai cross-architecture byte-equality / windows-x86_64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · windows (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · windows (pull_request) Has been cancelled
wai: typed receipt classes and the soft binding class, reading OER through the OER module
A JWP receipt says what was delivered, by whom and at what energy cost, and
binds to its object one way. Other statements about the same objects had
nowhere to go, and energy-measurement §2 requires an acquisition class beside
every non-zero figure, which the object receipt has no member for.
jwp-receipts §2.4.3-§2.4.7 adds a `claim` kind with one envelope, one
canonical form (RFC 8785) and one signature rule for the classes energy,
lawful-basis and log-inclusion. §2.6 and §3.2 add a second, deliberately
weaker binding class: a publisher-signed soft binding with a resolution
endpoint, under the ancestor rule. §3.1 step 3 fixes a strict Ed25519 rule. A
declaration that a decision or a recovery record is checked against is
verified before anything is compared with it (`context_invalid`).

OER as settled on main is authoritative: this adds no second OER reader and
changes no OER rule, vector, verifier or document. An OER receipt an energy
claim names is read by oer::load under the document profile, verified by
oer::verify_receipt, and its figure read by the exact quantity reader the
composition checks use, through a new crate-private oer::rounds_to_micro.
claim_verify.py does the same with the OER reference's load, verify_receipt
and dec. The claim envelope keeps its own step-1 reader (claim_text.rs),
which reads claims only.

- Acquisition classes are the refined ones. The energy body carries the class
  as energy-measurement §2.7 writes it in JSON (energy_provenance,
  energy_uncertainty, energy_counter_filtering, energy_calibration_ref),
  present and null where a class has none, so a claim keeps one canonical
  form. Legacy HwShunt is refused, since no claim predates the refinement
  (energy_legacy_acquisition); missing, misplaced or sub-floor evidence is
  energy_acquisition_evidence. The grade correspondence is oer-receipts §2's,
  an exhaustive match over all five variants.
- §3.1 step 2's member tables give exactly the type step 2 checks, and step 2
  lists what the class rules add to a type as step 4's, never field_type. Both
  verifiers read a declaration in its table's order (they had read purposes
  before vocabulary and fragment, and every encoding after every member, so a
  declaration with two defects gave another reason than the text).
- oer::py_float_repr writes a double at a tie between two shortest decimals as
  CPython's repr does (round half to even at the shortest length). No content
  address or verdict depended on it: main's number profile keeps floats out of
  every receipt body. It changed the printed name of a vector named by such a
  number, which both OER verifiers print and the OER fuzzer compares. Fourteen
  doubles CPython 3.14.7 wrote are pinned.
- claim-conformance/: 154 vectors covering all 40 reasons, the canonical
  fixture recomputed by claim_verify.py and pinned, a second verifier
  (claim_verify.py) that shares no code with wai-rs, and differential.py,
  which runs both on inputs the corpus does not hold. receipt_claims joins the
  conformance library tests and both corpus loops.

Checked on this machine (macOS arm64) on the stream's tree:
- library tests with receipt_claims (dev; oer + receipt_claims also release);
- wai_claim_vectors and wai_oer_vectors verify their corpora;
- claim_verify.py reports every vector's declared outcome;
- differential.py: its default families and 20,000 random inputs (seed 7):
  no verdict differs and nothing crashes;
- oer2_verify.py passes every OER vector file and refuses every unreadable
  document, the C kernel's output equals firmware_vectors.json, and
  fuzz_differential.py (seed 1, 20,000 documents) finds no divergence.
And on this commit's tree, the per-link gate: build, library tests,
carriage, every corpus in the conformance loop, byte-exact, wasm32.

Not run here: the Linux and Windows legs (CI runs Linux; the Windows runner
is offline). The Python scripts are run by hand, not in CI.
Not done: key discovery (every claim signature presumes the verifier holds
the key), an authority class for who may sign which class, region- or
time-scoped soft bindings, and checking a log's receipt under its profile.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 01:42:45 -04:00

789 lines
45 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Differential check: wai-rs and claim_verify.py give the same verdict on inputs the corpus
does not hold.
The corpus pins one declared outcome per vector, which shows both verifiers agree on those
vectors and nothing more. This script generates inputs around the edges where two readings
of jwp-receipts §3.1-§3.2 could part — JSON numbers and strings at the limits of what a
parser accepts, nesting, calendar dates, URI authorities, acquisition classes with every
combination of evidence around energy-measurement §2.3-§2.5's rules, 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, integers beyond I-JSON's range) in members of every kind, context
receipts that do not themselves verify, and bodies of every class with one defect, or two
at different positions, where the order §3.1 step 2 reads a body in decides the reason —
writes them in the corpus's format with no declared outcome, runs both verifiers'
`verdicts` mode, and reports every input on which they differ.
Some inputs cannot share a file with the rest: a number whose nearest double is infinite,
`NaN`, an unpaired surrogate, a repeated member name or nesting past 64 levels makes a
whole file unreadable (oer-receipts §4 "The document"), for both verifiers or — if they
differ — for one. Each of those is written to a file of its own with a readable twin beside
it, and the two verifiers must either both refuse the file or give the same verdicts on it.
python3 differential.py <wai_claim_vectors binary> [--random N] [--seed S] [--keep FILE]
Keys are the corpus's fixed seeds, so every signed input is reproducible. Exit status 0
only if the two verdict lists are identical, every file of its own is read or refused
alike, and neither verifier printed `crash`; any difference, whatever step it is at,
exits 1.
An energy claim that names an OER receipt reaches the OER verifier of its own language
(§2.4.5 check 2): wai-rs's `crate::oer`, and the reference `oer2_verify.py`. A difference
there is still a difference in the claim verdict, and is reported like any other; the
listing marks it, since its cause is in the two OER verifiers rather than the claim rules.
Those two are compared on their own by `../oer-conformance/fuzz_differential.py`.
"""
import argparse, copy, io, json, os, random, subprocess, sys, tempfile
from decimal import Decimal, localcontext
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import claim_verify as CV # noqa: E402
from cryptography.hazmat.primitives import serialization # noqa: E402
from cryptography.hazmat.primitives.asymmetric import ed25519 # noqa: E402
OER = CV.OER
def key(seed):
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes([seed] * 32))
def pk(k):
return k.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw).hex()
PUBLISHER, METER, OPERATOR, VIEWER, RESOLVER = (key(s) for s in (0x11, 0x22, 0x33, 0x44, 0x55))
OER_SIGNER = "ed25519:claim-conformance-meter"
WM_ALG = "org.example.watermark.1"
def sign(c, k):
"""`c` with its signature made under `k` over §2.4.3's signing input."""
c = copy.deepcopy(c)
c.pop("sig", None)
c["signer_pubkey"] = pk(k)
c["sig"] = k.sign(CV.signing_input(c)).hex()
return c
RAW = {}
def raw(spelling):
"""A JSON value written into the input file exactly as `spelling` — `-0`, an integer
beyond 64 bits, a string with an unpaired surrogate escape — where json.dumps would
write another spelling or none. A placeholder string until text_of() writes it."""
k = f"\u0000raw{len(RAW)}\u0000"
RAW[k] = spelling
return k
def text_of(v, **kw):
"""json.dumps(v, ensure_ascii=True), every raw() placeholder written as its spelling."""
t = json.dumps(v, ensure_ascii=True, **kw)
if "\\u0000raw" in t:
for k, spelling in RAW.items():
t = t.replace(json.dumps(k), spelling)
return t
def seal_oer(template, joules_total, terms=None, floor=None, edit=None):
"""`template` recording `joules_total`, re-sealed and signed as oer-receipts §4-§5
define. The breakdown is `terms` (compute_j first) or, by default, `joules_total` as
compute_j with the other terms as the template has them (zero); `floor`, if given, is
(bits_committed, temperature_k, floor_j); `edit`, if given, changes the receipt last.
The content address is computed over the receipt as the file will be read: its text,
read as §4 reads a document (`OER.load`). A receipt whose body holds a number §4
forbids is sealed all the same; both verifiers must refuse it before its address."""
r = copy.deepcopy(template)
r.pop("chain", None)
r.pop("receipt_id", None)
r["energy"]["joules_total"] = joules_total
b = r["energy"]["breakdown"]
for k, t in zip(list(b), terms if terms is not None else [joules_total]):
b[k] = t
if floor is not None:
r["floor"]["bits_committed"], r["energy"]["temperature_k"], r["floor"]["floor_j"] = floor
if edit is not None:
edit(r)
body = OER.canonical_bytes(OER.load(io.StringIO(text_of(r))))
prev = "sha256:" + "0" * 64
rh = OER.h("sha-256", prev.encode() + body)
r["chain"] = {"prev_hash": prev, "receipt_hash": rh, "signer": OER_SIGNER,
"signature": METER.sign(rh.encode()).hex()}
r["receipt_id"] = OER.h("sha-256", body)
return r
class Inputs:
def __init__(self, corpus):
self.by_name = {v["name"]: v for v in corpus["vectors"]}
self.out = []
def receipt(self, name):
return json.loads(self.by_name[name]["receipt"])
def wire(self, name):
return self.by_name[name]["receipt"]
def add(self, name, receipt=None, context=None, procedure="claim"):
v = {"name": f"{len(self.out):05d}_{name}", "procedure": procedure}
if receipt is not None:
v["receipt"] = receipt
if context:
v["context"] = context
self.out.append(v)
# ------------------------------------------------------------------ input families
NUMBER_TOKENS = [
"0", "-0", "1", "-1", "9007199254740991", "9007199254740992", "18446744073709551615",
"18446744073709551616", "1" + "0" * 30, "1" + "0" * 308, "1" + "0" * 309, "1" + "0" * 400,
"-" + "1" + "0" * 400, "0.0", "-0.0", "1.0", "1e0", "1E2", "1e-2", "1e308", "1e309", "1e400",
"-1e400", "1E400", "1e-400", "1.7976931348623157e308", "1.7976931348623158e308",
"1.7976931348623159e308", "1.8e308", "2e308", "1e99999999999999999999",
"179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791",
"01", "+1", ".5", "1.", "1e", "1e+", "--1", "0x10", "NaN", "Infinity", "-Infinity", "1_0",
"1", "١",
]
STRING_BODIES = [
"\\ud800", "\\udc00", "\\udbff\\udfff", "\\ud800\\u0041", "\\ud83d\\ude00", "\\ud83d", "a\\u0000b",
"\\u00e9", "\\u00E9", "\\/", "\x01", "\x7f", "
", "", "￿", "﷐", "\\ud800x",
"\\uDFFF", "\\u", "\\u12", "\\x41", "\\'", "tab\there",
]
def numbers(ins):
wire = ins.wire("energy_accept")
for t in NUMBER_TOKENS:
ins.add("seq_" + t[:24], wire.replace('"seq":0', '"seq":' + t, 1))
ins.add("work_units_" + t[:24], wire.replace('"work_units":49152', '"work_units":' + t, 1))
def strings(ins):
wire = ins.wire("energy_accept")
for s in STRING_BODIES:
ins.add("signer_id_" + s.encode("unicode_escape").decode()[:24],
wire.replace('"did:example:meter"', '"' + s + '"', 1))
ins.add("member_name_" + s.encode("unicode_escape").decode()[:24],
wire.replace('{"body":', '{"' + s + '":1,"body":', 1))
# A validly signed claim whose signer_id holds characters outside the BMP and a control
# character, sent with every such character escaped: canonicalisation undoes escapes.
c = sign(dict(ins.receipt("energy_accept"), signer_id="did:example:m\U0001F600é\u001f"), METER)
escaped = json.dumps(c, ensure_ascii=True, separators=(", ", ": "))
ins.add("escaped_astral_signer_id", escaped)
ins.add("canonical_astral_signer_id", CV.jcs(c))
ins.add("bom_prefixed", "" + wire)
ins.add("trailing_garbage", wire + " x")
ins.add("trailing_whitespace", wire + " \n\t\r")
ins.add("nbsp_whitespace", " " + wire)
ins.add("empty", "")
def nesting(ins):
wire = ins.wire("energy_accept")
for d in list(range(1, 40)) + [63, 64, 65, 126, 127, 128, 129, 200, 999, 1001, 5000]:
ins.add(f"nesting_{d}", wire.replace('"did:example:meter"', "[" * d + "]" * d, 1))
ins.add("nesting_obj_40", wire.replace('"did:example:meter"', '{"a":' * 40 + "1" + "}" * 40, 1))
def dates(ins, rng, n):
base = ins.receipt("energy_accept")
fixed = ["2026-02-28T00:00:00Z", "2026-02-29T00:00:00Z", "2028-02-29T00:00:00Z", "2100-02-29T00:00:00Z",
"2000-02-29T00:00:00Z", "1900-02-29T00:00:00Z", "0000-02-29T00:00:00Z", "0004-02-29T00:00:00Z",
"2026-04-31T00:00:00Z", "2026-04-30T23:59:60Z", "2026-12-31T23:59:60+14:00", "2026-01-00T00:00:00Z",
"2026-00-10T00:00:00Z", "2026-13-01T00:00:00Z", "2026-09-27T24:00:00Z", "2026-09-27T12:60:00Z",
"2026-09-27T12:00:61Z", "2026-09-27T12:00:00.Z", "2026-09-27T12:00:00.000000001Z",
"2026-09-27t12:00:00z", "2026-09-27T12:00:00-00:00", "2026-09-27T12:00:00+24:00",
"2026-09-27T12:00:00+23:60", "2026-09-27 12:00:00Z", "2026-9-27T12:00:00Z",
"2026-09-27T12:00:00Z", "2026-09-2٧T12:00:00Z", "2026-09-27T12:00:00Z",
"+2026-09-27T12:00:00Z", "2026-09-27T12:00:00Z\n", "2026-09-31T12:00:00Z", "2026-06-31T12:00:00Z",
"2026-11-31T12:00:00Z", "2026-01-31T12:00:00Z", "2026-03-31T12:00:00Z"]
cases = list(fixed)
for _ in range(n):
y, mo, d = rng.randrange(0, 10000), rng.randrange(0, 14), rng.randrange(0, 33)
cases.append(f"{y:04d}-{mo:02d}-{d:02d}T{rng.randrange(0, 25):02d}:{rng.randrange(0, 61):02d}:"
f"{rng.randrange(0, 62):02d}{rng.choice(['Z', '+05:30', '-12:00', '.5Z', 'z'])}")
for t in cases:
ins.add("issued_at_" + t.encode("unicode_escape").decode()[:32], CV.jcs(sign(dict(base, issued_at=t), METER)))
def endpoints(ins, rng, n):
base = ins.receipt("soft_binding_accept")
fixed = ["https://resolver.example.org/matches", "https://:443/m", "https://:/m", "https://resolver.example.org:/m",
"https://resolver.example.org:8443/m", "https://resolver.example.org:x/m", "https://resolver.example.org:1:2/m",
"https://[2001:db8::1]:8443/m", "https://[2001:db8::1]/m", "https://[]/m", "https://[/m", "https://[::1/m",
"https://::1]/m", "https://[::1]x/m", "https://[::1]:/m", "https://[::1]:x/m", "https://h]:1/m",
"https://[email protected]/m", "https://@/m", "https://resolver.example.org",
"https://resolver.example.org?q", "https://resolver.example.org#f", "https://?q", "https://#f", "https://",
"https:///m", "HTTPS://resolver.example.org/m", "http://resolver.example.org/m", "https:resolver.example.org",
"https://résolver.example.org/m", "https://resolver.example.org:8/m", "https://1.2.3.4:5/m",
"https://%72esolver.example.org/m", "https://a/b@c"]
alphabet = "ab:[]@/?#1%."
cases = list(fixed) + ["https://" + "".join(rng.choice(alphabet) for _ in range(rng.randrange(0, 9))) for _ in range(n)]
for e in cases:
body = dict(base["body"], resolution_endpoint=e)
ins.add("endpoint_" + e.encode("unicode_escape").decode()[:32], CV.jcs(sign(dict(base, body=body), PUBLISHER)))
FIGURES = ["0.0120005", "0.012000500000000000000000000000000", "0.0120004999999999999999999999999999",
"0.0120005000000000000000000000000000", "0.012000500000000000000000000000000000000000000",
"0.0120004999999999999999999999999999999999999999", "0.012000", "0.0120004", "0.0120006", "1.20005e-2",
"120005e-7", "1.20005E-2", "1.20005e-02", "1.20005e-002", "1.20005e-0002", "12000.5e-6",
"0.0000000000000000000000000000000000000000000000120005e43",
"+0.0120005", " 0.0120005", "0.0120005 ", "00.0120005", ".0120005", "0.0120005e", "0_0120005", "1_2",
"0.0120005", "NaN", "Infinity", "-0.0120005", "0", "-0", "0e0", "1e-999", "1e999", "1e-1000",
"12000500000000000000000000000000000000000000000e-51", "0.00000050", "0.0000005", "0.0000015", "0.0000025",
"18446744073709.5516155", "18446744073709.551615", "9007199254.740991", "9007199254.7409915"]
# JSON numbers: §4 admits integers within I-JSON's range in a body and nothing else, so every
# fraction, `-0` and integer beyond the range here must make the receipt malformed. Among
# them, doubles half-way between two shortest decimals, which a formatter could write two
# ways — no receipt body may carry one, so no content address may depend on it.
NUMERIC_FIGURES = [0.0120005, 0.012, 0.0120004, 0.0120006, 0.0000025, 1.5e-6, 2.5e-6, 12, 0, -0.0120005, 1e-300,
9007199254.740991, 1.2000500000000001e-2, True, False, None, -0.0,
1e37, 9.999999999999999e37, 1e38, 1e-38, 9.9e-39, 1.7976931348623157e308, 5e-324,
2 ** 53 - 1, 2 ** 53, -(2 ** 53 - 1), -(2 ** 53), 2 ** 63 - 1, 2 ** 64 - 1, 2 ** 64, 10 ** 30,
75.036163330078125, 2365.15167236328125, 5.9604644775390625e-8, 983123238.6575775]
# §4's decimal strings at their limits: 34 significand digits and a three-digit exponent.
RANGE_FIGURES = ["9" * 34, "9" * 35, "0." + "0" * 32 + "1", "0." + "0" * 33 + "1", "-" + "9" * 34, "1e999", "1e-999",
"9.999999999999999999999999999999999e999", "9.9999999999999999999999999999999999e999",
"0.0120004" + "9" * 26, "0.0120004" + "9" * 27, "0.0120005" + "0" * 26, "0.0120005" + "0" * 26 + "1"]
LABELS = ["OnChipCounter", "CalibratedInstrument", "ModelBased", "Estimator", "HwShunt", "onchipcounter",
"HWSHUNT", "Measured", "", " OnChipCounter", "OnChipCounter\u200b"]
UNCERTAINTIES = [None, {"relative_ppm": 20000, "window_us": 1000000}, {"relative_ppm": 0, "window_us": 1},
{"relative_ppm": 1, "window_us": 0}, {"relative_ppm": 2 ** 32 - 1, "window_us": 1},
{"relative_ppm": 2 ** 32, "window_us": 1}, {"relative_ppm": 2 ** 53 - 1, "window_us": 2 ** 53 - 1},
{"relative_ppm": 288676, "window_us": 1}, {"relative_ppm": 288675, "window_us": 1},
{"relative_ppm": 144338, "window_us": 1}, {"relative_ppm": 144337, "window_us": 1},
{"relative_ppm": 20000}, {"window_us": 1}, {}, {"relative_ppm": 20000, "window_us": 1, "k": 2},
{"relative_ppm": "20000", "window_us": 1}, [20000, 1], "20000", 20000, True]
FILTERINGS = [None, "On", "Off", "Unknown", "unknown", "", "Maybe", 0, False]
# A calibration reference is blank when it is White_Space only (energy-measurement §2.5, as
# the reference implementation trims it); U+001C to U+001F, U+200B and U+FEFF are not.
CALIBRATION_REFS = [None, "cert 7", "", " ", "\t", "\u3000", "\u0085", "\u00a0", "\u2028", "\u001c", "\u001f",
"\u200b", "\ufeff", " x ", "https://calibration.example.org/certificates/0001", 7]
CLASS_FIGURES = [1, 2, 14, 15, 12000, 2 ** 53 - 1, 0]
def classes(ins, rng, n):
"""§2.4.5 rule 2 and its interaction with rule 5: energy claims with every acquisition
label, uncertainty, filtering state and calibration reference above in fixed
combinations — each field varied alone from a valid counter, a valid instrument and a
ModelBased figure — and `n` random combinations of all four with a figure and a grade.
Each is signed, so its verdict is decided at step 2, 4 or later, never by the signature."""
base = ins.receipt("energy_accept")
bases = {
"OnChipCounter": dict(base["body"], energy_provenance="OnChipCounter",
energy_uncertainty={"relative_ppm": 20000, "window_us": 1000000},
energy_counter_filtering="Unknown", grade="measured"),
"CalibratedInstrument": dict(base["body"], energy_provenance="CalibratedInstrument",
energy_uncertainty={"relative_ppm": 5000, "window_us": 1000000},
energy_calibration_ref="cert 7", grade="measured"),
"ModelBased": dict(base["body"], oer_receipt=None),
}
cases = []
for bname, b in bases.items():
b = dict(b, oer_receipt=None)
cases += [(f"{bname}_label_{i}", dict(b, energy_provenance=x)) for i, x in enumerate(LABELS)]
cases += [(f"{bname}_uncertainty_{i}", dict(b, energy_uncertainty=x)) for i, x in enumerate(UNCERTAINTIES)]
cases += [(f"{bname}_filtering_{i}", dict(b, energy_counter_filtering=x)) for i, x in enumerate(FILTERINGS)]
cases += [(f"{bname}_calibration_{i}", dict(b, energy_calibration_ref=x)) for i, x in enumerate(CALIBRATION_REFS)]
cases += [(f"{bname}_figure_{i}", dict(b, joules_micro=x)) for i, x in enumerate(CLASS_FIGURES)]
cases += [(f"{bname}_grade_{g}", dict(b, grade=g)) for g in OER.GRADES + ["certified"]]
for i in range(n):
cases.append((f"random_{i}", dict(base["body"], oer_receipt=None, energy_provenance=rng.choice(LABELS),
energy_uncertainty=rng.choice(UNCERTAINTIES),
energy_counter_filtering=rng.choice(FILTERINGS),
energy_calibration_ref=rng.choice(CALIBRATION_REFS),
joules_micro=rng.choice(CLASS_FIGURES), grade=rng.choice(OER.GRADES))))
for name, body in cases:
ins.add("class_" + name, CV.jcs(sign(dict(base, body=body), METER)))
# §3.1 step 2 reads a body's members, then each member's type and encoding together in its
# table's order, a nested object's members where the table reaches it and a lawful-basis
# body's `record` first. One defect shows where each is caught; two at different positions
# show the order. Per accepted corpus claim: its signer and, in table order, each position
# (a member, or a member of a nested object) with the values put there. DELETE removes the
# member; a position ending in EXTRA adds a member the table does not have. Values stay in
# what the canonical form can write, so every input is signed — numbers at the edges of
# what a claim may hold are the `numbers` family's.
DELETE = object()
EXTRA = "+"
BODY_DEFECTS = {
"lawful_basis_declaration_accept": ("OPERATOR", [
(("record",), ["consent", "decision", 5, DELETE]),
(("vocabulary",), [5, None, "w3id.org/dpv", "", "https://w3id.org/ dpv", DELETE]),
(("fragment",), [5, "zz", "AB" * 32, "ab", DELETE]),
(("purposes",), ["x", [], [5], {}]),
(("purposes", 0, "purpose"), [5, "dpv:A B", "", "dpv:\u3000", DELETE]),
(("purposes", 0, "basis"), [5, None, "", "a b", DELETE]),
(("purposes", 0, "data"), ["x", [5], [""], ["a b"], [], DELETE]),
(("purposes", 0, EXTRA), [1]),
(("purposes", 1, "purpose"), [5, "dpv:A B", "dpv:ServiceProvision", DELETE]),
(("purposes", 1, "basis"), [5, "", "a\tb", DELETE]),
(("purposes", 1, "data"), [[5], ["x y"], DELETE]),
(("purposes", 1, EXTRA), [1]),
((EXTRA,), [1]),
]),
"lawful_basis_decision_accept": ("VIEWER", [
(("record",), ["declaration", "x", 5, DELETE]),
(("vocabulary",), [5, "dpv", DELETE]),
(("declaration",), [5, "zz", "AB" * 32, DELETE]),
(("purpose",), [5, "", "a b", DELETE]),
(("outcome",), [5, "", DELETE]),
((EXTRA,), [1]),
]),
"energy_on_chip_counter_accept": ("METER", [
(("joules_micro",), ["1", 0, True, None, DELETE]),
(("energy_provenance",), [5, "Measured", "HwShunt", DELETE]),
(("energy_uncertainty",), ["x", [1, 1], None, {}, DELETE]),
(("energy_uncertainty", "relative_ppm"), ["1", 0, DELETE]),
(("energy_uncertainty", "window_us"), [None, 0, DELETE]),
(("energy_uncertainty", EXTRA), [2]),
(("energy_counter_filtering",), [5, None, "Maybe", DELETE]),
(("energy_calibration_ref",), [5, "cert", DELETE]),
(("coverage",), [5, "Most", DELETE]),
(("grade",), [5, "certified", DELETE]),
(("method",), [5, "x", "", DELETE]),
(("work_units",), ["1", 0, DELETE]),
(("work_unit",), [5, "", "a b", DELETE]),
(("oer_receipt",), [5, "md5:1", DELETE]),
((EXTRA,), [1]),
]),
"soft_binding_accept": ("PUBLISHER", [
(("alg",), [5, "", "a b", "bare", DELETE]),
(("registry",), [5, "x", DELETE]),
(("binding_type",), [5, "hash", DELETE]),
(("value",), [5, "zz", "ABCD", "", DELETE]),
(("resolution_endpoint",), [5, "x", "http://x/", "https://u@x/", DELETE]),
((EXTRA,), [1]),
]),
"binding_recovery_accept": ("RESOLVER", [
(("alg",), [5, "", "bare", DELETE]),
(("binding_type",), [5, "hash", DELETE]),
(("value",), [5, "zz", "", DELETE]),
(("declaration",), [5, "zz", DELETE]),
(("resolution_endpoint",), [5, "x", "http://x/", DELETE]),
(("observed",), [5, "zz", DELETE]),
(("attests",), [5, "object", DELETE]),
((EXTRA,), [1]),
]),
"log_inclusion_accept": ("PUBLISHER", [
(("log",), [5, "x", DELETE]),
(("profile",), [5, "", DELETE]),
(("entry",), [5, "", "a b", DELETE]),
(("log_receipt",), [5, "zz", "", DELETE]),
((EXTRA,), [1]),
]),
}
def edit_at(body, path, value):
"""Put `value` at `path` in `body` (DELETE removes the member there; a path ending in
EXTRA adds the member `note`). False if an earlier edit left no object on the path."""
*parents, last = path
node = body
for p in parents:
if isinstance(p, int):
if not (isinstance(node, list) and p < len(node)):
return False
elif not (isinstance(node, dict) and p in node):
return False
node = node[p]
if not isinstance(node, dict):
return False
if last == EXTRA:
node["note"] = value
elif value is DELETE:
node.pop(last, None)
else:
node[last] = value
return True
def bodies(ins):
keys = {"OPERATOR": OPERATOR, "VIEWER": VIEWER, "METER": METER, "PUBLISHER": PUBLISHER, "RESOLVER": RESOLVER}
for name, (signer, positions) in BODY_DEFECTS.items():
base = ins.receipt(name)
singles = [(path, i, value) for path, values in positions for i, value in enumerate(values)]
cases = [[s] for s in singles]
for a, first in enumerate(singles):
for second in singles[a + 1:]:
p, q = first[0], second[0]
if p[:len(q)] != q and q[:len(p)] != p: # different positions, neither inside the other
cases.append([first, second])
for case in cases:
body = copy.deepcopy(base["body"])
if not all(edit_at(body, path, value) for path, _, value in case):
continue
label = "+".join(".".join(map(str, path)) + f"#{i}" for path, i, _ in case)
ins.add(f"body_{name[:-len('_accept')]}_{label}", CV.jcs(sign(dict(base, body=body), keys[signer])))
def figures(ins, rng, n):
oer_t = ins.by_name["energy_matches_oer_accept"]["context"]["oer"]["receipt"]
claim_t = ins.receipt("energy_matches_oer_accept")
keys = {OER_SIGNER: pk(METER)}
cases = [(f, uj) for f in FIGURES + NUMERIC_FIGURES + RANGE_FIGURES for uj in (1, 12000, 12001, 11999, 2, 3)]
for _ in range(n):
uj = rng.randrange(1, 10 ** rng.randrange(1, 16)) # a claim's integers stop at 2^53 - 1
frac = "".join(rng.choice("0123456789") for _ in range(rng.randrange(0, 50)))
pick = rng.randrange(4)
if pick == 0: # exactly half-way, with trailing zeros
f = f"{uj // 10**6}.{uj % 10**6:06d}5" + "0" * rng.randrange(0, 40)
elif pick == 1: # just either side of half-way
f = f"{uj // 10**6}.{uj % 10**6:06d}{rng.choice(['49', '50', '51'])}{frac}"
elif pick == 2: # exponent form of the same digits
s = f"{uj}5{frac}"
f = f"{s}e-{len(s) - len(str(uj)) + 6}"
else: # a double near the figure
f = (uj + rng.choice([0.5, 0.4999999, 0.5000001, 0.25])) / 1e6
cases.append((f, rng.choice([uj, uj + 1, uj - 1 if uj > 1 else uj])))
for f, uj in cases:
r = seal_oer(oer_t, f)
body = dict(claim_t["body"], joules_micro=uj, oer_receipt=r["receipt_id"])
c = sign(dict(claim_t, body=body), METER)
ins.add(f"figure_{str(f)[:40]}_uj{uj}", CV.jcs(c), {"oer": {"receipt": r, "keys": keys}})
def sums(ins, rng, n):
"""OER receipts whose breakdown or floor exercises §6's exact arithmetic: large terms
that cancel, differences at and one unit past the 1e-9 tolerance, floors exactly on
the 1% bound, and quantities at §4's limits. The claim names each receipt with the
figure's microjoules, so the verdict turns on whether each OER verifier accepts the
receipt."""
oer_t = ins.by_name["energy_matches_oer_accept"]["context"]["oer"]["receipt"]
claim_t = ins.receipt("energy_matches_oer_accept")
keys = {OER_SIGNER: pk(METER)}
e37 = "1" + "0" * 37
cases = [
("cancel_1e24", "0.012", ["1000000000000000000000000.012", "-1000000000000000000000000", "0", "0", "0"], None),
("cancel_1e30", "0.012", ["1000000000000000000000000000000.012", "-1000000000000000000000000000000", "0", "0", "0"], None),
("cancel_1e37", "0.012", ["10000000000000000000000000000000000000.012", "-" + e37, "0", "0", "0"], None),
("wide_1e37_1e-38", e37, [e37, "0." + "0" * 37 + "1", "0", "0", "0"], None),
("at_tolerance", "9999999990000000000000000000000000000", [e37, "0", "0", "0", "0"], None),
("past_tolerance", "9999999989999999999999999999999999999", [e37, "0", "0", "0", "0"], None),
("at_tolerance_unit", "1.000000001", ["2", "-1", "0", "0", "0"], None),
("past_tolerance_unit", "1.000000001000000000000000000000000001", ["2", "-1", "0", "0", "0"], None),
("negative_total", "-0.012", ["-0.012", "0", "0", "0", "0"], None),
("floor_tiny_temperature", "0.012", None, (256, "1e-38", "0")),
("floor_huge_temperature", "0.012", None, (256, "9e37", "1.6e+17")),
("floor_zero_bits", "0.012", None, (0, "300", "0")),
("floor_zero_bits_tiny_floor_j", "0.012", None, (0, "300", "1e-38")),
("floor_bits_beyond_64", "0.012", None, (2 ** 64, "300", "1e-18")),
("floor_bits_float", "0.012", None, (256.0, "300", "1.838e-18")),
]
# floor_j exactly on each 1% bound, and one unit of its last digit either side: §6's
# check is exact, so a difference exactly at the tolerance passes and one past it does
# not, in both verifiers or neither.
with localcontext(OER.EXACT):
for bits in (1, 256, 510, 4096, 2 ** 40):
for temp in ("300", "3.000000e+02", "0.001", "77.5"):
exact = Decimal(bits) * Decimal("1.380649e-23") * Decimal(temp) * Decimal("0.6931471805599453")
for side in ("0.99", "1.01"):
on = exact * Decimal(side)
unit = Decimal((0, (1,), on.as_tuple().exponent))
for name, fj in (("on", on), ("below", on - unit), ("above", on + unit)):
cases.append((f"floor_{name}_bound_{bits}_{temp}_{side}", "0.012", None, (bits, temp, format(fj, "e"))))
def term():
whole = "".join(rng.choice("0123456789") for _ in range(rng.randrange(1, 20))).lstrip("0") or "0"
frac = "".join(rng.choice("0123456789") for _ in range(rng.randrange(1, 25)))
return ("-" if rng.random() < 0.3 else "") + whole + "." + frac
for _ in range(n):
# Five random terms, some negative, up to 43 digits (so some exceed §4's 34 and make
# the receipt malformed), and a total exactly at, inside or outside the tolerance of
# their sum.
terms = [term() for _ in range(5)]
with localcontext(OER.EXACT):
total = sum(Decimal(t) for t in terms)
tol = Decimal("1e-9") * max(Decimal(1), abs(total))
jt = total + rng.choice([Decimal(0), tol, -tol, tol * 2, Decimal("1e-30")])
cases.append((f"random_sum_{len(cases)}", format(jt, "f"), terms, None))
for name, jt, terms, floor in cases:
r = seal_oer(oer_t, jt, terms, floor)
uj = Decimal(jt) * 1000000 if OER.DECIMAL.fullmatch(jt) else 1
uj = max(1, min(int(abs(uj).to_integral_value()), 2 ** 53 - 1))
body = dict(claim_t["body"], joules_micro=uj, oer_receipt=r["receipt_id"])
ins.add("sum_" + name, CV.jcs(sign(dict(claim_t, body=body), METER)), {"oer": {"receipt": r, "keys": keys}})
def readings(ins):
"""oer-receipts §4 "Numbers": numbers a receipt body must not carry — `-0` however
spelt, fractions, exponents, integers beyond I-JSON's range — beside the range's own
bounds, in figures and in members no check reads. A body holding a forbidden number is
`malformed` before its content address is computed, so the verdict is the same however
a reader would have rounded it. And a delivered object's microjoules spelt as numbers
the corpus does not give them as."""
oer_t = ins.by_name["energy_matches_oer_accept"]["context"]["oer"]["receipt"]
claim_t = ins.receipt("energy_matches_oer_accept")
keys = {OER_SIGNER: pk(METER)}
def k_floor(bits): # temperature_k "300", computed as §6 fixes it
with localcontext(OER.EXACT):
return format(Decimal(bits) * OER.KBOLTZ * Decimal(300) * OER.LN2, "e")
def at(path, spelling):
def edit(r):
*outer, last = path
for p in outer:
r = r[p]
r[last] = raw(spelling)
return edit
def both(*edits):
return lambda r: [e(r) for e in edits]
zero_terms = ["0.012000", "0", "0", "0", "0"]
cases = [ # (name, joules_total, breakdown, floor, edit, microjoules)
("term_neg_zero", "0.012000", zero_terms, None, at(("energy", "breakdown", "movement_j"), "-0"), 12000),
("term_neg_zero_float", "0.012000", zero_terms, None, at(("energy", "breakdown", "movement_j"), "-0.0"), 12000),
("term_neg_zero_exp", "0.012000", zero_terms, None, at(("energy", "breakdown", "movement_j"), "-0e7"), 12000),
("total_neg_zero", "0", ["0", "0", "0", "0", "0"], None,
both(at(("energy", "joules_total"), "-0"), at(("energy", "breakdown", "compute_j"), "-0")), 1),
("latency_neg_zero", "0.012000", None, None, at(("work", "latency_s"), "-0"), 12000),
("status_neg_zero", "0.012000", None, None, at(("work", "status"), "-0"), 12000),
("status_i64_min", "0.012000", None, None, at(("work", "status"), "-9223372036854775808"), 12000),
("status_below_i64", "0.012000", None, None, at(("work", "status"), "-9223372036854775809"), 12000),
("status_u64_max", "0.012000", None, None, at(("work", "status"), "18446744073709551615"), 12000),
("status_i_json_max", "0.012000", None, None, at(("work", "status"), "9007199254740991"), 12000),
("status_i_json_min", "0.012000", None, None, at(("work", "status"), "-9007199254740991"), 12000),
("status_beyond_i_json", "0.012000", None, None, at(("work", "status"), "9007199254740992"), 12000),
("status_below_i_json", "0.012000", None, None, at(("work", "status"), "-9007199254740992"), 12000),
("status_one_point_zero", "0.012000", None, None, at(("work", "status"), "1.0"), 12000),
("status_exponent", "0.012000", None, None, at(("work", "status"), "1e0"), 12000),
("status_beyond_u64", "0.012000", None, None, at(("work", "status"), "18446744073709551616"), 12000),
("status_1e30_integer", "0.012000", None, None, at(("work", "status"), "1" + "0" * 30), 12000),
("status_308_digits", "0.012000", None, None, at(("work", "status"), "1" + "0" * 308), 12000),
("status_exponent_zeros", "0.012000", None, None, at(("work", "status"), "1e" + "0" * 400 + "5"), 12000),
("status_underflow", "0.012000", None, None, at(("work", "status"), "-1e-400"), 12000),
("bits_beyond_u64", "0.012000", None, (2 ** 64, "300", k_floor(float(2 ** 64))),
at(("floor", "bits_committed"), "18446744073709551616"), 12000),
("bits_1e30_integer", "0.012000", None, (10 ** 30, "300", k_floor(1e30)),
at(("floor", "bits_committed"), "1" + "0" * 30), 12000),
("bits_neg_zero", "0.012000", None, (0, "300", "0"), at(("floor", "bits_committed"), "-0"), 12000),
("total_beyond_u64_micro", "18446744073709.551616", ["18446744073709.551616", "0", "0", "0", "0"], None,
at(("energy", "joules_total"), "18446744073709551616e-6"), 2 ** 53 - 1),
("total_number_spelt_long", "0.012000", zero_terms, None,
at(("energy", "joules_total"), "0.01200000000000000000000000000000000000000000000001"), 12000),
]
for name, jt, terms, floor, edit, uj in cases:
r = seal_oer(oer_t, jt, terms, floor, edit)
body = dict(claim_t["body"], joules_micro=uj, oer_receipt=r["receipt_id"])
c = CV.jcs(sign(dict(claim_t, body=body), METER))
ins.add(f"reading_{name}", c, {"oer": {"receipt": r, "keys": keys}})
delivery = ins.by_name["energy_matches_delivery_accept"]
for spelling in ["12000", "-0", "12000.0", "1.2e4", "18446744073709551616", "-12000"]:
ctx = dict(delivery["context"], object=dict(delivery["context"]["object"], joules_micro=raw(spelling)))
ins.add("delivered_joules_" + spelling[:24], delivery["receipt"], ctx)
# (name, spelling) of a value placed in an OER receipt's `work.outcome`, each in a file of
# its own: whether the file is read at all is the first thing compared.
DOCUMENTS = [
("control", '"encoded"'), ("max_double", "1.7976931348623157e308"), ("below_max_half_ulp", "1.7976931348623158e308"),
("past_max_half_ulp", "1.7976931348623159e308"), ("e400", "1e400"), ("neg_e400", "-1e400"), ("e308_8", "1.8e308"),
("digits_400", "1" + "0" * 400), ("neg_digits_400", "-1" + "0" * 400), ("zero_e_huge", "0e99999999999999999999"),
("one_e_huge", "1e99999999999999999999"), ("one_e_neg_huge", "1e-99999999999999999999"),
("nan", "NaN"), ("infinity", "Infinity"), ("neg_infinity", "-Infinity"),
("lone_high_surrogate", '"\\ud800"'), ("lone_low_surrogate", '"\\udc00"'), ("paired_surrogates", '"\\ud83d\\ude00"'),
("high_surrogate_then_letter", '"\\ud800\\u0041"'),
# A repeated member name, in an object no check reads, and a repeated figure.
("repeated_member", '{"a":1,"a":2}'), ("repeated_member_same_value", '{"a":1,"a":1}'),
("distinct_members", '{"a":1,"b":2}'),
]
# work.outcome sits at level 7 of a corpus file ({vectors:[{context:{oer:{receipt:{work:
# ...}}}}]}), so arrays nested d deep inside it reach level 7 + d; §4 reads 64 levels.
DOCUMENTS += [(f"nesting_to_level_{7 + d}", "[" * d + "]" * d) for d in (55, 56, 57, 58, 121, 200, 1100, 5000)]
def documents(corpus):
"""One corpus-format file per DOCUMENTS entry: an energy claim naming an OER receipt that
holds the value, sealed over §4's reading where the text is one §4 reads."""
ins = Inputs(corpus)
oer_t = ins.by_name["energy_matches_oer_accept"]["context"]["oer"]["receipt"]
claim_t = ins.receipt("energy_matches_oer_accept")
keys = {OER_SIGNER: pk(METER)}
out = []
for name, spelling in DOCUMENTS:
def edit(r, spelling=spelling):
r["work"]["outcome"] = raw(spelling)
try:
r = seal_oer(oer_t, "0.012000", None, None, edit)
except (ValueError, RecursionError):
# §4 does not read the receipt's own text. Sealed with a placeholder, which only
# matters if a verifier reads the file anyway — and then the verdicts differ.
r = seal_oer(oer_t, "0.012000")
edit(r)
body = dict(claim_t["body"], joules_micro=12000, oer_receipt=r["receipt_id"])
one = Inputs(corpus)
one.add("document_" + name, CV.jcs(sign(dict(claim_t, body=body), METER)), {"oer": {"receipt": r, "keys": keys}})
out.append((name, text_of({"about": "one differential input", "vectors": one.out})))
return out
def contexts(ins):
decl = ins.receipt("soft_binding_accept")
record = ins.receipt("binding_recovery_accept")
lawful = ins.receipt("lawful_basis_declaration_accept")
decision = ins.receipt("lawful_basis_decision_accept")
e0 = ins.receipt("energy_accept")
e1 = ins.receipt("energy_chain_accept")
ppk = pk(PUBLISHER)
def recovery_case(name, declaration_wire, rec, algs=(WM_ALG,), publisher=ppk):
ctx = {"declaration": declaration_wire, "publisher": publisher, "exact_match_algs": list(algs)}
ins.add(name, CV.jcs(rec), ctx)
def naming(rec_template, declaration, k, **body):
b = dict(rec_template["body"], declaration=CV.link_hash(declaration), **body)
return sign(dict(rec_template, body=b), k)
# A resolver-signed declaration with its signer_pubkey rewritten to the publisher's
# key, and a record naming that forgery: a declaration parsed but not verified.
forged = dict(sign(decl, RESOLVER), signer_pubkey=ppk)
recovery_case("recovery_forged_declaration", CV.jcs(forged), naming(record, forged, RESOLVER))
forged_pub = dict(decl, seq=decl["seq"] + 1) # the publisher's declaration, altered after signing
recovery_case("recovery_altered_declaration", CV.jcs(forged_pub), naming(record, forged_pub, RESOLVER))
recovery_case("recovery_declaration_not_json", "not json", record)
recovery_case("recovery_declaration_not_a_claim", CV.jcs(dict(decl, kind="group")), record)
recovery_case("recovery_declaration_lawful", CV.jcs(lawful), naming(record, lawful, RESOLVER))
recovery_case("recovery_declaration_energy_forged", CV.jcs(dict(e0, seq=5)), naming(record, dict(e0, seq=5), RESOLVER))
recovery_case("recovery_record_alg_differs", CV.jcs(decl), naming(record, decl, RESOLVER, alg="org.example.watermark.2"))
recovery_case("recovery_record_type_differs", CV.jcs(decl), naming(record, decl, RESOLVER, binding_type="fingerprint"))
recovery_case("recovery_record_no_matcher", CV.jcs(decl), record, algs=())
recovery_case("recovery_record_ok", CV.jcs(decl), record)
recovery_case("recovery_record_rogue_publisher", CV.jcs(decl), record, publisher=pk(RESOLVER))
# A decision naming a declaration altered after the operator signed it (a purpose added).
altered = copy.deepcopy(lawful)
altered["body"]["purposes"].append({"purpose": "dpv:PersonalisedAdvertising", "basis": "dpv:Consent", "data": []})
dec_b = dict(decision["body"], declaration=CV.link_hash(altered), purpose="dpv:PersonalisedAdvertising")
ins.add("decision_altered_declaration", CV.jcs(sign(dict(decision, body=dec_b), VIEWER)), {"declaration": CV.jcs(altered)})
ins.add("decision_declaration_not_json", CV.jcs(decision), {"declaration": "{"})
ins.add("decision_declaration_is_decision", CV.jcs(sign(dict(decision, body=dict(decision["body"], declaration=CV.link_hash(decision))), VIEWER)),
{"declaration": CV.jcs(decision)})
ins.add("decision_declaration_is_soft_binding", CV.jcs(sign(dict(decision, body=dict(decision["body"], declaration=CV.link_hash(decl))), VIEWER)),
{"declaration": CV.jcs(decl)})
st = sign(dict(decision, subject_type="receipt"), VIEWER)
ins.add("decision_subject_type_differs", CV.jcs(st), {"declaration": CV.jcs(lawful)})
ins.add("declaration_given_to_a_declaration", CV.jcs(lawful), {"declaration": CV.jcs(lawful)})
ins.add("declaration_given_to_energy", CV.jcs(e0), {"declaration": "not json"})
# Step 5 against a previous claim that does not itself verify.
bad_prev = dict(e0, seq=0, issued_at="2026-09-27T12:00:01Z") # altered after signing
e1_bad = sign(dict(e1, parent_receipt_hash=CV.link_hash(bad_prev)), METER)
ins.add("previous_altered_named", CV.jcs(e1_bad), {"previous": CV.jcs(bad_prev)})
ins.add("previous_altered_head", CV.jcs(sign(dict(e1, parent_receipt_hash=None), METER)), {"previous": CV.jcs(bad_prev)})
ins.add("previous_not_json", CV.jcs(e1), {"previous": "[]"})
ins.add("previous_ok", CV.jcs(e1), {"previous": CV.jcs(e0)})
# §3.2 with the same declarations.
for name, d in [("forged", forged), ("altered", forged_pub), ("ok", decl)]:
ins.add("recover_" + name, None, {"declaration": CV.jcs(d), "publisher": ppk, "observed_value": decl["body"]["value"],
"observed_hash": None, "exact_match_algs": [WM_ALG]}, procedure="recover")
# ------------------------------------------------------------------ run and compare
def run_raw(cmd):
env = dict(os.environ, PYTHONIOENCODING="utf-8", PYTHONDONTWRITEBYTECODE="1")
return subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", env=env)
def run(cmd):
p = run_raw(cmd)
if p.returncode != 0:
sys.exit(f"{' '.join(cmd)} exited {p.returncode}: {p.stderr.strip()[:400]}")
return [line.split("\t", 1) for line in p.stdout.splitlines()]
def document_outcome(p, python):
"""`refused`, the verdict lines, or `crash: ...`. The reference refuses a file with exit
status 1, no output and `not read:` on stderr; anything else that exits non-zero is a
crash. wai-rs exits 1 with its reader's error on stderr."""
if p.returncode == 0:
return "\n".join(p.stdout.splitlines())
if p.stdout or p.returncode != 1 or (python and "not read:" not in p.stderr):
return "crash: exit %d: %s" % (p.returncode, p.stderr.strip().splitlines()[-1:] or "")
return "refused"
def main(argv):
ap = argparse.ArgumentParser()
ap.add_argument("rust_bin")
ap.add_argument("--random", type=int, default=200)
ap.add_argument("--seed", type=int, default=1)
ap.add_argument("--keep")
a = ap.parse_args(argv[1:])
rng = random.Random(a.seed)
corpus = OER.load(open(os.path.join(HERE, "vectors.json"), encoding="utf-8"))
ins = Inputs(corpus)
numbers(ins)
strings(ins)
nesting(ins)
dates(ins, rng, a.random)
endpoints(ins, rng, a.random)
figures(ins, rng, a.random)
sums(ins, rng, a.random)
classes(ins, random.Random(f"classes {a.seed}"), a.random) # its own stream: other families' draws are unchanged
readings(ins)
contexts(ins)
bodies(ins)
doc = text_of({"about": "differential inputs; no outcome is declared", "vectors": ins.out})
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as f:
f.write(doc)
path = f.name
try:
rust = run([a.rust_bin, "verdicts", path])
py = run([sys.executable, os.path.join(HERE, "claim_verify.py"), "verdicts", path])
finally:
if a.keep:
os.replace(path, a.keep)
else:
os.unlink(path)
if len(rust) != len(ins.out) or len(py) != len(ins.out):
sys.exit(f"verdict counts differ: {len(ins.out)} inputs, wai-rs {len(rust)}, python {len(py)}")
differ = [(n, r, p) for (n, r), (_, p) in zip(rust, py) if r != p]
crashes = [(n, v) for n, v in rust + py if v.startswith("crash")]
tally = {}
for _, v in rust:
k = v.split(" link_hash=")[0].split(" ancestor=")[0]
tally[k] = tally.get(k, 0) + 1
oer = "reject step=6 reason=energy_oer_invalid"
for n, r, p in differ:
# Marked, never set apart: the verdicts differ either way.
mark = " (one side's OER verifier refused the receipt)" if oer in (r, p) else ""
print(f"DIFFER {n}{mark}\n wai-rs: {r}\n python: {p}")
for n, v in crashes:
print(f"CRASH {n}: {v}")
print(f"{len(ins.out)} inputs; verdicts: " + ", ".join(f"{k} x{c}" for k, c in sorted(tally.items())))
# Files of their own: read by both or refused by both, and the same verdicts if read.
doc_differ, doc_crash, doc_tally = 0, 0, {}
with tempfile.TemporaryDirectory() as d:
for name, text in documents(corpus):
path = os.path.join(d, name + ".json")
with open(path, "w", encoding="utf-8") as f:
f.write(text)
r = document_outcome(run_raw([a.rust_bin, "verdicts", path]), False)
p = document_outcome(run_raw([sys.executable, os.path.join(HERE, "claim_verify.py"), "verdicts", path]), True)
key = r.split("\t", 1)[-1].split(" link_hash=")[0]
doc_tally[key] = doc_tally.get(key, 0) + 1
if r.startswith("crash") or p.startswith("crash"):
doc_crash += 1
print(f"CRASH document {name}\n wai-rs: {r}\n python: {p}")
elif r != p:
doc_differ += 1
print(f"DIFFER document {name}\n wai-rs: {r}\n python: {p}")
print(f"{len(DOCUMENTS)} documents; wai-rs: " + ", ".join(f"{k} x{c}" for k, c in sorted(doc_tally.items())))
print(f"{len(differ)} differ, {len(crashes)} crash; documents: {doc_differ} differ, {doc_crash} crash")
return 1 if differ or crashes or doc_differ or doc_crash else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))