#!/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 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 _int_sr as SR # noqa: E402 import live_rooms_verify as LRV # noqa: E402 import _wai_mark as MARK # 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)) if t == "samples": members(v, ["type", "samples"]) return ("samples", hexb(v["samples"], 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)} # The derivations of exact-upscale §3, each with the factor its output's samples hold over its # input's (§4.3), checked with the prior its evidence names. SR_DERIVATIONS = {SR.SR2: 4, SR.SR4: 16} def evidence_prior(e): """§4.3: the prior an exact-upscale derivation's `params` names, `{"prior"}`, or None.""" p = e["params"] if not isinstance(p, dict) or set(p) != {"prior"} or not isinstance(p["prior"], str): return None try: return hexb(p["prior"], 32) except Bad: return None def replicate_admitted(e): """§4.3: a pinned derivation with its parameters, and an output of at most 2^26 bytes.""" w, h = e["width"], e["height"] if e["derivation"] in DERIVATIONS: return e["params"] == {} and DERIVATIONS[e["derivation"]][1] * w * h * 3 <= (1 << 26) if e["derivation"] in SR_DERIVATIONS: return evidence_prior(e) is not None and SR_DERIVATIONS[e["derivation"]] * w * h * 3 <= (1 << 26) return False # ---------------------------------------------------------------- §7, §9, §10 the other records def positive(v): need(integer(v) >= 1) return v def frames_of(v, digests): """§7.2, §7.3: an embedding or a read: the frames' count and size, and digests.""" if v is None: return None members(v, ["frames", "width", "height"] + digests) f = {k: positive(v[k]) for k in ("frames", "width", "height")} f.update({k: hexb(v[k], 32) for k in digests}) return f 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), "embedding": frames_of(v["embedding"], ["unmarked", "marked"])} 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), "read": frames_of(v["read"], ["samples"])} def scheme_check(scheme, digest, frames): """§7.4: whether the record's scheme is the registered one; refuses a registered term under another digest, a record of it with no frames the scheme admits, and frames stated for wai-mark:conformance.""" if scheme == MARK.SCHEME: if digest != SCHEME_DIGEST: raise Refuse("scheme_mismatch") if frames is None or not MARK.admits(frames["width"], frames["height"], frames["frames"]): raise Refuse("mark_evidence_invalid") return True if scheme == "wai-mark:conformance" and frames is not None: raise Refuse("mark_evidence_invalid") return False def marking_statement(m): """§7.7: the marking's C2PA statement, as its JCS text.""" return jcs({"actions": [{"action": "c2pa.watermarked.bound"}], "assertions": [{"label": "c2pa.soft-binding", "data": {"alg": m["scheme"], "alg-params": m["scheme_digest"].hex(), "blocks": [{"scope": {}, "value": m["payload"].hex()}]}}]}) _EMBEDDED, _DETECTED = {}, {} def embedded(px, e, payload): """§7.5.4, memoised: the SHA-256 of the marked frames.""" k = (hashlib.sha256(px).digest(), e["width"], e["height"], e["frames"], payload) if k not in _EMBEDDED: _EMBEDDED[k] = hashlib.sha256(MARK.embed(px, e["width"], e["height"], e["frames"], payload)[0]).digest() return _EMBEDDED[k] def detected(px, r): """§7.5.5, memoised: the payload read, or None.""" k = (hashlib.sha256(px).digest(), r["width"], r["height"], r["frames"]) if k not in _DETECTED: got = MARK.detect(px, r["width"], r["height"], r["frames"]) _DETECTED[k] = None if got is None else got[0] return _DETECTED[k] def recomputes(px, e, payload): """§7.6 step 2: the embedding of the unmarked frames is the marked frames.""" return embedded(px, e, payload) == e["marked"] def reproduces(px, r, payload): """§7.6: the detector, run again, reads the detection's payload.""" return detected(px, r) == payload def inherits(c, p, by_link): """§7.3 step 4: p is the payload of a held claim c reaches through inputs along steps convert and assist.""" seen, stack = set(), [c] while stack: x = stack.pop() if x["step"] not in ("convert", "assist") or x["link"] in seen: continue seen.add(x["link"]) for i in x["inputs"]: held = by_link.get(i["origin"]) if held is not None: if payload_of(held) == p: return True stack.append(held) return False 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": [], "markings": [], "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", "embedding", "issued_at", "signer_pubkey", "sig"], marking_fields, "marking") detections = each("detections", "wai-detection", ["kind", "subject", "scheme", "scheme_digest", "payload", "read", "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"]} unmarked = {bytes.fromhex(d["object"]): bytes.fromhex(d["samples"]) for d in case["unmarked"]} prove = case["policy"]["second_layer"] == "require-proof" sha = lambda b: hashlib.sha256(b).digest() # noqa: E731 priors = {sha(bytes.fromhex(p)): bytes.fromhex(p) for p in case["priors"]} # 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 not replicate_admitted(e): 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 e["derivation"] in DERIVATIONS: out = DERIVATIONS[e["derivation"]][0](px, w, hh) else: prior = priors.get(evidence_prior(e)) try: out = None if prior is None else SR.derive(e["derivation"], prior, px, w, hh) except SR.Unreadable: no("evidence_invalid") if out is not None: if sha(out) != e["output_samples"]: no("assistive_recompute_mismatch") status = "proved" else: status = "asserted" subject = sha(decoded[h]) if kind == "object" and h in decoded else h if kind == "samples" else None if subject is not None and subject != 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: §7.2 and §7.6 for the markings. for i, m in enumerate(markings): def no(reason): raise Refuse(reason, ["marking", i]) c = by_link.get(m["origin"]) if c is None: no("claim_unknown") if c["subject"] != ("object", m["subject"]) or payload_of(c) != m["payload"]: no("marking_mismatch") e = m["embedding"] try: registered = scheme_check(m["scheme"], m["scheme_digest"], e) except Refuse as r: no(r.reason) status = "stated" if registered and m["subject"] in unmarked: px = unmarked[m["subject"]] if len(px) != e["frames"] * e["width"] * e["height"] * 3 or sha(px) != e["unmarked"]: no("mark_input_mismatch") if not recomputes(px, e, m["payload"]): no("mark_recompute_mismatch") status = "proved" if e is not None and m["subject"] in decoded and sha(decoded[m["subject"]]) != e["marked"]: no("samples_mismatch") if prove and status != "proved": no("layer_not_proved") st["markings"].append({"subject": m["subject"].hex(), "scheme": m["scheme"], "status": status, "c2pa": marking_statement(m)}) # §7.3 and §7.6 for the detections. for i, d in enumerate(detections): def no(reason): raise Refuse(reason, ["detection", i]) 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"]): no("detection_mismatch") r = d["read"] try: registered = scheme_check(d["scheme"], d["scheme_digest"], r) except Refuse as x: no(x.reason) rerun = "not-rerun" if r is not None and d["subject"] in decoded: px = decoded[d["subject"]] if len(px) != r["frames"] * r["width"] * r["height"] * 3 or sha(px) != r["samples"]: no("samples_mismatch") if registered: if not reproduces(px, r, d["payload"]): no("detection_irreproducible") rerun = "reproduced" if prove and rerun != "reproduced": no("layer_not_proved") if d["payload"] is None: if marking is not None: no("watermark_absent") result = "unmarked" elif claim is None: result = "not-checked" elif d["payload"] == payload_of(claim): result = "agree" elif inherits(claim, d["payload"], by_link): result = "agree-inherited" else: no("layers_disagree") st["detections"].append({"subject": d["subject"].hex(), "result": result, "rerun": rerun}) 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("| `")] # §13: the codes only the scheme's corpus gives. _m = re.search(r"The codes ((?:`[a-z_]+`(?:, | and )?)+) need frames", " ".join(section(_TEXT, "## 13. Conformance").split())) MARK_ONLY = re.findall(r"`([a-z_]+)`", _m.group(1)) if _m else [] # §7.4: the registered scheme's digest. _d = re.search(r"its digest is\s+`([0-9a-f]{64})`", section(_TEXT, "### 7.4 ")) SCHEME_DIGEST = bytes.fromhex(_d.group(1)) if _d else b"" 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, sink = vn["chain"], vn["generated"], vn["ai_room"], vn["certified"], vn["sink_upscale"] 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:])) def prior_byte(vs): p = vs[sink]["priors"][0] vs[sink]["priors"][0] = p[:40] + ("0" if p[40] != "0" else "1") + p[41:] return [ mutate("a byte of the sink's prior: the prior its evidence names is not held", prior_byte), mutate("a byte of the decoded picture the sink upscaled", lambda vs: vs[sink]["decoded"][0].update(samples=flip_hex_digit(vs[sink]["decoded"][0]["samples"], 2))), mutate("the sink's upscale stated as not recomputed", lambda vs: vs[sink]["statements"]["claims"][2].update(assistive="not-recomputed")), 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 and r not in MARK_ONLY] if len(MARK_ONLY) != 3 or not set(MARK_ONLY) <= set(REASONS) or len(SCHEME_DIGEST) != 32: fails.append("§13's codes of the scheme's corpus, or §7.4's digest, do not read") 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", "sink_upscale", "generated", "ai_room", "certified"} if not want <= accepted: fails.append("vectors not accepted: %s" % ", ".join(sorted(want - accepted))) print("%s every reason of §12 but %s refuses a vector, and every case of §13 is accepted" % ("PASS" if not missing and want <= accepted else "FAIL", ", ".join(MARK_ONLY))) 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 ", file=sys.stderr) sys.exit(2) sys.exit(main(sys.argv[1]))