open-standards/wai/live-conformance/live_worlds_verify.py
Transaction Science 376668c58b wai.live.world: the independent verifier, and the text made exact where it read two ways
live_worlds_verify.py is the corpus's second implementation, written from
the extension's text and the sections it cites, sharing no code with
wai-rs; it agrees with the reference on every vector.

Writing it found readings the text left open, now closed: a CausalOp's
integers are little-endian and its signature is over the domain then the
preceding bytes (interactive-worlds 5.1); a world track's receipts are a
track's, not interactive-worlds 8's session receipts; the order of the
descriptor, grant and statement checks; the group total is checked; a
private world's first opened object above group 0 starts its chain; the
session hash chains each op with its signature; the outcome lists the
actors whose ops were admitted. The corpus gains a group-total refusal,
and section 12 states which codes it covers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 23:17:33 -04:00

657 lines
28 KiB
Python

#!/usr/bin/env python3
"""Independent verifier of the live-worlds corpus (extensions/live-worlds.md).
Written from the extension's text and the texts it cites, alone. It shares no code with the
reference implementation in Rust, which generates the corpus, and never runs it, nor the
generator. 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 computes BLAKE3 with ../claim-conformance/_blake3.py;
SHA-256 (FIPS 180-4) is the standard library's. Everything else is here:
- the room descriptor (live-rooms §3.1, §3.2): its reader (§3.4 steps 1 to 3), its digest,
the room id, and its signature under its `authority`;
- the room grants (live-rooms §4.1, §4.2, §4.4): each grant's reader (§4.3 step 1), its
signature under the descriptor's authority (step 2), its room (step 4) and its
attributes (the `attrs` rule of step 5). The other checks of live-rooms §4.3 (step 3's
exchange linkage, step 5's other field rules, steps 6 to 8) and the descriptor's field
rules (§3.4, `descriptor_invalid`) are the live-rooms verifier's, live_rooms_verify.py,
with its own corpus, and are not repeated here;
- the world statement (live-worlds §3): its reader and rules, in §3.4's order;
- the world tracks (live-worlds §4, §9.2): the grant attribute `world` (§5.1), the group
sequence, the receipts track's object (jwp-receipts §2.7, binary form `WJR1`) and its
verification by jwp-receipts §3 (key, group signature over the legacy or the labelled
payload of §2.3, the chain kept per track, the content hash of §2.1 over the bytes the
receipt covers, the class label, the proof's position and the Merkle walk of §2.2, and
the group total), the op batch (interactive-worlds §5.1), each op's signature, the
actor, the action body of `wai.world.model`, redelivery, equivocation and the Lamport
rule (§4.2);
- private worlds (live-worlds §8, §9.1, §9.2): the receipts cover `payload`, the ops are
the batch `opened` from it, and a track's first object may be at any group; above group
0 it starts its chain whatever parent it names (jwp-receipts §3 step 3, *Joining*), and
at group 0 it starts it as any first group does. Opening is confidentiality §6's, so the
relation between `payload` and `opened` is not checked here;
- the session hash (live-worlds §9.3) over the admitted ops in canonical order
(interactive-worlds §5.2), and the outcome (§9.4).
Of the acquisition classes (energy-measurement §2.7) it reads every tag, so that a class
that does not read is `malformed` as jwp-receipts §2.7.4 says; it holds tags 0 to 3 to
jwp-receipts §2.4.1 and §2.4.2. A refined hardware class (tags 4 and 5) needs the
resolution floor of energy-measurement §2.3, which this verifier does not implement: it
refuses such a receipt as `class_not_supported_here`, a verifier-local outcome that no
conforming corpus vector states.
For every vector it requires the vector's `expect`: for an accepted world its session
hash, the number of admitted ops and each actor's count; for a refused one its reason and
where (`"descriptor"`, `"statement"`, `{"grant": i}` or `{"track": i, "group": g}`).
python3 -I live_worlds_verify.py <live-conformance/worlds>
Exit status 0 only if every vector agrees. Python 3.8 or later; standard library only.
"""
import hashlib
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
from _blake3 import blake3 # noqa: E402
import _ed25519 as ED # noqa: E402
# live-rooms §3.1, §3.2, §4.1, §4.2
DESCRIPTOR_MAGIC = b"WRD1"
GRANT_MAGIC = b"WRG1"
DESCRIPTOR_DOMAIN = b"wai:live-room\x01" # 14 bytes
GRANT_DOMAIN = b"wai:live-room-grant\x01" # 20 bytes
# live-worlds §3
STATEMENT_MAGIC = b"WLW1"
STATEMENT_DOMAIN = b"wai:live-world\x01" # 15 bytes
CAPABILITY = b"wai.world.model"
WORLD_LEN = 52 # §3.2: id 32, seed 8, width, height, rate 4 each
MIN_OP, MAX_OP = 136, 65536
MIN_OPS, MAX_OPS = 1, 4096
# live-worlds §4.3, §9.3
WORLD_RECEIPT_ALIAS = 4
SESSION_DOMAIN = b"wai:world-model-session\x01" # 24 bytes
# interactive-worlds §5.1
OP_DOMAIN = b"wai:world-op\x01"
OP_HEAD = 32 + 8 + 8 + 2 # actor, lamport, effect_tick, n_deps
OP_DEP = 32 + 8
OP_MID = 16 + 2 + 4 # target_node, mutation_kind, mut_len
OP_SIG = 64
ACTION_KIND = 1 # wai.world.model: an action
ACTION_HEAD = 16 # dx i32, dy i32, buttons u32, payload_len u32
# jwp-receipts §2.1, §2.2, §2.3, §2.7, §3
RECEIPTS_MAGIC = b"WJR1"
SIGN_LEGACY = b"moq-jwp:group-sign\x01" # 19 bytes
SIGN_LABELLED = b"moq-jwp:group-sign\x02"
OBJECT_DOMAIN = b"moq-jwp:object\x01"
LEAF_LEGACY = b"moq-jwp:merkle-leaf\x01"
LEAF_LABELLED = b"moq-jwp:merkle-leaf\x02"
NODE_DOMAIN = b"moq-jwp:merkle-node\x01"
LINK_DOMAIN = b"moq-jwp:receipt-link\x01"
ZERO32 = bytes(32)
# energy-measurement §2.7: the tags, and §2's names for those this verifier compares
UNMETERED, HW_SHUNT, MODEL_BASED, ESTIMATOR, ON_CHIP, CALIBRATED = 0, 1, 2, 3, 4, 5
# The reasons the corpus is to refuse with (live-worlds §11 and the codes it names from
# live-events §9 and jwp-receipts §3.3). `world_incomplete` is a participant's report about
# its own copy (§7), not a refusal; `bad_key` at the statement is not listed, since a
# statement is signed by the descriptor's authority, whose key the descriptor's own
# signature has already held to SPEC §7's steps 1 and 2.
CORPUS_REASONS = [
"truncated", "malformed", "unknown_version", "world_capability_unknown",
"world_statement_invalid", "wrong_room", "bad_signature",
"world_not_granted", "world_gap", "moqt_mismatch", "signature_verify_failed",
"content_hash_mismatch", "chain_break", "parent_not_held",
"op_malformed", "batch_too_large", "op_too_large", "bad_op_signature",
"actor_not_owner", "op_not_action", "equivocation", "lamport_regress",
]
class Refuse(Exception):
def __init__(self, reason, at):
Exception.__init__(self, reason)
self.reason, self.at = reason, at
class Short(Exception):
"""The bytes end before a field the layout requires."""
class Reader:
"""Reads a record field by field, in either byte order."""
def __init__(self, b, order):
self.b, self.i, self.order = b, 0, order
def take(self, n):
if self.i + n > len(self.b):
raise Short()
out = self.b[self.i:self.i + n]
self.i += n
return out
def uint(self, n):
return int.from_bytes(self.take(n), self.order)
def u8(self):
return self.uint(1)
def short(self): # rooms: a u8 length and that many bytes
return self.take(self.u8())
def long(self): # rooms: a u16 (little-endian) length and that many bytes
return self.take(self.uint(2))
def string(self): # receipts: u32_be(len) and UTF-8
return self.take(self.uint(4))
def left(self):
return len(self.b) - self.i
def sha256(b):
return hashlib.sha256(b).digest()
def is_utf8(b):
try:
b.decode("utf-8")
return True
except UnicodeDecodeError:
return False
def is_slot_name(b): # live-rooms §2: 1 to 32 bytes of a-z, 0-9 and _
return 1 <= len(b) <= 32 and all(c in b"abcdefghijklmnopqrstuvwxyz0123456789_" for c in b)
def read_head(b, magic):
"""live-rooms §3.4 step 1, and live-worlds §3.4 step 1: the magic and the version."""
if len(b) < 4:
raise Refuse("truncated", None)
if b[:4] != magic:
raise Refuse("malformed", None)
if len(b) == 4:
raise Refuse("truncated", None)
if b[4] != 1:
raise Refuse("unknown_version", None)
# --- the room descriptor (live-rooms §3) ---------------------------------------------------
def read_descriptor(b):
"""§3.4 steps 1 to 3 and §3.2's signature; the field rules are live-rooms's verifier's."""
at = "descriptor"
try:
read_head(b, DESCRIPTOR_MAGIC)
r = Reader(b, "little")
r.take(5)
authority, _clock = r.take(32), r.take(32)
r.take(8 + 8) # t0, tick
r.long() # origin
r.take(4 + 2 + 4) # max_present, max_publishers, reval
r.take(65) # ceiling (WEP1)
for _ in range(r.u8()): # roles
r.short()
r.take(1 + 1 + 1 + 1 + 32)
for _ in range(r.u8()): # slots
r.short()
r.take(1 + 1 + 4)
for _ in range(r.u8()): # publish profiles (§8.1)
r.short()
r.take(1) # media
for _ in range(r.u8()):
r.short() # codecs
r.take(1) # layering
r.short() # mode
r.take(14 * r.u8()) # layers
r.take(4 + 1 + 1) # sample_rate, channels, processing
r.take(37) # review
r.take(1 + 2 + 4 + 4) # recording
r.take(2 * r.u8()) # regions
body_end = r.i
sig = r.take(64)
except Short:
raise Refuse("truncated", at)
except Refuse as e:
raise Refuse(e.reason, at)
if r.left():
raise Refuse("malformed", at)
body = b[:body_end]
bad = ED.verify(authority, DESCRIPTOR_DOMAIN + body, sig)
if bad:
raise Refuse(bad, at) # bad_key or bad_signature
return {"authority": authority, "room": sha256(body)}
# --- the grants (live-rooms §4) -----------------------------------------------------------
def read_attrs(b):
"""§4.4: keys (short, a slot name) in strictly ascending order, values (long) UTF-8,
at most 1024 bytes. None when the field breaks the rule."""
if len(b) > 1024:
return None
r, out, last = Reader(b, "little"), {}, None
try:
while r.left():
k, v = r.short(), r.long()
if not is_slot_name(k) or (last is not None and k <= last) or not is_utf8(v):
return None
out[k.decode()] = v.decode("utf-8")
last = k
except Short:
return None
return out
def read_grant(b, d, index):
at = {"grant": index}
try:
read_head(b, GRANT_MAGIC)
r = Reader(b, "little")
r.take(5)
room, principal = r.take(32), r.take(32)
r.take(32 + 8 + 8 + 8) # prev, effective, nbf, exp
r.take(1 + 4 + 1) # role, publish..powers, precedence
r.take(16 + 16 + 65) # once, viewer, policy
r.take(8 + 8 + 3) # joules_ceiling, funds_ceiling, currency
r.take(r.u8()) # profiles
for _ in range(r.u8()):
r.short() # origins
attrs = r.long()
body_end = r.i
sig = r.take(64)
except Short:
raise Refuse("truncated", at)
except Refuse as e:
raise Refuse(e.reason, at)
if r.left():
raise Refuse("malformed", at)
if ED.verify(d["authority"], GRANT_DOMAIN + b[:body_end], sig):
raise Refuse("bad_signature", at) # §4.3 step 2
if room != d["room"]:
raise Refuse("wrong_room", at) # §4.3 step 4
a = read_attrs(attrs)
if a is None:
raise Refuse("grant_invalid", at) # §4.3 step 5, the attrs rule
return {"principal": principal, "attrs": a}
# --- the world statement (live-worlds §3) -------------------------------------------------
def read_statement(b, d):
at = "statement"
try:
read_head(b, STATEMENT_MAGIC)
r = Reader(b, "little")
r.take(5)
room = r.take(32)
capability = r.short()
world = r.long()
max_op_bytes = r.uint(4)
max_ops = r.uint(2)
body_end = r.i
sig = r.take(64)
except Short:
raise Refuse("truncated", at)
except Refuse as e:
raise Refuse(e.reason, at)
if r.left():
raise Refuse("malformed", at)
if capability != CAPABILITY:
raise Refuse("world_capability_unknown", at)
if (len(world) != WORLD_LEN or any(int.from_bytes(world[i:i + 4], "little") == 0 for i in (40, 44, 48))
or not MIN_OP <= max_op_bytes <= MAX_OP or not MIN_OPS <= max_ops <= MAX_OPS):
raise Refuse("world_statement_invalid", at)
if room != d["room"]:
raise Refuse("wrong_room", at)
bad = ED.verify(d["authority"], STATEMENT_DOMAIN + b[:body_end], sig)
if bad:
raise Refuse(bad, at) # bad_key or bad_signature
return {"world": world, "max_op_bytes": max_op_bytes, "max_ops": max_ops}
# --- receipts (jwp-receipts §2.7, §3) -----------------------------------------------------
def read_class(r):
"""energy-measurement §2.7: a tag byte and its evidence. Returns (tag, raw bytes)."""
start = r.i
tag = r.u8()
if tag in (UNMETERED, HW_SHUNT, MODEL_BASED, ESTIMATOR):
pass
elif tag == ON_CHIP:
r.take(4 + 8)
if r.u8() > 2:
raise ValueError("filtering")
elif tag == CALIBRATED:
r.take(4 + 8)
if not is_utf8(r.string()):
raise ValueError("calibration_ref")
else:
raise ValueError("tag")
return tag, r.b[start:r.i]
def read_flag_parent(r):
flag, parent = r.u8(), r.take(32)
if flag not in (0, 1) or (flag == 0 and parent != ZERO32):
raise ValueError("parent flag")
return parent if flag else None
def read_receipts(b):
"""§2.7.3 read by §2.7.4: any failure is `malformed`."""
try:
r = Reader(b, "big")
if r.take(4) != RECEIPTS_MAGIC:
raise ValueError("magic")
start = r.i
domain = r.take(19)
if domain not in (SIGN_LEGACY, SIGN_LABELLED):
raise ValueError("domain")
g = {"labelled": domain == SIGN_LABELLED}
g["track_alias"], g["group_id"] = r.uint(8), r.uint(8)
g["root"], g["leaf_count"], g["total"] = r.take(32), r.uint(8), r.uint(8)
for k in ("group_start", "group_end"):
if not is_utf8(r.string()):
raise ValueError(k)
g["pubkey"] = r.take(32)
if not is_utf8(r.string()):
raise ValueError("signer_id")
if g["labelled"]:
g["class"] = read_class(r)
g["coverage"] = r.u8()
if g["coverage"] not in (0, 1):
raise ValueError("coverage")
g["parent"] = read_flag_parent(r)
payload_end = r.i
g["sig"] = r.take(64)
g["payload"] = b[start:payload_end]
g["canonical"] = b[start:r.i]
if not g["labelled"]:
g["parent"] = read_flag_parent(r)
objects = []
for _ in range(r.uint(4)):
o = {"object_id": r.uint(8), "content_hash": r.take(32), "joules": r.uint(8)}
o["origin"] = r.string()
if not is_utf8(o["origin"]):
raise ValueError("origin")
o["siblings"] = [r.take(32) for _ in range(r.u8())]
o["direction_bits"], o["leaf_index"] = r.uint(8), r.uint(8)
o["leaf_version"] = r.u8()
if o["leaf_version"] not in (1, 2):
raise ValueError("leaf_version")
o["class"] = read_class(r) if o["leaf_version"] == 2 else None
objects.append(o)
if r.left():
raise ValueError("trailing bytes")
except (Short, ValueError):
return None
return g, objects
def merkle_leaf(o):
"""§2.2: the labelled leaf where the receipt carries a label, the legacy one otherwise."""
if o["class"] is None:
return blake3(LEAF_LEGACY + o["content_hash"])
return blake3(LEAF_LABELLED + o["content_hash"] + o["joules"].to_bytes(8, "big") + o["class"][1]
+ len(o["origin"]).to_bytes(4, "big") + o["origin"])
def check_receipts(rb, covered, owner, group, chain, joins, at):
"""live-worlds §9.2 step 3. `chain` is the track's chain state: the head's link hash and
the link hashes of the earlier groups the verifier holds, and the head's group_id.
`joins` is True for a private world's first opened object at a group above 0."""
read = read_receipts(rb)
if read is None:
raise Refuse("malformed", at)
g, objects = read
if len(objects) != 1:
raise Refuse("malformed", at)
o = objects[0]
# moq-streaming-format §4.1: before any signature is checked.
if g["pubkey"] != owner or g["track_alias"] != WORLD_RECEIPT_ALIAS or g["group_id"] != group or o["object_id"] != 0:
raise Refuse("moqt_mismatch", at)
# jwp-receipts §3 step 1.
if g["labelled"] and g["class"][0] == UNMETERED and g["coverage"] != 0:
raise Refuse("group_label_invalid", at) # §2.7.4: a coverage beside the marker
if not ED.strict_key(g["pubkey"]):
raise Refuse("bad_signer_key", at)
# Step 2: the group signature, then the class with the group alone (§2.4.1).
if ED.verify(g["pubkey"], g["payload"], g["sig"]):
raise Refuse("signature_verify_failed", at)
if g["labelled"]:
tag = g["class"][0]
if tag == HW_SHUNT or (tag == UNMETERED) != (g["total"] == 0):
raise Refuse("group_label_invalid", at)
if tag in (ON_CHIP, CALIBRATED):
raise Refuse("class_not_supported_here", at)
# Step 3: the chain of the track (live-worlds §9.2 step 3).
link = blake3(LINK_DOMAIN + g["canonical"])
parent = g["parent"]
if chain["head"] is None:
if not joins and parent is not None:
raise Refuse("parent_not_held", at)
else:
if not g["labelled"] and g["group_id"] <= chain["head_group"]:
raise Refuse("group_id_not_increasing", at)
if parent != chain["head"]:
if parent is None or parent in chain["earlier"]:
raise Refuse("chain_break", at)
raise Refuse("parent_not_held", at)
# Step 4: the content hash over the bytes the receipt covers (§2.1).
if blake3(OBJECT_DOMAIN + covered) != o["content_hash"]:
raise Refuse("content_hash_mismatch", at)
# Step 5: the label (§2.4.2), the position, the walk to the verified root.
if o["class"] is not None:
tag = o["class"][0]
if tag == HW_SHUNT or (tag == UNMETERED) != (o["joules"] == 0):
raise Refuse("object_label_invalid", at)
if tag in (ON_CHIP, CALIBRATED):
raise Refuse("class_not_supported_here", at)
n = g["leaf_count"]
depth = (n - 1).bit_length() if n else -1
if (o["leaf_index"] >= n or len(o["siblings"]) != depth or o["direction_bits"] != o["leaf_index"]
or o["object_id"] != o["leaf_index"]):
raise Refuse("proof_position_mismatch", at)
cur = merkle_leaf(o)
for i, sib in enumerate(o["siblings"]):
cur = blake3(NODE_DOMAIN + (sib + cur if (o["direction_bits"] >> i) & 1 else cur + sib))
if cur != g["root"]:
raise Refuse("inclusion_proof_failed", at)
# The group total: with one leaf, the verifier holds an accepted receipt for every index.
if n == 1:
if o["joules"] != g["total"]:
raise Refuse("group_total_mismatch", at)
if g["labelled"]:
if o["joules"] != 0 and o["class"] is None:
raise Refuse("group_class_disagrees", at) # its objects compose to no class
want = (UNMETERED, bytes([UNMETERED])) if o["joules"] == 0 else o["class"]
if g["class"] != want:
raise Refuse("group_class_disagrees", at)
if g["coverage"] != 0:
raise Refuse("group_coverage_disagrees", at)
# Accepted: the group heads the track's chain.
if chain["head"] is not None:
chain["earlier"].add(chain["head"])
chain["head"], chain["head_group"] = link, g["group_id"]
# --- ops (interactive-worlds §5.1) --------------------------------------------------------
def read_batch(b):
"""One or more ops, each whole, concatenated with nothing between them. None when the
batch is not that."""
ops, i = [], 0
while i < len(b):
start = i
if len(b) - i < OP_HEAD:
return None
actor = b[i:i + 32]
lamport = int.from_bytes(b[i + 32:i + 40], "little")
tick = int.from_bytes(b[i + 40:i + 48], "little")
n_deps = int.from_bytes(b[i + 48:i + 50], "little")
i += OP_HEAD + OP_DEP * n_deps
if len(b) < i + OP_MID:
return None
kind = int.from_bytes(b[i + 16:i + 18], "little")
mut_len = int.from_bytes(b[i + 18:i + 22], "little")
i += OP_MID
mutation = b[i:i + mut_len]
i += mut_len
if len(b) < i + OP_SIG:
return None
i += OP_SIG
raw = b[start:i]
ops.append({"raw": raw, "actor": actor, "lamport": lamport, "tick": tick, "n_deps": n_deps,
"kind": kind, "mutation": mutation, "signed": raw[:-OP_SIG], "sig": raw[-OP_SIG:]})
return ops if ops else None
def is_action(op):
"""`wai.world.model`'s action: mutation kind 1, no dependency, and a body that reads:
dx i32, dy i32, buttons u32, payload_len u32 (little-endian), then the payload."""
m = op["mutation"]
if op["kind"] != ACTION_KIND or op["n_deps"] != 0 or len(m) < ACTION_HEAD:
return False
return len(m) == ACTION_HEAD + int.from_bytes(m[12:16], "little")
# --- a world (live-worlds §9) -------------------------------------------------------------
def verify_world(v):
d = read_descriptor(bytes.fromhex(v["descriptor"]))
grants = [read_grant(bytes.fromhex(g), d, i) for i, g in enumerate(v["grants"])]
st = read_statement(bytes.fromhex(v["statement"]), d)
private = v["private"]
if private not in (True, False):
raise ValueError("private is not a boolean")
actors = {g["principal"] for g in grants if g["attrs"].get("world") == "act"}
admitted = {} # op id -> op bytes
for ti, track in enumerate(v["tracks"]):
owner = bytes.fromhex(track["owner"])
chain = {"head": None, "head_group": None, "earlier": set()}
last_group, owner_lamports = None, []
for obj in track["objects"]:
group = obj["group"]
at = {"track": ti, "group": group}
# Step 1.
if owner not in actors:
raise Refuse("world_not_granted", at)
# Step 2.
if last_group is None:
if not private and group != 0:
raise Refuse("world_gap", at)
elif group != last_group + 1:
raise Refuse("world_gap", at)
joins = private and last_group is None and group > 0
last_group = group
# Step 3: the receipts cover `payload`; in a private world the ops are `opened`.
covered = bytes.fromhex(obj["payload"])
if private:
if obj.get("opened") is None:
raise ValueError("a private world's object without its opened batch")
batch = bytes.fromhex(obj["opened"])
else:
if obj.get("opened") is not None:
raise ValueError("an opened batch in a world that is not private")
batch = covered
check_receipts(bytes.fromhex(obj["receipts"]), covered, owner, group, chain, joins, at)
# Step 4.
ops = read_batch(batch)
if ops is None:
raise Refuse("op_malformed", at)
if len(ops) > st["max_ops"]:
raise Refuse("batch_too_large", at)
if any(len(op["raw"]) > st["max_op_bytes"] for op in ops):
raise Refuse("op_too_large", at)
# Step 5, each op in batch order.
for op in ops:
if ED.verify(op["actor"], OP_DOMAIN + op["signed"], op["sig"]):
raise Refuse("bad_op_signature", at)
if not private and op["actor"] != owner:
raise Refuse("actor_not_owner", at)
if not is_action(op):
raise Refuse("op_not_action", at)
oid = (op["actor"], op["lamport"])
held = admitted.get(oid)
if held is not None and held != op["raw"]:
raise Refuse("equivocation", at)
redelivery = held is not None
if op["actor"] == owner:
if not redelivery and owner_lamports and op["lamport"] <= max(owner_lamports):
raise Refuse("lamport_regress", at)
owner_lamports.append(op["lamport"])
if not redelivery:
admitted[oid] = op["raw"]
return session(st["world"], admitted)
def session(world, admitted):
"""§9.3 over the admitted ops in canonical order, and §9.4's outcome."""
order = sorted(admitted.items(), key=lambda kv: (int.from_bytes(kv[1][40:48], "little"), kv[0][1], kv[0][0]))
h = blake3(SESSION_DOMAIN + world)
counts = {}
for (actor, _lamport), raw in order:
h = blake3(SESSION_DOMAIN + h + raw)
counts[actor.hex()] = counts.get(actor.hex(), 0) + 1
return {"accept": True, "session_hash": h.hex(), "ops": len(order), "actors": counts}
def outcome(v):
try:
return verify_world(v)
except Refuse as e:
return {"accept": False, "reason": e.reason, "at": e.at}
def main(corpus_dir):
fails = []
st = ED._self_test()
print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st))
if st:
fails.append("ed25519 self-test")
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
reasons = set()
for v in doc["vectors"]:
name, want = v["name"], v["expect"]
try:
got = outcome(v)
except (ValueError, KeyError, TypeError) as e:
got = {"accept": None, "unreadable": repr(e)}
ok = got == want
if not want.get("accept"):
reasons.add(want.get("reason"))
if got.get("accept"):
what = "accept %s, %d ops" % (got["session_hash"][:16], got["ops"])
elif got.get("accept") is False:
what = "refuse %s at %s" % (got["reason"], json.dumps(got["at"]))
else:
what = "unreadable: %s" % got["unreadable"]
print(("PASS" if ok else "FAIL") + " vector %s: %s" % (name, what))
if not ok:
fails.append("%s: verifier reached %s, the corpus states %s" % (name, json.dumps(got), json.dumps(want)))
missing = [r for r in CORPUS_REASONS if r not in reasons]
accepted = [v for v in doc["vectors"] if v["expect"].get("accept")]
kinds = {v["private"] for v in accepted}
if missing:
fails.append("no vector refused with %s" % ", ".join(missing))
if kinds != {True, False}:
fails.append("the corpus does not accept both a public and a private world")
print("%s every refusal of §3.4 and §9.2 refuses a vector, and a public and a private world are accepted"
% ("PASS" if not missing and kinds == {True, False} else "FAIL"))
for f in fails:
print("FAIL " + f)
print(("PASS" if not fails else "FAIL") + " live-worlds corpus: %d vectors" % len(doc["vectors"]))
return 1 if fails else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: live_worlds_verify.py <live-conformance/worlds>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))