Written from extensions/revenue-share.md, sharing no code with wai-rs: reads every contract, run, marker and statement itself, verifies each ledger document with receipt_ledger_verify.py, recomputes every attribution, line, division and citation, and requires every mutated copy of the corpus and every weakened verifier to fail. The text states the aired test of a run exactly; the module's complex types are named. Co-Authored-By: Claude Opus 5.5 <[email protected]>
893 lines
39 KiB
Python
893 lines
39 KiB
Python
#!/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 <live-conformance/share>
|
|
python3 -I revenue_share_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 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 <live-conformance/share> | --document FILE", file=sys.stderr)
|
|
sys.exit(2)
|
|
sys.exit(main(sys.argv[1]))
|