open-standards/wai/live-conformance/splat_exact_verify.py
Transaction Science bd06796eec splat-exact: the extension text, the draft schema, and corpus cases that separate every check
extensions/splat-exact.md registers wai.splat.exact: carriage in glTF as
EXT_gaussian_splatting_compression_wai under KHR_gaussian_splatting, the
attribute layout and its dequantization, the renderer wai-splat-render:i1,
the commitment, the view statement, provenance, verification, reasons and
the measured quality cost. The draft JSON Schema of the extension object is
extensions/gltf/. The corpus gains cases the second implementation's
self-falsification showed were missing: an unrequired extension, a buffer
view past the envelope, a set whose bytes or pin alone disagree, a
commitment naming another object or set, and statements naming another
object or renderer or another pose's frame.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 03:22:48 -04:00

881 lines
34 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

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

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

#!/usr/bin/env python3
"""Second implementation of extensions/splat-exact.md (`wai.splat.exact`), over the corpus
`live-conformance/splat/`.
Written from the extension's text. It shares no code with wai-rs: it reads each glTF asset as §3
has it read, parses each `WAI1` envelope, decodes each `WIS1` payload with the second
implementations the repository already holds for the steps that come before rendering (the
payload parser and rANS decoder of int-payload-conformance/int_payload_verify.py, and the
`whm1/1` lowering and interpreter of tools/wai_ops_check.py), renders every view with its own
integer renderer written from §5, verifies every record with WAI's strict Ed25519 rule
(claim-conformance/_ed25519.py) and every link hash with BLAKE3 written from its specification
(claim-conformance/_blake3.py), and reads §10's codes from the extension.
python3 -I splat_exact_verify.py <live-conformance/splat> [--png DIR]
It gives every case of the corpus the outcome, the reason and the place the corpus states, and
checks that every reason of §10 refuses a case. It then falsifies itself three ways: it mutates
copies of the corpus and requires every copy to fail; it drops each of its own checks in turn and
requires the corpus to catch the weakened verifier; and it runs its renderer with each of a set of
changed arithmetic rules and requires the corpus's frames to catch every one. Exit status 0 only if
all of that holds. --png writes each committed frame as a PNG. Python 3.8 or later; standard
library only.
"""
import copy
import hashlib
import importlib.util
import json
import math
import os
import re
import struct
import sys
import unicodedata
import zlib
from fractions import Fraction
HERE = os.path.dirname(os.path.abspath(__file__))
WAI = os.path.join(HERE, "..")
sys.path.insert(0, os.path.join(WAI, "claim-conformance"))
import _ed25519 as ED # noqa: E402
from _blake3 import blake3 # noqa: E402
def _load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
return m
IPV = _load("int_payload_verify", os.path.join(WAI, "int-payload-conformance", "int_payload_verify.py"))
OPS = _load("wai_ops_check", os.path.join(WAI, "tools", "wai_ops_check.py"))
EXTENSION = os.path.join(WAI, "extensions", "splat-exact.md")
SIGN_DOMAIN = b"wai:splat-exact\x01" # §2
LINK_DOMAIN = b"wai:splat-exact-link\x01" # §2
OBJECT_DOMAIN = b"moq-jwp:object\x01" # jwp-receipts §2.1
SYN_SIGN = b"wai:synthesis\x01" # synthesis §2
SYN_LINK = b"wai:synthesis-link\x01"
RENDERER = "wai-splat-render:i1"
BASE = "KHR_gaussian_splatting"
EXT = "EXT_gaussian_splatting_compression_wai"
CAPABILITY = "wai.splat.int_codec"
ATTRIBUTES = [("POSITION", "VEC3"), ("KHR_gaussian_splatting:ROTATION", "VEC4"), ("KHR_gaussian_splatting:SCALE", "VEC3"),
("KHR_gaussian_splatting:OPACITY", "SCALAR"), ("KHR_gaussian_splatting:SH_DEGREE_0_COEF_0", "VEC3")]
FLOAT = 5126
PLANES = 14
MAX_INT = (1 << 53) - 1
MAX_COORD = 1 << 28
CLASSES = ["captured", "reconstructed", "assistive", "synthesized"]
STEP_CLASS = {"capture": "captured", "convert": "reconstructed", "assist": "assistive", "generate": "synthesized"}
# §5's constants.
NEAR, DILATION, MAX_RADIUS = 13107, 19661, 1023
CUTOFF, E_MAX = 9 << 16, 3 << 24
ALPHA_MAX, ALPHA_MIN, T_MIN = 64880, 257, 7
TILE = 16
WHITE_SPACE = set("\t\n\x0b\x0c\r \x85\xa0            "
"

   ")
# The checks this verifier makes, by name; `DROPPED` leaves one out (the self-falsification).
DROPPED = set()
# Changed arithmetic for the renderer's falsification; empty for the verification itself.
MUTANT = set()
def on(name):
return name not in DROPPED
class Refuse(Exception):
def __init__(self, reason, at=None):
super().__init__(reason)
self.reason, self.at = reason, at
class Bad(Exception):
pass
def need(ok):
if not ok:
raise Bad()
# ---------------------------------------------------------------- §2 records
def strict_json(text):
def pairs(ps):
d = {}
for k, v in ps:
if k in d:
raise Bad()
d[k] = v
return d
def no_float(s):
raise Bad()
def integer(s):
if s.startswith("-"):
raise Bad()
return int(s)
try:
v = json.loads(text, object_pairs_hook=pairs, parse_float=no_float, parse_int=integer, parse_constant=no_float)
except (ValueError, RecursionError):
raise Bad()
def walk(x, depth):
if isinstance(x, bool):
raise Bad()
if isinstance(x, (dict, list)):
need(depth <= 32)
for y in (x.values() if isinstance(x, dict) else x):
walk(y, depth + 1)
elif isinstance(x, str):
need(not any(0xD800 <= ord(c) <= 0xDFFF for c in x))
elif isinstance(x, int):
need(0 <= x <= MAX_INT)
walk(v, 1)
return v
def jcs(v):
return json.dumps(v, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def hexb(v, n):
need(isinstance(v, str) and re.fullmatch(r"[0-9a-f]*", v) is not None and len(v) == 2 * n)
return bytes.fromhex(v)
def integer(v):
need(isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= MAX_INT)
return v
def term(v):
need(isinstance(v, str) and 0 < len(v.encode("utf-8")) <= 2048
and not any(c in WHITE_SPACE or unicodedata.category(c) == "Cc" for c in v))
return v
def date_time(s):
m = re.fullmatch(r"([0-9]{4})-([0-9]{2})-([0-9]{2})[Tt]([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?([Zz]|[+-]([0-9]{2}):([0-9]{2}))",
s if isinstance(s, str) else "")
need(m is not None)
y, mo, d, h, mi, sec = (int(m.group(i)) for i in range(1, 7))
leap = y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
days = [31, 29 if leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
need(1 <= mo <= 12 and 1 <= d <= days[mo - 1] and h <= 23 and mi <= 59 and sec <= 60)
if m.group(9) is not None:
need(int(m.group(9)) <= 23 and int(m.group(10)) <= 59)
def members(v, names):
need(isinstance(v, dict) and set(v) == set(names))
return v
def sint(v):
"""§2: `0`, or an optional `-` and a digit 1-9 followed by digits; magnitude at most 2^28."""
need(isinstance(v, str) and re.fullmatch(r"0|-?[1-9][0-9]*", v) is not None)
n = int(v)
need(abs(n) <= MAX_COORD)
return n
def read_pose(v):
members(v, ["eye", "target", "up", "focal", "width", "height"])
vec = []
for k in ("eye", "target", "up"):
need(isinstance(v[k], list) and len(v[k]) == 3)
vec.append([sint(x) for x in v[k]])
f, w, h = integer(v["focal"]), integer(v["width"]), integer(v["height"])
need(1 <= f <= 4096 and 1 <= w <= 1024 and 1 <= h <= 1024 and w * h <= 1 << 20)
return {"eye": vec[0], "target": vec[1], "up": vec[2], "focal": f, "width": w, "height": h}
def pose_key(p):
return json.dumps(p, sort_keys=True)
def signed(v, domain):
body = dict(v)
del body["sig"]
return domain + jcs(body)
def read_record(text, kind, names, fields):
"""§2: strict JSON, exactly the kind's members, then the kind's fields, then the signature."""
try:
v = strict_json(text)
members(v, names)
need(v["kind"] == kind)
signer = hexb(v["signer_pubkey"], 32)
sig = hexb(v["sig"], 64)
date_time(v["issued_at"])
r = fields(v)
except (Bad, KeyError, TypeError, AttributeError):
raise Refuse("record_malformed")
if on("signature"):
bad = ED.verify(signer, signed(v, SIGN_DOMAIN), sig)
if bad:
raise Refuse(bad)
r["link"] = blake3(LINK_DOMAIN + jcs(v))
return r
COMMITMENT = ["kind", "object", "set", "attributes", "renderer", "class", "origin", "views", "issued_at", "signer_pubkey", "sig"]
STATEMENT = ["kind", "session", "commitment", "object", "pose", "canonical", "frame", "renderer", "class", "at", "issued_at",
"signer_pubkey", "sig"]
def commitment_fields(v):
need(isinstance(v["views"], list) and 1 <= len(v["views"]) <= 64)
views, seen = [], set()
for e in v["views"]:
members(e, ["pose", "frame"])
p = read_pose(e["pose"])
need(pose_key(p) not in seen)
seen.add(pose_key(p))
views.append({"pose": p, "frame": hexb(e["frame"], 32)})
need(v["class"] in CLASSES)
return {"object": hexb(v["object"], 32), "set": hexb(v["set"], 32), "attributes": hexb(v["attributes"], 32),
"renderer": term(v["renderer"]), "class": v["class"],
"origin": None if v["origin"] is None else hexb(v["origin"], 32), "views": views}
def statement_fields(v):
need(v["class"] in CLASSES)
return {"session": hexb(v["session"], 32), "commitment": hexb(v["commitment"], 32), "object": hexb(v["object"], 32),
"pose": read_pose(v["pose"]), "canonical": None if v["canonical"] is None else integer(v["canonical"]),
"frame": hexb(v["frame"], 32), "renderer": term(v["renderer"]), "class": v["class"], "at": integer(v["at"])}
# ---------------------------------------------------------------- §5 the renderer
def rdiv(a, b):
"""a / b to the nearest integer, a tie toward +infinity (b > 0)."""
if "rdiv_floor" in MUTANT:
return a // b
return (2 * a + b) // (2 * b)
def rhu(p, s):
"""R_half_up(p, s) of integer-operators §2."""
return p if s == 0 else (p + (1 << (s - 1))) >> s
def isqrt_ceil(n):
r = math.isqrt(n)
return r if r * r == n else r + 1
def exp_neg_q16(x):
"""exp_neg_q16 of integer-operators §2, x <= 0."""
if "exp_float" in MUTANT:
return int(math.exp(x / 65536.0) * 65536.0)
u = -x
n = u // 45426
r = x + 45426 * n
t = r + 88670
q = (t * t) >> 16
y = ((23495 * q) >> 16) + 22544
return 0 if n >= 63 else y >> n
def q16(v):
"""§4.2: v * 2^16 rounded half up, exactly, from a binary32 value."""
return math.floor(Fraction(v) * 65536 + Fraction(1, 2))
def dot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def cross(a, b):
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
def gaussians(dec):
"""§5.2: the renderable form, or a refusal."""
if on("layout") and dec["planes"] != PLANES:
raise Refuse("layout_mismatch")
lohi = []
for i, (lo, hi) in enumerate(dec["ranges"]):
lq, hq = q16(lo), q16(hi)
if on("ranges") and (abs(lq) > MAX_COORD or abs(hq) > MAX_COORD or (3 <= i < 6 and lq < 0)):
raise Refuse("range_unrenderable")
lohi.append((lq, hq))
cells = dec["s"] * dec["s"]
b = dec["bytes"]
out = []
for k in range(dec["n"]):
a = [lohi[p][0] + ((lohi[p][1] - lohi[p][0]) * b[p * cells + k] + 127) // 255 for p in range(PLANES)]
out.append(a)
return out
def camera(pose):
"""§5.3."""
fw = [pose["target"][i] - pose["eye"][i] for i in range(3)]
nf = math.isqrt(dot(fw, fw))
if nf == 0:
raise Refuse("pose_degenerate")
f = [rdiv(x << 14, nf) for x in fw]
rr = cross(fw, pose["up"])
nr = math.isqrt(dot(rr, rr))
if nr == 0:
raise Refuse("pose_degenerate")
r = [rdiv(x << 14, nr) for x in rr]
u = [rhu(x, 14) for x in cross(r, f)]
return r, u, f
def rotation(w, x, y, z):
n = w * w + x * x + y * y + z * z
if n == 0:
return [[1 << 16, 0, 0], [0, 1 << 16, 0], [0, 0, 1 << 16]]
def e(num):
return rdiv(num << 16, n)
return [[e(w * w + x * x - y * y - z * z), e(2 * (x * y - w * z)), e(2 * (x * z + w * y))],
[e(2 * (x * y + w * z)), e(w * w - x * x + y * y - z * z), e(2 * (y * z - w * x))],
[e(2 * (x * z - w * y)), e(2 * (y * z + w * x)), e(w * w - x * x - y * y + z * z)]]
def project(a, pose, cam):
"""§5.4-§5.5: the Gaussian's key and what compositing reads, or None where it is culled."""
r, u, f = cam
w, h, fl = pose["width"], pose["height"], pose["focal"]
d = [a[i] - pose["eye"][i] for i in range(3)]
xc, yc, zc = rhu(dot(r, d), 14), rhu(dot(u, d), 14), rhu(dot(f, d), 14)
if zc < (0 if "near_zero" in MUTANT else NEAR) or zc <= 0:
return None
key = zc if "sort_by_z" in MUTANT else dot(d, d)
mx = 128 * w + rdiv(256 * fl * xc, zc)
my = 128 * h - rdiv(256 * fl * yc, zc)
lx, ly = rdiv(13 * w * zc, 20 * fl), rdiv(13 * h * zc, 20 * fl)
xcl, ycl = min(max(xc, -lx), lx), min(max(yc, -ly), ly)
j00 = rdiv(fl << 32, zc)
j02 = rdiv((-fl * xcl) << 32, zc * zc)
j11 = -j00
j12 = rdiv((fl * ycl) << 32, zc * zc)
t = [[rhu(j00 * r[k] + j02 * f[k], 14) for k in range(3)], [rhu(j11 * u[k] + j12 * f[k], 14) for k in range(3)]]
rot = rotation(a[6], a[7], a[8], a[9])
s = a[3:6]
m = [[rhu(rot[i][j] * s[j], 16) for j in range(3)] for i in range(3)]
s3 = [[sum(m[i][k] * m[j][k] for k in range(3)) for j in range(3)] for i in range(3)]
uu = [[sum(t[i][j] * s3[j][k] for j in range(3)) for k in range(3)] for i in range(2)]
def vv(i, l):
return rhu(sum(uu[i][k] * t[l][k] for k in range(3)), 48)
dil = 0 if "no_dilation" in MUTANT else DILATION
ca, cb, cc = vv(0, 0) + dil, vv(0, 1), vv(1, 1) + dil
if ca <= 0 or cc <= 0:
return None
if "rect_floor" in MUTANT:
rx, ry = math.isqrt(9 * ca) // 256, math.isqrt(9 * cc) // 256
else:
rx, ry = (isqrt_ceil(9 * ca) + 255) // 256, (isqrt_ceil(9 * cc) + 255) // 256
if rx > MAX_RADIUS or ry > MAX_RADIUS:
return None
x0 = max(0, -((-(mx - 256 * rx - 128)) // 256))
x1 = min(w - 1, (mx + 256 * rx - 128) // 256)
y0 = max(0, -((-(my - 256 * ry - 128)) // 256))
y1 = min(h - 1, (my + 256 * ry - 128) // 256)
if x0 > x1 or y0 > y1:
return None
det = ca * cc - cb * cb
if det <= 0:
return None
k1 = rdiv(1 << 40, math.isqrt(ca << 32))
sq = math.isqrt((ca * det) << 16)
k2 = rdiv(ca << 32, sq)
k3 = rdiv((-cb) << 32, sq)
if max(abs(k1), abs(k2), abs(k3)) > (1 << 31) - 1:
return None
def c16(x):
return min(max(x, 0), 1 << 16)
return key, (mx, my, x0, x1, y0, y1, k1, k2, k3, c16(a[13]), (c16(a[10]), c16(a[11]), c16(a[12])))
def alpha_at(g, i, j):
"""§5.6: the alpha a Gaussian gives pixel (i, j), or None where it adds nothing."""
mx, my, _, _, _, _, k1, k2, k3, o, _ = g
dx = 256 * i + 128 - mx
dy = 256 * j + 128 - my
e1 = k1 * dx
e2 = k3 * dx + k2 * dy
if abs(e1) > E_MAX or abs(e2) > E_MAX:
return None
a1, a2 = (e1 + 2048) >> 12, (e2 + 2048) >> 12
d2 = (a1 * a1 + a2 * a2 + 128) >> 8
if d2 > CUTOFF and "no_cutoff" not in MUTANT:
return None
w = exp_neg_q16(-(d2 >> 1))
alpha = (o * w) >> 16
if "no_alpha_max" not in MUTANT:
alpha = min(alpha, ALPHA_MAX)
return alpha if alpha >= ALPHA_MIN else None
def render(gs, pose):
"""§5: the frame, RGB8, rows top to bottom."""
cam = camera(pose)
kept = []
for idx, a in enumerate(gs):
p = project(a, pose, cam)
if p is not None:
kept.append((p[0], -idx if "ties_desc" in MUTANT else idx, p[1]))
kept.sort(key=lambda e: (e[0], e[1]))
w, h = pose["width"], pose["height"]
T = [1 << 16] * (w * h)
acc = [[0, 0, 0] for _ in range(w * h)]
done = [False] * (w * h)
for _, _, g in kept:
_, _, x0, x1, y0, y1 = g[:6]
col = g[10]
for j in range(y0, y1 + 1):
for i in range(x0, x1 + 1):
p = j * w + i
if done[p]:
continue
alpha = alpha_at(g, i, j)
if alpha is None:
continue
if "contrib_floor" in MUTANT:
contrib = (alpha * T[p]) >> 16
else:
contrib = (alpha * T[p] + (1 << 15)) >> 16
tn = T[p] - contrib
if tn < T_MIN and "stop_after" not in MUTANT:
done[p] = True
continue
for ch in range(3):
acc[p][ch] += (col[ch] * contrib + (1 << 15)) >> 16
T[p] = tn
if tn < T_MIN:
done[p] = True
out = bytearray()
for p in range(w * h):
for ch in range(3):
out.append(min(255, (acc[p][ch] * 255 + (1 << 15)) >> 16))
return bytes(out)
RENDERS = {}
def frame_digest(gs, gkey, pose):
"""The SHA-256 of a frame, remembered per splat set, pose and renderer rules."""
k = (gkey, pose_key(pose), tuple(sorted(MUTANT)))
if k not in RENDERS:
RENDERS[k] = hashlib.sha256(render(gs, pose)).digest()
return RENDERS[k]
# ---------------------------------------------------------------- §3 carriage and the decode
def buffer_view(g, bufs, index):
need(isinstance(index, int) and not isinstance(index, bool) and index >= 0)
bv = g["bufferViews"][index]
b = g["buffers"][bv["buffer"]]
data = bufs[b["uri"]]
need(b["byteLength"] == len(data))
off, n = bv.get("byteOffset", 0), bv["byteLength"]
need(isinstance(off, int) and isinstance(n, int) and off >= 0 and n >= 0 and off + n <= len(data))
return data[off:off + n]
def read_asset(g, bufs):
"""§3.1-§3.4, the rules that need no decode."""
try:
need(g["asset"]["version"] == "2.0")
found = [p for m in g["meshes"] for p in m["primitives"] if BASE in p.get("extensions", {})]
need(len(found) == 1)
p = found[0]
base = p["extensions"][BASE]
if on("extensions_required"):
for name in (BASE, EXT):
need(name in g.get("extensionsUsed", []) and name in g.get("extensionsRequired", []))
if on("primitive"):
need(p.get("mode") == 0 and base.get("kernel") == "ellipse" and base.get("colorSpace") == "srgb_rec709_display")
need(base.get("projection", "perspective") == "perspective" and base.get("sortingMethod", "cameraDistance") == "cameraDistance")
attrs = p["attributes"]
need(isinstance(attrs, dict) and len(attrs) == len(ATTRIBUTES))
accessors = []
for name, ty in ATTRIBUTES:
a = g["accessors"][attrs[name]]
need("bufferView" not in a and a.get("componentType") == FLOAT and a.get("type") == ty)
integer(a["count"])
accessors.append(a)
ext = base["extensions"][EXT]
env = buffer_view(g, bufs, ext["bufferView"])
if on("capability") and ext["capability"] != CAPABILITY:
if isinstance(ext["capability"], str):
raise Refuse("capability_mismatch")
raise Bad()
obj = hexb(ext["object"], 32)
set_sha = hexb(ext["set"]["sha256"], 32)
set_inline = buffer_view(g, bufs, ext["set"]["bufferView"]) if "bufferView" in ext["set"] else None
ctext = None
if "commitment" in ext:
ctext = buffer_view(g, bufs, ext["commitment"]).decode("utf-8")
except (Bad, KeyError, TypeError, IndexError, AttributeError, UnicodeDecodeError):
raise Refuse("asset_malformed")
return {"envelope": env, "object": obj, "set": set_sha, "set_inline": set_inline, "commitment": ctext, "accessors": accessors}
def read_envelope(b):
"""SPEC §2: magic, manifest, payload, and nothing after it."""
try:
need(b[:4] == b"WAI1")
ml = struct.unpack_from("<I", b, 4)[0]
manifest = json.loads(b[8:8 + ml].decode("utf-8"))
pl = struct.unpack_from("<I", b, 8 + ml)[0]
need(12 + ml + pl == len(b) or not on("envelope_length"))
return manifest, b[12 + ml:]
except (Bad, ValueError, struct.error, UnicodeDecodeError):
raise Refuse("asset_malformed")
DECODES = {}
def decode_wis1(payload, set_bytes):
"""integer-payloads §6 through the second implementations named above."""
key = (hashlib.sha256(payload).digest(), hashlib.sha256(set_bytes).digest())
if key in DECODES:
return DECODES[key]
try:
v = IPV.parse_wis1(payload)
need(set_bytes[:4] == b"WHM1" and struct.unpack_from("<I", set_bytes, 4)[0] == v["afrac"])
g = OPS.load(OPS.whm1_lower(set_bytes, True))
r = IPV.Rans(v["rans"])
lat = []
for ch in range(v["c"]):
for _ in range(v["sh"] * v["sw"]):
s = r.decode(v["cdfs"][ch], v["offsets"][ch])
need(not r.exhausted)
lat.append(s * (1 << v["afrac"]) + v["median_q"][ch])
need(r.i == len(r.w) and r.x == 1 << 31)
out = OPS.run(g, [OPS.T(v["c"], v["sh"], v["sw"], lat)], [], {})[0]
need((out.c, out.h, out.w) == (v["attr"], v["s"], v["s"]))
res = {"bytes": bytes(out.d), "planes": v["attr"], "s": v["s"], "n": v["n_gaussians"], "ranges": v["ranges"]}
except Exception as e: # noqa: BLE001 - every refusal of the decode is one code here
if isinstance(e, (KeyboardInterrupt, SystemExit)):
raise
res = None
DECODES[key] = res
return res
def decode(c, held):
"""§3.3: the envelope, its pin against the set, the decode."""
manifest, payload = read_envelope(c["envelope"])
try:
cap = manifest["model_requirement"]["capability"]
except (KeyError, TypeError):
raise Refuse("asset_malformed")
if on("capability") and cap != CAPABILITY:
raise Refuse("capability_mismatch")
if on("object") and blake3(OBJECT_DOMAIN + c["envelope"]) != c["object"]:
raise Refuse("object_mismatch")
pin = (manifest.get("conditioning") or {}).get("prior") or {}
if on("pin") and pin.get("sha256") != c["set"].hex():
raise Refuse("set_mismatch")
if c["set_inline"] is not None:
set_bytes = c["set_inline"]
else:
found = [b for b in held if hashlib.sha256(b).digest() == c["set"]]
if not found:
raise Refuse("set_not_held")
set_bytes = found[0]
if on("set_digest") and hashlib.sha256(set_bytes).digest() != c["set"]:
raise Refuse("set_mismatch")
d = decode_wis1(payload, set_bytes)
if d is None:
raise Refuse("decode_refused")
return d
def check_accessors(c, d):
"""§3.4: every count is the Gaussian count; POSITION's min and max are the dequantized extremes."""
if not on("accessors"):
return
try:
need(d["planes"] >= 3)
for a in c["accessors"]:
need(a["count"] == d["n"])
cells = d["s"] * d["s"]
pos = c["accessors"][0]
need(len(pos["min"]) == 3 and len(pos["max"]) == 3)
for axis in range(3):
lq, hq = q16(d["ranges"][axis][0]), q16(d["ranges"][axis][1])
qs = [lq + ((hq - lq) * d["bytes"][axis * cells + k] + 127) // 255 for k in range(d["n"])]
need(pos["min"][axis] == min(qs) / 65536 and pos["max"][axis] == max(qs) / 65536)
except (Bad, KeyError, TypeError, IndexError):
raise Refuse("asset_malformed")
# ---------------------------------------------------------------- §8 the origin claim
def check_origin(text, link, obj, cls):
"""§9.2 step 6: the origin claim the commitment names (synthesis §2, §3.1, §3.2)."""
try:
v = strict_json(text)
need(v.get("kind") == "wai-origin")
need(ED.verifies(hexb(v["signer_pubkey"], 32), signed(v, SYN_SIGN), hexb(v["sig"], 64)))
need(blake3(SYN_LINK + jcs(v)) == link)
need(v["subject"] == {"type": "object", "content_hash": obj.hex()})
need(v["class"] == cls)
composed = max([CLASSES.index(STEP_CLASS[v["step"]])] + [CLASSES.index(i["class"]) for i in v["inputs"]])
need(CLASSES[composed] == v["class"])
except (Bad, KeyError, TypeError, ValueError, AttributeError):
raise Refuse("origin_mismatch", "origin")
# ---------------------------------------------------------------- §9 verification
def verify_case(case, files, held_sets):
asset = case["asset"]
g = json.loads(files[asset])
d0 = os.path.dirname(asset)
bufs = {}
for b in g.get("buffers", []):
path = os.path.join(d0, b["uri"]) if d0 else b["uri"]
if path in files:
bufs[b["uri"]] = files[path]
held = [held_sets[p] for p in case["held"]]
try:
c = read_asset(g, bufs)
d = decode(c, held)
check_accessors(c, d)
except Refuse as r:
raise Refuse(r.reason, "asset")
attributes = hashlib.sha256(d["bytes"]).digest()
report = {"object": c["object"].hex(), "attributes": attributes.hex(), "n_gaussians": d["n"], "frames": [], "statements": []}
if c["commitment"] is None:
return report
try:
cm = read_record(c["commitment"], "wai-splat-commitment", COMMITMENT, commitment_fields)
if on("commitment_object") and cm["object"] != c["object"]:
raise Refuse("object_mismatch")
if on("commitment_set") and cm["set"] != c["set"]:
raise Refuse("set_mismatch")
if on("attributes") and cm["attributes"] != attributes:
raise Refuse("attributes_mismatch")
if on("renderer") and cm["renderer"] != RENDERER:
raise Refuse("renderer_unknown")
gs = gaussians(d)
except Refuse as r:
raise Refuse(r.reason, "commitment")
gkey = attributes
for i, v in enumerate(cm["views"]):
try:
got = frame_digest(gs, gkey, v["pose"])
except Refuse as r:
raise Refuse(r.reason, "view %d" % i)
if on("frames") and got != v["frame"]:
raise Refuse("frame_mismatch", "view %d" % i)
report["frames"].append(v["frame"].hex())
if cm["origin"] is not None and case["origin"] is not None and on("origin"):
check_origin(case["origin"], cm["origin"], c["object"], cm["class"])
for n, t in enumerate(case["statements"]):
at = "statement %d" % n
try:
s = read_record(t, "wai-splat-view", STATEMENT, statement_fields)
if on("statement_commitment") and s["commitment"] != cm["link"]:
raise Refuse("commitment_mismatch")
if on("statement_object") and s["object"] != c["object"]:
raise Refuse("object_mismatch")
if on("statement_renderer") and s["renderer"] != RENDERER:
raise Refuse("renderer_unknown")
if s["canonical"] is not None:
i = s["canonical"]
if on("canonical") and (i >= len(cm["views"]) or pose_key(cm["views"][i]["pose"]) != pose_key(s["pose"])):
raise Refuse("pose_not_canonical")
if on("canonical_frame") and i < len(cm["views"]) and cm["views"][i]["frame"] != s["frame"]:
raise Refuse("frame_mismatch")
elif on("own_frame") and frame_digest(gs, gkey, s["pose"]) != s["frame"]:
raise Refuse("frame_mismatch")
want = CLASSES[max(1, CLASSES.index(cm["class"]))]
if on("class") and s["class"] != want:
raise Refuse("class_mismatch")
except Refuse as r:
raise Refuse(r.reason, at)
report["statements"].append({"canonical": s["canonical"], "class": s["class"]})
return report
def outcome(case, files, held_sets):
try:
r = verify_case(case, files, held_sets)
return dict(outcome="accepted", **r)
except Refuse as r:
return {"outcome": "refused", "reason": r.reason, "at": r.at}
def reasons_of_extension():
text = open(EXTENSION, encoding="utf-8").read()
start = text.index("## 10. Reasons")
end = text.index("\n## 11.", start)
return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", text[start:end], re.M)]
def run_all(files, held_sets):
"""Every case against its stated outcome: the names of the cases that differ."""
index = json.loads(files["vectors.json"])
bad = []
for c in index["cases"]:
try:
got = outcome(c, files, held_sets)
except (IndexError, KeyError, TypeError, ValueError, ZeroDivisionError):
# A weakened verifier (step 4 below) can fail on an input a dropped check kept out.
got = {"outcome": "error"}
if got != c["expect"]:
bad.append(c["name"])
return bad
def png(path, w, h, rgb):
raw = b"".join(b"\x00" + rgb[y * w * 3:(y + 1) * w * 3] for y in range(h))
def chunk(t, d):
return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xFFFFFFFF)
with open(path, "wb") as f:
f.write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
def main(argv):
if len(argv) < 2:
print(__doc__)
return 2
root = argv[1]
files = {}
for dp, _, names in os.walk(root):
for n in names:
p = os.path.join(dp, n)
rel = os.path.relpath(p, root).replace(os.sep, "/")
if rel != "README.md":
files[rel] = open(p, "rb").read()
index = json.loads(files["vectors.json"])
held_sets = {}
for c in index["cases"]:
for p in c["held"]:
held_sets[p] = open(os.path.join(WAI, p), "rb").read()
fails = 0
def check(ok, what):
nonlocal fails
print(("PASS " if ok else "FAIL ") + what)
fails += 0 if ok else 1
# 1. Every case.
for c in index["cases"]:
got = outcome(c, files, held_sets)
check(got == c["expect"], "%s: %s%s" % (c["name"], got["outcome"],
"" if got["outcome"] == "accepted" else " %s at %s" % (got["reason"], got["at"])))
if got != c["expect"]:
print(" stated:", json.dumps(c["expect"])[:300])
print(" got: ", json.dumps(got)[:300])
# 2. Every reason of §10 refuses a case, and the corpus gives no other.
table = reasons_of_extension()
given = {c["expect"].get("reason") for c in index["cases"]} - {None}
check(set(table) == given, "every reason of §10 refuses a case (%d codes), and no case gives another" % len(table))
if "--png" in argv:
out = argv[argv.index("--png") + 1]
os.makedirs(out, exist_ok=True)
for c in index["cases"]:
if c["expect"]["outcome"] != "accepted" or not c["expect"]["frames"]:
continue
g = json.loads(files[c["asset"]])
bufs = {b["uri"]: files[os.path.join(os.path.dirname(c["asset"]), b["uri"])] for b in g["buffers"]}
a = read_asset(g, bufs)
d = decode(a, [held_sets[p] for p in c["held"]])
cm = json.loads(a["commitment"])
gs = gaussians(d)
for i, v in enumerate(cm["views"]):
pose = read_pose(v["pose"])
png(os.path.join(out, "%s_%d.png" % (c["name"], i)), pose["width"], pose["height"], render(gs, pose))
# 3. Mutated copies of the corpus fail.
def mut_bytes(path, at, x=1):
def f(fs):
b = bytearray(fs[path])
b[at % len(b)] ^= x
fs[path] = bytes(b)
return f
def mut_index(fn):
def f(fs):
v = json.loads(fs["vectors.json"])
fn(v)
fs["vectors.json"] = json.dumps(v).encode()
return f
def mut_gltf(path, fn):
def f(fs):
g = json.loads(fs[path])
fn(g)
fs[path] = json.dumps(g).encode()
return f
def flip_frame(statements, i):
t = statements[i]
at = t.index('"frame":"') + len('"frame":"')
statements[i] = t[:at] + ("1" if t[at] == "0" else "0") + t[at + 1:]
def case_named(v, name):
return next(c for c in v["cases"] if c["name"] == name)
mutations = [
("a byte of the tiny scene's latent stream", mut_bytes("assets/tiny.bin", 900)),
("a byte of the cluster's envelope manifest", mut_bytes("assets/cluster.bin", 40)),
("a byte of the identity set inline in the cluster's buffer", mut_bytes("assets/cluster.bin", -300)),
("a digit of a frame in the cluster's commitment", mut_bytes("assets/cluster.commitment.json", 600)),
("the shell's POSITION min", mut_gltf("assets/shell.gltf", lambda g: g["accessors"][0]["min"].__setitem__(0, 0.0))),
("the cluster's kernel", mut_gltf("assets/cluster.gltf",
lambda g: g["meshes"][0]["primitives"][0]["extensions"][BASE].__setitem__("kernel", "box"))),
("the shell case's held set dropped", mut_index(lambda v: case_named(v, "shell").__setitem__("held", []))),
("the tiny case's origin claim dropped", mut_index(lambda v: case_named(v, "statement_class").__setitem__("statements", []))),
("a digit of a statement's frame", mut_index(lambda v: flip_frame(case_named(v, "cluster")["statements"], 2))),
("a stated frame", mut_index(lambda v: case_named(v, "tiny")["expect"]["frames"].__setitem__(0, "00" * 32))),
("a stated reason", mut_index(lambda v: case_named(v, "cluster_reordered")["expect"].__setitem__("reason", "pose_degenerate"))),
]
for what, f in mutations:
fs = dict(files)
f(fs)
check(fs != files and bool(run_all(fs, held_sets)), "mutation caught: " + what)
# 4. Each check dropped in turn: the corpus catches the weakened verifier.
for name in ["signature", "layout", "ranges", "extensions_required", "primitive", "capability", "object", "pin",
"set_digest", "accessors", "commitment_object", "commitment_set", "attributes", "renderer", "frames", "origin",
"statement_commitment", "statement_object", "statement_renderer", "canonical", "canonical_frame", "own_frame",
"class", "envelope_length"]:
DROPPED.clear()
DROPPED.add(name)
bad = run_all(files, held_sets)
check(bool(bad), "check dropped and caught: %s (%s)" % (name, ", ".join(bad[:3])))
DROPPED.clear()
# 5. The renderer's arithmetic changed, rule by rule: the corpus's frames catch every one.
for name in ["ties_desc", "sort_by_z", "no_dilation", "rdiv_floor", "exp_float", "contrib_floor", "stop_after",
"no_alpha_max", "no_cutoff", "rect_floor", "near_zero"]:
MUTANT.clear()
MUTANT.add(name)
bad = run_all(files, held_sets)
check(bool(bad), "renderer rule changed and caught: %s (%s)" % (name, ", ".join(bad[:3])))
MUTANT.clear()
print("%s: %d failure(s)" % ("PASS" if fails == 0 else "FAIL", fails))
return 1 if fails else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))