Humuhumu33 commited on
Commit
7b5c4b3
Β·
verified Β·
1 Parent(s): a349783

Upload holo-blake3.mjs with huggingface_hub

Browse files
Files changed (1) hide show
  1. 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
+ }