open-standards/wai/examples/verify-receipts/python/verify_receipts.py
Transaction Science 632f8cf394 wai: the developer's path, hello, create, dev and verify, a floor build with no system library, and nine examples
wai-rs gains the feature floor (the mandatory floor of SPEC 5: PNG, FLAC and
zstd, decoded with no system codec library) and quickstart (the floor, and the
receipt checks hello and verify run). The wai command line gains four verbs,
one module each, named in one table (dx::VERBS):

- hello: name, dispatch, rebuild and receipt on a picture made here, the
  receipt laid out as http-delivery 8 lays receipts beside an object;
- create: a new project from one of the examples, whose files the binary
  holds; tests/cli_dx.rs holds them to wai/examples/ byte for byte;
- dev: the relay, the room service and the room page on one loopback origin,
  as a production front serves them; --check checks each piece through it;
- verify: the receipt indexes under a directory, checked offline.

The project file a template writes, wai.json, is read in one place
(dx/project.rs). wai/examples/ holds video-room, watch-together, messaging,
encoder-ingest, package-and-deliver, verify-receipts, apple-app, android-app
and remote-desktop, each with a README and a test that runs it through
wai create and wai dev. wai/quickstart/ builds the room client and the
archive that holds wai, the relay, the room service, the remote host and the
packager.

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

124 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Verify WAI delivery receipts in a directory, with nothing but Python's standard library.
python3 verify_receipts.py <dir>
For each receipt index `<cid>.wai.receipts` in <dir> (laid out as http-delivery §8 lays them
out beside an object, as `wai hello` writes them), each delivery item is checked:
1. the group receipt is read from `rcpt/<link hash>.json`, and its link hash, recomputed, is
its name;
2. its Ed25519 signature verifies over its signed payload (jwp-receipts §2.3, the labelled
form), under the strict rule every WAI verifier holds;
3. the object `<cid>.wai` is there and hashes to the receipt's content hash, which is its
name, and the object's leaf reaches the group's Merkle root by its proof (§3 steps 4, 5).
This example reads groups whose objects are explicitly unmetered (energy not measured), as
`wai hello` writes; a receipt with a measured energy class is reported, not read. It is a
second implementation: it shares no code with the Rust one in ../rust/. Exit 0 when every
item of every index verifies.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _blake3 import blake3 # noqa: E402
from _ed25519 import verify as ed25519_verify # noqa: E402
LEAF = b"moq-jwp:merkle-leaf\x02"
NODE = b"moq-jwp:merkle-node\x01"
SIGN = b"moq-jwp:group-sign\x02"
LINK = b"moq-jwp:receipt-link\x01"
OBJECT = b"moq-jwp:object\x01"
UNMETERED = b"\x00" # the signed encoding of "not measured" (energy-measurement §2.7)
def u32(n):
return n.to_bytes(4, "big")
def u64(n):
return n.to_bytes(8, "big")
def text(s):
b = s.encode()
return u32(len(b)) + b
def signed_payload(g):
"""The labelled group payload (jwp-receipts §2.3)."""
parent = g["parent_receipt_hash"]
return (SIGN + u64(g["track_alias"]) + u64(g["group_id"]) + bytes.fromhex(g["root_hash"])
+ u64(g["leaf_count"]) + u64(g["joules_total"]) + text(g["group_start"]) + text(g["group_end"])
+ bytes.fromhex(g["signer_pubkey"]) + text(g["signer_id"])
+ UNMETERED + b"\x00" # the class, and its coverage: whole
+ ((b"\x01" + bytes.fromhex(parent)) if parent else b"\x00" + bytes(32)))
def check_item(d, cid, item):
"""One delivery item; raises ValueError with the reason when it does not verify."""
o = item["object_receipt"]
gname = item["property"]["group_receipt"]
with open(os.path.join(d, "rcpt", gname + ".json"), "rb") as f:
g = json.load(f)
if g.get("kind") != "group" or g.get("sign_version") != 2:
raise ValueError("not a group receipt in the labelled form")
if any(k.startswith("energy_") and k != "energy_coverage" for k in g) or o.get("leaf_version") != 2 or any(k.startswith("energy_") for k in o):
raise ValueError("a measured energy class: this example reads unmetered receipts alone")
payload = signed_payload(g)
if blake3(LINK + payload + bytes.fromhex(g["sig"])).hex() != gname:
raise ValueError("receipt_name_mismatch: the group's link hash is not its file's name")
why = ed25519_verify(bytes.fromhex(g["signer_pubkey"]), payload, bytes.fromhex(g["sig"]))
if why is not None:
raise ValueError(f"{why}: the group's signature")
with open(os.path.join(d, cid + ".wai"), "rb") as f:
envelope = f.read()
content = blake3(OBJECT + envelope)
if content.hex() != o["content_hash"] or content.hex() != cid:
raise ValueError("content_hash_mismatch: the object is not the one the receipt names")
# The object's leaf, up its proof to the root.
p = o["merkle_proof"]
if o["object_id"] != p["leaf_index"] or p["direction_bits"] != p["leaf_index"] or o["group_id"] != g["group_id"]:
raise ValueError("proof_position_mismatch")
if o["joules_micro"] != 0:
raise ValueError("object_label_invalid: an unmetered object states no energy")
cur = blake3(LEAF + content + u64(0) + UNMETERED + text(o["origin"]))
for i, s in enumerate(p["siblings"]):
s = bytes.fromhex(s)
cur = blake3(NODE + s + cur) if (p["direction_bits"] >> i) & 1 else blake3(NODE + cur + s)
if cur.hex() != g["root_hash"]:
raise ValueError("inclusion_proof_failed")
return g
def main(d):
names = sorted(n for n in os.listdir(d) if n.endswith(".wai.receipts"))
if not names:
print(f"no receipt index (*.wai.receipts) in {d}")
return 2
ok = True
for n in names:
cid = n[: -len(".wai.receipts")]
with open(os.path.join(d, n), "rb") as f:
index = json.load(f)
if index.get("kind") != "wai-receipt-index" or index.get("object") != cid:
print(f"FAIL {n}: not this object's receipt index")
ok = False
continue
for item in index["delivery"]:
try:
g = check_item(d, cid, item)
print(f"ok {cid[:16]}: delivered and verified; group {item['property']['group_receipt'][:16]} signed by {g['signer_pubkey'][:16]}… ({g['signer_id'][:24]}…)")
except (ValueError, OSError, KeyError) as e:
print(f"FAIL {cid[:16]}: {e}")
ok = False
return 0 if ok else 1
if __name__ == "__main__":
if len(sys.argv) != 2:
print(__doc__.split("\n\n")[1], file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))