open-standards/wai/live-conformance/contribution_verify.py
Transaction Science 0a62b9394e wai.media.contribution: the corpus's second implementation, contribution_verify.py
Written from the extension's text; ports the pinned generator and checks it against
the generator's own conformance vector first; mutates copies of the corpus and drops
each of its own checks in turn.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 12:53:12 -04:00

1273 lines
53 KiB
Python

#!/usr/bin/env python3
"""Second implementation of extensions/contribution.md (`wai.media.contribution`), over the
corpus `live-conformance/contribution/`.
Written from the extension's text. It shares no code with wai-rs: it reads every record as §2
has it read, with the strict JSON reading and the encodings of the synthesis corpus's second
implementation (synthesis_verify.py), which also reads each origin claim (§9.1); it verifies
every signature with WAI's strict Ed25519 rule (claim-conformance/_ed25519.py) and every link
hash and tree node with BLAKE3 written from its specification (claim-conformance/_blake3.py);
it evaluates every pinned operation of §4.5 and runs the pinned generator in its own
arithmetic, which it first checks against the generator's conformance vector
(wai-rs/tests/vectors/generate/sha256.json); and it computes every authorship map, every
share and every C2PA statement itself.
python3 -I contribution_verify.py <live-conformance/contribution>
It gives every vector of the corpus the outcome, the reason, the place and the statements the
corpus states and checks that every reason of §11 refuses a vector. Then it 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. Exit status 0 only if all of that holds.
Python 3.8 or later; standard library only.
"""
import copy
import hashlib
import json
import math
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
import synthesis_verify as SYN # noqa: E402
import _ed25519 as ED # noqa: E402
from _blake3 import blake3 # noqa: E402
EXTENSION = os.path.join(HERE, "..", "extensions", "contribution.md")
GENERATOR_VECTOR = os.path.join(HERE, "..", "wai-rs", "tests", "vectors", "generate")
SIGN_DOMAIN = b"wai:contribution\x01" # §2
LINK_DOMAIN = b"wai:contribution-link\x01" # §2
LEAF_DOMAIN = b"wai:contribution-leaf\x01" # §5.2
NODE_DOMAIN = b"wai:contribution-node\x01" # §5.2
MAP_DOMAIN = b"wai:contribution-map\x01" # §7.3
SPECTRAL = "wai.generate.spectral"
LUT = "wai-contribution:lut"
CROP, ROTATION, MIRROR, SCALING = "wai.derive.crop", "wai.derive.rotation", "wai.derive.mirror", "wai.derive.scaling"
OVERLAY, GRID = "wai.derive.overlay_composition", "wai.derive.grid_composition"
UPSCALE2, DENOISE3 = "wai-synthesis:upscale2", "wai-synthesis:denoise3"
PINNED_TRANSFORMS = [CROP, ROTATION, MIRROR, UPSCALE2, DENOISE3, LUT]
PINNED_COMPOSITIONS = [OVERLAY, GRID]
EMPTY = "http://c2pa.org/digitalsourcetype/empty"
MAX_SAMPLES = 1 << 26
ACTS = ["author", "generate", "candidates", "select", "edit", "inpaint", "transform", "compose", "reject"]
TIERS = ["stated", "checked", "recomputed"]
UNKNOWN, UNDISCLOSED = "unknown", "undisclosed"
# The checks a mutation run may drop, by the reason each raises (§11).
DROPPED = set()
class Refuse(Exception):
def __init__(self, reason, at=None):
super().__init__(reason)
self.reason, self.at = reason, at
Bad = SYN.Bad
need, hexb, opt_hex, integer, term, one_of, members = SYN.need, SYN.hexb, SYN.opt_hex, SYN.integer, SYN.term, SYN.one_of, SYN.members
jcs = SYN.jcs
def fail(reason, at):
"""Refuse with `reason` at `at`, unless a mutation run dropped that check."""
if reason not in DROPPED:
raise Refuse(reason, at)
def sha(*parts):
h = hashlib.sha256()
for p in parts:
h.update(p)
return h.digest()
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 size_ok(w, h):
return w >= 1 and h >= 1 and w * h * 3 <= MAX_SAMPLES
# ---------------------------------------------------------------- §2-§6 reading
def read(text, kind, names, fields):
"""§2: a signed record of `kind`: its members, then its kind's own, then its signature."""
try:
v = SYN.strict_json(text)
members(v, names)
need(v["kind"] == kind)
signer = hexb(v["signer_pubkey"], 32)
sig = hexb(v["sig"], 64)
SYN.date_time(v["issued_at"])
r = fields(v, signer)
except (Bad, KeyError, TypeError, AttributeError, ValueError):
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
def session_fields(v, signer):
"""§3.1."""
hexb(v["session"], 32)
tool = term(v["tool"])
one_of(v["media"], ["image"])
need(isinstance(v["parties"], list) and 1 <= len(v["parties"]) <= 64)
parties = []
for p in v["parties"]:
need(isinstance(p, dict))
role = p.get("role")
if role in ("human", "audience"):
members(p, ["role", "key"])
parties.append({"role": role, "key": hexb(p["key"], 32), "digest": None, "trained": False})
elif role == "model":
members(p, ["role", "digest", "type"])
parties.append({"role": role, "key": None, "digest": hexb(p["digest"], 32),
"trained": one_of(p["type"], ["trained", "algorithmic"]) == "trained"})
else:
raise Bad("a party of no role")
keys = [p["key"] for p in parties if p["key"] is not None]
digests = [p["digest"] for p in parties if p["digest"] is not None]
need(len(set(keys)) == len(keys) and len(set(digests)) == len(digests) and signer not in keys)
return {"tool": tool, "parties": parties}
def membership_fields(v, signer):
"""§3.2."""
return {"session": hexb(v["session"], 32), "party": integer(v["party"])}
def output_of(v, act):
"""§4.1's output of an act."""
if act == "reject":
need(v is None)
return None
if act == "candidates":
members(v, ["set", "width", "height"])
need(isinstance(v["set"], list) and 2 <= len(v["set"]) <= 16)
s = [hexb(d, 32) for d in v["set"]]
need(len(set(s)) == len(s))
w, h = integer(v["width"]), integer(v["height"])
need(size_ok(w, h))
return {"set": s, "w": w, "h": h}
members(v, ["samples", "width", "height"])
w, h = integer(v["width"]), integer(v["height"])
need(size_ok(w, h))
return {"samples": hexb(v["samples"], 32), "w": w, "h": h}
def params_of(p, act, n_members):
"""§4.3."""
if act == "author":
members(p, ["source"])
return {"source": one_of(p["source"], SYN.CAPTURE_SOURCES)}
if act in ("generate", "candidates", "inpaint"):
names = {"generate": ["generator", "prompt", "prompt_by", "seed", "steps"],
"candidates": ["generator", "prompt", "prompt_by", "seeds", "steps"],
"inpaint": ["generator", "prompt", "prompt_by", "mask_by", "seed", "steps"]}[act]
members(p, names)
r = {"generator": term(p["generator"]), "prompt": opt_hex(p["prompt"], 32),
"prompt_by": None if p["prompt_by"] is None else integer(p["prompt_by"]),
"steps": None if p["steps"] is None else integer(p["steps"]), "seed": None, "seeds": None, "mask_by": None}
need((r["prompt"] is None) == (r["prompt_by"] is None))
if "seed" in p:
r["seed"] = None if p["seed"] is None else integer(p["seed"])
if "seeds" in p and p["seeds"] is not None:
need(isinstance(p["seeds"], list) and len(p["seeds"]) == n_members)
r["seeds"] = [integer(s) for s in p["seeds"]]
if "mask_by" in p:
r["mask_by"] = integer(p["mask_by"])
return r
if act in ("select", "edit", "reject"):
members(p, [])
return {}
members(p, ["derivation", "params"])
need(isinstance(p["params"], dict))
return {"derivation": term(p["derivation"]), "params": p["params"]}
def step_fields(v, signer):
"""§4.1-§4.3. A step of the reserved act is refused once its members are read."""
act = v["act"]
need(isinstance(act, str))
if act == "direct":
raise Refuse("act_reserved")
one_of(act, ACTS)
seq = integer(v["seq"])
need(isinstance(v["inputs"], list))
inputs = []
for i in v["inputs"]:
members(i, ["step", "samples"])
x = (integer(i["step"]), hexb(i["samples"], 32))
need(x[0] < seq and x not in inputs)
inputs.append(x)
out = output_of(v["output"], act)
lo, hi = {"author": (0, 0), "generate": (0, 0), "candidates": (0, 0), "compose": (1, 16)}.get(act, (1, 1))
need(lo <= len(inputs) <= hi)
region = None
if act in ("edit", "inpaint"):
need(isinstance(v["region"], list) and 1 <= len(v["region"]) <= 256)
region = []
for r in v["region"]:
members(r, ["x", "y", "w", "h"])
x, y, w, h = (integer(r[k]) for k in ("x", "y", "w", "h"))
need(w >= 1 and h >= 1 and x + w <= out["w"] and y + h <= out["h"])
region.append((x, y, w, h))
else:
need(v["region"] is None)
n_members = len(out["set"]) if out is not None and "set" in out else 0
return {"session": hexb(v["session"], 32), "seq": seq, "prev": opt_hex(v["prev"], 32), "party": integer(v["party"]),
"act": act, "inputs": inputs, "output": out, "region": region,
"params": params_of(v["params"], act, n_members),
"replicate": one_of(v["check"], ["replicate", "attest"]) == "replicate"}
def record_fields(v, signer):
"""§6."""
steps = integer(v["steps"])
need(steps >= 1)
o = v["output"]
members(o, ["step", "samples", "width", "height", "content_hash"])
w, h = integer(o["width"]), integer(o["height"])
need(size_ok(w, h) and integer(o["step"]) < steps)
content_hash = opt_hex(o["content_hash"], 32)
origin = opt_hex(v["origin"], 32)
need(origin is None or content_hash is not None)
need(isinstance(v["estimates"], list) and len(v["estimates"]) <= 16)
for e in v["estimates"]:
members(e, ["estimator", "class", "party", "basis", "share_ppm"])
term(e["estimator"])
term(e["class"])
integer(e["party"])
one_of(e["basis"], ["made_by", "directed_by"])
need(integer(e["share_ppm"]) <= 1000000)
return {"session": hexb(v["session"], 32), "steps": steps, "root": hexb(v["root"], 32), "out_step": o["step"],
"out_samples": hexb(o["samples"], 32), "w": w, "h": h, "content_hash": content_hash,
"map": hexb(v["map"], 32), "origin": origin, "estimates": v["estimates"]}
def read_set(a, n):
"""§7.3: parties in ascending order, then "unknown" where it holds it."""
need(isinstance(a, list))
out, last = set(), -1
for i, e in enumerate(a):
if e == UNKNOWN:
need(i == len(a) - 1)
out.add(UNKNOWN)
continue
p = integer(e)
need(p < n and p > last)
last = p
out.add(p)
return frozenset(out)
def read_map(text, n):
"""§7.3: the stated map, in its one encoding; `map_malformed` otherwise."""
try:
v = SYN.strict_json(text)
members(v, ["kind", "output", "width", "height", "palette", "rows"])
need(v["kind"] == "wai-authorship-map")
w, h = integer(v["width"]), integer(v["height"])
need(size_ok(w, h))
need(isinstance(v["palette"], list))
palette = []
for l in v["palette"]:
members(l, ["made_by", "directed_by"])
palette.append((read_set(l["made_by"], n), read_set(l["directed_by"], n)))
need(len(set(palette)) == len(palette))
need(isinstance(v["rows"], list) and len(v["rows"]) == h)
labels, seen = [], []
for row in v["rows"]:
need(isinstance(row, list))
total, prev = 0, None
for run in row:
need(isinstance(run, list) and len(run) == 2)
i, k = integer(run[0]), integer(run[1])
need(k >= 1 and i < len(palette) and i != prev)
if i not in seen:
need(i == len(seen))
seen.append(i)
prev = i
total += k
need(total <= w)
labels.extend([palette[i]] * k)
need(total == w)
need(len(seen) == len(palette))
return {"digest": blake3(MAP_DOMAIN + jcs(v).encode("utf-8")), "output": hexb(v["output"], 32), "w": w, "h": h,
"labels": labels}
except (Bad, KeyError, TypeError, AttributeError, ValueError):
raise Refuse("map_malformed")
def read_opening(text):
"""§8.1."""
try:
v = SYN.strict_json(text)
members(v, ["commitment", "salt", "text"])
need(isinstance(v["text"], str))
return {"commitment": hexb(v["commitment"], 32), "salt": hexb(v["salt"], 32), "text": v["text"]}
except (Bad, KeyError, TypeError, AttributeError, ValueError):
raise Refuse("record_malformed")
# ---------------------------------------------------------------- §4.5 the pinned operations
def parse_op(derivation, p):
"""A pinned derivation's parameters, exactly (§4.5; deriving-edge §4.2), or None."""
try:
def pos(x):
n = integer(x)
need(n >= 1)
return n
if derivation == CROP:
members(p, ["x", "y", "w", "h"])
return ("crop", integer(p["x"]), integer(p["y"]), pos(p["w"]), pos(p["h"]))
if derivation == ROTATION:
members(p, ["quarter_turns"])
q = integer(p["quarter_turns"])
need(1 <= q <= 3)
return ("rotation", q)
if derivation == MIRROR:
members(p, ["flip"])
return ("mirror", one_of(p["flip"], ["left-right", "top-bottom"]) == "left-right")
if derivation in (UPSCALE2, DENOISE3):
members(p, [])
return ("upscale2",) if derivation == UPSCALE2 else ("denoise3",)
if derivation == LUT:
members(p, ["table"])
t = p["table"]
need(isinstance(t, str) and re.fullmatch(r"(?:[0-9a-f]{2}){768}", t) is not None)
return ("lut", bytes.fromhex(t))
if derivation == OVERLAY:
members(p, ["width", "height", "fill", "at"])
fill = hexb(p["fill"], 3)
need(isinstance(p["at"], list))
at = []
for a in p["at"]:
members(a, ["x", "y"])
at.append((integer(a["x"]), integer(a["y"])))
w, h = pos(p["width"]), pos(p["height"])
need(size_ok(w, h))
return ("overlay", w, h, fill, at)
if derivation == GRID:
members(p, ["columns", "rows"])
return ("grid", pos(p["columns"]), pos(p["rows"]))
except (Bad, KeyError, TypeError, AttributeError):
return None
return None
def out_size(op, sizes):
"""The output size over inputs of these sizes, or None where the parameters do not hold."""
k = op[0]
if k in ("crop", "rotation", "mirror", "upscale2", "denoise3", "lut"):
if len(sizes) != 1:
return None
iw, ih = sizes[0]
if k == "crop":
_, x, y, w, h = op
if x + w > iw or y + h > ih:
return None
r = (w, h)
elif k == "rotation":
r = (iw, ih) if op[1] == 2 else (ih, iw)
elif k == "upscale2":
r = (2 * iw, 2 * ih)
else:
r = (iw, ih)
elif k == "overlay":
if not sizes or len(op[4]) != len(sizes):
return None
r = (op[1], op[2])
else:
_, columns, rows = op
if len(sizes) != columns * rows or any(s != sizes[0] for s in sizes):
return None
r = (columns * sizes[0][0], rows * sizes[0][1])
return r if size_ok(*r) else None
def sources(op, sizes, x, y):
"""§4.5: the input pixels output pixel (x, y) reads, as (input, x, y)."""
k = op[0]
if k == "crop":
return [(0, op[1] + x, op[2] + y)]
if k == "rotation":
iw, ih = sizes[0]
return [(0,) + {1: (y, ih - 1 - x), 2: (iw - 1 - x, ih - 1 - y), 3: (iw - 1 - y, x)}[op[1]]]
if k == "mirror":
iw, ih = sizes[0]
return [(0, iw - 1 - x, y)] if op[1] else [(0, x, ih - 1 - y)]
if k == "lut":
return [(0, x, y)]
if k == "upscale2":
iw, ih = sizes[0]
sx, sy = x // 2, y // 2
x1, y1 = min(sx + 1, iw - 1), min(sy + 1, ih - 1)
pts = [(sx, sy)] + ([(x1, sy)] if x % 2 else []) + ([(sx, y1)] if y % 2 else []) + ([(x1, y1)] if x % 2 and y % 2 else [])
return sorted({(0, a, b) for a, b in pts})
if k == "denoise3":
iw, ih = sizes[0]
return sorted({(0, min(max(x + i, 0), iw - 1), min(max(y + j, 0), ih - 1)) for i in (-1, 0, 1) for j in (-1, 0, 1)})
if k == "overlay":
for i in reversed(range(len(sizes))):
ax, ay = op[4][i]
iw, ih = sizes[i]
if ax <= x < ax + iw and ay <= y < ay + ih:
return [(i, x - ax, y - ay)]
return []
cw, ch = sizes[0]
return [((y // ch) * op[1] + x // cw, x % cw, y % ch)]
def evaluate(op, bufs):
"""§4.5: the operation over its inputs' samples: (w, h, samples)."""
sizes = [(w, h) for w, h, _ in bufs]
size = out_size(op, sizes)
if size is None:
return None
w, h = size
k = op[0]
if k == "upscale2":
return w, h, SYN.upscale2(bufs[0][2], *sizes[0])
if k == "denoise3":
return w, h, SYN.denoise3(bufs[0][2], *sizes[0])
if k == "lut":
t = op[1]
return w, h, bytes(t[256 * (i % 3) + b] for i, b in enumerate(bufs[0][2]))
fill = op[3] if k == "overlay" else b"\0\0\0"
out = bytearray()
for y in range(h):
for x in range(w):
s = sources(op, sizes, x, y)
if s:
i, sx, sy = s[0]
o = (sy * sizes[i][0] + sx) * 3
out += bufs[i][2][o:o + 3]
else:
out += fill
return w, h, bytes(out)
# ---------------------------------------------------------------- the pinned generator
M64 = (1 << 64) - 1
def tdiv(a, b):
"""Integer division truncated toward zero, as the reference generator divides."""
q = abs(a) // abs(b)
return q if (a >= 0) == (b > 0) else -q
def fnv1a(data):
h = 14695981039346656037
for b in data:
h = ((h ^ b) * 1099511628211) & M64
return h
def splitmix(state):
state = (state + 0x9E3779B97F4A7C15) & M64
z = state
z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & M64
z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & M64
return state, z ^ (z >> 31)
def iatan2(dy, dx, t):
if dx == 0:
return 0 if dy == 0 else (tdiv(t, 4) if dy > 0 else tdiv(3 * t, 4))
if dy == 0:
return 0 if dx > 0 else tdiv(t, 2)
x, y, ang = abs(dx) << 12, abs(dy) << 12, 0
for i, a in enumerate([128, 76, 40, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0, 0]):
if y > 0:
x, y, ang = x + (y >> i), y - (x >> i), ang + a
elif y < 0:
x, y, ang = x - (y >> i), y + (x >> i), ang - a
if dx >= 0 and dy >= 0:
q = ang
elif dx < 0 and dy >= 0:
q = tdiv(t, 2) - ang
elif dx < 0:
q = tdiv(t, 2) + ang
else:
q = t - ang
return q % t
def read_model(data):
"""The generator's model file, where it has every member the generator reads, each in a
range it computes with; None otherwise."""
try:
m = json.loads(data)
lut = m["sine_lut"]
ok = (isinstance(m["version"], str) and isinstance(m["lut_size"], int) and m["lut_size"] >= 1
and isinstance(lut, list) and len(lut) == m["lut_size"]
and all(isinstance(v, int) and abs(v) <= 1 << 20 for v in lut)
and all(isinstance(m[k], int) and 0 <= m[k] <= c for k, c in
(("freq_max", 1 << 12), ("amp_bits", 16), ("base_components", 1024), ("step_components", 1024)))
and (("lut_bits" not in m) or (isinstance(m["lut_bits"], int) and 0 <= m["lut_bits"] <= 20))
and isinstance(m["color_offsets"], list) and len(m["color_offsets"]) == 3
and all(isinstance(v, int) and abs(v) <= 1 << 20 for v in m["color_offsets"]))
except (ValueError, KeyError, TypeError):
return None
if not ok:
return None
m.setdefault("lut_bits", 14)
lex = {}
for k, e in (m.get("lexicon") or {}).items():
lex[k] = ([v if isinstance(v, int) else 0 for v in e] + [0] * 10)[:10]
m["lexicon"] = lex
return m
def generate(m, prompt, seed, steps, h, w):
"""`generate(model, prompt, seed, steps, h, w)` of `wai.generate.spectral` (SPEC §5): an
h x w RGB8 picture."""
k = m["base_components"] + steps * m["step_components"]
hs, f, o, r, mi, gr, an, sp, n = [0, 0, 0], 0, 0, 0, 0, 0, 0, 0, 0
for tok in re.split(r"[^0-9a-z]", prompt.lower()):
e = m["lexicon"].get(tok) if tok else None
if e is not None:
hs = [hs[0] + e[0], hs[1] + e[1], hs[2] + e[2]]
f, o, r, mi, gr, an, sp, n = f + e[3], o + e[4], r + e[5], mi + e[6], gr + e[7], an + e[8], sp + e[9], n + 1
if n == 0:
hue, freq, orient, radial, mirror, gradient, angular, spiral = [256] * 3, 256, 0, 0, 0, 0, 0, 0
else:
mx = max(max(hs), 1)
hue = [tdiv(c * 256, mx) for c in hs]
clamp = lambda v, lo, hi: min(max(v, lo), hi) # noqa: E731
freq, orient = tdiv(f, n), clamp(tdiv(o, n), -256, 256)
radial, mirror, gradient = clamp(tdiv(r, n), 0, 256), clamp(tdiv(mi, n), 0, 256), clamp(tdiv(gr, n), -256, 256)
angular, spiral = clamp(tdiv(an, n), 0, 256), clamp(tdiv(sp, n), 0, 256)
fx_scale, fy_scale = (max(256 + orient, 0), 256) if orient < 0 else (256, max(256 - orient, 0))
fmax = max(tdiv(m["freq_max"] * freq, 256), 1)
rng = fnv1a(prompt.encode("utf-8")) ^ seed ^ 0xD1B54A32D192ED03
amp_max = 1 << m["amp_bits"]
span = 2 * fmax + 1
thr = [radial, mirror, angular, spiral]
plane_w = max(256 - sum(thr), 48)
total = plane_w + sum(thr)
t = m["lut_size"]
comps = []
for _ in range(k):
rng, pick = splitmix(rng)
pick %= total
typ = 0
if pick >= plane_w:
bound = plane_w
for i, wgt in enumerate(thr):
bound += wgt
if pick < bound:
typ = i + 1
break
rng, a = splitmix(rng)
rng, b = splitmix(rng)
rng, c = splitmix(rng)
rng, d = splitmix(rng)
fxr, fyr, amp, phase = a % span - fmax, b % span - fmax, c % amp_max + 1, d % t
if typ == 1:
comps.append((1, max(abs(fxr), 2), 0, amp, phase))
elif typ == 3:
comps.append((3, abs(fxr) % 11 + 2, 0, amp, phase))
elif typ == 4:
comps.append((4, abs(fxr) % 9 + 2, max(abs(fyr), 2), amp, phase))
else:
comps.append((typ, tdiv(fxr * fx_scale, 256), tdiv(fyr * fy_scale, 256), amp, phase))
cx, cy = w // 2, h // 2
need_r = any(c[0] in (1, 4) for c in comps)
need_th = any(c[0] in (3, 4) for c in comps)
gscale = tdiv((1 << m["lut_bits"]) * amp_max * k, 2048 * max(cy, 1))
lut = m["sine_lut"]
acc = [0] * (3 * h * w)
for ch, coff in enumerate(m["color_offsets"]):
for y in range(h):
grad = gradient * (cy - y) * gscale
for x in range(w):
dx, dy = x - cx, y - cy
rr = math.isqrt(dx * dx + dy * dy) if need_r else 0
th = iatan2(dy, dx, t) if need_th else 0
v = grad
for typ, fx, fy, amp, phase in comps:
if typ == 1:
arg = fx * rr + phase + coff
elif typ == 2:
arg = fx * abs(dx) + fy * abs(dy) + phase + coff
elif typ == 3:
arg = fx * th + phase + coff
elif typ == 4:
arg = fx * th + fy * rr + phase + coff
else:
arg = fx * x + fy * y + phase + coff
v += amp * lut[arg % t]
acc[ch * h * w + y * w + x] = v
gmin, gmax = min(acc), max(acc)
rng_ = max(gmax - gmin, 1)
eh = [64 + tdiv(c * (256 - 64), 256) for c in hue]
out = bytearray(h * w * 3)
for ch in range(3):
for p in range(h * w):
q = tdiv((acc[ch * h * w + p] - gmin) * 255 + tdiv(rng_, 2), rng_)
out[p * 3 + ch] = min(max(tdiv(q * eh[ch], 256), 0), 255)
return bytes(out)
# ---------------------------------------------------------------- §5.2 the tree
def leaf(link):
return blake3(LEAF_DOMAIN + link)
def node(a, b):
return blake3(NODE_DOMAIN + a + b)
def proof_len(n):
return 0 if n <= 1 else (n - 1).bit_length()
def walk(link, seq, siblings):
h = leaf(link)
for i, s in enumerate(siblings):
h = node(s, h) if (seq >> i) & 1 else node(h, s)
return h
# ---------------------------------------------------------------- §7 maps
def label(made, directed):
return (frozenset(made), frozenset(directed))
L_UNKNOWN = label([UNKNOWN], [UNKNOWN])
L_UNDISCLOSED = label([UNDISCLOSED], [UNDISCLOSED])
class StateMap:
"""One label for every pixel, or a label for each."""
def __init__(self, const=None, w=None, labels=None):
self.const, self.w, self.labels = const, w, labels
def at(self, x, y):
return self.const if self.const is not None else self.labels[y * self.w + x]
def expand(self, w, h):
return [self.const] * (w * h) if self.const is not None else list(self.labels)
def shares(labels, n):
"""§7.4, as §10.4 states them."""
def per(i):
return [{"party": p, "samples": c} for p in range(n) for c in [sum(1 for l in labels if p in l[i])] if c > 0]
return {"samples": len(labels), "made_by": per(0),
"made_by_unknown": sum(1 for l in labels if UNKNOWN in l[0]),
"made_by_undisclosed": sum(1 for l in labels if UNDISCLOSED in l[0]),
"directed_by": per(1),
"directed_by_unknown": sum(1 for l in labels if UNKNOWN in l[1]),
"directed_by_undisclosed": sum(1 for l in labels if UNDISCLOSED in l[1])}
# ---------------------------------------------------------------- §10 verification
def size_of(out):
return None if out is None else (out["w"], out["h"])
def holds(out, d):
if out is None:
return False
return d in out["set"] if "set" in out else out["samples"] == d
def states_of(out):
if out is None:
return []
return list(out["set"]) if "set" in out else [out["samples"]]
def in_region(region, x, y):
return any(rx <= x < rx + rw and ry <= y < ry + rh for rx, ry, rw, rh in region)
def expected_replicate(s):
"""§4.4: the step's check."""
a, p = s["act"], s["params"]
if a in ("author", "edit", "select", "reject"):
return True
if a in ("generate", "candidates", "inpaint"):
return p["generator"] == SPECTRAL
if a == "transform":
return p["derivation"] in PINNED_TRANSFORMS
return p["derivation"] in PINNED_COMPOSITIONS
def replicate_params_ok(s):
"""§4.4: a replicate step's parameters."""
p = s["params"]
if s["act"] in ("generate", "candidates", "inpaint"):
w, h = size_of(s["output"])
if s["act"] == "candidates":
seeds_ok = p["seeds"] is not None and all(x <= 0xFFFFFFFF for x in p["seeds"])
else:
seeds_ok = p["seed"] is not None and p["seed"] <= 0xFFFFFFFF
return seeds_ok and p["steps"] is not None and 1 <= p["steps"] <= 64 and w <= 4096 and h <= 4096 and w * h <= 1 << 23
if s["act"] in ("transform", "compose"):
return parse_op(p["derivation"], p["params"]) is not None
return True
def verify(case, models_by_digest):
"""§10.2: the statements of §10.4, or Refuse with its reason and place."""
# Step 1: reading.
def at(place, f):
try:
return f()
except Refuse as e:
raise Refuse(e.reason, place)
sess = at(["session"], lambda: read(case["session"], "wai-session",
["kind", "session", "tool", "media", "parties", "issued_at", "signer_pubkey", "sig"], session_fields))
parties, n, tool_key = sess["parties"], len(sess["parties"]), sess["signer"]
mems = [at(["membership", i], lambda t=t: read(t, "wai-membership", ["kind", "session", "party", "issued_at", "signer_pubkey", "sig"],
membership_fields)) for i, t in enumerate(case["memberships"])]
rec = at(["record"], lambda: read(case["record"], "wai-contribution",
["kind", "session", "steps", "root", "output", "map", "origin", "estimates", "issued_at",
"signer_pubkey", "sig"], record_fields))
steps, proofs = [], []
for i, d in enumerate(case["steps"]):
steps.append(at(["step", i], lambda d=d: read(d["step"], "wai-step",
["kind", "session", "seq", "prev", "party", "act", "inputs", "output", "region",
"params", "check", "issued_at", "signer_pubkey", "sig"], step_fields)))
try:
proofs.append([hexb(p, 32) for p in d["proof"]])
except Bad:
raise Refuse("record_malformed", ["step", i])
smap = at(["map"], lambda: read_map(case["map"], n))
openings = [at(["opening", i], lambda t=t: read_opening(t)) for i, t in enumerate(case["openings"])]
origins = []
for i, t in enumerate(case["origins"]):
try:
origins.append(SYN.read(t, "wai-origin", SYN.ORIGIN, SYN.origin_fields))
except SYN.Refuse as e:
raise Refuse(e.reason, ["origin", i])
# Step 2: openings.
for i, o in enumerate(openings):
if sha(o["salt"], o["text"].encode("utf-8")) != o["commitment"]:
fail("opening_mismatch", ["opening", i])
# Step 3: membership.
signed = set()
for i, m in enumerate(mems):
p = parties[m["party"]] if m["party"] < n else None
if m["session"] != sess["link"] or p is None or p["key"] != m["signer"]:
fail("membership_invalid", ["membership", i])
signed.add(m["party"])
for i, p in enumerate(parties):
if p["key"] is not None and i not in signed:
fail("party_not_countersigned", ["session", "party", i])
# Step 4: the record.
if rec["session"] != sess["link"]:
fail("session_mismatch", ["record"])
if rec["signer"] != tool_key:
fail("signer_mismatch", ["record"])
if any(e["party"] >= n for e in rec["estimates"]):
fail("party_unknown", ["record"])
# Step 5: each step, in the order given.
first = {}
for i, s in enumerate(steps):
first.setdefault(s["seq"], i)
for i, s in enumerate(steps):
place = ["step", i]
if s["session"] != sess["link"]:
fail("session_mismatch", place)
p = s["params"]
named = [x for x in (p.get("prompt_by"), p.get("mask_by")) if x is not None]
if s["party"] >= n or any(x >= n for x in named):
fail("party_unknown", place)
continue
performer = parties[s["party"]]
if s["act"] in ("author", "edit") and performer["role"] != "human":
fail("party_role_mismatch", place)
if s["act"] in ("generate", "candidates", "inpaint") and performer["role"] != "model":
fail("party_role_mismatch", place)
if s["signer"] != (performer["key"] if performer["key"] is not None else tool_key):
fail("signer_mismatch", place)
if s["replicate"] != expected_replicate(s):
fail("check_mismatch", place)
if s["replicate"] and not replicate_params_ok(s):
fail("params_invalid", place)
before = first.get(s["seq"] - 1) if s["seq"] > 0 else None
if (s["seq"] >= rec["steps"] or first[s["seq"]] != i or (s["seq"] == 0) != (s["prev"] is None)
or (before is not None and s["prev"] != steps[before]["link"])):
fail("chain_broken", place)
if len(proofs[i]) != proof_len(rec["steps"]) or walk(s["link"], s["seq"], proofs[i]) != rec["root"]:
fail("inclusion_failed", place)
# Step 6: each step against the states, in seq order.
states = {sha(bytes.fromhex(b)): bytes.fromhex(b) for b in case["states"]}
models = {}
for d in case["models"]:
data = models_by_digest[d]
models[sha(data)] = data
opened = {o["commitment"]: o["text"] for o in openings}
by_seq = {seq: steps[i] for seq, i in first.items()}
def held_state(d, w, h):
b = states.get(d)
return b if b is not None and len(b) == w * h * 3 else None
def prompt_text(c):
return "" if c is None else opened.get(c)
status = {}
for seq in sorted(by_seq):
s = by_seq[seq]
status[seq] = check_step(s, by_seq, parties, states, models, held_state, prompt_text, ["step", first[seq]])
# Step 7: the output.
out_step = by_seq.get(rec["out_step"])
if out_step is not None and out_step["output"] != {"samples": rec["out_samples"], "w": rec["w"], "h": rec["h"]}:
fail("output_mismatch", ["record"])
b = states.get(rec["out_samples"])
if b is not None and len(b) != rec["w"] * rec["h"] * 3:
fail("samples_mismatch", ["record"])
# Step 8: the map.
if (smap["digest"], smap["output"], smap["w"], smap["h"]) != (rec["map"], rec["out_samples"], rec["w"], rec["h"]):
fail("map_unbound", ["map"])
computed = computed_map(by_seq, rec["out_step"], rec["out_samples"], rec["w"], rec["h"])
for y in range(rec["h"]):
for x in range(rec["w"]):
c, st = computed[y * rec["w"] + x], smap["labels"][y * rec["w"] + x]
for cs, ss in ((c[0], st[0]), (c[1], st[1])):
ok = (cs - {UNDISCLOSED}) <= ss if UNDISCLOSED in cs else cs == ss
if not ok:
fail("unknown_attributed" if UNKNOWN in cs and UNKNOWN not in ss else "map_mismatch", ["map", x, y])
# Step 9: the origin claim.
origin = None
if rec["origin"] is not None:
origin = "not-checked"
for i, o in enumerate(origins):
if o["link"] != rec["origin"]:
continue
if o["subject"] != ("object", rec["content_hash"]):
fail("origin_mismatch", ["origin", i])
model_parties = {k for k, p in enumerate(parties) if p["role"] == "model"}
if any(l[0] & model_parties for l in smap["labels"]) and o["class"] != "synthesized":
fail("origin_understated", ["origin", i])
origin = "agree"
break
# §10.3: the tier.
behind, todo, all_checked = set(), [rec["out_step"]], True
while todo:
seq = todo.pop()
if seq in behind:
continue
behind.add(seq)
s = by_seq.get(seq)
if s is None:
all_checked = False
else:
all_checked = all_checked and status[seq] == "checked"
todo.extend(i[0] for i in s["inputs"])
tier = "stated" if not steps else ("recomputed" if all_checked else "checked")
committed = rec["steps"] - len(steps)
c2pa = jcs(c2pa_statement(sess, by_seq, behind)) if committed == 0 else None
# Step 10: policy.
if TIERS.index(tier) < TIERS.index(case["policy"]["tier"]):
fail("tier_not_met", ["policy"])
return {"tier": tier, "session": sess["link"].hex(), "record": rec["link"].hex(),
"steps": [{"seq": seq, "act": by_seq[seq]["act"], "party": by_seq[seq]["party"],
"check": "replicate" if by_seq[seq]["replicate"] else "attest", "status": status[seq]} for seq in sorted(by_seq)],
"committed": committed, "computed": shares(computed, n), "stated": shares(smap["labels"], n),
"estimates": rec["estimates"], "origin": origin, "c2pa": c2pa}
def check_step(s, by_seq, parties, states, models, held_state, prompt_text, place):
"""§10.2 step 6 for one step; its status (§10.3)."""
out = s["output"]
# 6.1
if out is not None:
for d in states_of(out):
b = states.get(d)
if b is not None and len(b) != out["w"] * out["h"] * 3:
fail("samples_mismatch", place)
# 6.2
inputs_held = all(i[0] in by_seq for i in s["inputs"])
if s["act"] == "select":
step, d = s["inputs"][0]
if not holds(out, d):
fail("selection_not_in_set", place)
c = by_seq.get(step)
if c is not None and (c["act"] != "candidates" or not holds(c["output"], d)):
fail("selection_not_in_set", place)
else:
for step, d in s["inputs"]:
if step in by_seq and not holds(by_seq[step]["output"], d):
fail("input_unknown", place)
w, h = size_of(out) or (0, 0)
# 6.3
if s["act"] in ("edit", "inpaint"):
src = by_seq.get(s["inputs"][0][0])
if src is not None and size_of(src["output"]) != (w, h):
fail("region_invalid", place)
# 6.4
p = s["params"]
op = parse_op(p["derivation"], p["params"]) if s["act"] in ("transform", "compose") and s["replicate"] else None
sizes = [size_of(by_seq[i[0]]["output"]) for i in s["inputs"]] if inputs_held else None
if op is not None and sizes is not None:
size = out_size(op, sizes)
if size is None:
fail("params_invalid", place)
elif size != (w, h):
fail("recompute_mismatch", place)
# 6.5
performer = parties[s["party"]]
model = None
if performer["digest"] is not None and performer["digest"] in models:
model = read_model(models[performer["digest"]])
ran = False
a = s["act"]
if a in ("author", "reject"):
ran = True
elif a == "select":
ran = inputs_held
elif a in ("edit", "inpaint"):
b = states.get(s["inputs"][0][1])
if b is not None and len(b) != w * h * 3:
fail("region_invalid", place)
src, dst = held_state(s["inputs"][0][1], w, h), held_state(out["samples"], w, h)
if src is not None and dst is not None:
for y in range(h):
for x in range(w):
k = (y * w + x) * 3
if not in_region(s["region"], x, y) and src[k:k + 3] != dst[k:k + 3]:
fail("outside_region_changed", place)
ran = True
if a == "inpaint" and s["replicate"]:
text = prompt_text(p["prompt"])
if model is not None and text is not None and dst is not None:
fill = generate(model, text, p["seed"], p["steps"], h, w)
for y in range(h):
for x in range(w):
k = (y * w + x) * 3
if in_region(s["region"], x, y) and fill[k:k + 3] != dst[k:k + 3]:
fail("regeneration_mismatch", place)
else:
ran = False
elif a in ("generate", "candidates") and s["replicate"]:
text = prompt_text(p["prompt"])
if model is not None and text is not None:
seeds = p["seeds"] if a == "candidates" else [p["seed"]]
for d, seed in zip(states_of(out), seeds):
if sha(generate(model, text, seed, p["steps"], h, w)) != d:
fail("regeneration_mismatch", place)
ran = True
elif a in ("transform", "compose") and s["replicate"] and sizes is not None:
bufs = [(iw, ih, held_state(d, iw, ih)) for (_, d), (iw, ih) in zip(s["inputs"], sizes)]
if all(b is not None for _, _, b in bufs):
r = evaluate(op, bufs)
if r is None:
fail("params_invalid", place)
elif (r[0], r[1]) != (w, h) or sha(r[2]) != out["samples"]:
fail("recompute_mismatch", place)
ran = True
if not s["replicate"]:
return "attested"
return "checked" if ran and inputs_held else "not-checked"
def computed_map(by_seq, out_step, out_samples, w, h):
"""§7.2: the output's map from the steps held."""
memo = {}
def state(step, d):
if (step, d) in memo:
return memo[(step, d)]
s = by_seq.get(step)
if s is None:
return StateMap(const=L_UNDISCLOSED)
a, p = s["party"], s["params"]
sw, sh = size_of(s["output"]) or (0, 0)
directors = frozenset(x for x in (p.get("prompt_by"), p.get("mask_by")) if x is not None)
act = s["act"]
if act == "author":
m = StateMap(const=label([a], []))
elif act in ("generate", "candidates"):
m = StateMap(const=(frozenset([a]), directors))
elif act in ("edit", "inpaint"):
inner = (frozenset([a]), frozenset() if act == "edit" else directors)
src = state(*s["inputs"][0])
m = StateMap(w=sw, labels=[inner if in_region(s["region"], x, y) else src.at(x, y) for y in range(sh) for x in range(sw)])
elif act == "select":
src = state(*s["inputs"][0])
if src.const is not None:
m = StateMap(const=(src.const[0], src.const[1] | {a}))
else:
m = StateMap(w=src.w, labels=[(l[0], l[1] | {a}) for l in src.labels])
elif act in ("transform", "compose") and not s["replicate"]:
m = StateMap(const=L_UNKNOWN)
elif act in ("transform", "compose"):
op = parse_op(p["derivation"], p["params"])
if not all(i[0] in by_seq for i in s["inputs"]):
m = StateMap(const=label([UNDISCLOSED], [UNDISCLOSED, a]))
else:
sizes = [size_of(by_seq[i[0]]["output"]) for i in s["inputs"]]
srcs = [state(*i) for i in s["inputs"]]
labels = []
for y in range(sh):
for x in range(sw):
src = sources(op, sizes, x, y)
if not src:
labels.append(label([a], [a]))
continue
made, directed = set(), {a}
for k, sx, sy in src:
l = srcs[k].at(sx, sy)
made |= l[0]
directed |= l[1]
labels.append(label(made, directed))
m = StateMap(w=sw, labels=labels)
else:
m = StateMap(const=L_UNDISCLOSED)
memo[(step, d)] = m
return m
if out_step not in by_seq:
return StateMap(const=L_UNDISCLOSED).expand(w, h)
return state(out_step, out_samples).expand(w, h)
def c2pa_statement(sess, by_seq, behind):
"""§9.2."""
dst = lambda code: SYN.IPTC + code # noqa: E731
actions = [{"action": "c2pa.created", "digitalSourceType": EMPTY, "softwareAgent": {"name": sess["tool"]}}]
by_derivation = {CROP: ("c2pa.cropped", None), ROTATION: ("c2pa.orientation", None), MIRROR: ("c2pa.orientation", None),
UPSCALE2: ("c2pa.resized", "algorithmicallyEnhanced"), DENOISE3: ("c2pa.enhanced", "algorithmicallyEnhanced"),
LUT: ("c2pa.adjustedColor", None), SCALING: ("c2pa.resized", None)}
for seq in sorted(behind):
s = by_seq[seq]
p = sess["parties"][s["party"]]
agent = SYN.MODEL_ID + p["digest"].hex() if p["digest"] is not None else sess["tool"]
act = s["act"]
if act in ("select", "reject"):
continue
if act == "author":
name, source = "c2pa.edited", s["params"]["source"]
elif act in ("generate", "candidates"):
name, source = "c2pa.edited", "trainedAlgorithmicMedia" if p["trained"] else "algorithmicMedia"
elif act == "inpaint":
name, source = "c2pa.edited", "compositeWithTrainedAlgorithmicMedia" if p["trained"] else "algorithmicMedia"
elif act == "edit":
name, source = "c2pa.drawing", "digitalCreation"
elif act == "transform":
name, source = by_derivation.get(s["params"]["derivation"], ("c2pa.edited", None))
else:
name, source = "c2pa.edited", None
a = {"action": name, "softwareAgent": {"name": agent}}
if source is not None:
a["digitalSourceType"] = dst(source)
if s["region"] is not None:
a["changes"] = [{"region": [{"type": "spatial", "shape": {"type": "rectangle", "unit": "pixel", "origin": {"x": x, "y": y},
"width": w, "height": h}} for x, y, w, h in s["region"]]}]
actions.append(a)
return {"actions": actions}
# ---------------------------------------------------------------- the corpus
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()
REASONS = [l.split("`")[1] for l in section(_TEXT, "## 11. Reasons").splitlines() if l.startswith("| `")]
def outcome(v, models):
try:
return {"outcome": "accept", "statements": verify(v, models)}
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 models_of(doc):
out = {}
for m in doc["models"]:
data = m["file"].encode("utf-8")
if sha(data).hex() != m["sha256"]:
raise ValueError("a model file whose SHA-256 is not the one stated")
out[m["sha256"]] = data
return out
def check(doc):
"""Failures of a corpus document: an outcome other than the one stated."""
fails = []
try:
models = models_of(doc)
except (ValueError, KeyError, TypeError) as e:
return ["the model files do not read: %r" % (e,)]
for v in doc["vectors"]:
try:
got = outcome(v, models)
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
def retext(vs, name, key, k, f):
v = json.loads(vs[vn[name]][key][k]["step"] if key == "steps" else vs[vn[name]][key][k])
f(v)
if key == "steps":
vs[vn[name]][key][k]["step"] = json.dumps(v)
else:
vs[vn[name]][key][k] = json.dumps(v)
def held_state(vs, name, k, at):
vs[vn[name]]["states"][k] = flip_hex_digit(vs[vn[name]]["states"][k], at)
def stmt(name, f):
return lambda vs: f(vs[vn[name]]["statements"])
first_refusal = next(k for k, v in enumerate(doc["vectors"]) if v["outcome"] == "refuse")
return [
mutate("a digit of the brush edit's signature", lambda vs: retext(vs, "brush", "steps", 1, lambda v: v.update(sig=flip_hex_digit(v["sig"], 9)))),
mutate("a byte of the brushed picture", lambda vs: held_state(vs, "brush", 1, 0)),
mutate("the inpainting's prompt opened to another text",
lambda vs: vs[vn["inpaint"]]["openings"].__setitem__(0, json.dumps(dict(json.loads(vs[vn["inpaint"]]["openings"][0]), text="a calm ocean")))),
mutate("the generator's model file withheld from the candidates", lambda vs: vs[vn["candidates"]].update(models=[])),
mutate("a sibling of the candidates step's proof", lambda vs: vs[vn["candidates"]]["steps"][0]["proof"].__setitem__(
0, flip_hex_digit(vs[vn["candidates"]]["steps"][0]["proof"][0], 2))),
mutate("the brush map's rows reordered", lambda vs: vs[vn["brush"]].update(map=json.dumps(dict(
json.loads(vs[vn["brush"]]["map"]), rows=list(reversed(json.loads(vs[vn["brush"]]["map"])["rows"])))))),
mutate("the collaborator's membership withheld", lambda vs: vs[vn["brush"]]["memberships"].pop(1)),
mutate("the inpainting's origin claim withheld", lambda vs: vs[vn["inpaint"]].update(origins=[])),
mutate("the compose arranger's count raised by one",
stmt("compose", lambda s: s["computed"]["directed_by"][0].update(samples=s["computed"]["directed_by"][0]["samples"] + 1))),
mutate("the scaling's unknown samples stated as none", stmt("scaling_unknown", lambda s: s["computed"].update(made_by_unknown=0))),
mutate("the partial disclosure stated as recomputed", stmt("candidates_partial", lambda s: s.update(tier="recomputed"))),
mutate("the transforms' first action stated with another source",
stmt("transforms", lambda s: s.update(c2pa=s["c2pa"].replace("digitalsourcetype/empty", "digitalsourcetype/digitalCapture")))),
mutate("the estimate stated as another share", stmt("estimate", lambda s: s["estimates"][0].update(share_ppm=1))),
mutate("the first refusal stated at another place", lambda vs: vs[first_refusal].update(at=["record"])),
mutate("a regeneration refusal stated as a recomputation", lambda vs: vs[vn["regeneration_mismatch"]].update(reason="recompute_mismatch")),
("the model file changed by one byte", dict(copy.deepcopy(doc), models=[dict(doc["models"][0], file=doc["models"][0]["file"].replace("1", "2", 1))])),
]
def self_test_generator():
"""The generator's own conformance vector: the picture of its instruction object, by
SHA-256 (wai-rs/tests/vectors/generate)."""
with open(os.path.join(GENERATOR_VECTOR, "generate_spectral_model.json"), "rb") as f:
m = read_model(f.read())
with open(os.path.join(GENERATOR_VECTOR, "generate.json"), encoding="utf-8") as f:
g = json.load(f)
with open(os.path.join(GENERATOR_VECTOR, "sha256.json"), encoding="utf-8") as f:
want = json.load(f)["generate"]["sha256"]
return sha(generate(m, g["prompt"], g["seed"], g["steps"], g["h"], g["w"])).hex() == want
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")
gen_ok = self_test_generator()
print(("PASS" if gen_ok else "FAIL") + " the generator's arithmetic gives its conformance vector's SHA-256")
if not gen_ok:
fails.append("the generator's conformance vector")
with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f:
doc = json.load(f)
models = models_of(doc)
reasons_seen = set()
for v in doc["vectors"]:
try:
got = outcome(v, models)
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 " " + got.get("statements", {}).get("tier", ""))))
if not ok:
fails.append("%s: %s, the corpus states %s" % (v["name"], json.dumps(got)[:600], json.dumps(stated(v))[:600]))
missing = [r for r in REASONS if r not in reasons_seen]
tiers = {v["statements"]["tier"] for v in doc["vectors"] if v["outcome"] == "accept"}
if missing:
fails.append("no vector refused with %s" % ", ".join(missing))
if tiers != set(TIERS):
fails.append("accepted vectors do not reach every tier: %s" % sorted(tiers))
print("%s every reason of §11 refuses a vector, and accepted vectors reach every tier" % ("PASS" if not missing and tiers == set(TIERS) else "FAIL"))
for label_, d in mutations(doc):
caught = check(d)
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 r in REASONS:
DROPPED.clear()
DROPPED.add(r)
caught = check(doc)
DROPPED.clear()
if r in ("record_malformed", "bad_key", "bad_signature", "act_reserved", "map_malformed"):
continue # reading: a record that does not read has nothing to continue with
print(("PASS" if caught else "FAIL") + " the corpus catches a verifier without its %s check" % r)
if not caught:
fails.append("a verifier without its %s check passes the corpus" % r)
for f in fails:
print("FAIL " + f)
print(("PASS" if not fails else "FAIL") + " contribution corpus: %d vectors" % len(doc["vectors"]))
return 1 if fails else 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: contribution_verify.py <live-conformance/contribution>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))