Spaces:
Running
Running
File size: 6,066 Bytes
999eb85 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | // Incremental SHA-256 (FIPS 180-4).
//
// crypto.subtle.digest() is one-shot: it wants the whole input as a single
// ArrayBuffer. A quantised GGUF is hundreds of MB (and can be several GB), so
// hashing it that way would mean holding the entire model in JS memory twice.
// This implementation takes the bytes 4 MiB at a time as they stream out of
// IndexedDB (or off the user's disk) and keeps a 64 byte block of state.
//
// Verified against node's crypto.createHash("sha256") in test/sha256-test.mjs,
// including every padding boundary and non-block-aligned streaming.
const K = new Uint32Array([
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
]);
export class Sha256 {
constructor() {
this.h = new Uint32Array([
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
]);
this.w = new Uint32Array(64);
this.block = new Uint8Array(64);
this.blockLen = 0;
this.bytes = 0;
this.finished = false;
}
update(data) {
if (this.finished) throw new Error("Sha256: update() after digest()");
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
const len = bytes.length;
this.bytes += len;
let off = 0;
// top up a partially filled block first
if (this.blockLen > 0) {
const need = 64 - this.blockLen;
if (len < need) {
this.block.set(bytes, this.blockLen);
this.blockLen += len;
return this;
}
this.block.set(bytes.subarray(0, need), this.blockLen);
this._compress(this.block, 0);
this.blockLen = 0;
off = need;
}
while (off + 64 <= len) {
this._compress(bytes, off);
off += 64;
}
if (off < len) {
this.block.set(bytes.subarray(off), 0);
this.blockLen = len - off;
}
return this;
}
_compress(buf, off) {
const w = this.w;
const h = this.h;
for (let i = 0; i < 16; i++) {
const j = off + i * 4;
w[i] = (buf[j] << 24) | (buf[j + 1] << 16) | (buf[j + 2] << 8) | buf[j + 3];
}
for (let i = 16; i < 64; i++) {
const a = w[i - 15];
const b = w[i - 2];
const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
}
let a = h[0], b = h[1], c = h[2], d = h[3];
let e = h[4], f = h[5], g = h[6], k = h[7];
for (let i = 0; i < 64; i++) {
const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
const ch = (e & f) ^ (~e & g);
const t1 = (k + S1 + ch + K[i] + w[i]) | 0;
const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
const maj = (a & b) ^ (a & c) ^ (b & c);
const t2 = (S0 + maj) | 0;
k = g; g = f; f = e;
e = (d + t1) | 0;
d = c; c = b; b = a;
a = (t1 + t2) | 0;
}
h[0] = (h[0] + a) | 0;
h[1] = (h[1] + b) | 0;
h[2] = (h[2] + c) | 0;
h[3] = (h[3] + d) | 0;
h[4] = (h[4] + e) | 0;
h[5] = (h[5] + f) | 0;
h[6] = (h[6] + g) | 0;
h[7] = (h[7] + k) | 0;
}
// 64 hex characters, lowercase; the object is unusable afterwards
digestHex() {
if (this.finished) throw new Error("Sha256: digest() twice");
this.finished = true;
const tail = new Uint8Array(this.blockLen < 56 ? 64 : 128);
tail.set(this.block.subarray(0, this.blockLen));
tail[this.blockLen] = 0x80;
// message length in bits, as a 64-bit big-endian integer
const bits = this.bytes * 8;
const hi = Math.floor(bits / 4294967296);
const lo = bits % 4294967296;
const end = tail.length;
tail[end - 8] = (hi >>> 24) & 0xff;
tail[end - 7] = (hi >>> 16) & 0xff;
tail[end - 6] = (hi >>> 8) & 0xff;
tail[end - 5] = hi & 0xff;
tail[end - 4] = (lo >>> 24) & 0xff;
tail[end - 3] = (lo >>> 16) & 0xff;
tail[end - 2] = (lo >>> 8) & 0xff;
tail[end - 1] = lo & 0xff;
for (let off = 0; off < tail.length; off += 64) this._compress(tail, off);
let hex = "";
for (let i = 0; i < 8; i++) hex += (this.h[i] >>> 0).toString(16).padStart(8, "0");
return hex;
}
}
/**
* Hash an async iterable of Uint8Array/ArrayBuffer chunks.
*
* @param {AsyncIterable<Uint8Array|ArrayBuffer>} source
* @param {(done:number)=>void} [onProgress] bytes hashed so far
* @returns {Promise<string>} lowercase hex digest
*/
export async function sha256Chunks(source, onProgress) {
const hasher = new Sha256();
let done = 0;
for await (const chunk of source) {
const bytes = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
hasher.update(bytes);
done += bytes.length;
if (onProgress) onProgress(done);
}
return hasher.digestHex();
}
|