wai-rs gains the feature floor (the mandatory floor of SPEC 5: PNG, FLAC and zstd, decoded with no system codec library) and quickstart (the floor, and the receipt checks hello and verify run). The wai command line gains four verbs, one module each, named in one table (dx::VERBS): - hello: name, dispatch, rebuild and receipt on a picture made here, the receipt laid out as http-delivery 8 lays receipts beside an object; - create: a new project from one of the examples, whose files the binary holds; tests/cli_dx.rs holds them to wai/examples/ byte for byte; - dev: the relay, the room service and the room page on one loopback origin, as a production front serves them; --check checks each piece through it; - verify: the receipt indexes under a directory, checked offline. The project file a template writes, wai.json, is read in one place (dx/project.rs). wai/examples/ holds video-room, watch-together, messaging, encoder-ingest, package-and-deliver, verify-receipts, apple-app, android-app and remote-desktop, each with a README and a test that runs it through wai create and wai dev. wai/quickstart/ builds the room client and the archive that holds wai, the relay, the room service, the remote host and the packager. Co-Authored-By: Claude Opus 5.5 <[email protected]>
113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
"""BLAKE3 in hash mode, written from its specification in pure Python.
|
|
|
|
Shared by the claim-conformance verifier (`claim_verify.py`), the staged-delivery
|
|
verifier (`../staged-conformance/staged_verify.py`) and the HTTP delivery verifier
|
|
(`../http-conformance/http_verify.py`), which import from here. It shares no code with
|
|
wai-rs, which computes the same hashes with the `blake3` crate.
|
|
|
|
`subtree_cv` and `parent_cv` expose the tree (the BLAKE3 specification, §2.1): the
|
|
non-root chaining value of a subtree that starts at a given byte offset of the whole
|
|
input, and a parent node with or without the ROOT flag. The HTTP head proof is built of
|
|
these (http-delivery §6).
|
|
"""
|
|
|
|
_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])
|
|
|
|
|
|
def _bytes(words):
|
|
return b"".join(x.to_bytes(4, "little") for x in words)
|
|
|
|
|
|
def _left_chunks(n):
|
|
"""The left subtree of `n` chunks (n >= 2): the largest power of two below n."""
|
|
return 1 << ((n - 1).bit_length() - 1)
|
|
|
|
|
|
def subtree_cv(data: bytes, start: int) -> bytes:
|
|
"""The non-root chaining value of `data` as a whole subtree of the input whose first
|
|
chunk is at byte offset `start` (a multiple of 1024): chunk counters run from
|
|
start // 1024."""
|
|
assert start % 1024 == 0
|
|
chunks = [data[i:i + 1024] for i in range(0, len(data), 1024)] or [b""]
|
|
|
|
def node(cs, c0):
|
|
if len(cs) == 1:
|
|
return _cv(_chunk_output(cs[0], c0))
|
|
k = _left_chunks(len(cs))
|
|
return _cv((_IV, node(cs[:k], c0) + node(cs[k:], c0 + k), 0, 64, _PARENT))
|
|
|
|
return _bytes(node(chunks, start // 1024))
|
|
|
|
|
|
def parent_cv(left: bytes, right: bytes, root: bool = False) -> bytes:
|
|
"""A parent node over two chaining values: its chaining value, or with `root` the
|
|
hash it outputs as the root."""
|
|
m = [int.from_bytes((left + right)[i:i + 4], "little") for i in range(0, 64, 4)]
|
|
return _bytes(_compress(_IV, m, 0, 64, _PARENT | (_ROOT if root else 0))[:8])
|