staged-conformance/ holds stage sets and the steps a sink takes with them: image layers over a pinned integer image base (identity, up2 to even and odd sizes) and over a PNG base; video layers per frame and per GOP, with prev runs, over integer motion bases and over a pinned neural-keyframe clip; audio layers over a pinned integer audio base, and a stereo rate ladder over a FLAC base; a splat attribute layer; and one case per refusal and order rule (another base, a wrong producer, a missing layer, conflicts held and after an application, out of order, a fallback base, geometry, truncation, a malformed table, q of 0, a word after the last symbol, a stream that runs out, clamped and bypassed residuals, malformed refines, a capability that is not a refinement, a base not admitted, inclusion-only evidence, a cost limit, a missed deadline, the hold bound). Each case's case.json states what a sink reports at every step and holds at the end; schedule.json holds arrival traces and what a sink presents. wai_staged_vectors gen writes the corpus; verify regenerates it byte for byte, replays every case through StageSink, decodes the integer bases with their pins against stage0.bin and checks their tiers. staged_verify.py is a standard-library reimplementation of the envelope, refines, WIR1, the sink's algorithm, tiers and stage hashes, and the PNG, FLAC and WIV1 keyframe-0 decodes; it checks every layer application in every case. The pure-Python BLAKE3 moves from claim_verify.py to _blake3.py, which both verifiers import. A unit held and decided within one step is reported by its decision alone. Co-Authored-By: Claude Opus 5.5 <[email protected]>
75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
"""BLAKE3 in hash mode, written from its specification in pure Python.
|
|
|
|
Shared by the claim-conformance verifier (`claim_verify.py`) and the staged-delivery
|
|
verifier (`../staged-conformance/staged_verify.py`), which import `blake3` from here. It
|
|
shares no code with wai-rs, which computes the same hashes with the `blake3` crate.
|
|
"""
|
|
|
|
_IV = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]
|
|
_PERM = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]
|
|
_CHUNK_START, _CHUNK_END, _PARENT, _ROOT = 1, 2, 4, 8
|
|
_M32 = 0xFFFFFFFF
|
|
|
|
|
|
def _rotr(x, n):
|
|
return ((x >> n) | (x << (32 - n))) & _M32
|
|
|
|
|
|
def _g(s, a, b, c, d, x, y):
|
|
s[a] = (s[a] + s[b] + x) & _M32
|
|
s[d] = _rotr(s[d] ^ s[a], 16)
|
|
s[c] = (s[c] + s[d]) & _M32
|
|
s[b] = _rotr(s[b] ^ s[c], 12)
|
|
s[a] = (s[a] + s[b] + y) & _M32
|
|
s[d] = _rotr(s[d] ^ s[a], 8)
|
|
s[c] = (s[c] + s[d]) & _M32
|
|
s[b] = _rotr(s[b] ^ s[c], 7)
|
|
|
|
|
|
def _compress(cv, m, counter, blen, flags):
|
|
s = list(cv) + _IV[:4] + [counter & _M32, (counter >> 32) & _M32, blen, flags]
|
|
m = list(m)
|
|
for r in range(7):
|
|
_g(s, 0, 4, 8, 12, m[0], m[1]); _g(s, 1, 5, 9, 13, m[2], m[3])
|
|
_g(s, 2, 6, 10, 14, m[4], m[5]); _g(s, 3, 7, 11, 15, m[6], m[7])
|
|
_g(s, 0, 5, 10, 15, m[8], m[9]); _g(s, 1, 6, 11, 12, m[10], m[11])
|
|
_g(s, 2, 7, 8, 13, m[12], m[13]); _g(s, 3, 4, 9, 14, m[14], m[15])
|
|
if r < 6:
|
|
m = [m[i] for i in _PERM]
|
|
return [s[i] ^ s[i + 8] for i in range(8)] + [s[i + 8] ^ cv[i] for i in range(8)]
|
|
|
|
|
|
def _words(block):
|
|
block = block + b"\0" * (64 - len(block))
|
|
return [int.from_bytes(block[i:i + 4], "little") for i in range(0, 64, 4)]
|
|
|
|
|
|
def _chunk_output(chunk, counter):
|
|
blocks = [chunk[i:i + 64] for i in range(0, len(chunk), 64)] or [b""]
|
|
cv = _IV
|
|
for j, b in enumerate(blocks[:-1]):
|
|
cv = _compress(cv, _words(b), counter, 64, _CHUNK_START if j == 0 else 0)[:8]
|
|
last = blocks[-1]
|
|
return (cv, _words(last), counter, len(last), _CHUNK_END | (_CHUNK_START if len(blocks) == 1 else 0))
|
|
|
|
|
|
def _cv(out):
|
|
cv, w, c, bl, f = out
|
|
return _compress(cv, w, c, bl, f)[:8]
|
|
|
|
|
|
def blake3(data: bytes) -> bytes:
|
|
chunks = [data[i:i + 1024] for i in range(0, len(data), 1024)] or [b""]
|
|
stack = []
|
|
for idx, ch in enumerate(chunks[:-1]):
|
|
cv = _cv(_chunk_output(ch, idx))
|
|
total = idx + 1
|
|
while total & 1 == 0:
|
|
cv = _cv((_IV, stack.pop() + cv, 0, 64, _PARENT))
|
|
total >>= 1
|
|
stack.append(cv)
|
|
out = _chunk_output(chunks[-1], len(chunks) - 1)
|
|
while stack:
|
|
out = (_IV, stack.pop() + _cv(out), 0, 64, _PARENT)
|
|
cv, w, _, bl, f = out
|
|
return b"".join(x.to_bytes(4, "little") for x in _compress(cv, w, 0, bl, f | _ROOT)[:8])
|