// Pure-JS SHA-1/SHA-256: the sync call sites (bucket routing, script shas, // token digests) cannot await crypto.subtle, and node:crypto is off-limits // in the platform-free core. const encoder = new TextEncoder(); const toBytes = (data: string | Uint8Array): Uint8Array => typeof data === "string" ? encoder.encode(data) : data; const toHex = (words: number[]): string => words.map((word) => (word >>> 0).toString(16).padStart(8, "0")).join(""); const padMessage = (bytes: Uint8Array): DataView => { const bitLength = bytes.length * 8; const padded = new Uint8Array((((bytes.length + 8) >> 6) + 1) << 6); padded.set(bytes); padded[bytes.length] = 0x80; const view = new DataView(padded.buffer); view.setUint32(padded.length - 8, Math.floor(bitLength / 0x100000000)); view.setUint32(padded.length - 4, bitLength >>> 0); return view; }; const K256 = [ 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, ]; const rotr = (value: number, bits: number): number => (value >>> bits) | (value << (32 - bits)); const sha256Words = (data: string | Uint8Array): number[] => { const view = padMessage(toBytes(data)); const h = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, ]; const w = new Array(64); for (let offset = 0; offset < view.byteLength; offset += 64) { for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4); for (let i = 16; i < 64; i++) { const s0 = rotr(w[i - 15]!, 7) ^ rotr(w[i - 15]!, 18) ^ (w[i - 15]! >>> 3); const s1 = rotr(w[i - 2]!, 17) ^ rotr(w[i - 2]!, 19) ^ (w[i - 2]! >>> 10); w[i] = (w[i - 16]! + s0 + w[i - 7]! + s1) | 0; } let [a, b, c, d, e, f, g, hh] = h as [ number, number, number, number, number, number, number, number, ]; for (let i = 0; i < 64; i++) { const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); const ch = (e & f) ^ (~e & g); const t1 = (hh + s1 + ch + K256[i]! + w[i]!) | 0; const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); const maj = (a & b) ^ (a & c) ^ (b & c); const t2 = (s0 + maj) | 0; hh = 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]! + hh) | 0; } return h; }; export const sha256Hex = (data: string | Uint8Array): string => toHex(sha256Words(data)); export const sha256Bytes = (data: string | Uint8Array): Uint8Array => { const words = sha256Words(data); const bytes = new Uint8Array(32); const view = new DataView(bytes.buffer); words.forEach((word, i) => view.setUint32(i * 4, word)); return bytes; }; export const sha1Hex = (data: string | Uint8Array): string => { const view = padMessage(toBytes(data)); const h = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]; const w = new Array(80); for (let offset = 0; offset < view.byteLength; offset += 64) { for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4); for (let i = 16; i < 80; i++) { const value = w[i - 3]! ^ w[i - 8]! ^ w[i - 14]! ^ w[i - 16]!; w[i] = (value << 1) | (value >>> 31); } let [a, b, c, d, e] = h as [number, number, number, number, number]; for (let i = 0; i < 80; i++) { const f = i < 20 ? ((b & c) | (~b & d)) + 0x5a827999 : i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 : i < 60 ? ((b & c) | (b & d) | (c & d)) + 0x8f1bbcdc : (b ^ c ^ d) + 0xca62c1d6; const next = (((a << 5) | (a >>> 27)) + f + e + w[i]!) | 0; e = d; d = c; c = (b << 30) | (b >>> 2); b = a; a = next; } 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; } return toHex(h); }; export const timingSafeEqualBytes = (a: Uint8Array, b: Uint8Array): boolean => { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) diff |= a[i]! ^ b[i]!; return diff === 0; }; export const randomHex = (byteLength: number): string => Array.from(crypto.getRandomValues(new Uint8Array(byteLength)), (byte) => byte.toString(16).padStart(2, "0"), ).join("");