open-standards/wai/live-conformance/receipt_ledger_verify.py
Transaction Science ccaca14181 wai: receipt ledger — streams stitched by others, from stitched-presentation's records
A stitched source (extensions/stitched-presentation.md, PR #470, which this
branch merges): the stitcher's pod-input records (WSI1) and the players'
claims (WSC1), verified by that extension with the tariff's ad scope as its
signers. Its presentations bill as presented impressions, each line citing the
accepted records and the presented own claims; its report is that extension's
§7.5 statement unchanged, a form-1 receipt's own quartiles and spend beside the
presented ones. The corpus bills two vectors of live-conformance/stitched/ and
refuses a stitched source given twice and a stitcher outside the scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 22:13:26 -04:00

936 lines
41 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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
"""Independent verifier of the receipt-ledger corpus (extensions/receipt-ledger.md).
Written from the extension's text. It shares no code with wai-rs, which generates the corpus,
and never runs it. It checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's
strict rule (SPEC §7 *Signatures*) written from RFC 8032 with the standard library. Each source
is verified by the second implementation of the corpus of its own extension, each written from
its text alone: a room-session receipt by live_metering_verify.py (live-metering §5), a
settlement statement by settlement_verify.py (settlement §8), a package receipt by
../pack-conformance/pack_verify.py (packaging §9), an ads room by live_ads_verify.py with
ad_presentation_verify.py (live-ads §9, ad-presentation §9), and a stream stitched by others by
stitched_presentation_verify.py (stitched-presentation §7), whose statement the ledger reports as
it is. Everything else is here:
- the tariff (§4): reading, rules, both signatures, its scope;
- the invoice (§6): reading, rules, signature, chain;
- what each source contributes (§5.2), its placement in time (§5.3), and the checks across
sources (§7.2 step 6);
- every line, citation, energy row and amount recomputed (§6.2 to §6.6), compared in §7.2 step
7's order;
- the two reports (§8).
For every vector it requires the outcome the corpus states; of the corpus, that every reason of
§10 appears in it and that no other file is there. Then it mutates copies of the corpus, one
defect each, and requires every copy to fail; and it drops each of its own checks in turn and
requires the corpus to catch the weakened verifier.
python3 -I receipt_ledger_verify.py <live-conformance/ledger>
python3 -I receipt_ledger_verify.py --document <verification document>
Exit status 0 only if every vector verifies, every mutated copy fails and every weakened verifier
fails. Python 3.8 or later; standard library only.
"""
import copy
import hashlib
import json
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
sys.path.insert(0, os.path.join(HERE, "..", "pack-conformance"))
import _ed25519 as ED # noqa: E402
import live_rooms_verify as LRV # noqa: E402
import live_metering_verify as LMV # noqa: E402
import settlement_verify as SV # noqa: E402
import live_ads_verify as LAV # noqa: E402
import ad_presentation_verify as APV # noqa: E402
import pack_verify as PV # noqa: E402
import stitched_presentation_verify as STV # noqa: E402
EXTENSION = os.path.join(HERE, "..", "extensions", "receipt-ledger.md")
TARIFF_MAGIC, INVOICE_MAGIC = b"WLT1", b"WLI1" # §4.1, §6.1
TARIFF_DOMAIN = b"wai:ledger-tariff\x01" # §4.2
ACCEPT_DOMAIN = b"wai:ledger-accept\x01" # §4.2
INVOICE_DOMAIN = b"wai:ledger-invoice\x01" # §6.7
CITES_DOMAIN = b"wai:ledger-cites\x01" # §6.5
ZERO = bytes(32)
U64 = 2 ** 64 - 1
ROOM, DELIVERY, PACKAGE, ADS = 1, 2, 3, 4 # §6.3 source kinds
SESSION, HOP, STATEMENT, ACK, PACK, AD_RECEIPT, CLAIM, POD_INPUT, STITCHED_CLAIM = 1, 2, 3, 4, 5, 6, 7, 8, 9 # §5.2
KIND_NAMES = {ROOM: "room", DELIVERY: "delivery", PACKAGE: "package", ADS: "ads"}
STITCHED = "stitched" # §5.1: a source kind billed as ads
SCOPES = {1: "room_hops", 2: "delivery_hops", 3: "packaging", 4: "ad_decisions"} # §6.6
LABELS = {2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"}
TAGS = {v: k for k, v in LABELS.items()}
CLASS_NAMES = ["live", "fetch", "fill", "ancillary"]
UNITS = { # §6.3: name, divisor
ROOM: [("publisher_us", 10 ** 6)] + [("subscriber_us_%d" % c, 10 ** 6) for c in range(9)]
+ [("composition_us", 10 ** 6), ("recorded_us", 10 ** 6), ("events_sent", 1)],
DELIVERY: [(n, 10 ** 9) for n in CLASS_NAMES],
PACKAGE: [("packages", 1), ("entries", 1), ("stored_bytes", 10 ** 9)],
ADS: [("presented_impressions", 1), ("billable_impressions", 1)],
}
SKIP = set()
class Refuse(Exception):
def __init__(self, reason, at):
super().__init__(reason)
self.reason, self.at = reason, at
def sha(*parts):
h = hashlib.sha256()
for p in parts:
h.update(p)
return h.digest()
class R:
"""A cursor over little-endian fields; running out is `truncated`."""
def __init__(self, b, p=0):
self.b, self.p = b, p
def take(self, n):
if self.p + n > len(self.b):
raise ValueError("truncated")
s = self.b[self.p:self.p + n]
self.p += n
return s
def u(self, n):
return int.from_bytes(self.take(n), "little")
def head(b, magic):
"""§4.3's first steps."""
if len(b) < 4:
return "truncated"
if b[:4] != magic:
return "malformed"
if len(b) < 5:
return "truncated"
if b[4] != 1:
return "unknown_version"
return None
def capitals(c):
return len(c) == 3 and all(0x41 <= x <= 0x5A for x in c)
def ascending(xs):
return all(a < b for a, b in zip(xs, xs[1:]))
def verify_sig(key, domain, body, sig):
return None if "signatures" in SKIP else ED.verify(key, domain + body, sig)
# ---------------------------------------------------------------- §4 the tariff
def read_tariff(b):
bad = head(b, TARIFF_MAGIC)
if bad:
raise Refuse(bad, ["tariff"])
r = R(b, 5)
try:
t = {"seller": r.take(32), "buyer": r.take(32), "start": r.u(8), "end": r.u(8), "currency": r.take(3)}
t["room"] = [r.u(4) for _ in range(13)]
t["delivery"] = [r.u(8) for _ in range(4)]
t["package"] = [r.u(8) for _ in range(3)]
t["ads"] = [r.u(8) for _ in range(2)]
t["scope"] = [(r.u(1), r.take(32)) for _ in range(r.u(2))]
body_end = r.p
seller_sig, buyer_sig = r.take(64), r.take(64)
except ValueError:
raise Refuse("truncated", ["tariff"])
if r.p != len(b):
raise Refuse("malformed", ["tariff"])
rules = (t["start"] < t["end"] and capitals(t["currency"]) and t["seller"] != ZERO and t["buyer"] != ZERO
and t["seller"] != t["buyer"] and t["scope"] and ascending(t["scope"])
and all(1 <= k <= 4 and i != ZERO for k, i in t["scope"]))
if not rules and "tariff-rules" not in SKIP:
raise Refuse("tariff_invalid", ["tariff"])
body = b[:body_end]
bad = verify_sig(t["seller"], TARIFF_DOMAIN, body, seller_sig)
if bad:
raise Refuse(bad, ["tariff", "seller"])
bad = None if "buyer-signature" in SKIP else verify_sig(t["buyer"], ACCEPT_DOMAIN, body, buyer_sig)
if bad:
raise Refuse(bad, ["tariff", "buyer"])
t["digest"] = sha(body)
return t
def price(t, kind, unit):
return {ROOM: t["room"], DELIVERY: t["delivery"], PACKAGE: t["package"], ADS: t["ads"]}[kind][unit - 1]
# ---------------------------------------------------------------- §6 the invoice
def read_invoice(b, at):
bad = head(b, INVOICE_MAGIC)
if bad:
raise Refuse(bad, at)
r = R(b, 5)
try:
i = {"seller": r.take(32), "buyer": r.take(32), "tariff": r.take(32), "prev": r.take(32), "start": r.u(8),
"end": r.u(8), "currency": r.take(3)}
i["records"] = [(r.u(1), r.take(32)) for _ in range(r.u(4))]
i["lines"] = []
for _ in range(r.u(2)):
i["lines"].append({"kind": r.u(1), "source": r.take(32), "unit": r.u(1), "quantity": r.u(8), "price": r.u(8),
"amount": r.u(8), "cited": r.u(4), "cites": r.take(32)})
i["energy"] = [(r.u(1), r.u(1), r.u(4), r.u(8), r.u(4)) for _ in range(r.u(1))]
i["unmetered"] = [r.u(4) for _ in range(4)]
i["amount"] = r.u(8)
sig = r.take(64)
except ValueError:
raise Refuse("truncated", at)
if r.p != len(b):
raise Refuse("malformed", at)
if any(e[1] == 0 for e in i["energy"]) and "row-of-no-class" not in SKIP:
raise Refuse("classes_summed", at)
keys = [(x["kind"], x["source"], x["unit"]) for x in i["lines"]]
rules = (i["start"] < i["end"] and capitals(i["currency"]) and ascending(i["records"])
and all(1 <= k <= 9 for k, _ in i["records"]) and ascending(keys)
and all(x["kind"] in UNITS and 1 <= x["unit"] <= len(UNITS[x["kind"]]) and x["quantity"] > 0 and x["cited"] > 0
for x in i["lines"])
and ascending([(e[0], e[1]) for e in i["energy"]])
and all(1 <= e[0] <= 4 and 2 <= e[1] <= 5 and e[2] > 0 for e in i["energy"]))
if not rules and "invoice-rules" not in SKIP:
raise Refuse("invoice_invalid", at)
bad = verify_sig(i["seller"], INVOICE_DOMAIN, b[:-64], sig)
if bad:
raise Refuse(bad, at)
i["digest"] = sha(b[:-64])
return i
# ---------------------------------------------------------------- §5 sources
def figure(joules, cls):
"""(tag or None, joules, declared ppm): a figure as §6.6 counts it."""
if cls is None or joules == 0:
return (None, 0, 0)
return (cls[0], joules, cls[1] if cls[0] in (4, 5) else 0)
def room_source(s):
keys = {"kind", "room", "receipt", "hops", "descriptors", "compositions", "takes", "logs"}
if set(s) != keys or not isinstance(s["room"], dict) or set(s["room"]) != {"descriptor", "grants", "key_log_head", "chains"}:
raise Refuse("malformed", [])
doc = {"rooms": {"r": s["room"]}}
v = {"room": "r", "receipt": s["receipt"], "hops": s["hops"], "descriptors": s["descriptors"],
"compositions": s["compositions"], "takes": s["takes"], "logs": s["logs"]}
got = LMV.verify_vector(v, doc, LMV.Cache())
if got["outcome"] != "accept":
raise Refuse(got["reason"], got["at"])
d = LRV.read_descriptor(bytes.fromhex(s["room"]["descriptor"]))
rc = LMV.read_receipt(bytes.fromhex(s["receipt"]), d["room"])
u = rc["units"]
units = [u["publisher_us"]] + list(u["subscriber_us"]) + [u["composition_us"], u["recorded_us"], u["events_sent"]]
return {"kind": ROOM, "digest": rc["digest"], "room": d["room"], "t0": d["t0"], "start": rc["start"], "end": rc["end"],
"units": units, "currency": rc["currency"], "prices": rc["prices"],
"hops": [(h["record"], figure(h["joules"], h["class"])) for h in rc["hops"]]}
def delivery_source(s):
if set(s) != {"kind", "statement", "records", "proofs", "hops", "rooms"}:
raise Refuse("malformed", [])
doc = {"records": [{"record": r["record"], "items": r["items"]} for r in s["records"]],
"proofs": [{"proof": p} for p in s["proofs"]], "hops": [{"hop": h} for h in s["hops"]],
"rooms": {str(k): r for k, r in enumerate(s["rooms"])}}
v = {"statement": s["statement"], "rooms": [str(k) for k in range(len(s["rooms"]))], "withhold": [],
"extra": {"records": [], "proofs": []}}
got = SV.statement_outcome(doc, v)
if got["outcome"] != "accept":
raise Refuse(got["reason"], got["at"])
st = SV.read_statement(bytes.fromhex(s["statement"]))
given = {}
for r in s["records"]:
b = bytes.fromhex(r["record"])
given[sha(b[:-64])] = (b, [bytes.fromhex(x) for x in r["items"]])
acks = []
for d in st["records"]:
b, lists = given[d]
a = SV.read_ack(b)
a["item_lists"] = [SV.read_items(x) for x in lists]
acks.append(a)
hops = {}
for h in s["hops"]:
b = bytes.fromhex(h)
hops[sha(b[:-64])] = b
figs = []
for d in st["hops"]:
x = LMV.read_hop(hops[d], hops[d][5:37])
figs.append((d, figure(x["joules"], x["class"])))
return {"kind": DELIVERY, "digest": st["digest"], "statement": st, "acks": acks, "hops": figs}
def package_source(s):
if set(s) != {"kind", "receipt", "indexes"} or not isinstance(s["receipt"], str):
raise Refuse("malformed", [])
try:
r = PV.read_receipt(s["receipt"].encode("utf-8"))
PV.verify_receipt_v2(r, [bytes.fromhex(x) for x in s["indexes"]])
except PV.Refused as e:
raise Refuse(e.code, ["receipt"])
j = r["json"]
cls = None
if j["energy_provenance"] is not None:
tag = TAGS[j["energy_provenance"]]
cls = (tag, j["energy_uncertainty"]["relative_ppm"] if tag in (4, 5) else 0)
return {"kind": PACKAGE, "digest": bytes.fromhex(j["receipt_hash"]), "signer": bytes.fromhex(j["signer_pubkey"]),
"entries": j["total_entries"], "stored": j["stored_bytes"], "figure": figure(j["joules_micro"], cls)}
def ads_source(s):
keys = {"kind", "descriptor", "grants", "key_log", "chains", "policy", "pt0_ms", "receipts", "pods", "claims",
"creatives", "issuers"}
if set(s) != keys:
raise Refuse("malformed", [])
holder = {"creatives": s["creatives"], "issuers": s["issuers"]}
APV.held(holder)
v = dict(s, name="source")
got, _ = LAV.ads(v, judge=lambda vv, d, p: APV.Judge(holder, vv, p))
if got["outcome"] == "gap":
raise Refuse("source_gap", [])
if got["outcome"] != "accept":
raise Refuse(got["reason"], got["at"])
d = LRV.read_descriptor(bytes.fromhex(s["descriptor"]))
claims_required = "claim_facts" in got
breaks = {bytes.fromhex(b["digest"]): b["t"] for b in got["breaks"]}
claim_digests = [None if c["digest"] is None else bytes.fromhex(c["digest"]) for c in got.get("claim_facts", [])]
receipts = []
for o, fact in zip(s["receipts"], got["receipt_facts"]):
r = LAV.read_receipt(o)
imps = []
for k, i in enumerate(r["impressions"]):
billable = i["quartiles_reached"] >= 1
if claims_required:
c = fact["impressions"][k]
counts = c["verdict"] == "presented"
verdict = c["verdict"]
claim = claim_digests[c["claim"]] if counts and c["claim"] is not None else None
else:
counts, verdict, claim = billable, "beacons", None
imps.append({"slot": i["slot"], "ad_id": i["ad_id"], "price_micros": i["price_micros"],
"quartiles_reached": i["quartiles_reached"], "billable": billable, "counts": counts,
"verdict": verdict, "claim": claim})
cls = None
if r["class"] is not None:
tag = TAGS[r["class"]["label"]]
cls = (tag, r["class"].get("ppm", 0))
receipts.append({"identity": bytes.fromhex(fact["identity"]), "decider": r["signer_pubkey"], "cue": r["cue"],
"session": r["session"], "t": breaks[r["cue"]], "spend": r["budget_spent_micros"],
"counted_spend": fact.get("presented_spend_micros", r["budget_spent_micros"]),
"impressions": imps, "figure": figure(r["joules_micro"], cls)})
return {"kind": ADS, "room": d["room"], "t0": d["t0"], "claims_required": claims_required, "receipts": receipts,
"claims": [c for c in claim_digests if c is not None]}
def stitched_source(s, signers):
"""§5.1: a stitched source, verified as stitched-presentation §7 verifies, with the tariff's
ad scope as its signers (§4.4). That verification refuses nothing as a whole."""
keys = {"kind", "records", "claims", "manifests", "responses", "receipts", "creatives", "issuers"}
if set(s) != keys:
raise Refuse("malformed", [])
creatives, issuers, _ = STV.held({"creatives": s["creatives"], "issuers": s["issuers"], "signers": []})
records = [bytes.fromhex(x) for x in s["records"]]
inp = {"records": records, "claims": [bytes.fromhex(x) for x in s["claims"]],
"manifests": [bytes.fromhex(x) for x in s["manifests"]], "responses": [bytes.fromhex(x) for x in s["responses"]],
"receipts": s["receipts"], "creatives": creatives, "issuers": issuers, "signers": list(signers)}
got = STV.verify(inp)
claims = got["claim_facts"]
accepted = []
for b, f in zip(records, got["record_facts"]):
if f["verdict"] != "accepted":
continue
ads = [(a["presentations"], [bytes.fromhex(claims[j]["digest"]) for j in a["own_claims"]
if claims[j]["verdict"] == "presented" and claims[j]["digest"] is not None])
for a in f["ads"]]
accepted.append({"digest": bytes.fromhex(f["digest"]), "signer": STV.read_record(b)["signer"], "ads": ads})
return {"kind": STITCHED, "records": accepted, "claims": [bytes.fromhex(c["digest"]) for c in claims if c["digest"] is not None],
"statement": got}
# A source's verification is a pure function of the source, and the corpus verifies the same
# sources under every mutated copy and every weakened verifier (none of whose checks is a
# source's own): each is verified once.
_SOURCES = {}
def verify_source(s, signers):
"""§5.1: the source verified by its own extension, a stitched source with `signers`, or
Refuse with that extension's place."""
key = json.dumps([s, sorted(k.hex() for k in signers)], sort_keys=True)
if key not in _SOURCES:
try:
_SOURCES[key] = read_source(s, signers)
except Refuse as e:
_SOURCES[key] = e
got = _SOURCES[key]
if isinstance(got, Refuse):
raise Refuse(got.reason, got.at)
return got
def read_source(s, signers):
if not isinstance(s, dict):
raise Refuse("malformed", [])
if s.get("kind") == "stitched":
return stitched_source(s, signers)
readers = {"room": room_source, "delivery": delivery_source, "package": package_source, "ads": ads_source}
if s.get("kind") not in readers:
raise Refuse("malformed", [])
return readers[s["kind"]](s)
def heads(f):
"""§5.2."""
if f["kind"] == ROOM:
return [(SESSION, f["digest"])]
if f["kind"] == DELIVERY:
return [(STATEMENT, f["digest"])]
if f["kind"] == PACKAGE:
return [(PACK, f["digest"])]
if f["kind"] == STITCHED:
return [(POD_INPUT, r["digest"]) for r in f["records"]]
return [(AD_RECEIPT, r["identity"]) for r in f["receipts"]]
def contributed(f):
"""§5.2: every record a source contributes."""
out = set(heads(f))
if f["kind"] == ROOM:
out |= {(HOP, d) for d, _ in f["hops"]}
elif f["kind"] == DELIVERY:
out |= {(ACK, d) for d in f["statement"]["records"]} | {(HOP, d) for d in f["statement"]["hops"]}
elif f["kind"] == ADS:
out |= {(CLAIM, c) for c in f["claims"]}
elif f["kind"] == STITCHED:
out |= {(STITCHED_CLAIM, c) for c in f["claims"]}
return out
def in_scope(t, kind, i):
return "scope" in SKIP or (kind, i) in t["scope"]
def check_source(t, start, end, f):
"""§7.2 step 5 after the source's own verification: scope, one tariff (§3), period (§5.3)."""
def within(a, b):
return "period" in SKIP or (start <= a and b <= end)
if f["kind"] == ROOM:
if not in_scope(t, 1, f["room"]):
return "outside_scope"
if f["currency"] != bytes(3) and (f["currency"] != t["currency"] or f["prices"] != t["room"]) and "conflict" not in SKIP:
return "tariff_conflict"
if not within(f["t0"] + f["start"], f["t0"] + f["end"]):
return "outside_period"
elif f["kind"] == DELIVERY:
st = f["statement"]
if not in_scope(t, 2, st["party"]):
return "outside_scope"
if st["currency"] != bytes(3) and (st["currency"] != t["currency"] or st["prices"] != t["delivery"]) and "conflict" not in SKIP:
return "tariff_conflict"
if not within(st["start"], st["end"]):
return "outside_period"
elif f["kind"] == PACKAGE:
if not in_scope(t, 3, f["signer"]):
return "outside_scope"
elif f["kind"] == STITCHED:
return None # accepted only under the tariff's signers (§4.4); it states no time (§5.3)
else:
if not all(in_scope(t, 4, r["decider"]) for r in f["receipts"]):
return "outside_scope"
if "period" not in SKIP and not all(start <= f["t0"] + r["t"] < end for r in f["receipts"]):
return "outside_period"
return None
def overlapping(a, b):
return a[0] < b[1] and b[0] < a[1]
def bills_again(f, g):
"""§5.3: whether f bills what g, an earlier source, billed."""
if f["kind"] != g["kind"]:
return False
if f["kind"] == ROOM:
return f["room"] == g["room"] and overlapping((f["start"], f["end"]), (g["start"], g["end"]))
if f["kind"] == DELIVERY:
a, b = f["statement"], g["statement"]
return a["party"] == b["party"] and overlapping((a["start"], a["end"]), (b["start"], b["end"]))
if f["kind"] == ADS:
return f["room"] == g["room"] and any((x["cue"], x["session"]) == (y["cue"], y["session"])
for x in f["receipts"] for y in g["receipts"])
return False
def check_across(facts, previous):
"""§7.2 step 6: (the index, the reason) of the first source refused, or None."""
billed = set(previous["records"]) if previous else set()
seen = set()
for k, f in enumerate(facts):
hs = heads(f)
if "duplicate" not in SKIP and (any(h in seen for h in hs) or len(set(hs)) != len(hs)):
return k, "record_duplicate"
if "overlap" not in SKIP and any(bills_again(f, g) for g in facts[:k]):
return k, "record_overlap"
if "rebilled" not in SKIP and contributed(f) & billed:
return k, "record_rebilled"
seen |= set(hs)
return None
# ---------------------------------------------------------------- §6.2–§6.6 the figures
def free(c, grant):
return c in (2, 3) and grant == ZERO
def cites_digest(cited):
return sha(CITES_DOMAIN, b"".join(bytes([k]) + i for k, i in sorted(cited)))
def compute(t, facts):
"""The records, lines (with their cited records), rows, unmetered counts, amount and the sums
per (scope, tag); None when a figure does not fit its field."""
records = set()
for f in facts:
records |= contributed(f)
q = {}
def add(kind, source, unit, n, cites):
if n == 0:
return
e = q.setdefault((kind, source, unit), [0, set()])
e[0] += n
e[1] |= set(cites)
for f in facts:
if f["kind"] == ROOM:
for unit in range(1, 14):
add(ROOM, f["room"], unit, f["units"][unit - 1], [(SESSION, f["digest"])])
elif f["kind"] == DELIVERY:
st = f["statement"]
for c in range(1, 5):
n = sum(x["bytes"] for x in st["lines"] if x["kind"] == 1 and x["class"] == c and not free(c, x["payer"]))
if n == 0:
continue
cites = [(STATEMENT, f["digest"])]
cites += [(ACK, a["digest"]) for a in f["acks"]
if a["mode"] == 1 and any(e["class"] == c and not free(c, e["grant"]) for e in a["entries"])]
add(DELIVERY, st["party"], c, n, cites)
elif f["kind"] == PACKAGE:
cite = [(PACK, f["digest"])]
add(PACKAGE, f["signer"], 1, 1, cite)
add(PACKAGE, f["signer"], 2, f["entries"], cite)
add(PACKAGE, f["signer"], 3, f["stored"], cite)
elif f["kind"] == STITCHED:
for r in f["records"]:
cites = [(POD_INPUT, r["digest"])] + [(STITCHED_CLAIM, c) for _, cs in r["ads"] for c in cs]
add(ADS, r["signer"], 1, sum(p for p, _ in r["ads"]), cites)
else:
unit = 1 if f["claims_required"] else 2
for r in f["receipts"]:
counted = [i for i in r["impressions"] if i["counts"]]
cites = [(AD_RECEIPT, r["identity"])] + [(CLAIM, i["claim"]) for i in counted if i["claim"] is not None]
add(ADS, r["decider"], unit, len(counted), cites)
lines, total = [], 0
for (kind, source, unit) in sorted(q):
n, cited = q[(kind, source, unit)]
p = price(t, kind, unit)
a = n * p // UNITS[kind][unit - 1][1]
if n > U64 or a > U64:
return None
total += a
lines.append({"kind": kind, "source": source, "unit": unit, "quantity": n, "price": p, "amount": a,
"cited": len(cited), "cites": cites_digest(cited), "cited_records": sorted(cited)})
figs, seen = [], set()
for f in facts:
if f["kind"] == ROOM:
for d, fig in f["hops"]:
if d not in seen:
seen.add(d)
figs.append((1, fig))
for f in facts:
if f["kind"] == DELIVERY:
for d, fig in f["hops"]:
if d not in seen:
seen.add(d)
figs.append((2, fig))
for f in facts:
if f["kind"] == PACKAGE:
figs.append((3, f["figure"]))
elif f["kind"] == ADS:
figs += [(4, r["figure"]) for r in f["receipts"]]
rows, unmetered, sums = rows_of(figs)
if rows is None or total > U64:
return None
return {"records": sorted(records), "lines": lines, "energy": rows, "unmetered": unmetered, "amount": total, "sums": sums}
def rows_of(figs):
unmetered = [0, 0, 0, 0]
acc = {}
for s, (tag, joules, ppm) in figs:
if tag is None:
unmetered[s - 1] += 1
continue
e = acc.setdefault((s, tag), [0, 0, 0])
e[0] += 1
e[1] += joules
e[2] = max(e[2], ppm)
rows = [(s, tag) + tuple(acc[(s, tag)]) for s, tag in sorted(acc)]
if any(r[3] > U64 for r in rows):
return None, unmetered, {}
return rows, unmetered, {k: v[1] for k, v in acc.items()}
def summed_across(row, sums):
"""§6.6: a row whose joules sum its class and another class of its scope, not its own."""
s, tag, _, joules, _ = row
own = sums.get((s, tag))
if own is None or own == joules:
return False
others = [j for (sc, tg), j in sums.items() if sc == s and tg != tag]
for m in range(1, 1 << len(others)):
if own + sum(j for i, j in enumerate(others) if m >> i & 1) == joules:
return True
return False
def check_figures(inv, c):
"""§7.2 step 7, in its order."""
got = {(x["kind"], x["source"], x["unit"]): x for x in c["lines"]}
for x in inv["lines"]:
g = got.get((x["kind"], x["source"], x["unit"]))
if "quantity" not in SKIP and (g is None or x["quantity"] > g["quantity"]):
return "quantity_not_recorded"
if "summed" not in SKIP and any(summed_across(r, c["sums"]) for r in inv["energy"]):
return "classes_summed"
for x in inv["lines"]:
g = got.get((x["kind"], x["source"], x["unit"]))
if "citations" not in SKIP and g is not None and (x["cited"] != g["cited"] or x["cites"] != g["cites"]):
return "citation_mismatch"
strip = [{k: v for k, v in x.items() if k != "cited_records"} for x in c["lines"]]
if "mismatch" not in SKIP and (inv["records"] != c["records"] or inv["lines"] != strip or inv["energy"] != c["energy"]
or inv["unmetered"] != c["unmetered"] or inv["amount"] != c["amount"]):
return "invoice_mismatch"
return None
# ---------------------------------------------------------------- §8 the reports
def rows_json(rows):
return [{"class": LABELS[r[1]], "figures": r[2], "joules": r[3], "max_ppm": r[4]} for r in rows]
def ads_report(f):
receipts, nc = [], {}
tot = {"b": 0, "c": 0, "bs": 0, "cs": 0}
for r in f["receipts"]:
b = sum(1 for i in r["impressions"] if i["billable"])
c = sum(1 for i in r["impressions"] if i["counts"])
for i in r["impressions"]:
if i["billable"] and not i["counts"]:
nc[i["verdict"]] = nc.get(i["verdict"], 0) + 1
tot["b"] += b
tot["c"] += c
tot["bs"] += r["spend"]
tot["cs"] += r["counted_spend"]
receipts.append({
"identity": r["identity"].hex(), "decider": r["decider"].hex(), "cue": r["cue"].hex(), "session": r["session"].hex(),
"impressions": [{k: i[k] for k in ("slot", "ad_id", "price_micros", "quartiles_reached", "billable", "counts", "verdict")}
for i in r["impressions"]],
"billable_impressions": b, "billable_spend_micros": r["spend"], "counted_impressions": c,
"counted_spend_micros": r["counted_spend"]})
return {"room": f["room"].hex(), "claims_required": f["claims_required"], "receipts": receipts,
"billable_impressions": tot["b"], "counted_impressions": tot["c"], "billable_spend_micros": min(tot["bs"], U64),
"counted_spend_micros": min(tot["cs"], U64), "not_counted": dict(sorted(nc.items()))}
def overlaps_of(facts):
"""§8.2: by (content hash, downstream, first party, first record, second party, second record),
the bytes their items share."""
acks = [a for f in facts if f["kind"] == DELIVERY for a in f["acks"] if a["mode"] == 1]
out = {}
for x in acks:
for y in acks:
if not (x["upstream"] < y["upstream"] and x["downstream"] == y["downstream"]
and overlapping((x["start"], x["end"]), (y["start"], y["end"]))):
continue
for a in [i for l in x["item_lists"] for i in l]:
for b in [i for l in y["item_lists"] for i in l]:
if a[0] == b[0] and overlapping(a[1:], b[1:]):
k = (a[0], x["downstream"], x["upstream"], x["digest"], y["upstream"], y["digest"])
out[k] = min(out.get(k, 0) + min(a[2], b[2]) - max(a[1], b[1]), U64)
return out
def delivery_report(facts, c):
parties = {}
for f in facts:
if f["kind"] == DELIVERY:
parties.setdefault(f["statement"]["party"], []).append(f)
rows = []
for party in sorted(parties):
fs = parties[party]
classes = [[0, 0] for _ in range(4)]
free_bytes = stated = 0
declared = [0, 0, 0]
figs, seen = [], set()
currency = next((f["statement"]["currency"] for f in fs if f["statement"]["currency"] != bytes(3)), None)
for f in fs:
st = f["statement"]
for k in range(4):
classes[k][0] += st["classes"][k][0]
classes[k][1] += st["classes"][k][1]
free_bytes += sum(x["bytes"] for x in st["lines"] if x["kind"] == 1 and free(x["class"], x["payer"]))
stated += st["amount"]
for k in range(3):
declared[k] += st["unack"][k]
for d, fig in f["hops"]:
if d not in seen:
seen.add(d)
figs.append((2, fig))
energy, unmetered, _ = rows_of(figs)
rows.append({
"party": party.hex(), "statements": [f["digest"].hex() for f in fs],
"currency": None if currency is None else currency.decode("ascii"),
"classes": {CLASS_NAMES[k]: {"objects": classes[k][0], "bytes": classes[k][1]} for k in range(4)},
"free_bytes": free_bytes,
"declared": {"declarations": declared[0], "objects": declared[1], "bytes": declared[2]},
"stated_amount": stated,
"invoiced_amount": sum(x["amount"] for x in c["lines"] if x["kind"] == DELIVERY and x["source"] == party),
"energy": rows_json(energy or []), "unmetered": unmetered[1]})
ov = overlaps_of(facts)
overlaps = [{"content_hash": h.hex(), "downstream": d.hex(), "bytes": ov[(h, d, p1, r1, p2, r2)],
"first": {"party": p1.hex(), "record": r1.hex()}, "second": {"party": p2.hex(), "record": r2.hex()}}
for (h, d, p1, r1, p2, r2) in sorted(ov)]
return {"parties": rows, "overlaps": overlaps}
# ---------------------------------------------------------------- §7 verification
def unhex_field(doc, k):
v = doc.get(k)
if not isinstance(v, str) or not re.fullmatch(r"(?:[0-9a-f]{2})*", v):
raise Refuse("malformed", [k])
return bytes.fromhex(v)
def verify(doc):
"""§7.2: the outcome of a verification document."""
try:
tb, ib = unhex_field(doc, "tariff"), unhex_field(doc, "invoice")
pb = None if doc.get("previous") is None else unhex_field(doc, "previous")
sources = doc.get("sources")
if not isinstance(sources, list):
raise Refuse("malformed", ["source"])
t = read_tariff(tb) # step 1
inv = read_invoice(ib, ["invoice"]) # step 2
if "binding" not in SKIP and (inv["tariff"] != t["digest"] or inv["seller"] != t["seller"]
or inv["buyer"] != t["buyer"] or inv["currency"] != t["currency"]): # step 3
raise Refuse("tariff_mismatch", ["invoice"])
if "outside-tariff" not in SKIP and not (t["start"] <= inv["start"] and inv["end"] <= t["end"]):
raise Refuse("outside_tariff", ["invoice"])
prev = None
if inv["prev"] != ZERO: # step 4
if "previous-given" not in SKIP and (pb is None or sha(pb[:-64]) != inv["prev"]):
raise Refuse("record_missing", ["previous"])
if pb is not None:
prev = read_invoice(pb, ["previous"])
if prev["tariff"] != inv["tariff"] or prev["seller"] != inv["seller"] or prev["buyer"] != inv["buyer"]:
raise Refuse("tariff_mismatch", ["previous"])
if "previous-overlap" not in SKIP and prev["end"] > inv["start"]:
raise Refuse("invoice_overlap", ["previous"])
facts = []
for k, s in enumerate(sources): # step 5
try:
f = verify_source(s, [i for k, i in t["scope"] if k == 4])
except Refuse as e:
raise Refuse(e.reason, ["source", k] + list(e.at))
bad = check_source(t, inv["start"], inv["end"], f)
if bad:
raise Refuse(bad, ["source", k])
facts.append(f)
across = check_across(facts, prev) # step 6
if across:
raise Refuse(across[1], ["source", across[0]])
c = compute(t, facts) # step 7
if c is None:
raise Refuse("invoice_mismatch", ["invoice"])
bad = check_figures(inv, c)
if bad:
raise Refuse(bad, ["invoice"])
except Refuse as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}
return accepted(inv, facts, c)
def accepted(inv, facts, c):
unmetered, coverage = {}, {}
for s in range(1, 5):
n = inv["unmetered"][s - 1]
unmetered[SCOPES[s]] = n
coverage[SCOPES[s]] = "partial" if n > 0 else "whole" if any(r[0] == s for r in inv["energy"]) else "none"
return {
"outcome": "accept",
"digest": inv["digest"].hex(),
"tariff": inv["tariff"].hex(),
"currency": inv["currency"].decode("ascii"),
"start": inv["start"],
"end": inv["end"],
"records": len(inv["records"]),
"lines": [{"source_kind": KIND_NAMES[x["kind"]], "source": x["source"].hex(), "unit": UNITS[x["kind"]][x["unit"] - 1][0],
"quantity": x["quantity"], "price": x["price"], "amount": x["amount"],
"cites": [[k, i.hex()] for k, i in x["cited_records"]]} for x in c["lines"]],
"energy": [{"scope": SCOPES[r[0]], "class": LABELS[r[1]], "figures": r[2], "joules": r[3], "max_ppm": r[4]}
for r in inv["energy"]],
"unmetered": unmetered,
"coverage": coverage,
"amount": inv["amount"],
"reports": {"ads": [ads_report(f) for f in facts if f["kind"] == ADS],
"stitched": [f["statement"] for f in facts if f["kind"] == STITCHED],
"delivery": delivery_report(facts, c)},
}
# ---------------------------------------------------------------- the corpus
def document(doc, v):
return {"tariff": v["tariff"], "invoice": v["invoice"], "previous": v["previous"],
"sources": [doc["sources"][n] for n in v["sources"]]}
def check(doc, quiet=False):
"""The failures: every vector whose stated outcome is not the one this verifier reaches."""
fails = []
for v in doc.get("vectors", []):
try:
got = verify(document(doc, v))
except Exception as e: # a crash on a mutated copy is a failure to verify it
got = {"outcome": "crash", "error": "%s: %s" % (type(e).__name__, e)}
ok = got == v.get("outcome")
if not quiet:
what = got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if got["outcome"] == "refuse" else "")
print(("PASS" if ok else "FAIL") + " %s: %s" % (v.get("name"), what))
if not ok:
fails.append(v.get("name"))
return fails
def reasons_listed():
"""§10's table, in order."""
text = open(EXTENSION, encoding="utf-8").read()
at = text.index("\n## 10. Reasons")
end = text.find("\n## ", at + 1)
return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", text[at:end], re.M)]
def mutations(doc):
"""Copies of the corpus, one defect each, that a verifier must reject."""
def flip_hex(h, at):
b = bytearray(bytes.fromhex(h))
b[at % len(b)] ^= 0x01
return b.hex()
out = []
for k, v in enumerate(doc["vectors"]):
if v["outcome"]["outcome"] != "accept":
continue
for field, at in (("tariff", 6), ("tariff", -1), ("invoice", 40), ("invoice", -70), ("invoice", -1)):
m = copy.deepcopy(doc)
m["vectors"][k][field] = flip_hex(v[field], at)
out.append(("%s: %s byte %d flipped" % (v["name"], field, at), m))
m = copy.deepcopy(doc)
m["vectors"][k]["sources"] = v["sources"][:-1]
out.append(("%s: its last source dropped" % v["name"], m))
m = copy.deepcopy(doc)
m["vectors"][k]["outcome"]["amount"] += 1
out.append(("%s: its stated amount raised" % v["name"], m))
m = copy.deepcopy(doc)
m["vectors"][k]["outcome"]["lines"][0]["cites"] = []
out.append(("%s: its first line's citations dropped" % v["name"], m))
m = copy.deepcopy(doc)
rep = m["vectors"][0]["outcome"]["reports"]
rep["delivery"]["overlaps"] = []
out.append(("a_day: its overlaps dropped", m))
m = copy.deepcopy(doc)
m["vectors"][0]["outcome"]["reports"]["ads"][0]["not_counted"] = {}
out.append(("a_day: its ad report's not-counted figures dropped", m))
for k, v in enumerate(doc["vectors"]):
if v["outcome"]["outcome"] == "refuse":
m = copy.deepcopy(doc)
m["vectors"][k]["outcome"]["reason"] = "invoice_mismatch" if v["outcome"]["reason"] != "invoice_mismatch" else "malformed"
out.append(("%s: its stated reason changed" % v["name"], m))
break
return out
CHECKS = ["signatures", "buyer-signature", "tariff-rules", "invoice-rules", "row-of-no-class", "binding", "outside-tariff",
"previous-given", "previous-overlap", "scope", "conflict", "period", "duplicate", "overlap", "rebilled", "quantity",
"summed", "citations", "mismatch"]
def main(corpus_dir):
doc = json.load(open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8"))
fails = check(doc)
files = sorted(os.listdir(corpus_dir))
if files != ["README.md", "vectors.json"]:
fails.append("the corpus holds other files: %s" % files)
seen = {v["outcome"].get("reason") for v in doc["vectors"]}
listed = reasons_listed()
missing = [r for r in listed + ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "record_missing",
"classes_summed"] if r not in seen]
if missing:
fails.append("no vector is refused %s" % missing)
print("%s every reason of §10 refuses a vector (%d listed)" % ("PASS" if not missing else "FAIL", len(listed)))
weak = []
for name, m in mutations(doc):
if not check(m, quiet=True):
weak.append(name)
print("%s every mutated copy of the corpus fails (%d copies)" % ("PASS" if not weak else "FAIL", len(mutations(doc))))
fails += ["the mutated copy %r verifies" % w for w in weak]
undetected = []
for c in CHECKS:
SKIP.add(c)
try:
if not check(doc, quiet=True):
undetected.append(c)
finally:
SKIP.discard(c)
print("%s the corpus catches the verifier with each of its checks dropped (%d checks)" % ("PASS" if not undetected else "FAIL", len(CHECKS)))
fails += ["dropping the check %r goes unnoticed" % c for c in undetected]
for f in fails:
print("FAIL", f)
return 1 if fails else 0
def check_document(path):
"""One verification document (§7.1), such as a ledger service serves with an invoice: its
outcome, printed. Status 0 only when it is accepted."""
doc = json.load(open(path, encoding="utf-8"))
got = verify(doc)
print(json.dumps(got, indent=1))
return 0 if got["outcome"] == "accept" else 1
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--document":
sys.exit(check_document(sys.argv[2]))
if len(sys.argv) != 2:
print("usage: receipt_ledger_verify.py <live-conformance/ledger> | --document FILE", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))