Two claim classes report on one energy figure without changing the receipt that seals it. `measurement` carries a bracket's values as integers with each bracket's liveness facts, the build and the power reader, and is accepted only against the receipt that carries its figure. `operating-point` records the requested, committed and delivered operating points and a prediction. The ratio, verdict and residual are derived by every reader, never stated. energy-measurement §6.2 gives the figure and the least declaration a report's integers support as exact integer formulas; wai-rs `energy_report` implements them with no floating-point arithmetic. §3.1 defines a live power reader: a rail must advance in two consecutive intervals, in at least five and in at least four of every five, over at least 300 ms. A frozen or held rail is refused rather than sealed under a hardware class. An unmetered `0` is never a figure of zero joules. A JWP object's figure, class and origin are bound into its Merkle leaf (`\x02`), and a labelled group payload signs the total's class, its coverage and the parent link. An object must sit at the position its inclusion proof walks, and group signatures follow the strict Ed25519 rule claims use. `jwp_delivery` and `energy_figure` implement this in wai-rs; joule-moq 0.2.0 builds and verifies both labelled forms. New corpus `jwp-conformance/` with `jwp_verify.py`, more claim vectors with the earlier ones pinned, and a CI job that runs claim_verify.py, differential.py and jwp_verify.py. Legacy receipts keep their bytes. External JWP implementations must adopt the `\x02` leaf and the labelled group payload before verifying objects or groups that use them. Co-Authored-By: Claude Opus 5.5 <[email protected]>
966 lines
56 KiB
Python
966 lines
56 KiB
Python
#!/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;
|
||
measurement reports whose brackets sit at the least declaration and one ppm below it, at a
|
||
half microjoule, at the ratio thresholds, with a flat idle bracket, with products far past
|
||
128 bits and with every pattern of liveness facts; operating-point claims with every
|
||
presence combination of their points and prediction; reports checked against figure views
|
||
that agree and disagree in each member; and energy claims against labelled objects —
|
||
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 reports(ins, rng, n):
|
||
"""measurement and operating-point claims (§2.4.8-§2.4.9) around the edges of their
|
||
rules: brackets at and one ppm below the least declaration, figures one off, ties at a
|
||
half microjoule, the ratio thresholds, flat brackets and a held value, products past 128 bits,
|
||
every pattern of liveness facts; operating points in every presence combination; and
|
||
reports and energy claims against figure views and labelled objects."""
|
||
m0 = ins.receipt("measurement_accept")
|
||
fig0 = ins.by_name["measurement_accept"]["context"]["figure"]
|
||
op0 = ins.receipt("operating_point_accept")
|
||
hw = ["OnChipCounter", "CalibratedInstrument"]
|
||
|
||
def summary(n_, mean, sd, live=None, run=None):
|
||
live = n_ if live is None else live
|
||
return {"n": n_, "mean_uw": mean, "sd_uw": sd, "live_intervals": live, "longest_live_run": n_ if run is None else run}
|
||
|
||
def class_fields(label, ppm, window, figure):
|
||
b = {"energy_provenance": label, "energy_uncertainty": None, "energy_counter_filtering": None,
|
||
"energy_calibration_ref": None}
|
||
if label == "OnChipCounter":
|
||
b.update(energy_uncertainty={"relative_ppm": ppm, "window_us": window}, energy_counter_filtering="Unknown")
|
||
elif label == "CalibratedInstrument":
|
||
b.update(energy_uncertainty={"relative_ppm": ppm, "window_us": window},
|
||
energy_calibration_ref="https://calibration.example.org/certificates/0001")
|
||
return dict(b, joules_micro=figure)
|
||
|
||
def report(br, label, ppm, window, figure, name, ctx=None):
|
||
body = dict(m0["body"], bracket=br, **class_fields(label, ppm, window, figure))
|
||
ins.add(name, CV.jcs(sign(dict(m0, body=body), METER)), ctx)
|
||
|
||
def no_reader(br, label, ppm, window, figure, name, ctx=None):
|
||
body = dict(m0["body"], bracket=br, power_reader=None, **class_fields(label, ppm, window, figure))
|
||
ins.add(name, CV.jcs(sign(dict(m0, body=body), METER)), ctx)
|
||
|
||
def around(name, br, labels=("OnChipCounter", "CalibratedInstrument", "ModelBased")):
|
||
f = CV.bracket_figure(br)
|
||
for label in labels:
|
||
if f is None:
|
||
report(br, label, 300_000, br["window_us"], 1, f"{name}_nofig_{label}")
|
||
continue
|
||
p = CV.least_ppm(br, f) or 2 ** 32 - 1
|
||
for d_ppm, d_fig, d_win in [(0, 0, 0), (-1, 0, 0), (1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1)]:
|
||
ppm = max(1, min(2 ** 32 - 1, p + d_ppm))
|
||
fig = f + d_fig
|
||
if fig <= 0 or fig > CV.MAX_INT:
|
||
continue
|
||
report(br, label, ppm, br["window_us"] + d_win, fig, f"{name}_{label}_{d_ppm}_{d_fig}_{d_win}")
|
||
|
||
def bracket(idle, busy, t, c, nn, opening):
|
||
return {"idle": idle, "busy": busy, "window_us": t, "completions": c, "in_flight": nn, "opening": opening}
|
||
|
||
# Fixed edges.
|
||
around("half_tie", bracket(summary(10, 20_000_000, 1_000), summary(14, 21_000_000, 1_000), 2_500_000, 1_000_000, 1, "running"))
|
||
around("flat_idle", bracket(summary(10, 20_000_000, 0), summary(14, 30_000_000, 115_471), 3_000_000, 60, 10, "from-rest"))
|
||
around("flat_busy", bracket(summary(10, 20_000_000, 115_471), summary(14, 30_000_000, 0), 3_000_000, 60, 10, "from-rest"))
|
||
around("held_value", bracket(summary(10, 5_000_000, 0, 1, 1), summary(14, 10_500_000, 2_980_708, 2, 1), 3_000_000, 60, 10, "from-rest"))
|
||
# A report that names no power reader, with and without a bracket, under each class.
|
||
for label in ("OnChipCounter", "CalibratedInstrument", "ModelBased", "Estimator"):
|
||
unit_br = m0["body"]["bracket"]
|
||
f = CV.bracket_figure(unit_br)
|
||
p = CV.least_ppm(unit_br, f)
|
||
for br in (unit_br, None):
|
||
no_reader(br, label, p, unit_br["window_us"], f, f"no_reader_{label}_{br is None}", fig0)
|
||
around("wide", bracket(summary(10, 20_000_000, 115_471), summary(14, 30_000_000, 115_471), 10 ** 11, 10 ** 13, 64, "from-rest"))
|
||
for k in (10, 3, 1):
|
||
for d in (0, -1):
|
||
around(f"ratio_{k}_{d}", bracket(summary(10, 20_000_000, 100_000), summary(14, 20_000_000 + k * 100_000 + d, 100_000),
|
||
3_000_000, 60, 10, "from-rest"), labels=("OnChipCounter", "ModelBased"))
|
||
# Liveness facts: every pattern around the thresholds, in each bracket.
|
||
for n_ in (4, 5, 6, 10, 14):
|
||
for live in sorted({0, 1, 2, 4, 5, n_ * 4 // 5, n_ * 4 // 5 + 1, n_ - 1, n_, n_ + 1}):
|
||
for run in sorted({0, 1, 2, 3, live, live + 1}):
|
||
idle = summary(10, 20_000_000, 115_471)
|
||
busy = summary(n_, 30_000_000, 115_471, live, run)
|
||
br = bracket(idle, busy, 3_000_000, 60, 10, "from-rest")
|
||
f = CV.bracket_figure(br)
|
||
p = CV.least_ppm(br, f)
|
||
report(br, "OnChipCounter", p, 3_000_000, f, f"live_busy_{n_}_{live}_{run}")
|
||
br2 = bracket(summary(n_, 20_000_000, 115_471, live, run), summary(14, 30_000_000, 115_471), 3_000_000, 60, 10, "from-rest")
|
||
report(br2, "OnChipCounter", CV.least_ppm(br2, f), 3_000_000, f, f"live_idle_{n_}_{live}_{run}")
|
||
# Random brackets.
|
||
for i in range(n):
|
||
ni, nb = rng.choice([1, 2, 3, 5, 10, 14, 50]), rng.choice([1, 2, 3, 5, 10, 14, 50])
|
||
idle_mean = rng.randrange(0, 50_000_000)
|
||
busy_mean = idle_mean + rng.choice([-1, 0, 1, rng.randrange(1, 40_000_000)])
|
||
sd = lambda: rng.choice([0, 1, rng.randrange(1, 5_000_000)]) # noqa: E731
|
||
live = lambda m: rng.choice([m, m - 1, 0, 2, 5]) # noqa: E731
|
||
idle = summary(ni, idle_mean, sd(), live(ni))
|
||
busy = summary(nb, max(0, busy_mean), sd(), live(nb))
|
||
t = rng.choice([0, 1, rng.randrange(1, 10 ** 7), rng.randrange(1, 10 ** 12)])
|
||
c = rng.choice([0, 1, rng.randrange(1, 10 ** 6), rng.randrange(1, 10 ** 13)])
|
||
nn = rng.choice([0, 1, rng.randrange(1, 65)])
|
||
br = bracket(idle, busy, t, c, nn, rng.choice(["from-rest", "running"]))
|
||
f = CV.bracket_figure(br)
|
||
label = rng.choice(hw + ["ModelBased", "Estimator"])
|
||
if f is None:
|
||
report(br, label, 300_000, max(1, t), rng.randrange(1, 10 ** 6), f"random_{i}_nofig")
|
||
continue
|
||
p = CV.least_ppm(br, f)
|
||
ppm = max(1, min(2 ** 32 - 1, (p or 2 ** 32 - 1) + rng.choice([0, 0, -1, 1, 1000])))
|
||
if f * ppm < 288_676:
|
||
ppm = 288_676 // f + 1
|
||
report(br, label, ppm, rng.choice([max(1, t), t + 1]), f + rng.choice([0, 0, 0, 1, -1]) or 1, f"random_{i}")
|
||
|
||
# Operating points: every presence combination, and points that are empty, not terms,
|
||
# or not integers.
|
||
pts = [None, {}, {"height": 1080}, {"height": 720}, {"frame rate": 1}, {"height": "1080"}, {"height": -1}]
|
||
pred = op0["body"]["prediction"]
|
||
for ri, r in enumerate(pts):
|
||
for ci, c in enumerate(pts):
|
||
for di, d in enumerate(pts):
|
||
for pi, pr in enumerate([None, pred]):
|
||
body = dict(op0["body"], op_requested=r, op_committed=c, op_delivered=d, prediction=pr)
|
||
ins.add(f"op_{ri}{ci}{di}{pi}", CV.jcs(sign(dict(op0, body=body), METER)) if all(
|
||
not isinstance(x, dict) or all(isinstance(v, int) and v >= 0 for v in x.values()) for x in (r, c, d))
|
||
else text_of(sign_raw(dict(op0, body=body), METER)), {"figure": fig0})
|
||
|
||
# Figure views that agree and disagree in each member.
|
||
e = json.loads(fig0["claim"])
|
||
eb = e["body"]
|
||
base = {"profile": "moq-jwp:claim-link", "identity": CV.link_hash(e), "group": None,
|
||
"figures": [{"index": None, "joules_micro": eb["joules_micro"], "energy_provenance": eb["energy_provenance"],
|
||
"energy_uncertainty": eb["energy_uncertainty"],
|
||
"energy_counter_filtering": eb["energy_counter_filtering"]}],
|
||
"signer": pk(METER)}
|
||
variants = [("same", {}), ("profile", {"profile": "wai:asset-receipt-id"}), ("identity", {"identity": "ab" * 32}),
|
||
("group", {"group": "cd" * 32}), ("signer", {"signer": pk(OPERATOR)}), ("no_signer", {"signer": None})]
|
||
fig_variants = [("figure_plus", {"joules_micro": eb["joules_micro"] + 1}), ("unlabelled", {"energy_provenance": None,
|
||
"energy_uncertainty": None, "energy_counter_filtering": None}),
|
||
("filtering_absent", {"energy_counter_filtering": None}), ("filtering_off", {"energy_counter_filtering": "Off"}),
|
||
("ppm", {"energy_uncertainty": dict(eb["energy_uncertainty"], relative_ppm=eb["energy_uncertainty"]["relative_ppm"] + 1)}),
|
||
("index", {"index": 0})]
|
||
for name, edit in variants:
|
||
ins.add("view_" + name, CV.jcs(m0), {"figure": {"view": dict(base, **edit)}})
|
||
ins.add("view_op_" + name, CV.jcs(op0), {"figure": {"view": dict(base, **edit)}})
|
||
for name, edit in fig_variants:
|
||
fv = [{k: v for k, v in dict(base["figures"][0], **edit).items() if v is not None or k == "index"}]
|
||
ins.add("view_fig_" + name, CV.jcs(m0), {"figure": {"view": dict(base, figures=fv)}})
|
||
ins.add("view_none_given_claim_not_json", CV.jcs(m0), {"figure": {"claim": "not json"}})
|
||
|
||
# Energy claims against labelled objects.
|
||
ec = ins.receipt("energy_on_chip_counter_accept")
|
||
b = ec["body"]
|
||
lab = {"energy_provenance": "OnChipCounter", "energy_uncertainty": b["energy_uncertainty"], "energy_counter_filtering": "Unknown"}
|
||
for name, j, extra in [("same", b["joules_micro"], lab), ("no_label", b["joules_micro"], {}),
|
||
("filtering_absent", b["joules_micro"], {k: v for k, v in lab.items() if k != "energy_counter_filtering"}),
|
||
("model", b["joules_micro"], {"energy_provenance": "ModelBased"}),
|
||
("ppm", b["joules_micro"], dict(lab, energy_uncertainty={"relative_ppm": 20_001, "window_us": 1_000_000})),
|
||
("figure", b["joules_micro"] + 1, lab)]:
|
||
ins.add("labelled_object_" + name, CV.jcs(ec), {"object": dict({"content_hash": ec["subject"], "joules_micro": j}, **extra)})
|
||
|
||
|
||
def sign_raw(c, k):
|
||
"""`c` signed over its JCS where JCS can serialise it, with a member JCS refuses (a
|
||
string or negative point value) left for the verifier to refuse at step 2. The
|
||
signature covers a canonical form computed with that member as written."""
|
||
c = copy.deepcopy(c)
|
||
c.pop("sig", None)
|
||
c["signer_pubkey"] = pk(k)
|
||
def canon(v):
|
||
if isinstance(v, int) and not isinstance(v, bool) and v < 0:
|
||
return str(v)
|
||
if isinstance(v, dict):
|
||
keys = sorted(v, key=lambda x: x.encode("utf-16-be"))
|
||
return "{" + ",".join(json.dumps(x, ensure_ascii=False) + ":" + canon(v[x]) for x in keys) + "}"
|
||
if isinstance(v, list):
|
||
return "[" + ",".join(canon(x) for x in v) + "]"
|
||
return CV.jcs(v)
|
||
c["sig"] = k.sign(b"moq-jwp:claim\x01" + canon(c).encode()).hex()
|
||
return c
|
||
|
||
|
||
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)
|
||
reports(ins, random.Random(f"reports {a.seed}"), a.random) # its own stream, as classes()
|
||
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))
|