open-standards/wai/live-conformance/synthesis_verify.py
Transaction Science b5094c6329 synthesis_verify.py: the synthesis corpus's second implementation
Written from extensions/synthesis.md: strict JSON and JCS of its own, the pinned
derivations in its own arithmetic, the digital source type and the C2PA statement written
from §5 and §6, rooms through live_rooms_verify.py, Ed25519 and BLAKE3 through the
claim-conformance helpers. It agrees with wai-rs on every vector and refuses every mutated
copy of the corpus.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 05:20:57 -04:00

864 lines
37 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Second implementation of extensions/synthesis.md (`wai.media.synthesis`), over the corpus
`live-conformance/synthesis/`.
Written from the extension's text. It shares no code with wai-rs: it reads every record as §2
has it read (strict JSON, the encodings of jwp-receipts §2.4.3), verifies every signature with
WAI's strict Ed25519 rule (claim-conformance/_ed25519.py) and every link hash with BLAKE3
written from its specification (claim-conformance/_blake3.py), runs the two pinned derivations
of §4 in its own arithmetic, writes the digital source type of §5 and the C2PA statement of §6
itself, and verifies each room with the live-rooms corpus's second implementation
(live_rooms_verify.py). It reads §10.2's pinned icons and §12's codes from the extension.
python3 -I synthesis_verify.py <live-conformance/synthesis>
It gives every vector of the corpus the outcome, the reason, the place and the statements the
corpus states, checks that every reason of §12 refuses a vector, and then mutates copies of the
corpus and requires every copy to fail. Exit status 0 only if all of that holds. Python 3.8 or
later; standard library only.
"""
import copy
import hashlib
import json
import os
import re
import sys
import unicodedata
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 _ed25519 as ED # noqa: E402
from _blake3 import blake3 # noqa: E402
EXTENSION = os.path.join(HERE, "..", "extensions", "synthesis.md")
REGISTRY = os.path.join(HERE, "..", "registry", "capabilities.json")
SIGN_DOMAIN = b"wai:synthesis\x01" # §2
LINK_DOMAIN = b"wai:synthesis-link\x01" # §2
OBJECT_DOMAIN = b"moq-jwp:object\x01" # jwp-receipts §2.1
IPTC = "http://cv.iptc.org/newscodes/digitalsourcetype/"
MODEL_ID = "tag:transaction.science,2026:wai/model/" # §6
UPSCALE2, DENOISE3 = "wai-synthesis:upscale2", "wai-synthesis:denoise3"
MAX_INT = (1 << 53) - 1
CLASSES = ["captured", "reconstructed", "assistive", "synthesized"] # §3.2, in order
STEP_CLASS = {"capture": "captured", "convert": "reconstructed", "assist": "assistive", "generate": "synthesized"}
CAPTURE_SOURCES = ["digitalCapture", "computationalCapture", "screenCapture", "digitalCreation"]
SOURCES = CAPTURE_SOURCES + ["algorithmicallyEnhanced", "trainedAlgorithmicMedia", "compositeWithTrainedAlgorithmicMedia",
"algorithmicMedia", "composite", "compositeCapture", "compositeSynthetic"] # §5
OVERSIGHT = ["fully_autonomous", "prompt_guided", "human_validated"]
MEDIA = ["audio", "video", "image", "text"]
PATH_TIERS = ["decode-equivalence", "generation-equivalence", "entropy-consistency", "none"]
REVIEW_TIERS = ["decode-equivalence", "generation-equivalence"]
# Unicode's White_Space property (a term holds none of these, jwp-receipts §2.4.3).
WHITE_SPACE = set("\t\n\x0b\x0c\r \x85\xa0         "
"   

   ")
class Refuse(Exception):
def __init__(self, reason, at=None):
super().__init__(reason)
self.reason, self.at = reason, at
class Bad(Exception):
"""A record that does not read: `record_malformed`."""
# ---------------------------------------------------------------- §2 records
def strict_json(text):
"""RFC 8259, no member named twice, no number but an unsigned integer, no unpaired
surrogate, at most 32 levels."""
def pairs(ps):
d = {}
for k, v in ps:
if k in d:
raise Bad("a member named twice")
d[k] = v
return d
def no_float(s):
raise Bad("a number that is not an unsigned integer")
def integer(s):
if s.startswith("-"):
raise Bad("a negative number")
return int(s)
try:
v = json.loads(text, object_pairs_hook=pairs, parse_float=no_float, parse_int=integer, parse_constant=no_float)
except (ValueError, RecursionError):
raise Bad("not JSON")
def walk(x, depth):
if isinstance(x, (dict, list)):
if depth > 32:
raise Bad("too deep")
for y in (x.values() if isinstance(x, dict) else x):
walk(y, depth + 1)
if isinstance(x, dict):
for k in x:
walk(k, depth)
elif isinstance(x, str):
if any(0xD800 <= ord(c) <= 0xDFFF for c in x):
raise Bad("an unpaired surrogate")
elif isinstance(x, bool):
raise Bad("a boolean")
elif isinstance(x, int):
if x > MAX_INT:
raise Bad("an integer past 2^53 - 1")
walk(v, 1)
return v
def jstr(s):
out = ['"']
for c in s:
o = ord(c)
if c == '"':
out.append('\\"')
elif c == "\\":
out.append("\\\\")
elif c == "\b":
out.append("\\b")
elif c == "\f":
out.append("\\f")
elif c == "\n":
out.append("\\n")
elif c == "\r":
out.append("\\r")
elif c == "\t":
out.append("\\t")
elif o < 0x20:
out.append("\\u%04x" % o)
else:
out.append(c)
out.append('"')
return "".join(out)
def jcs(v):
"""RFC 8785 on the values a record carries."""
if v is None:
return "null"
if isinstance(v, bool):
raise Bad("a boolean")
if isinstance(v, int):
return str(v)
if isinstance(v, str):
return jstr(v)
if isinstance(v, list):
return "[" + ",".join(jcs(x) for x in v) + "]"
keys = sorted(v, key=lambda k: k.encode("utf-16-be"))
return "{" + ",".join(jstr(k) + ":" + jcs(v[k]) for k in keys) + "}"
def link_hash(v):
return blake3(LINK_DOMAIN + jcs(v).encode("utf-8"))
def signing_bytes(v):
return SIGN_DOMAIN + jcs({k: x for k, x in v.items() if k != "sig"}).encode("utf-8")
def need(cond):
if not cond:
raise Bad("a member breaks its rule")
def hexb(v, n):
need(isinstance(v, str) and len(v) == 2 * n and re.fullmatch(r"[0-9a-f]*", v) is not None)
return bytes.fromhex(v)
def opt_hex(v, n):
return None if v is None else hexb(v, n)
def integer(v):
need(isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= MAX_INT)
return v
def term(v):
need(isinstance(v, str) and 0 < len(v.encode("utf-8")) <= 2048
and not any(c in WHITE_SPACE or unicodedata.category(c) == "Cc" for c in v))
return v
def opt_term(v):
return None if v is None else term(v)
def one_of(v, values):
need(isinstance(v, str) and v in values)
return v
def members(v, names):
need(isinstance(v, dict) and set(v) == set(names))
return v
def date_time(s):
"""RFC 3339 §5.6 date-time, with §5.7's days (jwp-receipts §2.4.3)."""
m = re.fullmatch(r"([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt]([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?([Zz]|[+-]([0-9]{2}):([0-9]{2}))", s or "")
need(m is not None)
y, mo, d, h, mi, sec = (int(m.group(i)) for i in range(1, 7))
leap = y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
days = [31, 29 if leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
need(1 <= mo <= 12 and 1 <= d <= days[mo - 1] and h <= 23 and mi <= 59 and sec <= 60)
if m.group(9) is not None:
need(int(m.group(9)) <= 23 and int(m.group(10)) <= 59)
def read(text, kind, names, fields):
"""§2: read one record, then its signature. `fields` reads the kind's own members."""
try:
v = strict_json(text)
members(v, names)
need(v["kind"] == kind)
signer = hexb(v["signer_pubkey"], 32)
sig = hexb(v["sig"], 64)
if "issued_at" in v:
date_time(v["issued_at"])
r = fields(v)
except (Bad, KeyError, TypeError, AttributeError):
raise Refuse("record_malformed")
bad = ED.verify(signer, signing_bytes(v), sig)
if bad:
raise Refuse(bad)
r.update(value=v, signer=signer, link=link_hash(v))
return r
# ---------------------------------------------------------------- §3 the origin claim
ORIGIN = ["kind", "subject", "media", "class", "step", "operation", "record", "source", "models", "oversight", "inputs",
"evidence", "issued_at", "signer_pubkey", "sig"]
def subject_of(v):
t = v.get("type")
if t == "object":
members(v, ["type", "content_hash"])
return ("object", hexb(v["content_hash"], 32))
if t == "group":
members(v, ["type", "receipt"])
return ("group", hexb(v["receipt"], 32))
if t == "track":
members(v, ["type", "track", "from_group"])
integer(v["from_group"])
return ("track", hexb(v["track"], 32))
raise Bad("a subject of no type")
def origin_fields(v):
c = {"subject": subject_of(v["subject"]), "media": one_of(v["media"], MEDIA), "class": one_of(v["class"], CLASSES),
"step": one_of(v["step"], list(STEP_CLASS)), "operation": term(v["operation"]), "record": opt_hex(v["record"], 32),
"source": one_of(v["source"], SOURCES),
"oversight": None if v["oversight"] is None else one_of(v["oversight"], OVERSIGHT)}
need(isinstance(v["models"], list) and len(v["models"]) <= 16)
c["models"] = []
for m in v["models"]:
members(m, ["digest", "type"])
c["models"].append({"digest": hexb(m["digest"], 32), "trained": one_of(m["type"], ["trained", "algorithmic"]) == "trained"})
need(len({m["digest"] for m in c["models"]}) == len(c["models"]))
need(isinstance(v["inputs"], list) and len(v["inputs"]) <= 16)
c["inputs"] = []
for i in v["inputs"]:
members(i, ["content_hash", "origin", "class", "source"])
c["inputs"].append({"content_hash": hexb(i["content_hash"], 32), "origin": opt_hex(i["origin"], 32),
"class": one_of(i["class"], CLASSES), "source": one_of(i["source"], SOURCES)})
need(len({i["content_hash"] for i in c["inputs"]}) == len(c["inputs"]))
e = v["evidence"]
if e is None:
c["evidence"] = None
else:
members(e, ["tier", "derivation", "params", "width", "height", "input_samples", "output_samples"])
need(isinstance(e["params"], dict))
c["evidence"] = {"tier": one_of(e["tier"], ["replicate", "attest"]), "derivation": term(e["derivation"]),
"params": e["params"], "width": integer(e["width"]), "height": integer(e["height"]),
"input_samples": hexb(e["input_samples"], 32), "output_samples": hexb(e["output_samples"], 32)}
need(c["evidence"]["width"] >= 1 and c["evidence"]["height"] >= 1)
n, m, s = len(c["inputs"]), len(c["models"]), c["step"]
plain = c["oversight"] is None and c["evidence"] is None
if s == "capture":
need(n == 0 and m == 0 and plain and c["source"] in CAPTURE_SOURCES)
elif s == "convert":
need(n >= 1 and m == 0 and plain)
elif s == "assist":
need(n == 1 and c["oversight"] is None and c["evidence"] is not None)
else:
need(m >= 1 and c["oversight"] is not None and c["evidence"] is None)
return c
def class_of(c):
"""§3.2: the greatest of the step's class and the inputs'."""
return max([STEP_CLASS[c["step"]]] + [i["class"] for i in c["inputs"]], key=CLASSES.index)
def generated_source(c):
"""§5."""
if not any(m["trained"] for m in c["models"]):
return "algorithmicMedia"
if c["oversight"] == "human_validated" or any(i["class"] != "synthesized" for i in c["inputs"]):
return "compositeWithTrainedAlgorithmicMedia"
return "trainedAlgorithmicMedia"
def source_of(c):
"""§5's table."""
ins = c["inputs"]
if c["step"] == "capture":
return c["source"]
if c["step"] == "convert":
if len(ins) == 1:
return ins[0]["source"]
if any(i["class"] == "synthesized" for i in ins):
return "compositeSynthetic"
if all(i["source"] in ("digitalCapture", "computationalCapture") for i in ins):
return "compositeCapture"
return "composite"
if c["step"] == "assist":
return ins[0]["source"] if ins[0]["class"] == "synthesized" else "algorithmicallyEnhanced"
return generated_source(c)
def statement(c):
"""§6, as its JCS text."""
if len(c["inputs"]) != 1:
actions = [{"action": "c2pa.created", "digitalSourceType": IPTC + c["source"]}]
elif c["step"] == "convert":
actions = [{"action": "c2pa.opened"}, {"action": "c2pa.converted"}]
elif c["step"] == "assist":
actions = [{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": IPTC + "algorithmicallyEnhanced"}]
else:
actions = [{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": IPTC + generated_source(c)}]
disclosures = []
for m in [m for m in c["models"] if m["trained"]]:
data = {"modelType": "c2pa.types.model", "modelIdentifier": MODEL_ID + m["digest"].hex()}
if c["step"] == "generate":
data["contentProfile"] = {"humanOversightLevel": c["oversight"]}
k = len(disclosures)
disclosures.append({"label": "c2pa.ai-disclosure" if k == 0 else "c2pa.ai-disclosure__%d" % k, "data": data})
return jcs({"actions": actions, "assertions": disclosures})
def payload_of(c):
"""§7.2: the class's code, then seven bytes of the claim's link hash."""
return bytes([CLASSES.index(c["class"])]) + c["link"][:7]
# ---------------------------------------------------------------- §4 the derivations
def upscale2(px, w, h):
out = bytearray(4 * w * h * 3)
ow = 2 * w
def at(x, y, k):
return px[(y * w + x) * 3 + k]
for y in range(h):
for x in range(w):
x1, y1 = min(x + 1, w - 1), min(y + 1, h - 1)
for k in range(3):
a, b, c, d = at(x, y, k), at(x1, y, k), at(x, y1, k), at(x1, y1, k)
out[((2 * y) * ow + 2 * x) * 3 + k] = a
out[((2 * y) * ow + 2 * x + 1) * 3 + k] = (a + b + 1) >> 1
out[((2 * y + 1) * ow + 2 * x) * 3 + k] = (a + c + 1) >> 1
out[((2 * y + 1) * ow + 2 * x + 1) * 3 + k] = (a + b + c + d + 2) >> 2
return bytes(out)
def denoise3(px, w, h):
out = bytearray(w * h * 3)
for y in range(h):
for x in range(w):
for k in range(3):
s = 8
for j in (-1, 0, 1):
for i in (-1, 0, 1):
weight = (2 if i == 0 else 1) * (2 if j == 0 else 1)
xx, yy = min(max(x + i, 0), w - 1), min(max(y + j, 0), h - 1)
s += weight * px[(yy * w + xx) * 3 + k]
out[(y * w + x) * 3 + k] = s >> 4
return bytes(out)
DERIVATIONS = {UPSCALE2: (upscale2, 4), DENOISE3: (denoise3, 1)}
# ---------------------------------------------------------------- §7, §9, §10 the other records
def marking_fields(v):
return {"subject": hexb(v["subject"], 32), "origin": hexb(v["origin"], 32), "scheme": term(v["scheme"]),
"scheme_digest": hexb(v["scheme_digest"], 32), "payload": hexb(v["payload"], 8)}
def detection_fields(v):
return {"subject": hexb(v["subject"], 32), "scheme": term(v["scheme"]), "scheme_digest": hexb(v["scheme_digest"], 32),
"payload": opt_hex(v["payload"], 8)}
def review_fields(v):
verdict = one_of(v["verdict"], ["approved", "approved-with-notes", "rejected"])
notes = opt_hex(v["notes"], 32)
need((notes is None) == (verdict == "approved"))
term(v["decoder"])
opt_term(v["variant"])
opt_term(v["role"])
one_of(v["scope"], ["accuracy", "rendering", "translation"])
return {"subject": hexb(v["subject"], 32), "origin": hexb(v["origin"], 32), "tier": one_of(v["tier"], REVIEW_TIERS),
"reconstruction": hexb(v["reconstruction"], 32), "verdict": verdict}
def policy_fields(v):
need(isinstance(v["classes"], list) and len(v["classes"]) >= 1)
idx = [CLASSES.index(one_of(c, CLASSES)) for c in v["classes"]]
need(all(a < b for a, b in zip(idx, idx[1:])))
interval = None if v["interval_us"] is None else integer(v["interval_us"])
need(interval != 0)
return {"classes": list(v["classes"]), "visual": hexb(v["visual"], 32), "audio": opt_hex(v["audio"], 32),
"interval_us": interval, "grace_us": integer(v["grace_us"])}
def entry_of(e):
t = e.get("type")
if t == "present":
members(e, ["type", "subject", "origin", "class", "media", "tier", "decoder", "variant", "output"])
term(e["decoder"])
opt_term(e["variant"])
return {"type": t, "subject": hexb(e["subject"], 32), "origin": opt_hex(e["origin"], 32),
"class": None if e["class"] is None else one_of(e["class"], CLASSES), "media": one_of(e["media"], MEDIA),
"tier": one_of(e["tier"], PATH_TIERS), "output": opt_hex(e["output"], 32)}
if t == "shown":
members(e, ["type", "label", "asset", "trigger"])
return {"type": t, "audio": one_of(e["label"], ["visual", "audio"]) == "audio", "asset": hexb(e["asset"], 32),
"trigger": one_of(e["trigger"], ["first-exposure", "resume", "interval"])}
if t == "resume":
members(e, ["type", "after"])
one_of(e["after"], ["ad-break", "other"])
return {"type": t}
if t == "review":
members(e, ["type", "subject", "review"])
return {"type": t, "subject": hexb(e["subject"], 32), "review": hexb(e["review"], 32)}
raise Bad("an entry of no type")
def player_fields(v):
return {"session": hexb(v["session"], 32), "seq": integer(v["seq"]), "prev": opt_hex(v["prev"], 32), "at": integer(v["at"]),
"entry": entry_of(v["entry"])}
# ---------------------------------------------------------------- §11 verification
def generates(env, classes):
"""§3.3: an envelope whose capability's registry class is generative-exact or behavioral, or
whose intent is create."""
if len(env) < 8 or env[:4] != b"WAI1":
return False
n = int.from_bytes(env[4:8], "little")
try:
m = json.loads(env[8:8 + n])
except ValueError:
return False
cap = m.get("model_requirement", {}).get("capability", "")
return classes.get(cap) in ("generative-exact", "behavioral") or m.get("intent") == "create"
def track_digest(room, owner, media, layer):
"""live-rooms §6.2."""
return hashlib.sha256(("-".join([room.hex(), owner, media]) + "\x00" + str(layer)).encode()).digest()
def verify(case, classes):
"""§11.2: the statements of §11.3, or Refuse with its reason and place."""
st = {"claims": [], "detections": [], "ai_tracks": [], "reviews_presented": [], "labels": [], "visual_icon": None, "room": None}
def each(key, kind, names, fields, place):
out = []
for i, t in enumerate(case[key]):
try:
out.append(read(t, kind, names, fields))
except Refuse as e:
raise Refuse(e.reason, [place, i])
return out
# Step 1.
claims = each("claims", "wai-origin", ORIGIN, origin_fields, "claim")
markings = each("markings", "wai-marking", ["kind", "subject", "origin", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"],
marking_fields, "marking")
detections = each("detections", "wai-detection", ["kind", "subject", "scheme", "scheme_digest", "payload", "issued_at", "signer_pubkey", "sig"],
detection_fields, "detection")
reviews = each("reviews", "wai-review", ["kind", "subject", "origin", "tier", "reconstruction", "decoder", "variant", "scope", "verdict", "notes",
"role", "issued_at", "signer_pubkey", "sig"], review_fields, "review")
policy = None
if case["label_policy"] is not None:
try:
policy = read(case["label_policy"], "wai-label-policy", ["kind", "classes", "visual", "audio", "interval_us", "grace_us", "issued_at",
"signer_pubkey", "sig"], policy_fields)
except Refuse as e:
raise Refuse(e.reason, ["label_policy"])
log = each("player", "wai-player", ["kind", "session", "seq", "prev", "at", "entry", "signer_pubkey", "sig"], player_fields, "player")
by_link = {c["link"]: c for c in claims}
objects = {blake3(OBJECT_DOMAIN + bytes.fromhex(o)): bytes.fromhex(o) for o in case["objects"]}
decoded = {bytes.fromhex(d["object"]): bytes.fromhex(d["samples"]) for d in case["decoded"]}
sha = lambda b: hashlib.sha256(b).digest() # noqa: E731
# Step 2.
seen = set()
for i, c in enumerate(claims):
def no(reason):
raise Refuse(reason, ["claim", i])
key = jcs(c["value"]["subject"])
if key in seen:
no("claim_conflict")
seen.add(key)
if c["class"] != class_of(c):
no("class_mismatch")
if c["source"] != source_of(c):
no("source_mismatch")
for inp in c["inputs"]:
held = by_link.get(inp["origin"])
if held is not None and (held["subject"] != ("object", inp["content_hash"]) or held["class"] != inp["class"]
or held["source"] != inp["source"]):
no("input_mismatch")
kind, h = c["subject"]
if kind == "object" and h in objects and generates(objects[h], classes) and c["step"] != "generate":
no("class_understated")
status = None
e = c["evidence"]
if e is not None:
w, hh = e["width"], e["height"]
if e["tier"] == "replicate":
if e["derivation"] not in DERIVATIONS or e["params"] != {} or DERIVATIONS[e["derivation"]][1] * w * hh * 3 > (1 << 26):
no("evidence_invalid")
status = "not-recomputed"
px = decoded.get(c["inputs"][0]["content_hash"])
if px is not None:
if len(px) != w * hh * 3 or sha(px) != e["input_samples"]:
no("input_samples_mismatch")
if sha(DERIVATIONS[e["derivation"]][0](px, w, hh)) != e["output_samples"]:
no("assistive_recompute_mismatch")
status = "proved"
else:
status = "asserted"
if kind == "object" and h in decoded and sha(decoded[h]) != e["output_samples"]:
no("samples_mismatch")
if case["policy"]["assistive"] == "require-proof" and status != "proved":
no("assistive_not_proved")
st["claims"].append({"link": c["link"].hex(), "class": c["class"], "source": c["source"], "c2pa": statement(c), "assistive": status})
# Step 3.
for i, m in enumerate(markings):
c = by_link.get(m["origin"])
if c is None:
raise Refuse("claim_unknown", ["marking", i])
if c["subject"] != ("object", m["subject"]) or payload_of(c) != m["payload"]:
raise Refuse("marking_mismatch", ["marking", i])
for i, d in enumerate(detections):
claim = next((c for c in claims if c["subject"] == ("object", d["subject"])), None)
marking = next((m for m in markings if m["subject"] == d["subject"]), None)
if marking is not None and (marking["scheme"], marking["scheme_digest"]) != (d["scheme"], d["scheme_digest"]):
raise Refuse("detection_mismatch", ["detection", i])
if d["payload"] is None:
if marking is not None:
raise Refuse("watermark_absent", ["detection", i])
result = "unmarked"
elif claim is None:
result = "not-checked"
elif d["payload"] != payload_of(claim):
raise Refuse("layers_disagree", ["detection", i])
else:
result = "agree"
st["detections"].append({"subject": d["subject"].hex(), "result": result})
if case["policy"]["dual_layer"] == "required":
for i, c in enumerate(claims):
if c["class"] == "synthesized" and c["subject"][0] == "object" and not any(m["origin"] == c["link"] for m in markings):
raise Refuse("layer_missing", ["claim", i])
# Step 4.
for i, r in enumerate(reviews):
c = by_link.get(r["origin"])
if c is None:
raise Refuse("claim_unknown", ["review", i])
if c["subject"] != ("object", r["subject"]) or c["class"] != "synthesized":
raise Refuse("review_subject_mismatch", ["review", i])
# Step 5.
room = case["room"]
if room is not None:
chains = [[bytes.fromhex(e) for e in ch] for ch in room["chains"]]
grants = [bytes.fromhex(g) for g in room["grants"]]
out = LRV.verify_room(bytes.fromhex(room["descriptor"]), grants, room["key_log"], chains)
if out["outcome"] == "refuse":
raise Refuse(out["reason"], ["room"] + list(out["at"]))
st["room"] = out["outcome"]
d = LRV.read_descriptor(bytes.fromhex(room["descriptor"]))
held, _ = LRV.check_grants(d, grants)
by_digest = {g["digest"]: g for g in held}
current = {}
for c_i, r_i, under in out["order"]:
if under is not None:
continue
ev = LEV.read_record(chains[c_i][r_i])
if not 0x10 <= ev["kind"] <= 0x16:
continue
subject, x = ev["payload"][:32], LRV.read_detail(ev["kind"], ev["payload"][32:])
if ev["kind"] == 0x10:
current[subject] = x["grant"]
continue
if ev["kind"] == 0x14:
current[subject] = x["new"]
continue
if ev["kind"] == 0x11:
current.pop(subject, None)
continue
if ev["kind"] not in (0x12, 0x15):
continue
g = by_digest.get(current.get(subject))
model = None
for k, v in (LRV.read_attrs(g["attrs"]) or []) if g else []:
if k == "ai":
model = bytes.fromhex(v)
if model is None:
continue
owner = subject.hex() if not x["slot"] else "_" + x["slot"]
media = {1: "audio", 2: "video", 3: "screen"}[x["media"]]
for layer in range(x["layers"]):
t = track_digest(d["room"], owner, media, layer)
found = next((c for c in claims if c["subject"] == ("track", t) and c["class"] == "synthesized" and c["step"] == "generate"
and any(m["digest"] == model for m in c["models"])
and (c["signer"] == subject or LRV.is_room_key(d, c["signer"]))), None)
if found is None:
raise Refuse("ai_track_unlabelled", ["room", "record", c_i, r_i])
st["ai_tracks"].append({"track": t.hex(), "claim": found["link"].hex()})
# Step 6.
for j, p in enumerate(log):
ok = p["seq"] == j and p["session"] == log[0]["session"] and p["signer"] == log[0]["signer"]
ok = ok and (p["prev"] is None if j == 0 else (p["prev"] == log[j - 1]["link"] and p["at"] >= log[j - 1]["at"]))
if not ok:
raise Refuse("player_chain_broken", ["player", j])
for j, p in enumerate(log):
e = p["entry"]
if e["type"] == "present":
c = by_link.get(e["origin"]) if e["origin"] is not None else None
if c is not None and (c["subject"] != ("object", e["subject"]) or e["class"] != c["class"]):
raise Refuse("present_mismatch", ["player", j])
if e["output"] is not None and e["subject"] in decoded and sha(decoded[e["subject"]]) != e["output"]:
raise Refuse("present_output_irreproducible", ["player", j])
elif e["type"] == "review":
r = next((r for r in reviews if r["link"] == e["review"]), None)
if r is None:
raise Refuse("claim_unknown", ["player", j])
last = next((q["entry"] for q in reversed(log[:j]) if q["entry"]["type"] == "present" and q["entry"]["subject"] == e["subject"]), None)
if last is None or r["subject"] != e["subject"]:
raise Refuse("review_subject_mismatch", ["player", j])
if r["verdict"] == "rejected":
raise Refuse("review_rejected", ["player", j])
if last["tier"] != r["tier"]:
raise Refuse("review_path_not_exact", ["player", j])
if last["output"] != r["reconstruction"]:
raise Refuse("review_reconstruction_mismatch", ["player", j])
st["reviews_presented"].append({"entry": j, "review": e["review"].hex()})
if policy is not None:
st["visual_icon"] = ICONS.get(policy["visual"].hex())
for j, p in enumerate(log):
e = p["entry"]
if e["type"] == "shown" and (policy["audio"] if e["audio"] else policy["visual"]) != e["asset"]:
raise Refuse("label_asset_mismatch", ["player", j])
def shown_in(audio, lo, hi):
return next((k for k, q in enumerate(log) if q["entry"]["type"] == "shown" and q["entry"]["audio"] == audio and lo <= q["at"] <= hi), None)
last = {False: None, True: None}
for j, p in enumerate(log):
e = p["entry"]
if e["type"] != "present" or e["class"] not in policy["classes"]:
continue
audio = e["media"] == "audio"
hi = p["at"] + policy["grace_us"]
prev = last[audio]
if prev is None:
trigger, k = "first-exposure", shown_in(audio, 0, hi)
else:
resumes = [q["at"] for q in log[prev + 1:j] if q["entry"]["type"] == "resume"]
if resumes:
trigger, k = "resume", shown_in(audio, resumes[-1], hi)
elif policy["interval_us"] is not None:
trigger, k = "interval", shown_in(audio, max(p["at"] - policy["interval_us"], 0), hi)
else:
last[audio] = j
continue
if k is None:
raise Refuse("label_not_shown", ["player", j])
st["labels"].append({"entry": j, "trigger": trigger, "shown": k})
last[audio] = j
return st
# ---------------------------------------------------------------- the extension's tables
def section(text, head):
at = text.index("\n" + head)
nxt = re.search(r"\n#{2,3} ", text[at + 1:])
return text[at: at + 1 + nxt.start()] if nxt else text[at:]
with open(EXTENSION, encoding="utf-8") as _f:
_TEXT = _f.read()
ICONS = {}
for _line in section(_TEXT, "### 10.2 ").splitlines():
_m = re.fullmatch(r"\| `(LABEL_AI[^`]*)` \| `([0-9a-f]{64})` \|", _line.strip())
if _m:
ICONS[_m.group(2)] = _m.group(1)
REASONS = [l.split("`")[1] for l in section(_TEXT, "## 12. Reasons").splitlines() if l.startswith("| `")]
def outcome(v, classes):
try:
return {"outcome": "accept", "statements": verify(v, classes)}
except Refuse as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}
def stated(v):
return {k: v[k] for k in ("outcome", "reason", "at", "statements") if k in v}
def check(doc, classes):
"""Failures of a corpus document: an outcome other than the one stated, or a table that
differs from the extension's."""
fails = []
pins = {d["sha256"]: d["file"] for d in doc["eu_icons"]}
if pins != ICONS or len(doc["eu_icons"]) != len(ICONS):
fails.append("the corpus's EU icons differ from §10.2's table")
for v in doc["vectors"]:
try:
got = outcome(v, classes)
except Exception as e: # noqa: BLE001 — a malformed copy fails, whatever it breaks
got = {"outcome": "unreadable: %r" % (e,)}
if got != stated(v):
fails.append("%s: %s" % (v["name"], json.dumps(got)[:300]))
return fails
def flip_hex_digit(s, at):
c = "0" if s[at] != "0" else "1"
return s[:at] + c + s[at + 1:]
def mutations(doc):
"""Copies of the corpus, each with one defect a conforming verifier must catch."""
vn = {v["name"]: k for k, v in enumerate(doc["vectors"])}
def mutate(label, f):
d = copy.deepcopy(doc)
f(d["vectors"])
return label, d
chain, gen, room, cert = vn["chain"], vn["generated"], vn["ai_room"], vn["certified"]
def retext(vs, i, key, k, f):
"""Rewrite record k of list `key` of vector i as JSON, applying f to its value."""
v = json.loads(vs[i][key][k])
f(v)
vs[i][key][k] = json.dumps(v)
def decoded_byte(vs):
d = vs[chain]["decoded"][0]
d["samples"] = flip_hex_digit(d["samples"], 0)
def upscale_digit(vs):
retext(vs, chain, "claims", 2, lambda v: v["evidence"].update(output_samples=flip_hex_digit(v["evidence"]["output_samples"], 5)))
def detection_class(vs):
retext(vs, gen, "detections", 0, lambda v: v.update(payload="00" + v["payload"][2:]))
return [
mutate("a digit of the upscale's signature", lambda vs: retext(vs, chain, "claims", 2, lambda v: v.update(sig=flip_hex_digit(v["sig"], 9)))),
mutate("the upscale's output digest changed", upscale_digit),
mutate("a byte of the reconstruction's decoded samples", decoded_byte),
mutate("the reconstruction's claim withheld from the chain", lambda vs: vs[chain]["claims"].pop(1)),
mutate("the generated video's detection reading class captured", detection_class),
mutate("the generated video's marking withheld", lambda vs: vs[gen]["markings"].pop(0)),
mutate("the composite's source stated compositeCapture",
lambda vs: vs[gen]["statements"]["claims"][4].update(source="compositeCapture")),
mutate("the transcode's statement stated as created",
lambda vs: vs[gen]["statements"]["claims"][5].update(c2pa=vs[gen]["statements"]["claims"][1]["c2pa"])),
mutate("the AI host's track claim withheld", lambda vs: vs[room].update(claims=[])),
mutate("the AI host's grant withheld from the room", lambda vs: vs[room]["room"]["grants"].pop(0)),
mutate("the certified log's first label withheld", lambda vs: vs[cert]["player"].pop(0)),
mutate("the label after the break stated as an interval",
lambda vs: vs[cert]["statements"]["labels"][1].update(trigger="interval")),
mutate("the review stated at generation-equivalence",
lambda vs: retext(vs, cert, "reviews", 0, lambda v: v.update(tier="generation-equivalence"))),
mutate("the presented video's decoded frame changed",
lambda vs: vs[cert]["decoded"][0].update(samples=flip_hex_digit(vs[cert]["decoded"][0]["samples"], 3))),
mutate("the policy's visual stated as another pinned icon",
lambda vs: vs[cert]["statements"].update(visual_icon="LABEL_AI_black.svg")),
mutate("the first refusal stated at another place", lambda vs: vs[next(k for k, v in enumerate(vs) if v["outcome"] == "refuse")].update(at=["claim", 1])),
("an EU icon's pin changed", dict(copy.deepcopy(doc), eu_icons=[dict(doc["eu_icons"][0], sha256="00" * 32)] + doc["eu_icons"][1:])),
]
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(REGISTRY, encoding="utf-8") as f:
classes = {c["capability"]: c["replicate"] for c in json.load(f)["capabilities"]}
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
if len(ICONS) != 24:
fails.append("§10.2's table does not read as 24 pinned icons")
reasons_seen = set()
for v in doc["vectors"]:
try:
got = outcome(v, classes)
except Exception as e: # noqa: BLE001
got = {"outcome": "unreadable: %r" % (e,)}
ok = got == stated(v)
if v.get("reason"):
reasons_seen.add(v["reason"])
print(("PASS" if ok else "FAIL") + " vector %s: %s" % (v["name"], got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if "reason" in got else "")))
if not ok:
fails.append("%s: %s, the corpus states %s" % (v["name"], json.dumps(got)[:400], json.dumps(stated(v))[:400]))
table = check(doc, classes)
if any("EU icons" in t for t in table):
fails.append("the corpus's EU icons differ from §10.2's table")
missing = [r for r in REASONS if r not in reasons_seen]
if missing:
fails.append("no vector refused with %s" % ", ".join(missing))
accepted = {v["name"] for v in doc["vectors"] if v["outcome"] == "accept"}
want = {"chain", "assistive_asserted", "generated", "ai_room", "certified"}
if not want <= accepted:
fails.append("vectors not accepted: %s" % ", ".join(sorted(want - accepted)))
print("%s every reason of §12 refuses a vector, and every case of §13 is accepted" % ("PASS" if not missing and want <= accepted else "FAIL"))
for label, d in mutations(doc):
caught = check(d, classes)
print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:100] 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") + " synthesis corpus: %d vectors" % len(doc["vectors"]))
return 1 if fails else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: synthesis_verify.py <live-conformance/synthesis>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))