Spaces:
Running
Running
Upload holo-blake3.mjs with huggingface_hub
Browse files- holo-blake3.mjs +128 -0
holo-blake3.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-blake3.mjs β BLAKE3 (the hologram substrate's Ο-axis, ADR-052), pure JS, lean +
|
| 2 |
+
// browser-native. This is the convergence seam made real: the substrate addresses content as
|
| 3 |
+
// `blake3:<hex>` = standard BLAKE3 of the canonical bytes (proven: its own differential test
|
| 4 |
+
// asserts the blake3 axis == the reference `blake3` crate). Reproducing standard BLAKE3 here β
|
| 5 |
+
// witnessed byte-identical against the substrate's own `kappa()` wasm across chunk boundaries β
|
| 6 |
+
// makes OS2 ΞΊ byte-identical to upstream WITHOUT a 6.5 MB wasm or restating substrate internals
|
| 7 |
+
// (BLAKE3 is the public standard the substrate itself consumes). Implements the official spec:
|
| 8 |
+
// 1024-byte chunks, 64-byte blocks, 7-round compression, binary tree of chaining values.
|
| 9 |
+
|
| 10 |
+
const IV = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
| 11 |
+
const MSG = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
|
| 12 |
+
const CHUNK_START = 1, CHUNK_END = 2, PARENT = 4, ROOT = 8, BLOCK = 64, CHUNK = 1024;
|
| 13 |
+
const rotr = (x, n) => ((x >>> n) | (x << (32 - n))) >>> 0;
|
| 14 |
+
|
| 15 |
+
function g(v, a, b, c, d, mx, my) {
|
| 16 |
+
v[a] = (v[a] + v[b] + mx) >>> 0; v[d] = rotr(v[d] ^ v[a], 16);
|
| 17 |
+
v[c] = (v[c] + v[d]) >>> 0; v[b] = rotr(v[b] ^ v[c], 12);
|
| 18 |
+
v[a] = (v[a] + v[b] + my) >>> 0; v[d] = rotr(v[d] ^ v[a], 8);
|
| 19 |
+
v[c] = (v[c] + v[d]) >>> 0; v[b] = rotr(v[b] ^ v[c], 7);
|
| 20 |
+
}
|
| 21 |
+
function roundFn(v, m) {
|
| 22 |
+
g(v, 0, 4, 8, 12, m[0], m[1]); g(v, 1, 5, 9, 13, m[2], m[3]);
|
| 23 |
+
g(v, 2, 6, 10, 14, m[4], m[5]); g(v, 3, 7, 11, 15, m[6], m[7]);
|
| 24 |
+
g(v, 0, 5, 10, 15, m[8], m[9]); g(v, 1, 6, 11, 12, m[10], m[11]);
|
| 25 |
+
g(v, 2, 7, 8, 13, m[12], m[13]); g(v, 3, 4, 9, 14, m[14], m[15]);
|
| 26 |
+
}
|
| 27 |
+
// compress(cv[8], m[16], counter, blockLen, flags) β out[16]
|
| 28 |
+
function compress(cv, m0, counter, blockLen, flags) {
|
| 29 |
+
const cl = counter >>> 0, ch = Math.floor(counter / 4294967296) >>> 0;
|
| 30 |
+
const v = [cv[0], cv[1], cv[2], cv[3], cv[4], cv[5], cv[6], cv[7],
|
| 31 |
+
IV[0], IV[1], IV[2], IV[3], cl, ch, blockLen >>> 0, flags >>> 0];
|
| 32 |
+
let m = m0.slice();
|
| 33 |
+
for (let r = 0; r < 7; r++) { roundFn(v, m); if (r < 6) { const p = new Array(16); for (let i = 0; i < 16; i++) p[i] = m[MSG[i]]; m = p; } }
|
| 34 |
+
const out = new Array(16);
|
| 35 |
+
for (let i = 0; i < 8; i++) { out[i] = (v[i] ^ v[i + 8]) >>> 0; out[i + 8] = (v[i + 8] ^ cv[i]) >>> 0; }
|
| 36 |
+
return out;
|
| 37 |
+
}
|
| 38 |
+
// read a 64-byte block (zero-padded) into 16 LE u32 words
|
| 39 |
+
function words(bytes, off, len) {
|
| 40 |
+
const m = new Array(16).fill(0);
|
| 41 |
+
for (let i = 0; i < len; i++) m[i >> 2] |= bytes[off + i] << ((i & 3) * 8);
|
| 42 |
+
for (let i = 0; i < 16; i++) m[i] >>>= 0;
|
| 43 |
+
return m;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// An "output" node: its chaining value, or (with ROOT) its 32-byte hash.
|
| 47 |
+
function nodeChainingValue(o) { return compress(o.cv, o.m, o.counter, o.blockLen, o.flags).slice(0, 8); }
|
| 48 |
+
function nodeRootBytes(o) {
|
| 49 |
+
const out = compress(o.cv, o.m, 0, o.blockLen, o.flags | ROOT); // output block counter 0 β first 32 bytes
|
| 50 |
+
const b = new Uint8Array(32);
|
| 51 |
+
for (let i = 0; i < 8; i++) { const w = out[i]; b[i * 4] = w & 255; b[i * 4 + 1] = (w >>> 8) & 255; b[i * 4 + 2] = (w >>> 16) & 255; b[i * 4 + 3] = (w >>> 24) & 255; }
|
| 52 |
+
return b;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// One chunk (β€1024 bytes) β its output node (its last block carries CHUNK_END).
|
| 56 |
+
function chunkNode(bytes, start, len, counter) {
|
| 57 |
+
let cv = IV.slice();
|
| 58 |
+
const nBlocks = Math.max(1, Math.ceil(len / BLOCK));
|
| 59 |
+
let flags = CHUNK_START;
|
| 60 |
+
for (let i = 0; i < nBlocks - 1; i++) { cv = compress(cv, words(bytes, start + i * BLOCK, BLOCK), counter, BLOCK, flags).slice(0, 8); flags = 0; }
|
| 61 |
+
const lastOff = start + (nBlocks - 1) * BLOCK;
|
| 62 |
+
const lastLen = len - (nBlocks - 1) * BLOCK; // 0..64 (0 only when len==0)
|
| 63 |
+
return { cv, m: words(bytes, lastOff, lastLen), counter, blockLen: lastLen, flags: flags | CHUNK_END };
|
| 64 |
+
}
|
| 65 |
+
function parentNode(leftCV, rightCV) {
|
| 66 |
+
const m = leftCV.concat(rightCV);
|
| 67 |
+
return { cv: IV.slice(), m, counter: 0, blockLen: BLOCK, flags: PARENT };
|
| 68 |
+
}
|
| 69 |
+
// Hash a subtree [start, start+len) starting at chunk index `counter` β an output node.
|
| 70 |
+
function subtree(bytes, start, len, counter) {
|
| 71 |
+
if (len <= CHUNK) return chunkNode(bytes, start, len, counter);
|
| 72 |
+
let left = CHUNK; while (left * 2 < len) left *= 2; // largest power-of-two chunk span < len
|
| 73 |
+
const leftCV = nodeChainingValue(subtree(bytes, start, left, counter));
|
| 74 |
+
const rightCV = nodeChainingValue(subtree(bytes, start + left, len - left, counter + left / CHUNK));
|
| 75 |
+
return parentNode(leftCV, rightCV);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
export function blake3(bytes) {
|
| 79 |
+
const b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
| 80 |
+
return nodeRootBytes(subtree(b, 0, b.length, 0));
|
| 81 |
+
}
|
| 82 |
+
export function blake3hex(bytes) {
|
| 83 |
+
const d = blake3(bytes); let s = "";
|
| 84 |
+
for (let i = 0; i < 32; i++) s += d[i].toString(16).padStart(2, "0");
|
| 85 |
+
return s;
|
| 86 |
+
}
|
| 87 |
+
// the hologram Ο-axis ΞΊ-label: "blake3:" + 64 hex (the 71-byte ContentLabel).
|
| 88 |
+
export function kappaBlake3(bytes) { return "blake3:" + blake3hex(bytes); }
|
| 89 |
+
|
| 90 |
+
// ββ tree internals (the Ο-axis Merkle structure) βββββββββββββββββββββββββββββββββββββ
|
| 91 |
+
// Exposed so holo-bao.mjs can build verified-streaming (Bao) proofs against the SAME ONE
|
| 92 |
+
// implementation (Law L2 β no second hash). These are the standard BLAKE3 tree pieces:
|
| 93 |
+
// a chunk β output node, two CVs β parent node, a node β its chaining value or ROOT bytes.
|
| 94 |
+
export const CHUNK_BYTES = CHUNK;
|
| 95 |
+
export { chunkNode, parentNode, nodeChainingValue, nodeRootBytes, subtree };
|
| 96 |
+
|
| 97 |
+
// ββ incremental hasher (stream-mint without a tail pass) βββββββββββββββββββββββββββββ
|
| 98 |
+
// BLAKE3's streaming Hasher: feed chunks via update(), get the SAME digest as one-shot blake3() at
|
| 99 |
+
// digest()/hex(). Buffers at most ONE 1024-byte chunk; each completed chunk's CV folds into a stack via the
|
| 100 |
+
// standard add-chunk-cv merge (collapse while the chunk count is even), and digest() merges the final chunk
|
| 101 |
+
// down the stack with the ROOT flag β the canonical left-balanced tree, byte-identical to blake3() across
|
| 102 |
+
// chunk boundaries. Lets the streaming seam mint a ΞΊ AS bytes arrive instead of re-hashing the whole buffer.
|
| 103 |
+
export function createBlake3() {
|
| 104 |
+
const stack = []; // CVs of completed left subtrees (bottom = leftmost)
|
| 105 |
+
let counter = 0; // index of the current chunk
|
| 106 |
+
const buf = new Uint8Array(CHUNK);
|
| 107 |
+
let bufLen = 0;
|
| 108 |
+
const add = (cv, totalChunks) => { let t = totalChunks, c = cv; while ((t & 1) === 0) { c = nodeChainingValue(parentNode(stack.pop(), c)); t >>= 1; } stack.push(c); };
|
| 109 |
+
const api = {
|
| 110 |
+
update(bytes) {
|
| 111 |
+
const b = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
| 112 |
+
let off = 0; const n = b.length;
|
| 113 |
+
while (off < n) {
|
| 114 |
+
if (bufLen === CHUNK) { const cv = nodeChainingValue(chunkNode(buf, 0, CHUNK, counter)); counter++; add(cv, counter); bufLen = 0; }
|
| 115 |
+
const take = Math.min(CHUNK - bufLen, n - off);
|
| 116 |
+
buf.set(b.subarray(off, off + take), bufLen); bufLen += take; off += take;
|
| 117 |
+
}
|
| 118 |
+
return api;
|
| 119 |
+
},
|
| 120 |
+
digest() {
|
| 121 |
+
let node = chunkNode(buf, 0, bufLen, counter);
|
| 122 |
+
for (let i = stack.length - 1; i >= 0; i--) node = parentNode(stack[i], nodeChainingValue(node));
|
| 123 |
+
return nodeRootBytes(node);
|
| 124 |
+
},
|
| 125 |
+
hex() { const d = api.digest(); let s = ""; for (let i = 0; i < 32; i++) s += d[i].toString(16).padStart(2, "0"); return s; },
|
| 126 |
+
};
|
| 127 |
+
return api;
|
| 128 |
+
}
|