open-standards/wai/live-conformance/live_record_verify.py
Transaction Science 673554abf0 live-recording: a record with the link record's magic, WLK1, is not a clip record
The link corpus refuses a part that bears the clip record's magic; this adds the reverse.
The live-recording corpus gains the clip link_record_magic: chunk 4's clip record beginning
WLK1 and signed under it, so the magic is its one defect, refused malformed at the clip
(§5.2, which now names WLK1 among the other magics). wai_live_record_vectors writes it;
the README lists it.

live_record_verify.py requires the same refusal, malformed at the clip, of a copy of each
accepted clip given WLK1, so a verifier that reads past the magic to the signature fails.
The CHANGELOG entry for WLK1 says so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:19:53 -05:00

887 lines
39 KiB
Python

#!/usr/bin/env python3
"""Independent verifier of the live-recording corpus (extensions/live-recording.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 take is verified by the capture-binding corpus's
own second implementation, capture_take_verify.py (capture-binding §9), and the room by
the live-rooms corpus's, live_rooms_verify.py (live-rooms §9), each written from its text
alone. Everything else is here:
- the package (§3): the listing of SPEC §3.1, written and checked here; the components and
their digests; the room's files and the events' chains split into their records;
- the recording manifest (§4.1, §4.2), who may sign it (§4.3), recording verification
(§4.4) and the verdicts of complete and publishable (§4.5);
- the clip record (§5.1, §5.2); the range proof of §5.3, by its recursion, and, for a run
of one chunk, by the inclusion-proof check of RFC 9162 §2.1.3.2 as that RFC states it,
which must agree; clip verification (§5.4); the time correspondence and its bound (§6);
and the FETCH address of §8.
For every vector it requires the outcome, reason and place, and the data the corpus states;
of the corpus, that every reason of §9 appears in it, and that every take it says it reuses
is capture binding's, byte for byte. Then it mutates copies of the corpus, one defect each,
and requires every copy to fail, and requires a copy of each accepted clip whose record
begins with the contribution link record's magic (live-contribution §6.2) to be refused as
`malformed` at the clip.
python3 live_record_verify.py <live-conformance/recordings>
Exit status 0 only if every vector verifies and every mutated copy 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"))
import live_events_verify as LEV # noqa: E402
import live_rooms_verify as LRV # noqa: E402
import capture_take_verify as CT # noqa: E402
import _ed25519 as ED # noqa: E402
Refuse = LEV.Refuse
MANIFEST_DOMAIN = b"wai:live-recording\x01"
CLIP_DOMAIN = b"wai:live-clip\x01"
LEAF_DOMAIN = b"wai:take-leaf\x01"
NODE_DOMAIN = b"wai:out-node\x01"
TAKE_DOMAIN = b"wai:capture-take\x01"
CLIP_MAGIC = b"WCL1" # §5.1
LINK_MAGIC = b"WLK1" # live-contribution §6.2: the contribution link record, another record's magic
LISTING, MANIFEST_FILE = "LISTING", "recording.wrm"
ROOM, EVENTS, TAKE, COMPOSITE = 1, 2, 3, 4
COMPLETE = 1
RECORD_POWER = 8
TAKE_COMMIT, CONTROL = 0x0C, 0x09
JOIN, LEAVE, GRANT_EXCHANGE = 0x10, 0x11, 0x14
OPEN, CLOSE = 1, 3
PURPOSES = [(1, "live"), (2, "record"), (4, "distribute"), (8, "train")]
ZERO = bytes(32)
# §9, in its order.
REASONS = [
"listing_mismatch", "recording_manifest_invalid", "component_invalid", "component_digest_mismatch",
"component_unlisted", "recording_signer_not_permitted", "outside_span", "take_omitted",
"clip_record_invalid", "clip_device_mismatch", "clip_take_mismatch", "tree_head_not_committed",
"clip_out_of_range", "clip_not_included", "clip_window_invalid", "clip_chunk_missing",
]
def sha(*parts):
h = hashlib.sha256()
for p in parts:
h.update(p)
return h.digest()
class Stop(Exception):
def __init__(self, at, reason):
Exception.__init__(self, reason)
self.at, self.reason = at, reason
class Reader:
"""Little-endian fields in order; running out is `truncated`."""
def __init__(self, b, p):
self.b, self.p = b, p
def take(self, n):
if self.p + n > len(self.b):
raise Refuse("truncated")
s = self.b[self.p:self.p + n]
self.p += n
return s
def uint(self, n):
return int.from_bytes(self.take(n), "little")
def done(self):
return self.p == len(self.b)
def read_head(b, magic):
"""live-events §3.3's first steps, with `magic` for WEV1."""
if len(b) < 4:
raise Refuse("truncated")
if b[:4] != magic:
raise Refuse("malformed")
if len(b) < 5:
raise Refuse("truncated")
if b[4] != 1:
raise Refuse("unknown_version")
def check_sig(key, domain, body, sig):
bad = ED.verify(key, domain + body, sig)
if bad:
raise Refuse(bad)
# ---------------------------------------------------------------- SPEC §3.1 the listing
SEGMENT = re.compile(r"[A-Za-z0-9_-]([A-Za-z0-9_.-]*[A-Za-z0-9_-])?")
def listing(files):
"""The listing of `files` (path -> bytes), or None when a path cannot be carried."""
paths = sorted(files, key=lambda p: p.encode())
if not paths:
return None
for p in paths:
if not all(SEGMENT.fullmatch(s) for s in p.split("/")):
return None
folded = [p.lower() for p in paths]
if len(set(folded)) != len(folded):
return None
if any(q.startswith(p + "/") for p in paths for q in paths):
return None
return b"".join(sha(files[p]).hex().encode() + b" " + p.encode() + b"\n" for p in paths)
def below(files, d):
return {p[len(d):]: b for p, b in files.items() if p.startswith(d)}
# ---------------------------------------------------------------- §3, §4 the package
def is_hex64(s):
return len(s) == 64 and all(c in "0123456789abcdef" for c in s)
def component_dir(kind, cid):
return {ROOM: "room/", EVENTS: "events/", TAKE: "takes/%s/" % cid}.get(kind, "composite/")
def read_manifest(b):
"""§4.1, §4.2: the recording manifest, or Refuse."""
read_head(b, b"WRM1")
r = Reader(b, 5)
m = {"room": r.take(32), "signer": r.take(32), "from": r.uint(8), "to": r.uint(8), "claims": r.uint(1),
"purposes": r.uint(1), "region": r.take(2), "components": []}
for _ in range(r.uint(2)):
kind = r.uint(1)
cid = r.take(r.uint(1))
m["components"].append({"kind": kind, "id": cid, "sha256": r.take(32)})
m["sig"] = r.take(64)
if not r.done():
raise Refuse("malformed")
c = m["components"]
ok = (m["claims"] & ~COMPLETE == 0 and m["purposes"] & ~0b1111 == 0 and m["from"] <= m["to"]
and all(0x41 <= c <= 0x5A for c in m["region"]))
ok = ok and len(c) >= 2 and (c[0]["kind"], c[0]["id"]) == (ROOM, b"room") and (c[1]["kind"], c[1]["id"]) == (EVENTS, b"events")
rest = c[2:]
if ok and rest and rest[-1]["kind"] == COMPOSITE:
ok = rest[-1]["id"] == b"composite"
rest = rest[:-1]
ok = ok and all(x["kind"] == TAKE and is_hex64(x["id"].decode("latin-1")) for x in rest)
ok = ok and all(a["id"] < z["id"] for a, z in zip(rest, rest[1:]))
if not ok:
raise Refuse("recording_manifest_invalid")
check_sig(m["signer"], MANIFEST_DOMAIN, b[:-64], m["sig"])
m["digest"] = sha(b[:-64])
for x in c:
x["dir"] = component_dir(x["kind"], x["id"].decode())
return m
def split_chain(b):
"""§3.3: the records of a chain file, each read by live-events §3.3, or None."""
out = []
while b:
if len(b) < 118 or b[117] not in (0, 1):
return None
after = 118 + (48 if b[117] == 1 else 0)
if len(b) < after + 3:
return None
n = after + 3 + int.from_bytes(b[after + 1:after + 3], "little") + 64
if n > len(b):
return None
try:
LEV.read_record(b[:n])
except Refuse:
return None
out.append(b[:n])
b = b[n:]
return out
def room_files(files):
"""§3.3: (descriptor, grants, head), or None."""
d, grants, head = None, [], None
for p, b in sorted(below(files, "room/").items()):
if p == "descriptor.wrd":
d = b
elif p == "key-log.json":
try:
head = b.decode("utf-8")
except UnicodeDecodeError:
return None
else:
m = re.fullmatch(r"grant-([0-9]{4})\.wrg", p)
if not m or int(m.group(1)) != len(grants):
return None
grants.append(b)
return None if d is None else (d, grants, head)
def event_chains(files):
"""§3.3: each principal's chain, in the order of the files' paths, or None."""
out = []
for p, b in sorted(below(files, "events/").items(), key=lambda x: x[0].encode()):
m = re.fullmatch(r"([0-9a-f]{64})\.wev", p)
recs = split_chain(b) if m else None
if not recs or not all(LEV.read_record(x)["principal"].hex() == m.group(1) for x in recs):
return None
out.append(recs)
return out
def take_files(files, d):
"""§3.4: (manifest, parts, receipts, consents), or None."""
manifest, parts, receipts, consents = None, [], [], []
for p, b in sorted(below(files, d).items(), key=lambda x: x[0].encode()):
if p == "manifest.wtm":
manifest = b
continue
m = re.fullmatch(r"(chunk|receipt|consent)-([0-9]+)\.(bin|wur|wcn)", p)
if not m or (m.group(1), m.group(3)) not in (("chunk", "bin"), ("receipt", "wur"), ("consent", "wcn")):
return None
width = 10 if m.group(1) == "chunk" else 4
if len(m.group(2)) != width:
return None
k = int(m.group(2))
if m.group(1) == "chunk":
parts.append((k, b))
else:
lst = receipts if m.group(1) == "receipt" else consents
if k != len(lst):
return None
lst.append(b)
return None if manifest is None else (manifest, parts, receipts, consents)
def session_takes(d, chains, order, span):
"""§4.5 complete 2: the takes the room's events open within the span, each a start its
device answered."""
seen, starts, commits = set(), [], set()
for c, i, _ in order:
ev = LEV.read_record(chains[c][i])
if ev["digest"] in seen:
continue
seen.add(ev["digest"])
if ev["kind"] == TAKE_COMMIT:
try:
commits.add((ev["principal"], CT.read_commit(ev["payload"])["take"]))
except Refuse:
pass
elif ev["kind"] == CONTROL:
try:
p = LEV.read_payload(CONTROL, ev["payload"], 65535)
except Refuse:
continue
if p["command"] == 1 and span[0] <= ev["t"] <= span[1]:
starts.append((p["target"], sha(TAKE_DOMAIN, d["room"], p["target"], ev["digest"])))
return [t for dev, t in starts if (dev, t) in commits]
def signer_lapse(d, chains, order, held, revoked, signer, span):
"""§4.3: the first instant of the span at which `signer` does not hold the record power,
or None. The room state at t holds every join, grant_exchange and leave of the signer
the room verifier accepted with its t at or below t."""
changes, seen = [], set()
for c, i, _ in order:
ev = LEV.read_record(chains[c][i])
if ev["digest"] in seen or not LRV.is_room_key(d, ev["principal"]):
continue
seen.add(ev["digest"])
if ev["kind"] not in (JOIN, LEAVE, GRANT_EXCHANGE):
continue
p = LEV.read_payload(ev["kind"], ev["payload"], 65535)
if p["subject"] != signer:
continue
x = LRV.read_detail(ev["kind"], p["detail"])
changes.append((ev["t"], x["grant"] if ev["kind"] == JOIN else x["new"] if ev["kind"] == GRANT_EXCHANGE else None))
lo, hi = span
current = None
for t, g in changes:
if t <= lo:
current = g
segments, at = [], lo
for t, g in changes:
if lo < t <= hi:
if t > at:
segments.append((at, t - 1, current))
current, at = g, t
segments.append((at, hi, current))
by = {g["digest"]: g for g in held}
for a, z, g in segments:
g = by.get(g) if g is not None else None
if g is None or not g["powers"] & RECORD_POWER or LRV.revoked(g, revoked):
return a
if not LRV.good_at(g, d, a):
return a
if not LRV.good_at(g, d, z):
return max(a, g["exp"] * 1000000 - d["t0"])
return None
def verify_recording(files, at):
"""§4.4, §4.5: a dict of what the corpus states."""
lst = files.get(LISTING)
if lst is None or listing({p: b for p, b in files.items() if p != LISTING}) != lst:
return {"outcome": "refuse", "reason": "listing_mismatch", "at": ["listing"]}
try:
if MANIFEST_FILE not in files:
raise Stop(["manifest"], "recording_manifest_invalid")
try:
m = read_manifest(files[MANIFEST_FILE])
except Refuse as e:
raise Stop(["manifest"], e.reason)
for i, c in enumerate(m["components"]):
inner = below(files, c["dir"])
if not inner:
raise Stop(["component", i], "component_invalid")
if sha(listing(inner) or b"") != c["sha256"]:
raise Stop(["component", i], "component_digest_mismatch")
dirs = [c["dir"] for c in m["components"]]
if any(p not in (LISTING, MANIFEST_FILE) and not any(p.startswith(d) for d in dirs) for p in files):
raise Stop(["listing"], "component_unlisted")
rf = room_files(files)
if rf is None:
raise Stop(["component", 0], "component_invalid")
chains = event_chains(files)
if chains is None:
raise Stop(["component", 1], "component_invalid")
descriptor, grants, head = rf
room = LRV.verify_room(descriptor, grants, head, chains)
if room["outcome"] == "refuse":
raise Stop(["room"] + room["at"], room["reason"])
d = LRV.read_descriptor(descriptor)
if m["room"] != d["room"]:
raise Stop(["manifest"], "wrong_room")
if d["mode"] == 0: # §4.4 step 5: a room never recorded has no recording
raise Stop(["manifest"], "recording_not_permitted")
if m["signer"] != d["authority"]:
held, _ = LRV.check_grants(d, grants)
revoked = LRV.check_key_log(d, head)
lapse = signer_lapse(d, chains, room["order"], held, revoked, m["signer"], (m["from"], m["to"]))
if lapse is not None:
raise Stop(["signer", lapse], "recording_signer_not_permitted")
if d["regions"] and m["region"] not in d["regions"]:
raise Stop(["manifest"], "store_outside_region")
times = {}
for c, i, _ in room["order"]:
ev = LEV.read_record(chains[c][i])
times[ev["digest"]] = ev["t"]
takes = []
for i, c in enumerate(m["components"]):
if c["kind"] != TAKE:
continue
tf = take_files(files, c["dir"])
if tf is None:
raise Stop(["component", i], "component_invalid")
tm, parts, receipts, consents = tf
v = {"descriptor": descriptor.hex(), "grants": [g.hex() for g in grants], "key_log_head": head,
"chains": [[r.hex() for r in ch] for ch in chains], "manifest": tm.hex(),
"parts": [{"index": k, "bytes": b.hex()} for k, b in parts], "receipts": [r.hex() for r in receipts],
"consents": [x.hex() for x in consents], "evaluated_at": at}
got = CT.verify_take(v)
if got["outcome"] == "refuse":
raise Stop(["take", i] + got["at"], got["reason"])
if got["take"] != c["id"].decode():
raise Stop(["component", i], "component_invalid")
opened = times.get(CT.read_manifest(tm, d["room"])["start"])
closed = times.get(bytes.fromhex(got["commits"][-1])) if got["commits"] else None
if opened is None or opened < m["from"] or (closed is not None and closed > m["to"]):
raise Stop(["component", i], "outside_span")
takes.append(got)
except Stop as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}
# §4.5.
complete = "holds"
held = {t["take"] for t in takes}
if room["outcome"] == "gap":
complete = "delivery_gap"
elif any(t.hex() not in held for t in session_takes(d, chains, room["order"], (m["from"], m["to"]))):
complete = "take_omitted"
else:
for t in takes:
vd = t["verdicts"]
if vd["single_take"] in ("delivery_gap", "commit_chain_broken", "take_not_closed"):
complete = vd["single_take"]
break
if vd["complete"] != "holds":
complete = vd["complete"]
break
publishable = {n: next((t["verdicts"]["publishable"][n] for t in takes if t["verdicts"]["publishable"][n] != "holds"), "holds")
for _, n in PURPOSES}
stands = ((not m["claims"] & COMPLETE or complete == "holds")
and all(not m["purposes"] & b or publishable[n] == "holds" for b, n in PURPOSES))
return {"outcome": "verdicts", "package": sha(lst).hex(), "manifest": m["digest"].hex(), "room": d["room"].hex(),
"complete": complete, "publishable": publishable, "stands": stands,
"takes": [{"take": t["take"], "single_take": t["verdicts"]["single_take"], "aligned": t["verdicts"]["aligned"],
"complete": t["verdicts"]["complete"], "publishable": t["verdicts"]["publishable"],
"stands": t["stands"]} for t in takes]}
# ---------------------------------------------------------------- §5.3 the range proof, RFC 9162
def leaf(desc):
return sha(LEAF_DOMAIN, desc)
def node(a, b):
return sha(NODE_DOMAIN, a, b)
def split(n):
"""The largest power of two below n, n >= 2."""
k = 1
while k * 2 < n:
k *= 2
return k
def mth(ls):
"""RFC 9162 §2.1.1, with the take tree's node; n >= 1."""
if len(ls) == 1:
return ls[0]
k = split(len(ls))
return node(mth(ls[:k]), mth(ls[k:]))
def path(m, d):
"""RFC 6962 §2.1.1's PATH(m, D[n])."""
if len(d) <= 1:
return []
k = split(len(d))
return path(m, d[:k]) + [mth(d[k:])] if m < k else path(m - k, d[k:]) + [mth(d[:k])]
def range_nodes(a, b, d):
"""§5.3's RANGE(a, b, D[n])."""
n = len(d)
if a == 0 and b == n:
return []
k = split(n)
if b <= k:
return range_nodes(a, b, d[:k]) + [mth(d[k:])]
if a >= k:
return range_nodes(a - k, b - k, d[k:]) + [mth(d[:k])]
return range_nodes(a, k, d[:k]) + range_nodes(0, b - k, d[k:])
def range_root(a, b, n, ls, nodes):
"""§5.3's ROOT(a, b, n, L), taking nodes from the front of `nodes`; None when they run out."""
if a == 0 and b == n:
return mth(ls)
k = split(n)
if b <= k:
x = range_root(a, b, k, ls, nodes)
return None if x is None or not nodes else node(x, nodes.pop(0))
if a >= k:
x = range_root(a - k, b - k, n - k, ls, nodes)
return None if x is None or not nodes else node(nodes.pop(0), x)
x = range_root(a, k, k, ls[:k - a], nodes)
y = None if x is None else range_root(0, b - k, n - k, ls[k - a:], nodes)
return None if y is None else node(x, y)
def range_verifies(first, ls, proof, size, root):
"""§5.3: the proof shows `ls` are leaves `first` onward of the tree (size, root)."""
if not ls or first + len(ls) > size:
return False
nodes = list(proof)
r = range_root(first, first + len(ls), size, ls, nodes)
return r is not None and not nodes and r == root
def rfc9162_inclusion(leaf_hash, leaf_index, tree_size, inclusion_path, root_hash):
"""RFC 9162 §2.1.3.2, as its steps state it, with node() for HASH(0x01 || ...)."""
if leaf_index >= tree_size:
return False
fn, sn = leaf_index, tree_size - 1
r = leaf_hash
for p in inclusion_path:
if sn == 0:
return False
if fn & 1 or fn == sn:
r = node(p, r)
if not fn & 1:
while not fn & 1 and fn != 0:
fn >>= 1
sn >>= 1
else:
r = node(r, p)
fn >>= 1
sn >>= 1
return sn == 0 and r == root_hash
def self_test():
"""The range check against RFC 9162 §2.1.3.2 and RFC 6962's PATH: the failures."""
fails = []
ls = [sha(b"t", bytes([i])) for i in range(22)]
for n in range(1, 22):
d, root = ls[:n], mth(ls[:n])
for a in range(n):
p = path(a, d)
if range_nodes(a, a + 1, d) != p:
fails.append("RANGE(%d, %d) is not PATH at n = %d" % (a, a + 1, n))
if not rfc9162_inclusion(d[a], a, n, p, root) or not range_verifies(a, [d[a]], p, n, root):
fails.append("an inclusion proof does not verify at n = %d, a = %d" % (n, a))
for bad in ([x for x in p[:-1]] + [bytes(32)] if p else [], p + [bytes(32)]):
if rfc9162_inclusion(d[a], a, n, bad, root) != range_verifies(a, [d[a]], bad, n, root):
fails.append("the two checks disagree at n = %d, a = %d" % (n, a))
for b in range(a + 1, n + 1):
q = range_nodes(a, b, d)
if not range_verifies(a, d[a:b], q, n, root):
fails.append("RANGE(%d, %d) does not verify at n = %d" % (a, b, n))
if b < n and range_verifies(a + 1, d[a:b], q, n, root):
fails.append("a moved run verifies at n = %d" % n)
if q and range_verifies(a, d[a:b], q[:-1], n, root):
fails.append("a short proof verifies at n = %d" % n)
return fails
# ---------------------------------------------------------------- §5 clips
def read_clip(b):
"""§5.1, §5.2: the clip record, or Refuse."""
read_head(b, CLIP_MAGIC)
r = Reader(b, 5)
c = {"room": r.take(32), "in": r.uint(8), "out": r.uint(8), "parts": []}
for _ in range(r.uint(1)):
p = {"device": r.take(32), "take": r.take(32), "head": r.take(32), "size": r.uint(8), "root": r.take(32),
"first": r.uint(8)}
count = r.uint(4)
p["trim_in"], p["trim_out"] = r.uint(8), r.uint(8)
p["chunks"] = [CT.read_chunk(CT.Reader(r.take(64), 0, "truncated")) for _ in range(count)]
p["proof"] = [r.take(32) for _ in range(r.uint(1))]
c["parts"].append(p)
c["signer"] = r.take(32)
c["sig"] = r.take(64)
if not r.done():
raise Refuse("malformed")
ps = c["parts"]
ok = 1 <= len(ps) <= 16 and len({p["take"] for p in ps}) == len(ps) and c["in"] < c["out"]
trimmed = any(p["trim_in"] or p["trim_out"] for p in ps)
ok = ok and (not trimmed or len(ps) == 1)
for p in ps:
ch = p["chunks"]
ok = (ok and len(ch) >= 1 and all(x["index"] == p["first"] + i and x["len"] >= 1 for i, x in enumerate(ch))
and len(p["proof"]) <= 128 and p["trim_in"] < ch[0]["len"] and p["trim_out"] < ch[-1]["len"]
and (len(ch) > 1 or p["trim_in"] + p["trim_out"] < ch[0]["len"]))
if not ok:
raise Refuse("clip_record_invalid")
check_sig(c["signer"], CLIP_DOMAIN, b[:-64], c["sig"])
c["digest"] = sha(b[:-64])
return c
def verify_part(k, p, given, d, clip, exempt=frozenset()):
"""§5.4 step 3 for part k: its facts, or Stop. A chunk whose leaf is in `exempt` is passed
over in step (k), as live-moderation §8.3 has a restricted chunk passed over; none is by
default."""
I, J = d["commit_interval_ms"] * 1000, d["jitter_ms"] * 1000
at_part = ["part", k]
if bytes.fromhex(given["device"]) != p["device"]: # (a)
raise Stop(at_part, "clip_device_mismatch")
held = []
for j, h in enumerate(given["commits"]): # (b)
at = ["commit", k, j]
b = bytes.fromhex(h)
try:
ev = LEV.read_record(b)
except Refuse as e:
raise Stop(at, e.reason)
if ev["room"] != d["room"]:
raise Stop(at, "wrong_room")
if ev["principal"] != p["device"]:
raise Stop(at, "clip_device_mismatch")
bad = ED.verify(ev["principal"], LEV.SIGN_DOMAIN + ev["body"], ev["sig"])
if bad:
raise Stop(at, bad)
if ev["kind"] != TAKE_COMMIT:
raise Stop(at, "commit_malformed")
try:
c = CT.read_commit(ev["payload"])
except Refuse as e:
raise Stop(at, e.reason)
if c["take"] != p["take"]:
raise Stop(at, "clip_take_mismatch")
held.append((ev, c))
if not held: # (c)
raise Stop(at_part, "commit_chain_broken")
if not CT.is_opening(held[0][1]):
raise Stop(["commit", k, 0], "commit_chain_broken")
for j in range(1, len(held)):
(pe, pc), (_, c) = held[j - 1], held[j]
if c["n"] > pc["n"] + 1:
raise Stop(["commit", k, j], "delivery_gap")
if not (pc["phase"] != CLOSE and c["phase"] != OPEN and c["n"] == pc["n"] + 1 and c["prev"] == pe["digest"]
and c["first"] == pc["size"]):
raise Stop(["commit", k, j], "commit_chain_broken")
for j in range(1, len(held)):
pc, c = held[j - 1][1], held[j][1]
if not CT.extends(c["first"], pc["root"], c["size"], c["root"], c["proof"]):
raise Stop(["commit", k, j], "root_not_extending")
hev, hc = held[-1] # (d)
if hev["digest"] != p["head"] or hc["size"] != p["size"] or hc["root"] != p["root"]:
raise Stop(at_part, "tree_head_not_committed")
ch = p["chunks"]
if p["first"] + len(ch) > p["size"]: # (e)
raise Stop(at_part, "clip_out_of_range")
if not range_verifies(p["first"], [leaf(x["desc"]) for x in ch], p["proof"], p["size"], p["root"]): # (f)
raise Stop(at_part, "clip_not_included")
if len(ch) == 1 and rfc9162_inclusion(leaf(ch[0]["desc"]), p["first"], p["size"], p["proof"], p["root"]) is not True:
raise Stop(at_part, "clip_not_included")
o = held[0][1]["offset"]
for j, (_, c) in enumerate(held): # (g)
if abs(c["offset"] - o) > J:
raise Stop(["commit", k, j], "clock_unstable")
if any(x["start"] >= x["end"] for x in ch) or any(b["start"] != a["end"] for a, b in zip(ch, ch[1:])): # (h)
raise Stop(at_part, "capture_discontinuous")
if any(x["end"] - x["start"] > I for x in ch):
raise Stop(at_part, "chunk_too_long")
def adder(x):
return next(ev for ev, c in held if c["first"] <= x["index"] < c["size"])
if any(adder(x)["t"] + J < x["end"] + o for x in ch): # (i)
raise Stop(at_part, "commit_early")
if any(adder(x)["t"] > x["end"] + o + I + J for x in ch):
raise Stop(at_part, "commit_late")
span = (ch[0]["start"] + o, ch[-1]["end"] + o) # (j)
wi, wo = clip["in"], clip["out"]
covers = span[0] <= wi and wo <= span[1] and wi < ch[0]["end"] + o and ch[-1]["start"] + o < wo
if not covers or ((p["trim_in"] or p["trim_out"]) and (wi, wo) != span):
raise Stop(at_part, "clip_window_invalid")
shown = b"" # (k)
trim_in, trim_out = p["trim_in"], p["trim_out"]
for j, x in enumerate(ch):
if leaf(x["desc"]) in exempt:
trim_in = 0 if j == 0 else trim_in
trim_out = 0 if j == len(ch) - 1 else trim_out
continue
h = given["bytes"][j] if j < len(given["bytes"]) else None
if h is None:
raise Stop(["chunk", k, j], "clip_chunk_missing")
b = bytes.fromhex(h)
if not CT.is_chunk(x, b):
other = any(y["index"] != x["index"] and CT.is_chunk(y, b) for y in ch)
raise Stop(["chunk", k, j], "chunk_reordered" if other else "chunk_altered")
shown += b
if len(given["bytes"]) > len(ch):
raise Stop(["chunk", k, len(ch)], "chunk_extra")
shown = shown[trim_in:len(shown) - trim_out]
return {"take": p["take"].hex(), "first": p["first"], "count": len(ch), "offset": o, "span": list(span),
"device_window": [wi - o, wo - o], "bytes": len(shown), "sha256": sha(shown).hex(),
"fetch": {"namespace": [d["room"].hex(), p["device"].hex(), "take"], "name": p["take"].hex(),
"start_group": p["first"], "start_object": 0, "end_group_delta": len(ch) - 1}}
def verify_clip(v, exempt=frozenset()):
"""§5.4: a dict of what the corpus states. `exempt`: as verify_part's."""
try:
try:
d = LRV.read_descriptor(bytes.fromhex(v["descriptor"]))
except Refuse as e:
raise Stop(["descriptor"], e.reason)
try:
clip = read_clip(bytes.fromhex(v["record"]))
except Refuse as e:
raise Stop(["clip"], e.reason)
if clip["room"] != d["room"]:
raise Stop(["clip"], "wrong_room")
if len(v["parts"]) != len(clip["parts"]):
raise Stop(["clip"], "clip_record_invalid")
facts = [verify_part(k, p, g, d, clip, exempt) for k, (p, g) in enumerate(zip(clip["parts"], v["parts"]))]
except Stop as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}
J = d["jitter_ms"] * 1000
return {"outcome": "accept", "clip": clip["digest"].hex(), "signer": clip["signer"].hex(), "window": [clip["in"], clip["out"]],
"bound_us": J if len(facts) == 1 else 2 * J, "facts": facts}
# ---------------------------------------------------------------- the corpus
def files_of(v):
return {p: bytes.fromhex(h) for p, h in v["files"].items()}
def problems(v, got):
out = [k for k in got if v.get(k) != got[k]]
return out + [k for k in ("outcome", "reason", "at") if k in v and k not in got]
def check(doc, quiet=False):
"""Every vector against its statement: the failures."""
fails = []
for kind, f in (("recordings", lambda v: verify_recording(files_of(v), v["evaluated_at"])), ("clips", verify_clip)):
for v in doc.get(kind, []):
try:
got = f(v)
except Exception as e: # a crash on a mutated copy is a failure to read it
got, bad = None, ["the vector does not verify: %s: %s" % (type(e).__name__, e)]
else:
bad = problems(v, got)
if not quiet:
what = "?" if got is None else got["outcome"] + (" " + got["reason"] if got["outcome"] == "refuse" else "")
print(("PASS" if not bad else "FAIL") + " %s %s: %s" % (kind[:-1], v["name"], what))
for k in bad:
fails.append("%s %s: %s is %s, the corpus states %s" % (
kind[:-1], v["name"], k, json.dumps(got.get(k))[:200] if got else k, json.dumps(v.get(k))[:200]))
return fails
def reuse_fails(doc, takes):
"""Every take the corpus says it reuses is capture binding's, byte for byte."""
by = {v["name"]: v for v in takes["vectors"]}
fails, n = [], 0
for v in doc["recordings"]:
for r in v["reuses"]:
t, d, f = by[r["vector"]], "takes/%s/" % r["take"], v["files"]
parts = sorted((int(p[len(d) + 6:len(d) + 16]), h) for p, h in f.items() if p.startswith(d + "chunk-"))
same = (t["take"] == r["take"] and f.get(d + "manifest.wtm") == t["manifest"]
and parts == [(p["index"], p["bytes"]) for p in t["parts"]]
and [h for p, h in sorted(f.items()) if p.startswith(d + "receipt-")] == t["receipts"]
and [h for p, h in sorted(f.items()) if p.startswith(d + "consent-")] == t["consents"])
if not same:
fails.append("recording %s: its take %s is not %s" % (v["name"], r["take"][:12], r["vector"]))
n += 1
for c in doc["clips"]:
for p in c["parts"]:
if p["take_vector"] is None:
continue
t = by[p["take_vector"]]
records = {x for ch in t["chains"] for x in ch}
parts = {x["bytes"] for x in t["parts"]}
if not all(x in records for x in p["commits"]) or not all(b is None or b in parts for b in p["bytes"]):
fails.append("clip %s: a part is not %s's" % (c["name"], p["take_vector"]))
n += 1
if n == 0:
fails.append("the corpus reuses no take")
return fails
def flip(h, at):
b = bytearray(bytes.fromhex(h))
b[at] ^= 0x01
return b.hex()
def mutations(doc):
"""Copies of the corpus, each with one defect a conforming verifier must catch."""
rn = {v["name"]: k for k, v in enumerate(doc["recordings"])}
cn = {v["name"]: k for k, v in enumerate(doc["clips"])}
def mutate(label, f):
d = copy.deepcopy(doc)
f(d["recordings"], d["clips"])
return label, d
def first_chunk(v):
return next(p for p in sorted(v["files"]) if "/chunk-" in p)
good, single, aligned = rn["lesson_session"], cn["single_chunk"], cn["two_takes_aligned"]
return [
mutate("a byte of the session's first chunk file", lambda rs, cs: rs[good]["files"].update({first_chunk(rs[good]): flip(rs[good]["files"][first_chunk(rs[good])], 3)})),
mutate("the session's listing dropped", lambda rs, cs: rs[good]["files"].pop("LISTING")),
mutate("a byte of the session's manifest signature", lambda rs, cs: rs[good]["files"].update({"recording.wrm": flip(rs[good]["files"]["recording.wrm"], -9)})),
mutate("the session's stated package digest", lambda rs, cs: rs[good].update(package="00" * 32)),
mutate("the session evaluated a year on", lambda rs, cs: rs[good].update(evaluated_at=rs[good]["evaluated_at"] + 365 * 86400)),
mutate("the omitted take stated complete", lambda rs, cs: rs[rn["take_omitted"]].update(complete="holds")),
mutate("the missing chunk stated complete", lambda rs, cs: rs[rn["missing_chunk"]].update(complete="holds")),
mutate("the unpermitted signer stated at the listing", lambda rs, cs: rs[rn["signer_not_permitted"]].update(at=["listing"])),
mutate("the removed signer's power stated lost a second early", lambda rs, cs: rs[rn["signer_removed"]].update(at=["signer", 17000000])),
mutate("the late recorder's span stated from 0", lambda rs, cs: rs[rn["signer_power_late"]].update(at=["signer", 1000000])),
mutate("a byte of the single chunk's bytes", lambda rs, cs: cs[single]["parts"][0]["bytes"].__setitem__(0, flip(cs[single]["parts"][0]["bytes"][0], 2))),
mutate("a byte of the single chunk's record", lambda rs, cs: cs[single].update(record=flip(cs[single]["record"], 200))),
mutate("the single chunk's last commitment dropped", lambda rs, cs: cs[single]["parts"][0]["commits"].pop()),
mutate("the single chunk's stated digest", lambda rs, cs: cs[single]["facts"][0].update(sha256="00" * 32)),
mutate("the aligned clip's stated bound", lambda rs, cs: cs[aligned].update(bound_us=500000)),
mutate("the aligned clip's parts given in each other's place", lambda rs, cs: cs[aligned]["parts"].reverse()),
mutate("the byte range's stated fetch", lambda rs, cs: cs[cn["byte_range"]]["facts"][0]["fetch"].update(start_group=3)),
mutate("the byte range's stated window on the device clock", lambda rs, cs: cs[cn["byte_range"]]["facts"][0]["device_window"].__setitem__(0, 0)),
mutate("the live head's commitment 2 dropped", lambda rs, cs: cs[cn["live_head"]]["parts"][0]["commits"].pop()),
mutate("the missing chunk given chunk 5's bytes", lambda rs, cs: cs[cn["missing_chunk"]]["parts"][0]["bytes"].__setitem__(1, cs[cn["missing_chunk"]]["parts"][0]["bytes"][0])),
mutate("the spliced clip stated accepted", lambda rs, cs: cs[cn["spliced_two_takes"]].update(outcome="accept")),
mutate("the uncommitted head stated at part 1", lambda rs, cs: cs[cn["uncommitted_head"]].update(at=["part", 1])),
mutate("the clean span's late commitment dropped", lambda rs, cs: cs[cn["late_take_clean_span"]]["parts"][0]["commits"].pop(3)),
]
def link_magic_fails(doc):
"""A copy of each accepted clip whose record begins with the contribution link record's
magic in place of WCL1: §5.2 reads it as live-events §3.3 does, so it is refused as
`malformed` at the clip, before its signature is read. The failures."""
fails, n = [], 0
for v in doc["clips"]:
if v["outcome"] != "accept":
continue
n += 1
record = LINK_MAGIC.hex() + v["record"][2 * len(CLIP_MAGIC):]
try:
got = verify_clip(dict(v, record=record))
except Exception as e: # a crash is a failure to refuse it
got = "%s: %s" % (type(e).__name__, e)
if got != {"outcome": "refuse", "reason": "malformed", "at": ["clip"]}:
fails.append("clip %s given the link record's magic: %s" % (v["name"], json.dumps(got)[:160]))
if n == 0:
fails.append("no accepted clip to give the link record's magic")
return fails, n
def main(corpus_dir):
fails = []
st = ED._self_test()
print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st))
fails += ["ed25519 self-test"] if st else []
rt = self_test()
print(("PASS" if not rt else "FAIL") + " range proofs: §5.3's recursion against RFC 9162 §2.1.3.2 and RFC 6962's PATH")
fails += rt
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
with open(os.path.join(corpus_dir, "..", "takes", "vectors.json"), encoding="utf-8") as f:
takes = json.load(f)
fails += check(doc)
seen = {v.get("reason") for v in doc["recordings"] + doc["clips"]} | {v.get("complete") for v in doc["recordings"]}
missing = [r for r in REASONS if r not in seen]
if missing:
fails.append("no vector gives %s" % ", ".join(missing))
good = [v["name"] for v in doc["recordings"] if v["name"] in ("lesson_session", "signed_by_director", "with_composite", "late_recorder_span") and not v.get("stands")]
good += [v["name"] for v in doc["clips"] if v["name"] in ("single_chunk", "byte_range", "whole_take", "first_chunk",
"last_chunk", "two_takes_aligned", "live_head") and v["outcome"] != "accept"]
if good:
fails.append("good vectors that do not stand or are not accepted: %s" % ", ".join(good))
print("%s every reason of §9 appears, the good recordings stand and the good clips are accepted" % ("PASS" if not (missing or good) else "FAIL"))
rf = reuse_fails(doc, takes)
print("%s every take the corpus reuses is capture binding's, byte for byte" % ("PASS" if not rf else "FAIL"))
fails += rf
lf, n = link_magic_fails(doc)
print("%s mutated copies refused: each of %d accepted clips given the link record's magic, WLK1, is malformed at the clip" % ("PASS" if not lf else "FAIL", n))
fails += lf
for label, mutated in mutations(doc):
changed = {k: [v for v, o in zip(mutated[k], doc[k]) if v != o] for k in ("recordings", "clips")}
caught = check(changed, quiet=True) if any(changed.values()) else []
print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:110] if caught else ""))
if not caught:
fails.append("a mutated copy verifies: %s" % label)
for f in fails:
print("FAIL " + f)
print(("PASS" if not fails else "FAIL") + " live-recording corpus: %d recordings, %d clips" % (len(doc["recordings"]), len(doc["clips"])))
return 1 if fails else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: live_record_verify.py <live-conformance/recordings>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))