#!/usr/bin/env python3 """Independent verifier of the revenue-share corpus (extensions/revenue-share.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, and BLAKE3 with ../claim-conformance/_blake3.py. Each ledger document is verified by the ledger corpus's own second implementation, receipt_ledger_verify.py (receipt-ledger §7), which states what each source contributes. Everything else is here: - the share contract (§3): reading, rules, splits, the operator's signature and each countersignature, and when two contracts overlap; - the program-as-run (§4): its JSON carriage, its signed bytes, its identity and when it verifies; - the impressions of the contract's carriers (§5.2), their prices, their instants (§5.3) and the date-time form a marker states (§5.4); - attribution (§6), the statement (§7): its reading, its lines, the largest-remainder division and the citations; verification (§8), compared in §8.2's order. For every vector it requires the outcome the corpus states; of the corpus, that every reason of §11 and every code it reuses 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 revenue_share_verify.py python3 -I revenue_share_verify.py --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 datetime import functools import hashlib import json import os import re import struct 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")) import _blake3 as B3 # noqa: E402 import _ed25519 as ED # noqa: E402 import receipt_ledger_verify as RLV # noqa: E402 EXTENSION = os.path.join(HERE, "..", "extensions", "revenue-share.md") CONTRACT_MAGIC, STATEMENT_MAGIC = b"WRC1", b"WRS1" # §3.1, §7.1 CONTRACT_DOMAIN = b"wai:share-contract\x01" # §3.3 ACCEPT_DOMAIN = b"wai:share-accept\x01" # §3.3 STATEMENT_DOMAIN = b"wai:share-statement\x01" # §7.5 CITES_DOMAIN = b"wai:share-cites\x01" # §7.4 ENTRY_DOMAIN = b"wai:video-channel-entry\x01" # §4.2 RUN_DOMAIN = b"wai:video-channel-receipt\x01" # §4.2 RUN_ID_DOMAIN = b"wai:video-channel-receipt-id\x01" # §4.2 LEAF, NODE = b"moq-jwp:merkle-leaf\x01", b"moq-jwp:merkle-node\x01" # jwp-receipts §2.2 WHOLE = 10 ** 6 U64, U32 = 2 ** 64 - 1, 2 ** 32 - 1 ZERO = bytes(32) ROOM, STITCHER = 1, 2 # §3.1 carriers PRECEDING, FOLLOWING, BETWEEN = 1, 2, 3 # §3.1 rule PRESENTED, BEACONS, UNPLACED = 1, 2, 3 # §5.2 classes CLASS_NAMES = {PRESENTED: "presented", BEACONS: "beacons", UNPLACED: "unplaced"} AD_RECEIPT, CLAIM, POD_INPUT, STITCHED_CLAIM, RUN, INVOICE = 6, 7, 8, 9, 10, 11 # §7.4 KINDS = {"program": 1, "ad": 2, "filler": 3} # §4.2 entry kind tags CLASS_TAG = {"ModelBased": 2, "Estimator": 3, "OnChipCounter": 4, "CalibratedInstrument": 5} # energy-measurement §2.7 EVIDENCE = {"ModelBased": [], "Estimator": [], "OnChipCounter": ["energy_uncertainty", "energy_counter_filtering"], "CalibratedInstrument": ["energy_uncertainty", "energy_calibration_ref"]} FILTERING = {"Unknown": 0, "Off": 1, "On": 2} FLOOR = 288676 # live-ads §8.5 rule 5 WHITE_SPACE = set(map(chr, [0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x85, 0xA0, 0x1680] + list(range(0x2000, 0x200B)) + [0x2028, 0x2029, 0x202F, 0x205F, 0x3000])) RUN_MEMBERS = ["channel_id", "schedule_root", "schedule", "window_start_ms", "window_end_ms", "aired", "work_schedule_ops", "joules_micro", "energy_provenance", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig"] REUSED = ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "outside_period", "line_unsourced", "citation_mismatch"] DATE = re.compile(rb"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})") # §5.4 EPOCH = datetime.date(1970, 1, 1).toordinal() SKIP = set() class Refuse(Exception): def __init__(self, reason, at): super().__init__(reason) self.reason, self.at = reason, at class NoRead(Exception): pass def sha(*parts): h = hashlib.sha256() for p in parts: h.update(p) return h.digest() def b3(*parts): return B3.blake3(b"".join(parts)) def be64(v): return struct.pack(">Q", v) def be32(v): return struct.pack(">I", v) @functools.lru_cache(maxsize=None) def ed_verify(key, msg, sig): """WAI's strict rule: None, `bad_key` or `bad_signature`. A pure function, held once.""" return ED.verify(key, msg, sig) 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 text(self): return self.take(self.u(2)) def head(b, magic): """§3.4'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 good_text(t): """UTF-8 of 1 to 1024 bytes (§3.4).""" if not 1 <= len(t) <= 1024: return False try: t.decode("utf-8") except UnicodeDecodeError: return False return True def unhex(v): if not isinstance(v, str) or len(v) % 2 or any(c not in "0123456789abcdef" for c in v): raise Refuse("malformed", []) return bytes.fromhex(v) # ---------------------------------------------------------------- §3 the contract def read_contract(b): """§3.4: the contract held to its rules and its splits' sums, or Refuse (place relative).""" bad = head(b, CONTRACT_MAGIC) if bad: raise Refuse(bad, []) r = R(b, 5) try: c = {"operator": r.take(32), "start": r.u(8), "end": r.u(8), "currency": r.take(3), "channel": r.text(), "channel_signer": r.take(32), "rule": r.u(1), "between": r.u(2)} c["carriers"] = [(r.u(1), r.take(32)) for _ in range(r.u(2))] c["owners"] = [r.take(32) for _ in range(r.u(1))] c["splits"] = [] for _ in range(r.u(2)): op = r.u(4) c["splits"].append({"operator_ppm": op, "shares": [(r.u(1), r.u(4)) for _ in range(r.u(1))]}) c["assets"] = [(r.text(), r.u(2)) for _ in range(r.u(4))] body_end = r.p c["operator_sig"] = r.take(64) c["countersigs"] = [r.take(64) for _ in c["owners"]] except ValueError: raise Refuse("truncated", []) if r.p != len(b): raise Refuse("malformed", []) n = len(c["owners"]) rules = (c["start"] < c["end"] and capitals(c["currency"]) and c["operator"] != ZERO and c["channel_signer"] != ZERO and c["operator"] not in c["owners"] and good_text(c["channel"]) and 1 <= c["rule"] <= 3 and c["between"] < len(c["splits"]) and c["carriers"] and ascending(c["carriers"]) and all(k in (ROOM, STITCHER) and i != ZERO for k, i in c["carriers"]) and n >= 1 and ascending(c["owners"]) and all(o != ZERO for o in c["owners"]) and c["splits"] and all(ascending([i for i, _ in s["shares"]]) and all(i < n and p > 0 for i, p in s["shares"]) for s in c["splits"]) and all(any(i == o for s in c["splits"] for i, _ in s["shares"]) for o in range(n)) and c["assets"] and ascending([a for a, _ in c["assets"]]) and all(good_text(a) and s < len(c["splits"]) for a, s in c["assets"])) if not rules and "contract-rules" not in SKIP: raise Refuse("contract_invalid", []) if any(s["operator_ppm"] + sum(p for _, p in s["shares"]) != WHOLE for s in c["splits"]) and "split-sums" not in SKIP: raise Refuse("split_not_whole", []) c["body"] = b[:body_end] c["digest"] = sha(c["body"]) return c def in_force(b): """§3.3, §3.4: read, then the operator's signature, then each countersignature in turn.""" c = read_contract(b) if "operator-signature" not in SKIP: bad = ed_verify(c["operator"], CONTRACT_DOMAIN + c["body"], c["operator_sig"]) if bad: raise Refuse(bad, ["operator"]) if "countersignatures" not in SKIP: for i, (k, s) in enumerate(zip(c["owners"], c["countersigs"])): if s == bytes(64): raise Refuse("not_countersigned", ["owner", i]) bad = ed_verify(k, ACCEPT_DOMAIN + c["body"], s) if bad: raise Refuse(bad, ["owner", i]) return c def contract_at(b, at): try: return in_force(b) except Refuse as e: raise Refuse(e.reason, at + e.at) def overlaps(a, b): """§3.5.""" meet = a["start"] < b["end"] and b["start"] < a["end"] return meet and (a["channel"] == b["channel"] or any(x in b["carriers"] for x in a["carriers"])) def parties(c, split): """§3.2: a split's parties in their order: its owners as it lists them, then the operator with a share.""" s = c["splits"][split] out = [(c["owners"][i], p) for i, p in s["shares"]] if s["operator_ppm"] > 0: out.append((c["operator"], s["operator_ppm"])) return out def split_of(c, asset): for a, s in c["assets"]: if a == asset: return s return None # ---------------------------------------------------------------- §4 the program-as-run def num(v, top=U64): if not isinstance(v, int) or isinstance(v, bool) or not 0 <= v <= top: raise NoRead() return v def hexn(v, n): if not isinstance(v, str) or len(v) != 2 * n or any(c not in "0123456789abcdef" for c in v): raise NoRead() return bytes.fromhex(v) def text(v): if not isinstance(v, str): raise NoRead() return v def read_run(o): """§4.1: the run, or NoRead.""" if not isinstance(o, dict): raise NoRead() label = o.get("energy_provenance") if label is not None and label not in EVIDENCE: raise NoRead() if set(o) != set(RUN_MEMBERS) | set(EVIDENCE.get(label, [])): raise NoRead() r = {"channel_id": text(o["channel_id"]), "schedule_root": hexn(o["schedule_root"], 32), "window_start_ms": num(o["window_start_ms"]), "window_end_ms": num(o["window_end_ms"]), "work_schedule_ops": num(o["work_schedule_ops"]), "joules_micro": num(o["joules_micro"]), "signer_pubkey": hexn(o["signer_pubkey"], 32), "signer_id": text(o["signer_id"]), "sig": hexn(o["sig"], 64)} if not isinstance(o["schedule"], list) or not isinstance(o["aired"], list): raise NoRead() r["schedule"] = [] for e in o["schedule"]: if not isinstance(e, dict) or set(e) != {"asset_id", "start_ms", "duration_ms", "kind"} or e.get("kind") not in KINDS: raise NoRead() x = {"asset_id": text(e["asset_id"]), "start_ms": num(e["start_ms"]), "duration_ms": num(e["duration_ms"]), "kind": e["kind"]} if x["start_ms"] + x["duration_ms"] > U64: raise NoRead() r["schedule"].append(x) r["aired"] = [num(a) for a in o["aired"]] g = o["grant"] if not isinstance(g, dict) or set(g) != {"grant_hash", "capability", "joule_ceiling_micro", "funds_ceiling"}: raise NoRead() r["grant"] = {"grant_hash": hexn(g["grant_hash"], 32), "capability": text(g["capability"]), "joule_ceiling_micro": num(g["joule_ceiling_micro"]), "funds_ceiling": None if g["funds_ceiling"] is None else num(g["funds_ceiling"])} r["parent"] = None if o["parent_receipt_hash"] is None else hexn(o["parent_receipt_hash"], 32) r["class"] = None if label is not None: c = {"label": label} if label in ("OnChipCounter", "CalibratedInstrument"): u = o["energy_uncertainty"] if not isinstance(u, dict) or set(u) != {"relative_ppm", "window_us"}: raise NoRead() c["ppm"], c["window"] = num(u["relative_ppm"], U32), num(u["window_us"]) if label == "OnChipCounter": if o["energy_counter_filtering"] not in FILTERING: raise NoRead() c["filtering"] = FILTERING[o["energy_counter_filtering"]] if label == "CalibratedInstrument": c["ref"] = text(o["energy_calibration_ref"]) r["class"] = c return r def leaf(e): a = e["asset_id"].encode() return b3(ENTRY_DOMAIN, be32(len(a)), a, be64(e["start_ms"]), be64(e["duration_ms"]), bytes([KINDS[e["kind"]]])) def merkle(hashes): """jwp-receipts §2.2, each leaf of its first form; zero for none.""" if not hashes: return ZERO level = [b3(LEAF, h) for h in hashes] while len(level) > 1: if len(level) % 2: level.append(level[-1]) level = [b3(NODE, level[k], level[k + 1]) for k in range(0, len(level), 2)] return level[0] def class_bytes(c): """energy-measurement §2.7's signed encoding; tag 0 for none.""" if c is None: return b"\x00" out = bytes([CLASS_TAG[c["label"]]]) if c["label"] == "OnChipCounter": out += be32(c["ppm"]) + be64(c["window"]) + bytes([c["filtering"]]) elif c["label"] == "CalibratedInstrument": ref = c["ref"].encode() out += be32(c["ppm"]) + be64(c["window"]) + be32(len(ref)) + ref return out def run_payload(r): """§4.2: the signing payload.""" g = r["grant"] cid, cap, sid = r["channel_id"].encode(), g["capability"].encode(), r["signer_id"].encode() out = [RUN_DOMAIN, be32(len(cid)), cid, r["schedule_root"], be64(r["window_start_ms"]), be64(r["window_end_ms"]), be64(r["work_schedule_ops"]), be64(r["joules_micro"]), class_bytes(r["class"]), g["grant_hash"], be32(len(cap)), cap, be64(g["joule_ceiling_micro"])] out.append(b"\x00" if g["funds_ceiling"] is None else b"\x01" + be64(g["funds_ceiling"])) out.append(b"\x00" if r["parent"] is None else b"\x01" + r["parent"]) out.append(be64(len(r["aired"]))) out += [be64(a) for a in r["aired"]] out.append(be64(len(r["schedule"]))) out += [leaf(e) for e in r["schedule"]] out += [r["signer_pubkey"], be32(len(sid)), sid] return b"".join(out) def run_identity(r): return b3(RUN_ID_DOMAIN, run_payload(r), r["sig"]) def coheres(j, c): """live-ads §8.5 rule 5: the figure and its class stand or fall together.""" if c is None: return j == 0 if j == 0: return False if c["label"] in ("OnChipCounter", "CalibratedInstrument"): if c["ppm"] == 0 or c["window"] == 0 or j * c["ppm"] < FLOOR: return False if c["label"] == "CalibratedInstrument" and all(ch in WHITE_SPACE for ch in c["ref"]): return False return True def run_verifies(r): """§4.3.""" s = r["schedule"] if merkle([leaf(e) for e in s]) != r["schedule_root"] or r["work_schedule_ops"] != len(s): return False if r["window_start_ms"] >= r["window_end_ms"]: return False if any(a["start_ms"] + a["duration_ms"] != b["start_ms"] for a, b in zip(s, s[1:])): return False touch = [k for k, e in enumerate(s) if e["start_ms"] < r["window_end_ms"] and e["start_ms"] + e["duration_ms"] > r["window_start_ms"]] if r["aired"] != touch: return False if not coheres(r["joules_micro"], r["class"]): return False return ed_verify(r["signer_pubkey"], run_payload(r), r["sig"]) is None # ---------------------------------------------------------------- §5 the instants def instant_of(s): """§5.4: the microseconds since 1970 a START-DATE states, or None.""" m = DATE.fullmatch(s) if not m: return None y, mo, d, h, mi, sec = (int(x) for x in m.groups()[:6]) frac, zone = m.group(7), m.group(8) if h > 23 or mi > 59 or sec > 59: return None try: days = datetime.date(y, mo, d).toordinal() - EPOCH except ValueError: return None off = 0 if zone != b"Z": oh, om = int(zone[1:3]), int(zone[4:6]) if oh > 23 or om > 59: return None off = (oh * 3600 + om * 60) * (1 if zone[:1] == b"+" else -1) secs = days * 86400 + h * 3600 + mi * 60 + sec - off if secs < 0: return None us = int((frac or b"").ljust(6, b"0")[:6] or b"0") return secs * 10 ** 6 + us def marker_instant(claim): """A stitched claim's marker (stitched-presentation §5.1, its long field at offset 125): the instant its START-DATE states, for carriage 1 or 2 (§5.4); None otherwise.""" carriage = claim[5] if carriage not in (1, 2): return None r = R(claim, 125) try: body = r.text() b = R(body) b.text() start_date = b.text() except ValueError: return None return instant_of(start_date) # ---------------------------------------------------------------- §7 the statement def read_statement(b): """§7.6: the statement held to its rules, or Refuse at `statement`.""" at = ["statement"] bad = head(b, STATEMENT_MAGIC) if bad: raise Refuse(bad, at) r = R(b, 5) try: s = {"contract": r.take(32), "party": r.take(32), "start": r.u(8), "end": r.u(8), "currency": r.take(3)} s["invoices"] = [r.take(32) for _ in range(r.u(2))] s["runs"] = [r.take(32) for _ in range(r.u(2))] s["lines"] = [] for _ in range(r.u(2)): s["lines"].append({"split": r.u(2), "class": r.u(1), "impressions": r.u(8), "revenue": r.u(8), "ppm": r.u(4), "amount": r.u(8), "cited": r.u(4), "cites": r.take(32)}) s["total"] = r.u(8) s["sig"] = r.take(64) except ValueError: raise Refuse("truncated", at) if r.p != len(b): raise Refuse("malformed", at) rules = (s["start"] < s["end"] and capitals(s["currency"]) and s["party"] != ZERO and ascending(s["invoices"]) and ascending(s["runs"]) and ascending([(x["split"], x["class"]) for x in s["lines"]]) and all(1 <= x["class"] <= 3 and x["impressions"] > 0 and x["cited"] > 0 and 1 <= x["ppm"] <= WHOLE and x["amount"] <= x["revenue"] for x in s["lines"])) if not rules and "statement-rules" not in SKIP: raise Refuse("share_statement_invalid", at) s["body"] = b[:-64] s["digest"] = sha(s["body"]) return s def divide(revenue, ppms): """§7.3: the largest-remainder rule. Ties go to the earlier party.""" quota = [revenue * p for p in ppms] parts = [q // WHOLE for q in quota] left = revenue - sum(parts) order = sorted(range(len(ppms)), key=lambda i: (-(quota[i] % WHOLE), i)) for i in order[:left]: parts[i] += 1 return parts def cites_digest(cited): return sha(CITES_DOMAIN, *[bytes([k]) + i for k, i in sorted(cited)]) # ---------------------------------------------------------------- §8 verification _LEDGERS = {} def ledger_outcome(d): """receipt-ledger §7: a ledger document's outcome, by the ledger corpus's own verifier, and, when it is accepted, its tariff, its invoice and what each source states (receipt-ledger §5.2). A pure function of the document: each is verified once.""" key = json.dumps(d, sort_keys=True) if key not in _LEDGERS: got, facts = RLV.verify(d), None if got["outcome"] == "accept": t = RLV.read_tariff(bytes.fromhex(d["tariff"])) inv = RLV.read_invoice(bytes.fromhex(d["invoice"]), ["invoice"]) signers = [i for k, i in t["scope"] if k == 4] facts = (t, inv, [RLV.verify_source(s, signers) for s in d["sources"]]) _LEDGERS[key] = (got, facts) return _LEDGERS[key] def attributed(c, start, end, runs, ledgers, docs): """§8.2 step 6: every impression of the contract's carriers, placed and attributed (§5, §6).""" ids = [run_identity(r) for r in runs] out = [] for k, (t, inv, facts) in enumerate(ledgers): prices = {PRESENTED: RLV.price(t, 4, 1), UNPLACED: RLV.price(t, 4, 1), BEACONS: RLV.price(t, 4, 2)} for j, f in enumerate(facts): at = ["ledger", k, "source", j] found = [] if f["kind"] == RLV.ADS and (ROOM, f["room"]) in c["carriers"]: for rc in f["receipts"]: for i in rc["impressions"]: if not i["counts"]: continue recs = [(AD_RECEIPT, rc["identity"])] if f["claims_required"]: if i["claim"] is not None: recs.append((CLAIM, i["claim"])) cls = PRESENTED else: cls = BEACONS found.append((recs, cls, True, f["t0"] + rc["t"])) elif f["kind"] == RLV.STITCHED: given = [bytes.fromhex(x) for x in docs[k]["sources"][j]["claims"]] digests = [sha(g[:-64]) if len(g) >= 64 else None for g in given] for rec in f["records"]: if (STITCHER, rec["signer"]) not in c["carriers"]: continue for _, claims in rec["ads"]: for i, d in sorted((digests.index(d), d) for d in claims if d in digests): inst = marker_instant(given[i]) found.append(([(POD_INPUT, rec["digest"]), (STITCHED_CLAIM, d)], PRESENTED if inst is not None else UNPLACED, inst is not None, inst)) for recs, cls, placed, inst in found: imp = {"ledger": k, "source": j, "records": recs, "class": cls, "price": prices[cls], "invoice": inv["digest"], "at": None, "run": None, "entry": None, "asset": None, "split": c["between"]} if placed: if not (start <= inst < end and inst <= U64) and "impression-period" not in SKIP: raise Refuse("outside_period", at) holds = [n for n, r in enumerate(runs) if r["window_start_ms"] * 1000 <= inst < r["window_end_ms"] * 1000] entry = None if holds: sched = runs[holds[0]]["schedule"] es = [e for e, x in enumerate(sched) if x["start_ms"] * 1000 <= inst < (x["start_ms"] + x["duration_ms"]) * 1000] entry = es[0] if es else None if entry is None: if "window" not in SKIP: raise Refuse("outside_window", at) out.append(imp) continue asset = asset_of(sched, entry, c["rule"]) if asset is not None: split = split_of(c, asset.encode()) if split is None: if "unmapped" not in SKIP: raise Refuse("asset_unmapped", at) split = c["between"] imp["split"] = split imp.update({"at": inst, "run": ids[holds[0]], "entry": entry, "asset": asset}) out.append(imp) return out def asset_of(sched, e, rule): """§6.2: a programme's own asset, or by the rule; None for none.""" if sched[e]["kind"] == "program": return sched[e]["asset_id"] if rule == PRECEDING: before = [x for x in sched[:e] if x["kind"] == "program"] return before[-1]["asset_id"] if before else None if rule == FOLLOWING: after = [x for x in sched[e + 1:] if x["kind"] == "program"] return after[0]["asset_id"] if after else None return None def groups_of(imps): g = {} for i in imps: e = g.setdefault((i["split"], i["class"]), {"impressions": 0, "revenue": 0, "cited": set()}) e["impressions"] += 1 e["revenue"] += i["price"] e["cited"].update(i["records"]) if i["run"] is not None: e["cited"].add((RUN, i["run"])) e["cited"].add((INVOICE, i["invoice"])) return g def lines_of(c, party, groups): """§7.2 to §7.4: the party's lines, each with its cited records and its split's division; None for a figure past its field.""" out = [] for split, cls in sorted(groups): g = groups[(split, cls)] ps = parties(c, split) mine = [n for n, (p, _) in enumerate(ps) if p == party] if not mine: continue if g["revenue"] > U64 or len(g["cited"]) > U32: return None parts = divide(g["revenue"], [p for _, p in ps]) me = mine[0] out.append({"split": split, "class": cls, "impressions": g["impressions"], "revenue": g["revenue"], "ppm": ps[me][1], "amount": parts[me], "cited": len(g["cited"]), "cites": cites_digest(g["cited"]), "cited_records": sorted(g["cited"]), "division": [(p, s, a) for (p, s), a in zip(ps, parts)]}) return out def verify(doc): """§8.2: the outcome of a verification document.""" try: if not isinstance(doc, dict) or set(doc) != {"contract", "contracts", "statement", "runs", "ledgers"}: raise Refuse("malformed", []) if not isinstance(doc["contracts"], list) or not isinstance(doc["runs"], list) or not isinstance(doc["ledgers"], list): raise Refuse("malformed", []) cb, sb = unhex(doc["contract"]), unhex(doc["statement"]) others = [unhex(x) for x in doc["contracts"]] c = contract_at(cb, ["contract"]) # step 1 for k, ob in enumerate(others): # step 2 o = contract_at(ob, ["contracts", k]) if o["digest"] == c["digest"]: continue if overlaps(o, c) and "overlap" not in SKIP: raise Refuse("contract_overlap", ["contracts", k]) s = read_statement(sb) # step 3 if "binding" not in SKIP and (s["contract"] != c["digest"] or s["currency"] != c["currency"] or (s["party"] != c["operator"] and s["party"] not in c["owners"])): raise Refuse("contract_mismatch", ["statement"]) if "contract-period" not in SKIP and not (c["start"] <= s["start"] and s["end"] <= c["end"]): raise Refuse("outside_contract", ["statement"]) if "statement-signature" not in SKIP: bad = ed_verify(c["operator"], STATEMENT_DOMAIN + s["body"], s["sig"]) if bad: raise Refuse(bad, ["statement"]) runs = [] # step 4 for k, o in enumerate(doc["runs"]): try: r = read_run(o) except NoRead: raise Refuse("malformed", ["run", k]) if "run-verify" not in SKIP and not run_verifies(r): raise Refuse("run_invalid", ["run", k]) if "run-foreign" not in SKIP and (r["channel_id"].encode() != c["channel"] or r["signer_pubkey"] != c["channel_signer"]): raise Refuse("run_foreign", ["run", k]) if "run-overlap" not in SKIP and any(r["window_start_ms"] < x["window_end_ms"] and x["window_start_ms"] < r["window_end_ms"] for x in runs): raise Refuse("run_overlap", ["run", k]) runs.append(r) ledgers, seen, breaks = [], set(), set() # step 5 for k, d in enumerate(doc["ledgers"]): if not isinstance(d, dict): raise Refuse("malformed", ["ledger", k, "tariff"]) got, held = ledger_outcome(d) if got["outcome"] != "accept": raise Refuse(got["reason"], ["ledger", k] + got["at"]) t, inv, facts = held if "invoice-foreign" not in SKIP and (inv["seller"] != c["operator"] or inv["currency"] != c["currency"]): raise Refuse("invoice_foreign", ["ledger", k]) if "ledger-period" not in SKIP and not (s["start"] <= inv["start"] and inv["end"] <= s["end"]): raise Refuse("outside_period", ["ledger", k]) records = {x for x in inv["records"] if 6 <= x[0] <= 9} these = {(f["room"], rc["cue"], rc["session"]) for f in facts if f["kind"] == RLV.ADS for rc in f["receipts"]} if "duplicate" not in SKIP and (records & seen or these & breaks): raise Refuse("impression_duplicate", ["ledger", k]) seen |= records breaks |= these ledgers.append((t, inv, facts)) imps = attributed(c, s["start"], s["end"], runs, ledgers, doc["ledgers"]) # step 6 groups = groups_of(imps) # step 7 for x in s["lines"]: g = groups.get((x["split"], x["class"])) holds = any(p == s["party"] for p, _ in parties(c, x["split"])) if x["split"] < len(c["splits"]) else False if "unsourced" not in SKIP and (g is None or not holds or x["impressions"] > g["impressions"]): raise Refuse("line_unsourced", ["statement"]) lines = lines_of(c, s["party"], groups) if lines is None: raise Refuse("share_statement_mismatch", ["statement"]) for x in s["lines"]: mine = [y for y in lines if (y["split"], y["class"]) == (x["split"], x["class"])] if mine and "citations" not in SKIP and (x["cited"] != mine[0]["cited"] or x["cites"] != mine[0]["cites"]): raise Refuse("citation_mismatch", ["statement"]) total = sum(y["amount"] for y in lines) same = (s["invoices"] == sorted(inv["digest"] for _, inv, _ in ledgers) and s["runs"] == sorted(run_identity(r) for r in runs) and [{k: x[k] for k in ("split", "class", "impressions", "revenue", "ppm", "amount", "cited", "cites")} for x in s["lines"]] == [{k: y[k] for k in ("split", "class", "impressions", "revenue", "ppm", "amount", "cited", "cites")} for y in lines] and s["total"] == total and total <= U64) if not same and "mismatch" not in SKIP: raise Refuse("share_statement_mismatch", ["statement"]) except Refuse as e: return {"outcome": "refuse", "reason": e.reason, "at": e.at} return accepted(c, s, imps, lines) def accepted(c, s, imps, lines): """§8.3.""" return { "outcome": "accept", "statement": s["digest"].hex(), "contract": s["contract"].hex(), "party": s["party"].hex(), "role": "operator" if s["party"] == c["operator"] else "owner", "currency": s["currency"].decode("ascii"), "start": s["start"], "end": s["end"], "invoices": [d.hex() for d in s["invoices"]], "runs": [d.hex() for d in s["runs"]], "impressions": [{"ledger": i["ledger"], "source": i["source"], "records": [[k, d.hex()] for k, d in i["records"]], "class": CLASS_NAMES[i["class"]], "price": i["price"], "at": i["at"], "run": None if i["run"] is None else i["run"].hex(), "entry": i["entry"], "asset": i["asset"], "split": i["split"]} for i in imps], "lines": [{"split": y["split"], "class": CLASS_NAMES[y["class"]], "impressions": y["impressions"], "revenue": y["revenue"], "ppm": y["ppm"], "amount": y["amount"], "cites": [[k, d.hex()] for k, d in y["cited_records"]], "division": [{"party": p.hex(), "ppm": pp, "part": a} for p, pp, a in y["division"]]} for y in lines], "total": s["total"], } # ---------------------------------------------------------------- the corpus def document(doc, v): """A vector as a verification document (§8.1), its parts resolved by name.""" def ledger(n): x = doc["ledgers"][n] return {"tariff": x["tariff"], "invoice": x["invoice"], "previous": x["previous"], "sources": [doc["sources"][s] for s in x["sources"]]} return {"contract": doc["contracts"][v["contract"]], "contracts": [doc["contracts"][n] for n in v["contracts"]], "statement": v["statement"], "runs": None if v["runs"] is None else [doc["runs"][n] for n in v["runs"]], "ledgers": [ledger(n) for n in v["ledgers"]]} 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(): """§11's table, in order.""" text_ = open(EXTENSION, encoding="utf-8").read() at = text_.index("\n## 11. 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 flip_hex(h, at): b = bytearray(bytes.fromhex(h)) b[at % len(b)] ^= 0x01 return b.hex() def mutations(doc): """Copies of the corpus, one defect each, that a verifier must reject.""" out = [] accepted_ = [(k, v) for k, v in enumerate(doc["vectors"]) if v["outcome"]["outcome"] == "accept" and v["outcome"]["lines"]] for k, v in accepted_: for at in (40, 70, 90, -100, -70, -1): m = copy.deepcopy(doc) m["vectors"][k]["statement"] = flip_hex(v["statement"], at) out.append(("%s: statement byte %d flipped" % (v["name"], at), m)) m = copy.deepcopy(doc) m["vectors"][k]["runs"] = v["runs"][:-1] out.append(("%s: its last run dropped" % v["name"], m)) m = copy.deepcopy(doc) m["vectors"][k]["ledgers"] = v["ledgers"][:-1] out.append(("%s: its last ledger document dropped" % v["name"], m)) m = copy.deepcopy(doc) m["vectors"][k]["outcome"]["total"] += 1 out.append(("%s: its stated total raised" % v["name"], m)) m = copy.deepcopy(doc) m["vectors"][k]["outcome"]["lines"][0]["division"].pop() out.append(("%s: its first line's division cut short" % v["name"], m)) m = copy.deepcopy(doc) m["vectors"][k]["outcome"]["impressions"][0]["asset"] = "a/ep-103" out.append(("%s: its first impression moved to another asset" % v["name"], m)) for name, at in (("preceding", 60), ("preceding", -1), ("preceding", -130)): m = copy.deepcopy(doc) m["contracts"][name] = flip_hex(doc["contracts"][name], at) out.append(("the contract %s: byte %d flipped" % (name, at), m)) m = copy.deepcopy(doc) m["runs"]["oct_25"]["schedule"][0]["asset_id"] = "b/doc-8" out.append(("the run of the 25th: its first programme renamed", m)) m = copy.deepcopy(doc) m["runs"]["oct_16"]["sig"] = flip_hex(doc["runs"]["oct_16"]["sig"], 3) out.append(("the run of the 16th: its signature flipped", m)) for k, v in enumerate(doc["vectors"]): if v["outcome"]["outcome"] == "refuse": m = copy.deepcopy(doc) m["vectors"][k]["outcome"]["reason"] = "share_statement_mismatch" if v["outcome"]["reason"] != "share_statement_mismatch" else "malformed" out.append(("%s: its stated reason changed" % v["name"], m)) break return out CHECKS = ["contract-rules", "split-sums", "operator-signature", "countersignatures", "overlap", "statement-rules", "binding", "contract-period", "statement-signature", "run-verify", "run-foreign", "run-overlap", "invoice-foreign", "ledger-period", "duplicate", "impression-period", "window", "unmapped", "unsourced", "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 + REUSED if r not in seen] if missing: fails.append("no vector is refused %s" % missing) print("%s every reason of §11 and every reused code refuses a vector (%d listed)" % ("PASS" if not missing else "FAIL", len(listed))) muts = mutations(doc) weak = [name for name, m in muts if not check(m, quiet=True)] print("%s every mutated copy of the corpus fails (%d copies)" % ("PASS" if not weak else "FAIL", len(muts))) 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 (§8.1): 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: revenue_share_verify.py | --document FILE", file=sys.stderr) sys.exit(2) sys.exit(main(sys.argv[1]))