// BLAKE3 in hash mode for an input of one chunk (at most 1024 bytes), written from the BLAKE3 // specification: the chunk's blocks compressed in turn from the IV, the first with CHUNK_START, // the last with CHUNK_END and ROOT, and the root output's first 32 bytes. That is all the // settlement tree root needs (jwp-receipts ยง2.2): a leaf hashes 52 bytes and a node 84. // An input longer than one chunk is refused, never hashed wrongly. const IV = Uint32Array.of(0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19); const PERMUTE = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]; const CHUNK_START = 1; const CHUNK_END = 2; const ROOT = 8; const rotr = (x, n) => ((x >>> n) | (x << (32 - n))) >>> 0; function g(s, a, b, c, d, x, y) { s[a] = (s[a] + s[b] + x) >>> 0; s[d] = rotr(s[d] ^ s[a], 16); s[c] = (s[c] + s[d]) >>> 0; s[b] = rotr(s[b] ^ s[c], 12); s[a] = (s[a] + s[b] + y) >>> 0; s[d] = rotr(s[d] ^ s[a], 8); s[c] = (s[c] + s[d]) >>> 0; s[b] = rotr(s[b] ^ s[c], 7); } /** The compression function: chaining value `cv`, 16 message words, counter 0, `len` bytes, `flags`. */ function compress(cv, m, len, flags) { const s = Uint32Array.of(...cv, IV[0], IV[1], IV[2], IV[3], 0, 0, len, flags); let w = Uint32Array.from(m); for (let r = 0; r < 7; r++) { g(s, 0, 4, 8, 12, w[0], w[1]); g(s, 1, 5, 9, 13, w[2], w[3]); g(s, 2, 6, 10, 14, w[4], w[5]); g(s, 3, 7, 11, 15, w[6], w[7]); g(s, 0, 5, 10, 15, w[8], w[9]); g(s, 1, 6, 11, 12, w[10], w[11]); g(s, 2, 7, 8, 13, w[12], w[13]); g(s, 3, 4, 9, 14, w[14], w[15]); if (r < 6) w = Uint32Array.from(PERMUTE, (i) => w[i]); } const out = new Uint32Array(8); for (let i = 0; i < 8; i++) out[i] = (s[i] ^ s[i + 8]) >>> 0; return out; } /** BLAKE3 of `input` (a Uint8Array of at most 1024 bytes), 32 bytes. */ export function blake3(input) { if (input.length > 1024) throw new RangeError("this BLAKE3 hashes one chunk, at most 1024 bytes"); const blocks = Math.max(1, Math.ceil(input.length / 64)); let cv = IV; for (let k = 0; k < blocks; k++) { const part = input.subarray(64 * k, 64 * k + 64); const block = new Uint8Array(64); block.set(part); const dv = new DataView(block.buffer); const m = Array.from({ length: 16 }, (_, i) => dv.getUint32(4 * i, true)); let flags = 0; if (k === 0) flags |= CHUNK_START; if (k === blocks - 1) flags |= CHUNK_END | ROOT; cv = compress(cv, m, part.length, flags); } const out = new Uint8Array(32); const dv = new DataView(out.buffer); for (let i = 0; i < 8; i++) dv.setUint32(4 * i, cv[i], true); return out; }