| 1 | import { toHex } from "./bytes.js"; |
| 2 | |
| 3 | /** SHA-1 using WebCrypto (available in Workers and Node 20+). */ |
| 4 | export async function sha1(data: Uint8Array): Promise<Uint8Array> { |
| 5 | const digest = await crypto.subtle.digest("SHA-1", data as BufferSource); |
| 6 | return new Uint8Array(digest); |
| 7 | } |
| 8 | |
| 9 | export async function sha1Hex(data: Uint8Array): Promise<string> { |
| 10 | return toHex(await sha1(data)); |
| 11 | } |
| 12 | |
| 13 | export async function sha256Hex(data: Uint8Array): Promise<string> { |
| 14 | return toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", data as BufferSource))); |
| 15 | } |
| 16 | |
| 17 | /** |
| 18 | * Incremental SHA-1, for checksums over data that is never held whole (a pack streamed to a client). |
| 19 | * WebCrypto only hashes a complete buffer. |
| 20 | */ |
| 21 | export class Sha1 { |
| 22 | private h = new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]); |
| 23 | private block = new Uint8Array(64); |
| 24 | private used = 0; |
| 25 | private total = 0; |
| 26 | private w = new Uint32Array(80); |
| 27 | |
| 28 | update(data: Uint8Array): this { |
| 29 | this.total += data.length; |
| 30 | let i = 0; |
| 31 | if (this.used) { |
| 32 | const take = Math.min(64 - this.used, data.length); |
| 33 | this.block.set(data.subarray(0, take), this.used); |
| 34 | this.used += take; |
| 35 | i = take; |
| 36 | if (this.used < 64) return this; |
| 37 | this.compress(this.block, 0); |
| 38 | this.used = 0; |
| 39 | } |
| 40 | for (; i + 64 <= data.length; i += 64) this.compress(data, i); |
| 41 | if (i < data.length) { |
| 42 | this.block.set(data.subarray(i), 0); |
| 43 | this.used = data.length - i; |
| 44 | } |
| 45 | return this; |
| 46 | } |
| 47 | |
| 48 | digest(): Uint8Array { |
| 49 | const bits = this.total * 8; |
| 50 | const pad = new Uint8Array((this.used < 56 ? 56 : 120) - this.used + 8); |
| 51 | pad[0] = 0x80; |
| 52 | const n = pad.length; |
| 53 | pad[n - 8] = Math.floor(bits / 2 ** 56) & 0xff; |
| 54 | pad[n - 7] = Math.floor(bits / 2 ** 48) & 0xff; |
| 55 | pad[n - 6] = Math.floor(bits / 2 ** 40) & 0xff; |
| 56 | pad[n - 5] = Math.floor(bits / 2 ** 32) & 0xff; |
| 57 | pad[n - 4] = (bits >>> 24) & 0xff; |
| 58 | pad[n - 3] = (bits >>> 16) & 0xff; |
| 59 | pad[n - 2] = (bits >>> 8) & 0xff; |
| 60 | pad[n - 1] = bits & 0xff; |
| 61 | const total = this.total; |
| 62 | this.update(pad); |
| 63 | this.total = total; |
| 64 | const out = new Uint8Array(20); |
| 65 | for (let j = 0; j < 5; j++) { |
| 66 | out[j * 4] = this.h[j]! >>> 24; |
| 67 | out[j * 4 + 1] = (this.h[j]! >>> 16) & 0xff; |
| 68 | out[j * 4 + 2] = (this.h[j]! >>> 8) & 0xff; |
| 69 | out[j * 4 + 3] = this.h[j]! & 0xff; |
| 70 | } |
| 71 | return out; |
| 72 | } |
| 73 | |
| 74 | private compress(b: Uint8Array, o: number) { |
| 75 | const w = this.w; |
| 76 | for (let t = 0; t < 16; t++) w[t] = (b[o + t * 4]! << 24) | (b[o + t * 4 + 1]! << 16) | (b[o + t * 4 + 2]! << 8) | b[o + t * 4 + 3]!; |
| 77 | for (let t = 16; t < 80; t++) { |
| 78 | const x = w[t - 3]! ^ w[t - 8]! ^ w[t - 14]! ^ w[t - 16]!; |
| 79 | w[t] = (x << 1) | (x >>> 31); |
| 80 | } |
| 81 | let [a, bb, c, d, e] = this.h as unknown as [number, number, number, number, number]; |
| 82 | for (let t = 0; t < 80; t++) { |
| 83 | const f = t < 20 ? (bb & c) | (~bb & d) : t < 40 ? bb ^ c ^ d : t < 60 ? (bb & c) | (bb & d) | (c & d) : bb ^ c ^ d; |
| 84 | const k = t < 20 ? 0x5a827999 : t < 40 ? 0x6ed9eba1 : t < 60 ? 0x8f1bbcdc : 0xca62c1d6; |
| 85 | const tmp = (((a << 5) | (a >>> 27)) + f + e + k + w[t]!) | 0; |
| 86 | e = d; |
| 87 | d = c; |
| 88 | c = (bb << 30) | (bb >>> 2); |
| 89 | bb = a; |
| 90 | a = tmp; |
| 91 | } |
| 92 | this.h[0] = (this.h[0]! + a) | 0; |
| 93 | this.h[1] = (this.h[1]! + bb) | 0; |
| 94 | this.h[2] = (this.h[2]! + c) | 0; |
| 95 | this.h[3] = (this.h[3]! + d) | 0; |
| 96 | this.h[4] = (this.h[4]! + e) | 0; |
| 97 | } |
| 98 | } |