open-standards/wai/edge-conformance/edge_verify.py
Transaction Science 3d98199668
Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m35s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m48s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 37s
WAI conformance / does this change touch it (pull_request) Successful in 51s
WAI energy receipts / does this change touch it (pull_request) Successful in 1m1s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 1m8s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 51s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 54s
wai-web wasm32 / does this change touch it (pull_request) Successful in 44s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 4m49s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 2m50s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 9m20s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m16s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 28s
WAI conformance / cargo-class corpora · linux (pull_request) Failing after 15m26s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m4s
wai-pack / wai-pack · linux (pull_request) Successful in 1m34s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m40s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m47s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m21s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 17s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 31s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m58s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Failing after 15m52s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 13m17s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 26m26s
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
deriving edge: cite C2PA 2.4 (§A.3.2, §18.15 actions); edge-conformance in the decoded-output exclusion list
C2 (#381) reads C2PA 2.4; actions moved from §18.14 to §18.15 there, and PNG embedding stays
§A.3.2. A new *-conformance directory must state a SHA-256 or be listed with its reason in
int_kernels_vectors.rs, which only the cargo-class corpora job runs.

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

1279 lines
57 KiB
Python

#!/usr/bin/env python3
"""Independent verifier of the deriving-edge corpus (extensions/deriving-edge.md).
Written from the extension's text and the texts it cites. It shares no code with wai-rs,
which generates the corpus. From the other corpora's second implementations, each written
from its own specification, it takes BLAKE3 and WAI's strict Ed25519 rule
(claim-conformance/_blake3.py and _ed25519.py), the WIV1 keyframe-0 decode
(int-payload-conformance/int_payload_verify.py), the explode of a WAI2 component
(staged-conformance/staged_verify.py) and the reading of a key-log revision
(live-conformance/live_rooms_verify.py). It implements the rest itself:
- the record's reading under §5.3's rules in their order, its canonical form (RFC 8785, on
the values a record carries), its signature and its hash (§5.2);
- the JWP group and object receipts the records name (jwp-receipts §2.2-§2.4.2): their
payloads, link hashes, labels and inclusion proofs;
- reading an input as samples (§4.1): packaging §5.7's still reader, with the chunk rule;
- the operations of §4.2, the pinned manifest of §4.3 and packaging §5.7's still writer, so
that every replicate hop is re-executed and compared byte for byte;
- verification of a chain (§7.2-§7.4) with the authority of §8, and the facts it states.
It reads §9's reasons and §4's derivations from the extension, requires every vector's
outcome, reason, place and facts, requires every reason of §9 to appear, mutates copies of
the corpus and requires every copy to fail, and drops each of its own checks in turn and
requires the corpus to catch the weakened verifier.
python3 -I edge_verify.py <edge-conformance dir>
Exit status 0 only if all of that holds. Standard library only.
"""
import copy
import hashlib
import json
import os
import re
import struct
import sys
import unicodedata
import zlib
HERE = os.path.dirname(os.path.abspath(__file__))
WAI = os.path.normpath(os.path.join(HERE, ".."))
for d in ("claim-conformance", "int-payload-conformance", "staged-conformance", "live-conformance"):
sys.path.insert(0, os.path.join(WAI, d))
from _blake3 import blake3 # noqa: E402
import _ed25519 as ED # noqa: E402
import int_payload_verify as IPV # noqa: E402
import staged_verify as SV # noqa: E402
import live_rooms_verify as LRV # noqa: E402
EXTENSION = os.path.join(WAI, "extensions", "deriving-edge.md")
MAX_INT = 2 ** 53 - 1
PNG, JPEG, INT_MOTION = "wai.image.png", "wai.image.jpeg", "wai.video.int_motion"
EXPLODE = "wai-edge:explode"
AUTHORITY = "edge/derive"
MAX_INPUTS, MAX_HOPS, MAX_SAMPLE_BYTES = 16, 8, 1 << 26
RECORD_MEMBERS = {"kind", "derivation", "params", "encoder", "check", "inputs", "output", "energy", "provenance",
"synthesis", "issued_at", "signer_pubkey", "signer_id", "sig"}
INPUT_MEMBERS = {"content_hash", "capability", "receipt", "object_id", "frame", "samples", "derivation", "synthesis"}
ITEM_MEMBERS = {"input", "kind", "digest", "compartment"}
MAX_PROVENANCE = 1024
LABELS = {None: 0, "assist": 1, "generate": 2} # §6.2, in rising order
OUTPUT_MEMBERS = {"content_hash", "capability", "receipt", "object_id", "width", "height", "samples"}
ENERGY_MEMBERS = {"joules_micro", "energy_provenance", "energy_uncertainty", "energy_counter_filtering",
"energy_calibration_ref"}
CLASS_ORDER = ["CalibratedInstrument", "OnChipCounter", "HwShunt", "ModelBased", "Estimator"]
RESOLUTION_FLOOR = 288676 # energy-measurement §2.3: joules_micro x relative_ppm, at least
# ---------------------------------------------------------------- the extension's tables
def section(text, heading):
at = text.index(heading)
end = text.find("\n## ", at + 1)
return text[at:] if end < 0 else text[at:end]
def first_cells(s):
return [line.split("`")[1] for line in s.splitlines() if line.startswith("| `")]
with open(EXTENSION, encoding="utf-8") as _f:
_TEXT = _f.read()
REASONS = first_cells(section(_TEXT, "\n## 9. Reasons"))
_S4 = section(_TEXT, "\n## 4. The derivations")
DERIVATIONS = first_cells(_S4[:_S4.index("\n### ")])
# ---------------------------------------------------------------- refusals and checks
class Refuse(Exception):
def __init__(self, reason, at):
super().__init__(reason)
self.reason, self.at = reason, at
# Every check this verifier makes, by name. The self-test drops each in turn.
CHECKS = (
"shape", "registered", "params", "writer", "input-count", "input-form", "check", "energy", "key", "signature",
"output-held", "output-receipts", "output-signer", "output-energy", "output-hash", "output-manifest",
"output-samples", "input-held", "input-receipts", "source", "depth", "hop-held", "hop-match", "input-hash",
"input-reads", "input-samples", "provenance-held", "provenance-hop", "synthesis-hop", "synthesis-record",
"evaluable", "replicate", "attested-samples", "inputs-held", "head-held",
"authorised", "not-revoked", "authority-required", "attested",
)
DROPPED = set()
def need(check, ok, reason, at):
assert check in CHECKS, check
if not ok and check not in DROPPED:
raise Refuse(reason, at)
# ---------------------------------------------------------------- JSON and its canonical form
class Outside:
"""A number that is not an unsigned integer written plainly: outside the record's subset."""
OUTSIDE = Outside()
def strict(text):
"""jwp-receipts §2.4.3's reading: RFC 8259, no member named twice, no unpaired surrogate,
nesting to 32 levels; a number outside the subset kept as OUTSIDE. ValueError otherwise."""
def pairs(ps):
d = {}
for k, v in ps:
if k in d:
raise ValueError("a member named twice")
d[k] = v
return d
def integer(s):
return OUTSIDE if s.startswith("-") or len(s) > 20 else int(s)
def constant(c):
raise ValueError(c)
def tree(v, level):
if isinstance(v, str):
if any("\ud800" <= ch <= "\udfff" for ch in v):
raise ValueError("an unpaired surrogate")
elif isinstance(v, (list, dict)):
if level + 1 > 32:
raise ValueError("nested too deeply")
for k in (v if isinstance(v, dict) else []):
tree(k, level)
for x in (v.values() if isinstance(v, dict) else v):
tree(x, level + 1)
try:
v = json.loads(text, object_pairs_hook=pairs, parse_float=lambda s: OUTSIDE, parse_int=integer,
parse_constant=constant)
except RecursionError:
raise ValueError("nested too deeply")
tree(v, 0)
return v
def jcs(v):
"""RFC 8785 on strings, unsigned integers to 2^53 - 1, booleans, null, arrays, objects."""
if v is None:
return "null"
if v is True or v is False:
return "true" if v else "false"
if isinstance(v, int):
if not 0 <= v <= MAX_INT:
raise ValueError("a number outside the subset")
return str(v)
if isinstance(v, str):
return json.dumps(v, ensure_ascii=False)
if isinstance(v, list):
return "[" + ",".join(jcs(x) for x in v) + "]"
if isinstance(v, dict):
keys = sorted(v, key=lambda k: k.encode("utf-16-be"))
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in keys) + "}"
raise ValueError("a value outside the subset")
def is_int(v):
return isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= MAX_INT
def is_hex(v, n):
return isinstance(v, str) and re.fullmatch("[0-9a-f]{%d}" % (2 * n), v) is not None
def is_term(s):
return (isinstance(s, str) and s != "" and len(s.encode()) <= 2048
and not any(c.isspace() or unicodedata.category(c) == "Cc" for c in s))
def days_in(y, m):
if m == 2:
return 29 if y % 4 == 0 and (y % 100 != 0 or y % 400 == 0) else 28
return 30 if m in (4, 6, 9, 11) else 31
def is_date_time(s):
"""RFC 3339 §5.6 with §5.7's days, ASCII digits, a second of 60 allowed."""
m = isinstance(s, str) and re.fullmatch(r"([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt]([0-9]{2}):([0-9]{2}):([0-9]{2})"
r"(\.[0-9]+)?([Zz]|[+-]([0-9]{2}):([0-9]{2}))", s)
if not m:
return False
y, mo, d, hh, mi, ss = (int(m.group(i)) for i in range(1, 7))
if not (1 <= mo <= 12 and 1 <= d <= days_in(y, mo) and hh <= 23 and mi <= 59 and ss <= 60):
return False
return m.group(9) is None or (int(m.group(9)) <= 23 and int(m.group(10)) <= 59)
def in_subset(v):
if v is None or isinstance(v, str):
return True
if isinstance(v, bool) or isinstance(v, Outside):
return False
if isinstance(v, int):
return is_int(v)
if isinstance(v, list):
return all(in_subset(x) for x in v)
if isinstance(v, dict):
return all(in_subset(x) for x in v.values())
return False
_SIGS = {}
def ed_verify(pk, msg, sig):
"""WAI's strict Ed25519 rule (_ed25519.verify), remembered: the self-test re-verifies the
corpus many times over, and a verdict is a function of its three inputs."""
k = (pk, msg, sig)
if k not in _SIGS:
_SIGS[k] = ED.verify(pk, msg, sig)
return _SIGS[k]
def content_hash(env):
return blake3(b"moq-jwp:object\x01" + env)
def sha256(b):
return hashlib.sha256(b).digest()
# ---------------------------------------------------------------- energy classes (energy-measurement §2.7)
TAG = {"HwShunt": 1, "ModelBased": 2, "Estimator": 3, "OnChipCounter": 4, "CalibratedInstrument": 5}
FILTERING = {"Unknown": 0, "Off": 1, "On": 2}
def read_class(m, filtering_written):
"""The class a set of `energy_*` members names, or None for none; ValueError where they do
not make one. With `filtering_written`, an on-chip counter's filtering state must be written
(a record writes it, as a claim does); a receipt's label may leave it out, as Unknown."""
label = m.get("energy_provenance")
u, f, r = m.get("energy_uncertainty"), m.get("energy_counter_filtering"), m.get("energy_calibration_ref")
if label is None:
if u is not None or f is not None or r is not None:
raise ValueError("evidence without a class")
return None
if label not in TAG:
raise ValueError("an unknown class")
if label in ("HwShunt", "ModelBased", "Estimator"):
if u is not None or f is not None or r is not None:
raise ValueError("evidence beside a class that carries none")
return {"label": label}
if not isinstance(u, dict) or set(u) != {"relative_ppm", "window_us"} or not is_int(u["relative_ppm"]) \
or not is_int(u["window_us"]) or not 1 <= u["relative_ppm"] <= 2 ** 32 - 1 or u["window_us"] == 0:
raise ValueError("no declared uncertainty")
if label == "OnChipCounter":
if r is not None or (f is None and filtering_written):
raise ValueError("an on-chip counter's evidence")
f = "Unknown" if f is None else f
if f not in FILTERING:
raise ValueError("a filtering state")
return {"label": label, "ppm": u["relative_ppm"], "window": u["window_us"], "filtering": f}
if f is not None or not isinstance(r, str) or r.strip() == "":
raise ValueError("a calibrated instrument's evidence")
return {"label": label, "ppm": u["relative_ppm"], "window": u["window_us"], "ref": r}
def encode_class(c):
if c is None:
return b"\x00"
b = bytes([TAG[c["label"]]])
if c["label"] == "OnChipCounter":
b += struct.pack(">IQB", c["ppm"], c["window"], FILTERING[c["filtering"]])
elif c["label"] == "CalibratedInstrument":
ref = c["ref"].encode()
b += struct.pack(">IQI", c["ppm"], c["window"], len(ref)) + ref
return b
def label_ok(figure, c):
"""A class beside a figure (energy-measurement §2.8): never HwShunt, never beside a zero
figure, at the resolution floor where it declares an uncertainty."""
return c["label"] != "HwShunt" and figure != 0 and ("ppm" not in c or figure * c["ppm"] >= RESOLUTION_FLOOR)
# ---------------------------------------------------------------- JWP receipts (jwp-receipts §2.2-§2.4.2)
GROUP = {"kind", "track_alias", "group_id", "root_hash", "leaf_count", "joules_total", "group_start", "group_end",
"signer_pubkey", "signer_id", "sig", "parent_receipt_hash"}
GROUP_LABEL = {"sign_version", "energy_provenance", "energy_uncertainty", "energy_counter_filtering",
"energy_calibration_ref", "energy_coverage"}
OBJECT = {"kind", "track_alias", "group_id", "object_id", "content_hash", "joules_micro", "origin", "merkle_proof"}
OBJECT_LABEL = {"leaf_version", "energy_provenance", "energy_uncertainty", "energy_counter_filtering",
"energy_calibration_ref"}
def u64(v):
return isinstance(v, int) and not isinstance(v, bool) and 0 <= v < 2 ** 64
def read_group(g):
"""§2.4.1's group receipt, or None where it does not read (the verifier does not hold it)."""
if not isinstance(g, dict) or g.get("kind") != "group" or not GROUP <= set(g) or not set(g) <= GROUP | GROUP_LABEL:
return None
if not (all(u64(g[k]) for k in ("track_alias", "group_id", "leaf_count", "joules_total"))
and all(isinstance(g[k], str) for k in ("group_start", "group_end", "signer_id"))
and is_hex(g["root_hash"], 32) and is_hex(g["signer_pubkey"], 32) and is_hex(g["sig"], 64)
and (g["parent_receipt_hash"] is None or is_hex(g["parent_receipt_hash"], 32))):
return None
out = dict(g, labelled=False)
if g.get("sign_version") is None:
return None if any(g.get(k) is not None for k in GROUP_LABEL) else out
if g["sign_version"] != 2 or isinstance(g["sign_version"], bool):
return None
try:
c = read_class(g, False)
except ValueError:
return None
cov = g.get("energy_coverage")
if c is None and cov is not None or c is not None and cov not in ("Whole", "Partial"):
return None
out.update(labelled=True, cls=c, coverage=cov or "Whole")
return out
def group_payload(g):
b = (b"moq-jwp:group-sign\x02" if g["labelled"] else b"moq-jwp:group-sign\x01")
b += struct.pack(">QQ", g["track_alias"], g["group_id"]) + bytes.fromhex(g["root_hash"])
b += struct.pack(">QQ", g["leaf_count"], g["joules_total"])
for s in (g["group_start"], g["group_end"]):
b += struct.pack(">I", len(s.encode())) + s.encode()
b += bytes.fromhex(g["signer_pubkey"]) + struct.pack(">I", len(g["signer_id"].encode())) + g["signer_id"].encode()
if g["labelled"]:
b += encode_class(g["cls"]) + (b"\x01" if g["cls"] is not None and g["coverage"] == "Partial" else b"\x00")
p = g["parent_receipt_hash"]
b += b"\x01" + bytes.fromhex(p) if p is not None else b"\x00" + bytes(32)
return b
def link(g):
return blake3(b"moq-jwp:receipt-link\x01" + group_payload(g) + bytes.fromhex(g["sig"]))
def group_verifies(g):
if ed_verify(bytes.fromhex(g["signer_pubkey"]), group_payload(g), bytes.fromhex(g["sig"])) is not None:
return False
if g["labelled"]:
c, t = g["cls"], g["joules_total"]
return (t == 0 and g["coverage"] == "Whole") if c is None else label_ok(t, c)
return True
def read_object(o):
if not isinstance(o, dict) or o.get("kind") != "object" or not OBJECT <= set(o) or not set(o) <= OBJECT | OBJECT_LABEL:
return None
p = o["merkle_proof"]
if not (all(u64(o[k]) for k in ("track_alias", "group_id", "object_id", "joules_micro"))
and is_hex(o["content_hash"], 32) and isinstance(o["origin"], str) and isinstance(p, dict)
and set(p) == {"siblings", "direction_bits", "leaf_index"} and u64(p["direction_bits"])
and u64(p["leaf_index"]) and isinstance(p["siblings"], list) and all(is_hex(s, 32) for s in p["siblings"])):
return None
r = dict(o, labelled=False, cls=None, unreadable=False)
lv = o.get("leaf_version")
if lv is None:
r["unreadable"] = any(o.get(k) is not None for k in OBJECT_LABEL)
elif lv == 2 and not isinstance(lv, bool):
try:
r.update(labelled=True, cls=read_class(o, False))
except ValueError:
r["unreadable"] = True
else:
r["unreadable"] = True
return r
def leaf(o):
h = bytes.fromhex(o["content_hash"])
if not o["labelled"]:
return blake3(b"moq-jwp:merkle-leaf\x01" + h)
origin = o["origin"].encode()
return blake3(b"moq-jwp:merkle-leaf\x02" + h + struct.pack(">Q", o["joules_micro"]) + encode_class(o["cls"])
+ struct.pack(">I", len(origin)) + origin)
def included(o, g):
"""§3 step 5: the label, the position, the walk to the group's root."""
if o["unreadable"]:
return False
if o["labelled"] and ((o["cls"] is None and o["joules_micro"] != 0) or (o["cls"] is not None and not label_ok(o["joules_micro"], o["cls"]))):
return False
n, p = g["leaf_count"], o["merkle_proof"]
depth = 0 if n <= 1 else (n - 1).bit_length()
if (o["track_alias"] != g["track_alias"] or o["group_id"] != g["group_id"] or o["object_id"] != p["leaf_index"]
or not p["leaf_index"] < n or len(p["siblings"]) != depth or p["direction_bits"] != p["leaf_index"]):
return False
cur = leaf(o)
for i, s in enumerate(p["siblings"]):
s = bytes.fromhex(s)
cur = blake3(b"moq-jwp:merkle-node\x01" + (s + cur if (p["direction_bits"] >> i) & 1 else cur + s))
return cur == bytes.fromhex(g["root_hash"])
# ---------------------------------------------------------------- PNG (packaging §5.7)
SIGNATURE = b"\x89PNG\r\n\x1a\n"
LEN_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258]
LEN_EXTRA = [0] * 8 + [1] * 4 + [2] * 4 + [3] * 4 + [4] * 4 + [5] * 4 + [0]
DIST_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073,
4097, 6145, 8193, 12289, 16385, 24577]
DIST_EXTRA = [0, 0, 0, 0] + [i // 2 for i in range(2, 28)]
class Bits:
"""Deflate's packing: values least significant bit first, Huffman codes most significant first."""
def __init__(self):
self.out, self.acc, self.n = bytearray(), 0, 0
def put(self, v, n):
self.acc |= v << self.n
self.n += n
while self.n >= 8:
self.out.append(self.acc & 255)
self.acc >>= 8
self.n -= 8
def code(self, c, n):
self.put(int(format(c, "0%db" % n)[::-1], 2) if n else 0, n)
def lit(self, s):
if s <= 143:
self.code(0x30 + s, 8)
elif s <= 255:
self.code(0x190 + s - 144, 9)
elif s <= 279:
self.code(s - 256, 7)
else:
self.code(0xC0 + s - 280, 8)
def deflate_fixed(d):
"""One final block of the fixed codes; at each position with three bytes left, the latest
earlier position within 32768 bytes that starts with the same three bytes is the match, as
far as the runs agree (258 at most); otherwise a literal. Every position a symbol covers with
three bytes left becomes its three bytes' latest position."""
b = Bits()
b.put(1, 1)
b.put(1, 2)
latest, n, i = {}, len(d), 0
while i < n:
j = latest.get(d[i:i + 3]) if i + 3 <= n else None
if j is not None and i - j <= 32768:
m = 0
while m < min(258, n - i) and d[j + m] == d[i + m]:
m += 1
li = max(k for k, x in enumerate(LEN_BASE) if x <= m)
b.lit(257 + li)
b.put(m - LEN_BASE[li], LEN_EXTRA[li])
di = max(k for k, x in enumerate(DIST_BASE) if x <= i - j)
b.code(di, 5)
b.put(i - j - DIST_BASE[di], DIST_EXTRA[di])
else:
b.lit(d[i])
m = 1
for q in range(i, i + m):
if q + 3 <= n:
latest[d[q:q + 3]] = q
i += m
b.lit(256)
return bytes(b.out) + (bytes([b.acc]) if b.n else b"")
def chunk(kind, data):
return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data))
def png_still(s):
"""packaging §5.7's writer: IHDR of 8-bit RGB, one IDAT of the rows filtered Sub under the
deflate above, IEND."""
w, h, rgb = s
raw = bytearray()
for y in range(h):
row = rgb[3 * w * y:3 * w * (y + 1)]
raw.append(1)
raw += bytes((row[i] - (row[i - 3] if i >= 3 else 0)) & 255 for i in range(3 * w))
z = b"\x78\x01" + deflate_fixed(bytes(raw)) + struct.pack(">I", zlib.adler32(bytes(raw)))
return SIGNATURE + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", z) + chunk(b"IEND", b"")
def png_read(b):
"""packaging §5.7's reader of an 8-bit RGB PNG without interlace: (w, h, rgb, the chunk
types in order); ValueError where it does not read."""
if b[:8] != SIGNATURE:
raise ValueError("not a PNG")
at, ihdr, z, ended, plte, idat, kinds = 8, None, b"", False, False, 0, []
while at < len(b):
if ended or at + 12 > len(b):
raise ValueError("bytes after IEND, or a chunk past the end")
n = struct.unpack_from(">I", b, at)[0]
kind, data = b[at + 4:at + 8], b[at + 8:at + 8 + n]
if at + 12 + n > len(b) or not all(65 <= c <= 90 or 97 <= c <= 122 for c in kind):
raise ValueError("a chunk")
if zlib.crc32(kind + data) != struct.unpack_from(">I", b, at + 8 + n)[0]:
raise ValueError("a CRC")
kinds.append(kind)
if idat == 1 and kind != b"IDAT":
idat = 2
if kind == b"IHDR":
if ihdr is not None or at != 8 or n != 13:
raise ValueError("IHDR")
w, h = struct.unpack(">II", data[:8])
if data[8:] != bytes([8, 2, 0, 0, 0]) or w == 0 or h == 0 or 3 * w * h > 1 << 26:
raise ValueError("not 8-bit RGB")
ihdr = (w, h)
elif ihdr is None:
raise ValueError("a chunk before IHDR")
elif kind == b"PLTE":
if plte or idat or n == 0 or n % 3 or n > 768:
raise ValueError("PLTE")
plte = True
elif kind == b"IDAT":
if idat == 2:
raise ValueError("IDATs not consecutive")
idat, z = 1, z + data
elif kind == b"IEND":
if n:
raise ValueError("IEND with data")
ended = True
elif not kind[0] & 0x20:
raise ValueError("an unknown critical chunk")
at += 12 + n
if ihdr is None or not ended or not z or z[0] >> 4 > 7:
raise ValueError("no IHDR, no IEND, or no stream")
w, h = ihdr
d = zlib.decompressobj()
try:
raw = d.decompress(z, (3 * w + 1) * h + 1)
except zlib.error as e:
raise ValueError(str(e))
if not d.eof or d.unused_data or d.unconsumed_tail or len(raw) != (3 * w + 1) * h:
raise ValueError("the rows")
stride, out = 3 * w, bytearray(3 * w * h)
for y in range(h):
f, row = raw[y * (stride + 1)], raw[y * (stride + 1) + 1:(y + 1) * (stride + 1)]
if f > 4:
raise ValueError("a filter")
for i in range(stride):
a = out[y * stride + i - 3] if i >= 3 else 0
up = out[(y - 1) * stride + i] if y else 0
c = out[(y - 1) * stride + i - 3] if y and i >= 3 else 0
if f == 4:
p = a + up - c
pr = a if abs(p - a) <= abs(p - up) and abs(p - a) <= abs(p - c) else (up if abs(p - up) <= abs(p - c) else c)
else:
pr = (0, a, up, (a + up) // 2)[f]
out[y * stride + i] = (row[i] + pr) & 255
return w, h, bytes(out), kinds
# ---------------------------------------------------------------- envelopes and inputs (§4.1, §4.4)
def split_wai1(env):
"""A WAI1 envelope's manifest and payload, exactly one envelope; None otherwise."""
if len(env) < 12 or env[:4] != b"WAI1":
return None
ml = struct.unpack_from("<I", env, 4)[0]
if 8 + ml + 4 > len(env):
return None
pl = struct.unpack_from("<I", env, 8 + ml)[0]
if 12 + ml + pl != len(env):
return None
return env[8:8 + ml], env[12 + ml:]
def capability_of(env):
parts = split_wai1(env)
if parts is None:
return None
try:
return json.loads(parts[0])["model_requirement"]["capability"]
except (ValueError, KeyError, TypeError):
return None
def read_samples(cap, env, frame):
"""§4.1: (w, h, rgb), or None where the input does not read as stated."""
parts = split_wai1(env)
if parts is None or capability_of(env) != cap:
return None
payload = parts[1]
if cap == PNG:
try:
w, h, rgb, kinds = png_read(payload)
except ValueError:
return None
if frame != 0 or any(k not in (b"IHDR", b"IDAT", b"IEND", b"caBX") for k in kinds):
return None
return w, h, rgb
if cap == INT_MOTION:
try:
v = IPV.parse_wiv1(payload, want_keyframe=0)
frames = IPV.decode_wiv1_k0(v)
except IPV.Refused:
return None
return (v["w"], v["h"], frames[frame]) if frame < len(frames) else None
return None
def png_chunks(p):
"""A PNG's chunks as (type, data), walked by their lengths alone; None where the signature
is not PNG's or a chunk runs past the end."""
if p[:8] != SIGNATURE:
return None
at, out = 8, []
while at < len(p):
if at + 8 > len(p):
return None
n = struct.unpack_from(">I", p, at)[0]
if at + 12 + n > len(p):
return None
out.append((p[at + 4:at + 8], p[at + 8:at + 8 + n]))
at += 12 + n
return out
def embedded_stores(cap, env):
"""§6.1: the C2PA Manifest Stores embedded in an input: for wai.image.png, the data of each
caBX chunk (C2PA 2.4 §A.3.2)."""
parts = split_wai1(env)
if parts is None or cap != PNG:
return []
chunks = png_chunks(parts[1])
return [] if chunks is None else [d for k, d in chunks if k == b"caBX"]
def claim_about(text):
"""§6.1: (subject, link hash) of a claim about an object, or None."""
try:
v = strict(text)
if not isinstance(v, dict) or v.get("kind") != "claim" or v.get("subject_type") != "object" \
or not is_hex(v.get("subject"), 32):
return None
return v["subject"], blake3(b"moq-jwp:claim-link\x01" + jcs(v).encode()).hex()
except (ValueError, TypeError):
return None
def explode(env, component, cap):
"""§4.4: the exploded envelope of entry `component`, or None."""
try:
_, entries, _ = SV.read_wai2(env)
out = SV.explode_each(env)
except Exception: # anything that is not a WAI2 the staged-delivery reader reads
return None
if component >= len(entries) or entries[component].get("capability") != cap:
return None
return out[component]
# ---------------------------------------------------------------- the operations (§4.2)
def px(s, x, y):
w, _, rgb = s
i = 3 * (y * w + x)
return rgb[i:i + 3]
def size_ok(w, h):
return w >= 1 and h >= 1 and w * h * 3 <= MAX_SAMPLE_BYTES
def evaluate(d, p, ins):
"""The output samples (w, h, rgb), or None where the inputs cannot satisfy the parameters.
Scaling is not pinned and is never evaluated here."""
def build(w, h, f):
return w, h, b"".join(f(i, j) for j in range(h) for i in range(w))
if d == "wai.derive.identity":
return ins[0]
if d in ("wai.derive.crop", "wai.derive.roi_selection"):
s = ins[0]
if p["x"] + p["w"] > s[0] or p["y"] + p["h"] > s[1]:
return None
return build(p["w"], p["h"], lambda i, j: px(s, p["x"] + i, p["y"] + j))
if d == "wai.derive.rotation":
s, q = ins[0], p["quarter_turns"]
W, H = s[0], s[1]
if q == 1:
return build(H, W, lambda i, j: px(s, j, H - 1 - i))
if q == 2:
return build(W, H, lambda i, j: px(s, W - 1 - i, H - 1 - j))
return build(H, W, lambda i, j: px(s, W - 1 - j, i))
if d == "wai.derive.mirror":
s = ins[0]
if p["flip"] == "left-right":
return build(s[0], s[1], lambda i, j: px(s, s[0] - 1 - i, j))
return build(s[0], s[1], lambda i, j: px(s, i, s[1] - 1 - j))
if d == "wai.derive.grid_composition":
cw, ch = ins[0][0], ins[0][1]
if any((s[0], s[1]) != (cw, ch) for s in ins) or len(ins) != p["columns"] * p["rows"]:
return None
if not size_ok(p["columns"] * cw, p["rows"] * ch):
return None
return build(p["columns"] * cw, p["rows"] * ch, lambda i, j: px(ins[(j // ch) * p["columns"] + i // cw], i % cw, j % ch))
if d == "wai.derive.overlay_composition":
W, H = p["width"], p["height"]
canvas = bytearray(bytes.fromhex(p["fill"]) * (W * H))
for s, at in zip(ins, p["at"]):
for j in range(s[1]):
for i in range(s[0]):
x, y = at["x"] + i, at["y"] + j
if x < W and y < H:
canvas[3 * (y * W + x):3 * (y * W + x) + 3] = px(s, i, j)
return W, H, bytes(canvas)
raise AssertionError("not an exact operation: " + d)
def exact(d):
"""§4.2's closing paragraph: every operation but scaling is exact; the explode evaluates none."""
return d != "wai.derive.scaling"
def check_of(d, out_cap, encoder):
"""§4.5."""
if d == EXPLODE:
return "replicate"
return "replicate" if exact(d) and out_cap == PNG and encoder is None else "attest"
# ---------------------------------------------------------------- the record (§5)
def record_hash(raw):
return blake3(b"wai:derivation-link\x01" + jcs(raw).encode())
def held_hash(text):
try:
return record_hash(strict(text))
except (ValueError, TypeError):
return None
def params_ok(d, p, n):
"""§5.3 rule 3's parameter rules."""
def ints(*ks):
return set(p) == set(ks) and all(is_int(p[k]) for k in ks)
if d == "wai.derive.identity":
return p == {}
if d in ("wai.derive.crop", "wai.derive.roi_selection"):
return ints("x", "y", "w", "h") and size_ok(p["w"], p["h"])
if d == "wai.derive.rotation":
return ints("quarter_turns") and p["quarter_turns"] in (1, 2, 3)
if d == "wai.derive.mirror":
return set(p) == {"flip"} and p["flip"] in ("left-right", "top-bottom")
if d == "wai.derive.grid_composition":
return ints("columns", "rows") and p["columns"] >= 1 and p["rows"] >= 1
if d == "wai.derive.overlay_composition":
return (set(p) == {"width", "height", "fill", "at"} and is_int(p["width"]) and is_int(p["height"])
and size_ok(p["width"], p["height"]) and is_hex(p["fill"], 3) and isinstance(p["at"], list)
and len(p["at"]) == n and all(isinstance(a, dict) and set(a) == {"x", "y"} and is_int(a["x"]) and is_int(a["y"]) for a in p["at"]))
if d == "wai.derive.scaling":
return ints("width", "height") and size_ok(p["width"], p["height"])
if d == EXPLODE:
return ints("component")
return False
def read_record(text, r):
"""§5.3, rule by rule: the record as a dict, or Refuse at ["record", r]."""
at = ["record", r]
try:
v = strict(text)
except (ValueError, TypeError):
raise Refuse("record_malformed", at)
def shaped():
if not isinstance(v, dict) or set(v) != RECORD_MEMBERS or v["kind"] != "wai-derivation":
return False
if not is_term(v["derivation"]) or not isinstance(v["params"], dict) or not in_subset(v["params"]):
return False
e = v["encoder"]
if e is not None and not (isinstance(e, dict) and set(e) == {"id", "settings"} and is_term(e["id"])
and isinstance(e["settings"], dict) and in_subset(e["settings"])):
return False
if v["check"] not in ("replicate", "attest"):
return False
ins = v["inputs"]
if not isinstance(ins, list) or not 1 <= len(ins) <= MAX_INPUTS:
return False
for i in ins:
if not (isinstance(i, dict) and set(i) == INPUT_MEMBERS and is_hex(i["content_hash"], 32)
and is_term(i["capability"]) and is_hex(i["receipt"], 32) and is_int(i["object_id"])
and (i["frame"] is None or is_int(i["frame"])) and (i["samples"] is None or is_hex(i["samples"], 32))
and (i["derivation"] is None or is_hex(i["derivation"], 32))):
return False
o = v["output"]
if not (isinstance(o, dict) and set(o) == OUTPUT_MEMBERS and is_hex(o["content_hash"], 32)
and is_term(o["capability"]) and is_hex(o["receipt"], 32) and is_int(o["object_id"])
and all(o[k] is None or is_int(o[k]) for k in ("width", "height"))
and (o["samples"] is None or is_hex(o["samples"], 32))):
return False
en = v["energy"]
if en is not None:
u = en.get("energy_uncertainty") if isinstance(en, dict) else None
if not (isinstance(en, dict) and set(en) == ENERGY_MEMBERS and is_int(en["joules_micro"])
and isinstance(en["energy_provenance"], str)
and (u is None or (isinstance(u, dict) and set(u) == {"relative_ppm", "window_us"}
and is_int(u["relative_ppm"]) and is_int(u["window_us"])))
and all(en[k] is None or isinstance(en[k], str) for k in ("energy_counter_filtering", "energy_calibration_ref"))):
return False
for i in ins:
if i["synthesis"] not in LABELS:
return False
if v["synthesis"] not in LABELS:
return False
items = v["provenance"]
if not isinstance(items, list) or len(items) > MAX_PROVENANCE:
return False
keys = []
for it in items:
if not (isinstance(it, dict) and set(it) == ITEM_MEMBERS and is_int(it["input"]) and it["input"] < len(ins)
and it["kind"] in ("claim", "c2pa") and is_hex(it["digest"], 32)
and it["compartment"] in ("system", "personal")):
return False
keys.append((it["input"], it["kind"], it["digest"]))
if any(a >= b for a, b in zip(keys, keys[1:])):
return False
return (is_date_time(v["issued_at"]) and is_hex(v["signer_pubkey"], 32) and isinstance(v["signer_id"], str)
and is_hex(v["sig"], 64))
need("shape", shaped(), "record_malformed", at)
d = v["derivation"]
need("registered", d in DERIVATIONS, "derivation_unknown", at)
ins, out, enc = v["inputs"], v["output"], v["encoder"]
evaluates = d != EXPLODE
need("params", params_ok(d, v["params"], len(ins)), "params_invalid", at)
if evaluates:
need("writer", out["capability"] in (PNG, JPEG) and not (out["capability"] == JPEG and enc is None), "params_invalid", at)
else:
need("writer", enc is None and out["capability"] == ins[0]["capability"], "params_invalid", at)
p = v["params"]
if d == "wai.derive.grid_composition":
fits = len(ins) == p["columns"] * p["rows"]
elif d == "wai.derive.overlay_composition":
fits = 1 <= len(ins) <= MAX_INPUTS
else:
fits = len(ins) == 1
need("input-count", fits, "inputs_invalid", at)
if evaluates:
form = all(i["frame"] is not None and i["samples"] is not None and i["capability"] in (PNG, INT_MOTION)
and (i["capability"] != PNG or i["frame"] == 0) for i in ins)
form = form and all(out[k] is not None for k in ("width", "height", "samples"))
else:
form = all(i["frame"] is None and i["samples"] is None for i in ins)
form = form and all(out[k] is None for k in ("width", "height", "samples"))
need("input-form", form, "inputs_invalid", at)
need("check", v["check"] == check_of(d, out["capability"], enc), "check_mismatch", at)
cls = None
if v["energy"] is not None:
try:
cls = read_class(v["energy"], True)
ok = cls is not None and label_ok(v["energy"]["joules_micro"], cls)
except ValueError:
ok = False
need("energy", ok, "energy_invalid", at)
need("synthesis-record", all(LABELS[i["synthesis"]] <= LABELS[v["synthesis"]] for i in ins), "synthesis_dropped", at)
return dict(v, raw=v, cls=cls)
def signing_input(raw):
return b"wai:derivation\x01" + jcs({k: x for k, x in raw.items() if k != "sig"}).encode()
def settings_digest(rec):
settings = {"derivation": rec["derivation"], "encoder": rec["encoder"], "frame": rec["inputs"][0]["frame"],
"params": rec["params"]}
return blake3(b"wai:render-settings\x01" + jcs(settings).encode())
def output_manifest(rec, w, h):
"""§4.3, exactly."""
cap = rec["output"]["capability"]
s = ('{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{"capability":"%s"},'
'"conditioning":{"kind":"%s"}' % (cap, cap.rsplit(".", 1)[-1]))
if len(rec["inputs"]) == 1:
i = rec["inputs"][0]
renderer = rec["derivation"] + "@1" if rec["encoder"] is None else rec["encoder"]["id"]
s += (',"derived_from":{"motif":"%s","motif_capability":"%s","renderer":"%s","settings":"%s"}'
% (i["content_hash"], i["capability"], renderer, settings_digest(rec).hex()))
return s + ',"target":{"frames":1,"h":%d,"w":%d}}' % (h, w)
def wai1(manifest, payload):
return b"WAI1" + struct.pack("<I", len(manifest)) + manifest + struct.pack("<I", len(payload)) + payload
# ---------------------------------------------------------------- verification (§7, §8)
def origin_of(signer_id):
"""jwp-receipts §7.1: the origin an origin-bound signer_id names, or None."""
m = re.fullmatch(r"did:web:([^#]+)#([A-Za-z0-9._-]{1,64})", signer_id)
if not m:
return None
host, _, port = m.group(1).partition("%3A")
labels = host.split(".")
if (len(host) > 253 or len(labels) < 2 or labels[-1].isdigit()
or not all(re.fullmatch(r"[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?", lab) for lab in labels)):
return None
if "%3A" in m.group(1) and not (re.fullmatch(r"[1-9][0-9]{0,4}", port) and int(port) <= 65535 and port != "443"):
return None
return "https://" + host + (":" + port if port else "")
def head_entry(text, origin, key):
"""§8 over a head: "not-held", "not-listed", "revoked" or "ok"."""
try:
v = json.loads(text, object_pairs_hook=LRV.no_duplicates)
except ValueError:
return "not-held"
if not LRV.revision_ok(v) or v["origin"] != origin:
return "not-held"
unsigned = {k: x for k, x in v.items() if k != "sig"}
if ed_verify(bytes.fromhex(v["root"]), b"wai:key-set\x01" + LRV.jcs(unsigned).encode(), bytes.fromhex(v["sig"])):
return "not-held"
e = next((e for e in v["keys"] if e["key"] == key and AUTHORITY in e["may_sign"]), None)
if e is None:
return "not-listed"
return "revoked" if e["status"] == "revoked" else "ok"
class Verifier:
def __init__(self, vec):
self.vec = vec
self.records = vec["records"]
self.by_hash = {}
for i, t in enumerate(self.records):
h = held_hash(t)
if h is not None:
self.by_hash.setdefault(h, i)
self.groups = [(link(g), g) for g in (read_group(r) for r in vec["receipts"]) if g is not None]
self.objects = [o for o in (read_object(r) for r in vec["receipts"]) if o is not None]
self.held = {bytes.fromhex(h): bytes.fromhex(b) for h, b in vec["objects"].items()}
self.sources = set(vec["sources"])
self.heads = vec["heads"]
self.claims = vec["claims"]
self.c2pa = {h: [bytes.fromhex(b) for b in st] for h, st in vec["c2pa"].items()}
pol = vec["policy"]
self.refuse_attested = pol.get("attested") == "refuse"
self.require_recompute = pol.get("recompute") == "required"
self.require_authority = pol.get("authority") == "required"
self.done, self.hops, self.sources_seen = {}, [], []
def receipts(self, receipt, object_id, content):
"""("not-held" | "invalid" | "ok", group)."""
g = next((g for lk, g in self.groups if lk.hex() == receipt), None)
if g is None:
return "not-held", None
cands = [o for o in self.objects if o["track_alias"] == g["track_alias"] and o["group_id"] == g["group_id"]
and o["object_id"] == object_id]
if not cands:
return "not-held", None
if not group_verifies(g):
return "invalid", g
if not any(o["content_hash"] == content and included(o, g) for o in cands):
return "invalid", g
return "ok", g
def hop(self, r, depth):
if r in self.done:
return self.done[r]
if r >= len(self.records):
raise Refuse("hop_not_held", ["record", r])
rec = read_record(self.records[r], r)
why = ed_verify(bytes.fromhex(rec["signer_pubkey"]), signing_input(rec["raw"]), bytes.fromhex(rec["sig"]))
need("key", why != "bad_key", "bad_key", ["record", r])
need("signature", why is None, "bad_signature", ["record", r])
me = len(self.hops)
en = rec["energy"]
self.hops.append({"record": record_hash(rec["raw"]).hex(), "signer": rec["signer_pubkey"],
"derivation": rec["derivation"], "check": rec["check"], "checked": "attested",
"energy": None if en is None else {"joules_micro": en["joules_micro"], "class": en["energy_provenance"]},
"authority": "none", "synthesis": rec["synthesis"]})
self.output(r, rec)
origins, reads = set(), []
for i, inp in enumerate(rec["inputs"]):
below = None
at = ["record", r, "input", i]
state, g = self.receipts(inp["receipt"], inp["object_id"], inp["content_hash"])
need("input-held", state != "not-held", "input_receipt_not_held", at)
need("input-receipts", state == "ok", "input_receipt_invalid", at)
if inp["derivation"] is None:
need("source", g["signer_pubkey"] in self.sources, "chain_skips_hop", at)
o = origin_of(g["signer_id"])
if o is not None:
origins.add(o)
if all(s["object"] != inp["content_hash"] for s in self.sources_seen):
self.sources_seen.append({"object": inp["content_hash"], "signer": g["signer_pubkey"],
"signer_id": g["signer_id"]})
else:
need("depth", depth + 1 <= MAX_HOPS, "chain_too_long", at)
j = self.by_hash.get(bytes.fromhex(inp["derivation"]))
need("hop-held", j is not None, "hop_not_held", at)
below = read_record(self.records[j], j)
bo = below["output"]
need("hop-match", all(bo[k] == inp[k] for k in ("content_hash", "capability", "receipt", "object_id")),
"hop_mismatch", at)
origins |= self.hop(j, depth + 1)
env = self.held.get(bytes.fromhex(inp["content_hash"]))
reads.append(None if env is None else self.read_input(rec, i, env, at))
# Step 4.5: the provenance it had, and its label.
listed = {(it["kind"], it["digest"]) for it in rec["provenance"] if it["input"] == i}
had = [("claim", c[1]) for c in (claim_about(t) for t in self.claims) if c is not None and c[0] == inp["content_hash"]]
had += [("c2pa", sha256(b).hex()) for b in self.c2pa.get(inp["content_hash"], [])]
if env is not None:
had += [("c2pa", sha256(b).hex()) for b in embedded_stores(inp["capability"], env)]
need("provenance-held", all(x in listed for x in had), "provenance_stripped", at)
if below is not None:
mine = {(it["kind"], it["digest"], it["compartment"]) for it in rec["provenance"] if it["input"] == i}
need("provenance-hop", all((it["kind"], it["digest"], it["compartment"]) in mine for it in below["provenance"]),
"provenance_stripped", at)
need("synthesis-hop", LABELS[inp["synthesis"]] >= LABELS[below["synthesis"]], "synthesis_dropped", at)
checked = self.recompute(r, rec, reads)
authority = self.authority(r, rec, origins)
need("attested", not (rec["check"] == "attest" and self.refuse_attested), "attested_refused", ["record", r, "policy"])
self.hops[me]["checked"], self.hops[me]["authority"] = checked, authority
self.done[r] = origins
return origins
def output(self, r, rec):
at = ["record", r, "output"]
out = rec["output"]
state, g = self.receipts(out["receipt"], out["object_id"], out["content_hash"])
need("output-held", state != "not-held", "output_receipt_not_held", at)
need("output-receipts", state == "ok", "output_receipt_invalid", at)
need("output-signer", g["signer_pubkey"] == rec["signer_pubkey"], "output_receipt_mismatch", at)
o = next(o for o in self.objects if o["track_alias"] == g["track_alias"] and o["group_id"] == g["group_id"]
and o["object_id"] == out["object_id"] and o["content_hash"] == out["content_hash"] and included(o, g))
figure = 0 if rec["energy"] is None else rec["energy"]["joules_micro"]
if not o["labelled"]:
agrees = True
elif o["cls"] is None:
agrees = rec["energy"] is None
else:
agrees = rec["energy"] is not None and o["cls"] == rec["cls"]
need("output-energy", o["joules_micro"] == figure and agrees, "energy_disagrees", at)
env = self.held.get(bytes.fromhex(out["content_hash"]))
if env is None:
return
need("output-hash", content_hash(env).hex() == out["content_hash"], "output_hash_mismatch", at)
if rec["derivation"] != EXPLODE:
w, h = out["width"], out["height"]
parts = split_wai1(env)
need("output-manifest", parts is not None and parts[0] == output_manifest(rec, w, h).encode(),
"output_manifest_mismatch", at)
if out["capability"] == PNG:
try:
pw, ph, rgb, _ = png_read(parts[1])
ok = (pw, ph) == (w, h) and sha256(rgb).hex() == out["samples"]
except ValueError:
ok = False
need("output-samples", ok, "samples_mismatch", at)
else:
need("output-manifest", capability_of(env) == out["capability"], "output_manifest_mismatch", at)
def read_input(self, rec, i, env, at):
inp = rec["inputs"][i]
need("input-hash", content_hash(env).hex() == inp["content_hash"], "input_hash_mismatch", at)
if rec["derivation"] == EXPLODE:
out = explode(env, rec["params"]["component"], inp["capability"])
need("input-reads", out is not None, "input_unreadable", at)
return out
s = read_samples(inp["capability"], env, inp["frame"])
need("input-reads", s is not None, "input_unreadable", at)
need("input-samples", sha256(s[2]).hex() == inp["samples"], "samples_mismatch", at)
return s
def recompute(self, r, rec, reads):
at = ["record", r, "recompute"]
if any(x is None for x in reads):
if rec["check"] == "attest":
return "attested"
need("inputs-held", not self.require_recompute, "inputs_not_held", at)
return "not-recomputed"
out = rec["output"]
if rec["derivation"] == EXPLODE:
need("replicate", content_hash(reads[0]).hex() == out["content_hash"], "replicate_mismatch", at)
return "recomputed"
if not exact(rec["derivation"]):
return "attested"
s = evaluate(rec["derivation"], rec["params"], reads)
need("evaluable", s is not None, "params_invalid", at)
agree = (s[0], s[1]) == (out["width"], out["height"]) and sha256(s[2]).hex() == out["samples"]
if rec["check"] == "attest":
need("attested-samples", agree, "samples_mismatch", at)
return "attested"
env = wai1(output_manifest(rec, s[0], s[1]).encode(), png_still(s))
need("replicate", agree and content_hash(env).hex() == out["content_hash"], "replicate_mismatch", at)
return "recomputed"
def authority(self, r, rec, origins):
confirmed = True
for o in sorted(origins):
at = ["record", r, "authority", o]
if o not in self.heads:
need("authority-required", not self.require_authority, "edge_not_authorised", at)
confirmed = False
continue
state = head_entry(self.heads[o], o, rec["signer_pubkey"])
need("head-held", state != "not-held", "key_log_not_held", at)
need("authorised", state != "not-listed", "edge_not_authorised", at)
need("not-revoked", state != "revoked", "edge_revoked", at)
if not origins:
need("authority-required", not self.require_authority, "edge_not_authorised", ["record", r, "authority", None])
return "none"
return "confirmed" if confirmed else "unconfirmed"
def verify_vector(vec):
"""§7: ("accept", facts) or ("refuse", reason, at)."""
v = Verifier(vec)
try:
v.hop(vec["subject"], 1)
except Refuse as e:
return ("refuse", e.reason, e.at)
totals, unmetered = {}, 0
for h in v.hops:
if h["energy"] is None:
unmetered += 1
else:
totals[h["energy"]["class"]] = totals.get(h["energy"]["class"], 0) + h["energy"]["joules_micro"]
subject = strict(vec["records"][vec["subject"]])
facts = {"subject": v.hops[0]["record"], "hops": v.hops, "sources": v.sources_seen,
"energy": [{"class": c, "joules_micro": totals[c]} for c in CLASS_ORDER if c in totals],
"unmetered": unmetered, "provenance": subject["provenance"]}
return ("accept", facts)
def stated(vec):
if vec["outcome"] == "accept":
return ("accept", vec["facts"])
return ("refuse", vec["reason"], vec["at"])
def check(doc, quiet=False):
"""Every vector's verdict against the one it states: the failures."""
fails = []
for vec in doc["vectors"]:
try:
got = verify_vector(vec)
except Exception as e: # a crash on a mutated or weakened run is a failure to verify
got = ("crash", repr(e))
if got != stated(vec):
fails.append("%s: got %s" % (vec["name"], str(got)[:160]))
if not quiet:
print("FAIL vector %s: got %s, stated %s" % (vec["name"], str(got)[:200], str(stated(vec))[:200]))
elif not quiet:
print("PASS vector %s: %s" % (vec["name"], got[0] if got[0] == "accept" else "%s %s" % (got[1], got[2])))
return fails
# ---------------------------------------------------------------- mutations
def mutations(doc):
"""Copies of the corpus, each with one change a verifier must catch."""
by = {v["name"]: i for i, v in enumerate(doc["vectors"])}
def mutate(label, f):
d = copy.deepcopy(doc)
f(d["vectors"])
return label, d
def flip_hex(s, at):
return s[:at] + ("1" if s[at] != "1" else "2") + s[at + 1:]
def edit_record(name, idx, f):
def go(vs):
v = vs[by[name]]
raw = json.loads(v["records"][idx])
f(raw)
v["records"][idx] = jcs(raw)
return go
def flip_object(name, which):
def go(vs):
v = vs[by[name]]
k = sorted(v["objects"])[which]
v["objects"][k] = flip_hex(v["objects"][k], len(v["objects"][k]) - 3)
return go
def last(name):
return len(doc["vectors"][by[name]]["records"]) - 1
return [
mutate("a byte of a still's record signature", edit_record("still_png", last("still_png"), lambda r: r.update(sig=flip_hex(r["sig"], 5)))),
mutate("a still's frame stated as another", edit_record("still_png", last("still_png"), lambda r: r["inputs"][0].update(frame=2))),
mutate("a crop's region moved in its record", edit_record("crop_after_still", last("crop_after_still"), lambda r: r["params"].update(x=0))),
mutate("a grid's rows and columns swapped", edit_record("grid", last("grid"), lambda r: r["params"].update(columns=1, rows=4))),
mutate("an overlay's fill changed", edit_record("overlay", last("overlay"), lambda r: r["params"].update(fill="203041"))),
mutate("the attested JPEG stated replicate", edit_record("still_jpeg", last("still_jpeg"), lambda r: r.update(check="replicate"))),
mutate("a metered hop's figure raised", edit_record("metered", last("metered"), lambda r: r["energy"].update(joules_micro=501))),
mutate("an input of the grid held changed", flip_object("grid", 0)),
mutate("the explode's input held changed", flip_object("explode", 1)),
mutate("the chain of crop_after_still with its first hop's record dropped",
lambda vs: vs[by["crop_after_still"]]["records"].pop(0) and vs[by["crop_after_still"]].update(subject=0)),
mutate("the publisher taken away from the sources", lambda vs: vs[by["rotations"]].update(sources=[])),
mutate("the confirmed chain stated unconfirmed", lambda vs: vs[by["authority_confirmed"]]["facts"]["hops"][0].update(authority="unconfirmed")),
mutate("the not-recomputed chain stated recomputed", lambda vs: vs[by["not_recomputed"]]["facts"]["hops"][1].update(checked="recomputed")),
mutate("the metered totals stated as one", lambda vs: vs[by["metered"]]["facts"].update(energy=[{"class": "ModelBased", "joules_micro": 6000}])),
mutate("a source stated with another signer_id", lambda vs: vs[by["still_png"]]["facts"]["sources"][0].update(signer_id="did:web:other.example#k")),
mutate("an attested scaling given the policy that refuses it", lambda vs: vs[by["scaling"]]["policy"].update(attested="refuse")),
mutate("a refused vector's place moved", lambda vs: vs[by["replicate_mismatch"]].update(at=["record", 0, "output"])),
mutate("the revoked edge's head taken away", lambda vs: vs[by["edge_revoked"]].update(heads={})),
mutate("a receipt of the explode withheld", lambda vs: vs[by["explode"]]["receipts"].pop()),
mutate("the personal store stated system in the facts",
lambda vs: [it.update(compartment="system") for it in vs[by["provenance_carried"]]["facts"]["provenance"]]),
mutate("the store beside the still held changed",
lambda vs: [st.__setitem__(0, st[0][:-2] + "00") for st in vs[by["provenance_carried"]]["c2pa"].values()]),
mutate("the claim about the still held with its value changed",
lambda vs: vs[by["provenance_carried"]]["claims"].__setitem__(0, vs[by["provenance_carried"]]["claims"][0].replace('"value":"', '"value":"00'))),
mutate("the generated chain stated unlabelled",
lambda vs: vs[by["synthesis_carried"]]["facts"]["hops"][0].update(synthesis=None)),
]
def main(corpus_dir):
fails = []
st = ED._self_test()
print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st))
fails += ["ed25519 self-test"] if st else []
ok = len(REASONS) == 31 and len(set(REASONS)) == len(REASONS) and len(DERIVATIONS) == 9
print(("PASS" if ok else "FAIL") + " §9 and §4 read from the extension: %d reasons, %d derivations" % (len(REASONS), len(DERIVATIONS)))
fails += [] if ok else ["the extension's tables do not read"]
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
fails += check(doc)
extra = sorted(set(os.listdir(corpus_dir)) - {"vectors.json", "README.md", "edge_verify.py", "__pycache__"})
print(("PASS" if not extra else "FAIL") + " no file but vectors.json, README.md and edge_verify.py")
fails += ["an extra file: %s" % n for n in extra]
seen = {v["reason"] for v in doc["vectors"]}
missing = [r for r in REASONS if r not in seen]
print(("PASS" if not missing else "FAIL") + " every reason of §9 appears in the corpus")
fails += ["no vector gives %s" % r for r in missing]
for label, d in mutations(doc):
caught = check(d, quiet=True)
print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:90] if caught else ""))
if not caught:
fails.append("a mutated copy verifies: %s" % label)
for name in CHECKS:
DROPPED.clear()
DROPPED.add(name)
caught = check(doc, quiet=True)
DROPPED.clear()
print(("PASS" if caught else "FAIL") + " the corpus catches the verifier without its check %s%s"
% (name, " (%s)" % caught[0][:90] if caught else ""))
if not caught:
fails.append("the check %s can be dropped unnoticed" % name)
for f in fails:
print("FAIL " + f)
print(("PASS" if not fails else "FAIL") + " deriving-edge corpus")
return 1 if fails else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: edge_verify.py <edge-conformance dir>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))