// SHA-1, SHA-256, SHA-384, SHA-512 (FIPS 180-4) fed a slice at a time; WebCrypto digests only a whole buffer // H K M W T T1 T2 a-h t as in FIPS 180-4 · h l high and low int32 of a 64-bit word · P primes · C 2 ** 32 · no refuse // K512 K256 H1 H256 H384 H512 FIPS tables · root integer root · frac fraction bits of prime roots · high even entries // load words, big-endian · S0 S1 s0 s1 Σ and σ of a 64-bit word · add a 64-bit sum into H · sha1 sha256 sha512 a block // SHA name → state, block bytes, digest bytes, kernel export {ALGORITHMS, createHash, selfTest}; const no = s => { throw new Error(s) }, C = 2 ** 32, P = []; for (let n = 2; P.length < 80; n++) if (P.every(p => n % p)) P.push(n); const root = (x, k) => { let r = 1n << BigInt(x.toString(2).length)/k+1n, s; while ((s = ((k - 1n)*r + x/r**(k - 1n)) / k) < r) r = s; return r }; // FIPS 180-4 §4.2.2-3, §5.3.3-5: K and the initial H are the first 64 fraction bits of cube and square roots of primes const frac = (k, a) => Int32Array.from(a.flatMap(p => { const r = root(BigInt(p) << 64n*k, k); return [r>>32n & 0xffffffffn, r & 0xffffffffn] }), Number); const high = a => a.filter((_, i) => i % 2 === 0); const K512 = frac(3n, P), K256 = high(K512.subarray(0, 128)); const H512 = frac(2n, P.slice(0, 8)), H384 = frac(2n, P.slice(8, 16)), H256 = high(H512); // FIPS 180-4 §4.2.1, §5.3.1: SHA-1's K and initial H are listed as values, not derived const H1 = Int32Array.of(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0); const load = (W, n, M, i) => { for (let t = 0; t < n; t++, i += 4) W[t] = M[i]<<24 | M[i+1]<<16 | M[i+2]<<8 | M[i+3] }; function sha1(H, W, M, i) { load(W, 16, M, i); for (let t = 16; t < 80; t++) { const x = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]; W[t] = x<<1 | x>>>31 } let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4]; for (let t = 0; t < 80; t++) { // cost: f folded into the sum for T runs at about 0.7 of this speed in V8 const f = t < 20 ? (b&c | ~b&d) + 0x5a827999 : t < 40 ? (b ^ c ^ d) + 0x6ed9eba1 : t < 60 ? (b&c | b&d | c&d) + 0x8f1bbcdc : (b ^ c ^ d) + 0xca62c1d6; const T = (a<<5 | a>>>27)+e+W[t]+f | 0; e = d; d = c; c = b<<30 | b>>>2; b = a; a = T } H[0] += a; H[1] += b; H[2] += c; H[3] += d; H[4] += e } function sha256(H, W, M, i) { load(W, 16, M, i); for (let t = 16; t < 64; t++) { const x = W[t-15], y = W[t-2]; W[t] = ((y>>>17 | y<<15) ^ (y>>>19 | y<<13) ^ y>>>10)+W[t-7]+((x>>>7 | x<<25) ^ (x>>>18 | x<<14) ^ x>>>3)+W[t-16] | 0 } let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7]; for (let t = 0; t < 64; t++) { // cost: with each rotation's halves spread through the ^ chain, V8 runs SHA-256 about 15% slower const T1 = h+((e>>>6 | e<<26) ^ (e>>>11 | e<<21) ^ (e>>>25 | e<<7))+(e&f ^ ~e&g)+K256[t]+W[t] | 0; const T2 = ((a>>>2 | a<<30) ^ (a>>>13 | a<<19) ^ (a>>>22 | a<<10))+(a&b ^ a&c ^ b&c) | 0; h = g; g = f; f = e; e = d+T1 | 0; d = c; c = b; b = a; a = T1+T2 | 0 } H[0] += a; H[1] += b; H[2] += c; H[3] += d; H[4] += e; H[5] += f; H[6] += g; H[7] += h } // inv: for the word h:l, S0(h, l) is the high half of Σ0 and S0(l, h) the low; σ's low halves also take h<<25, h<<26 const S0 = (x, y) => x>>>28 ^ y<<4 ^ y>>>2 ^ x<<30 ^ y>>>7 ^ x<<25; const S1 = (x, y) => x>>>14 ^ y<<18 ^ x>>>18 ^ y<<14 ^ y>>>9 ^ x<<23; const s0 = (x, y) => x>>>1 ^ y<<31 ^ x>>>8 ^ y<<24 ^ x>>>7; const s1 = (x, y) => x>>>19 ^ y<<13 ^ y>>>29 ^ x<<3 ^ x>>>6; // inv: low halves are summed unreduced, exact in a double below 2 ** 53, and sum/C is the carry into the high half const add = (H, j, h, l) => { const x = (H[j+1] >>> 0) + (l >>> 0); H[j] += h + (x/C | 0); H[j+1] = x }; function sha512(H, W, M, i) { load(W, 32, M, i); for (let t = 32; t < 160; t += 2) { const xh = W[t-30], xl = W[t-29], yh = W[t-4], yl = W[t-3]; const l = ((s1(yl, yh) ^ yh<<26) >>> 0) + (W[t-13] >>> 0) + ((s0(xl, xh) ^ xh<<25) >>> 0) + (W[t-31] >>> 0); W[t] = s1(yh, yl) + W[t-14] + s0(xh, xl) + W[t-32] + (l/C | 0); W[t+1] = l } let ah = H[0], al = H[1], bh = H[2], bl = H[3], ch = H[4], cl = H[5], dh = H[6], dl = H[7]; let eh = H[8], el = H[9], fh = H[10], fl = H[11], gh = H[12], gl = H[13], hh = H[14], hl = H[15]; for (let t = 0; t < 160; t += 2) { const T1l = (hl >>> 0) + (S1(el, eh) >>> 0) + ((el&fl ^ ~el&gl) >>> 0) + (K512[t+1] >>> 0) + (W[t+1] >>> 0); const T1h = hh+S1(eh, el)+(eh&fh ^ ~eh&gh)+K512[t]+W[t]+(T1l/C | 0) | 0; const T2l = (S0(al, ah) >>> 0) + ((al&bl ^ al&cl ^ bl&cl) >>> 0); const T2h = S0(ah, al) + (ah&bh ^ ah&ch ^ bh&ch) + (T2l/C | 0); const e = (dl >>> 0) + (T1l >>> 0), a = (T1l >>> 0) + (T2l >>> 0); hh = gh; hl = gl; gh = fh; gl = fl; fh = eh; fl = el; eh = dh+T1h+(e/C | 0) | 0; el = e | 0; dh = ch; dl = cl; ch = bh; cl = bl; bh = ah; bl = al; ah = T1h+T2h+(a/C | 0) | 0; al = a | 0 } add(H, 0, ah, al); add(H, 2, bh, bl); add(H, 4, ch, cl); add(H, 6, dh, dl); add(H, 8, eh, el); add(H, 10, fh, fl); add(H, 12, gh, gl); add(H, 14, hh, hl) } const SHA = { "SHA-1": () => [H1.slice(), 64, 20, sha1], "SHA-256": () => [H256.slice(), 64, 32, sha256], "SHA-384": () => [H384.slice(), 128, 48, sha512], "SHA-512": () => [H512.slice(), 128, 64, sha512] }; const ALGORITHMS = Object.keys(SHA); function createHash(algorithm) { const [H, block, size, f] = (SHA[algorithm] || no(`Unknown algorithm: ${algorithm}`))(); const W = new Int32Array(160), part = new Uint8Array(block); let fill = 0, seen = 0, done = false; const o = { update(b) { done && no("This hash is finished."); const n = b.length; let i = 0; seen += n; if (fill) { i = Math.min(block - fill, n); part.set(b.subarray(0, i), fill); if ((fill += i) < block) return o; f(H, W, part, 0); fill = 0 } for (; i + block <= n; i += block) f(H, W, b, i); if (i < n) { part.set(b.subarray(i), 0); fill = n - i } return o }, digest() { done && no("This hash is finished."); // FIPS 180-4 §5.1: 0x80, zeros, the bit length; a byte count below 2 ** 53 fills 8 of SHA-512's 16 length bytes const tail = new Uint8Array(block - (fill + block/8)%block + block/8), v = new DataView(tail.buffer); tail[0] = 0x80; v.setUint32(tail.length - 8, Math.floor(seen / 2**29)); v.setUint32(tail.length - 4, seen*8 >>> 0); o.update(tail); done = true; return Uint8Array.from({length: size}, (_, i) => H[i>>2] >>> 24-8*(i & 3)) } }; return o } async function selfTest() { try { for (const s of ALGORITHMS) for (const n of [0, 1, 55, 56, 63, 64, 65, 111, 112, 119, 127, 128, 129, 1e3]) { const b = crypto.getRandomValues(new Uint8Array(n)), want = new Uint8Array(await crypto.subtle.digest(s, b)); for (const j of [1, 64, 65]) { const o = createHash(s); for (let i = 0; i < n; i += j) o.update(b.subarray(i, i + j)); const got = o.digest(); if (got.length !== want.length || got.some((x, i) => x !== want[i])) return false } } return true } catch { return false } }