Run SHA-256 as WebAssembly with JS fallback; note ffmpeg.wasm as deferred
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
/* ============================================================================
|
||||
* sha256.js — incremental SHA-256 (pure JS; window.Sha256 / worker / node)
|
||||
*
|
||||
* The block function runs as WebAssembly (wasm/sha256.c → sha256-wasm.js) when the
|
||||
* browser allows it, with the pure-JS version below as the fallback — a failed
|
||||
* load or a failed self-test silently keeps hashing in JS.
|
||||
*
|
||||
* WebCrypto's digest() needs the whole input at once, which would pull a
|
||||
* multi-hundred-MB video into memory. This hasher takes chunks as they stream
|
||||
* past (downloads, peer transfers, OPFS reads) and keeps ~100 bytes of state.
|
||||
@@ -22,7 +26,41 @@
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
]);
|
||||
|
||||
function create() {
|
||||
// ---- WebAssembly engine (optional) -------------------------------------
|
||||
// Memory layout shared with wasm/sha256.c: state at STATE, input blocks at DATA.
|
||||
const STATE = 16384, DATA = 65536, CHUNK = 1 << 20; // 1 MiB staging window
|
||||
const IV = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
||||
let wasm; // undefined = not tried yet, null = unavailable, else { compress, state, data }
|
||||
function loadWasm() {
|
||||
if (wasm !== undefined) return wasm;
|
||||
wasm = null;
|
||||
try {
|
||||
if (typeof WebAssembly === 'undefined') return wasm;
|
||||
if (!root.SHA256_WASM_B64 && typeof importScripts === 'function') importScripts('/sha256-wasm.js');
|
||||
let b64 = root.SHA256_WASM_B64;
|
||||
if (!b64 && typeof require === 'function') b64 = require('./sha256-wasm.js');
|
||||
if (!b64) return wasm;
|
||||
const bin = typeof Buffer !== 'undefined' ? Uint8Array.from(Buffer.from(b64, 'base64'))
|
||||
: Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
||||
const memory = new WebAssembly.Memory({ initial: (DATA + CHUNK) / 65536, maximum: (DATA + CHUNK) / 65536 });
|
||||
const inst = new WebAssembly.Instance(new WebAssembly.Module(bin), { env: { memory } });
|
||||
const e = {
|
||||
compress: inst.exports.compress,
|
||||
state: new Uint32Array(memory.buffer, STATE, 8),
|
||||
data: new Uint8Array(memory.buffer, DATA, CHUNK),
|
||||
};
|
||||
// Self-test: one padded block of "abc" must give the well-known digest.
|
||||
e.state.set(IV);
|
||||
e.data.fill(0, 0, 64); e.data.set([0x61, 0x62, 0x63, 0x80], 0); e.data[63] = 24;
|
||||
e.compress(STATE, DATA, 1);
|
||||
if (e.state[0] !== 0xba7816bf || e.state[7] !== 0xf20015ad) return wasm;
|
||||
wasm = e;
|
||||
} catch { wasm = null; }
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function create(opts) {
|
||||
const useWasm = !(opts && opts.js) && loadWasm();
|
||||
const H = new Uint32Array([
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
]);
|
||||
@@ -58,6 +96,21 @@
|
||||
H[4] = (H[4] + e) | 0; H[5] = (H[5] + f) | 0; H[6] = (H[6] + g) | 0; H[7] = (H[7] + h) | 0;
|
||||
}
|
||||
|
||||
// Hash n whole 64-byte blocks of buf starting at off — wasm in 1 MiB windows, else JS.
|
||||
function blocks(buf, off, n) {
|
||||
if (!useWasm) { for (let k = 0; k < n; k++) compress(buf, off + k * 64); return; }
|
||||
const w = useWasm;
|
||||
w.state.set(H);
|
||||
const per = CHUNK / 64;
|
||||
for (let done = 0; done < n;) {
|
||||
const m = Math.min(per, n - done);
|
||||
w.data.set(buf.subarray(off + done * 64, off + (done + m) * 64));
|
||||
w.compress(STATE, DATA, m);
|
||||
done += m;
|
||||
}
|
||||
H.set(w.state);
|
||||
}
|
||||
|
||||
function update(data) {
|
||||
if (done) throw new Error('sha256: update() after digest');
|
||||
const u8 = data instanceof Uint8Array ? data : new Uint8Array(data);
|
||||
@@ -68,9 +121,10 @@
|
||||
block.set(u8.subarray(0, take), blockLen);
|
||||
blockLen += take;
|
||||
i = take;
|
||||
if (blockLen === 64) { compress(block, 0); blockLen = 0; }
|
||||
if (blockLen === 64) { blocks(block, 0, 1); blockLen = 0; }
|
||||
}
|
||||
for (; i + 64 <= u8.length; i += 64) compress(u8, i);
|
||||
const whole = Math.floor((u8.length - i) / 64);
|
||||
if (whole) { blocks(u8, i, whole); i += whole * 64; }
|
||||
if (i < u8.length) { block.set(u8.subarray(i), 0); blockLen = u8.length - i; }
|
||||
return api;
|
||||
}
|
||||
@@ -80,12 +134,12 @@
|
||||
done = true;
|
||||
const bits = total * 8;
|
||||
block[blockLen++] = 0x80;
|
||||
if (blockLen > 56) { block.fill(0, blockLen); compress(block, 0); blockLen = 0; }
|
||||
if (blockLen > 56) { block.fill(0, blockLen); blocks(block, 0, 1); blockLen = 0; }
|
||||
block.fill(0, blockLen, 56);
|
||||
const hi = Math.floor(bits / 0x100000000), lo = bits >>> 0;
|
||||
block[56] = hi >>> 24; block[57] = hi >>> 16; block[58] = hi >>> 8; block[59] = hi;
|
||||
block[60] = lo >>> 24; block[61] = lo >>> 16; block[62] = lo >>> 8; block[63] = lo;
|
||||
compress(block, 0);
|
||||
blocks(block, 0, 1);
|
||||
}
|
||||
const out = new Uint8Array(32);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
@@ -107,6 +161,8 @@
|
||||
const Sha256 = {
|
||||
create,
|
||||
hex: (data) => create().update(data).hex(),
|
||||
// Which engine create() will use: 'wasm' or 'js'.
|
||||
engine: () => (loadWasm() ? 'wasm' : 'js'),
|
||||
isHex: (s) => typeof s === 'string' && /^[0-9a-f]{64}$/.test(s),
|
||||
};
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = Sha256;
|
||||
|
||||
Reference in New Issue
Block a user