/** * Incremental SHA-1, needed for pack trailers. Pure JS because WebCrypto's * digest() is one-shot and merged packs are built from multiple regions; * git still addresses packs with SHA-1 regardless of platform. */ export class Sha1 { constructor() { this.h0 = 0x67452301; this.h1 = 0xefcdab89; this.h2 = 0x98badcfe; this.h3 = 0x10325476; this.h4 = 0xc3d2e1f0; this.block = new Uint8Array(64); this.blockLen = 0; this.byteLength = 0; this.words = new Int32Array(80); } /** * @param {Uint8Array} bytes * @returns {this} */ update(bytes) { this.byteLength += bytes.length; let offset = 0; while (offset < bytes.length) { const take = Math.min(64 - this.blockLen, bytes.length - offset); this.block.set(bytes.subarray(offset, offset + take), this.blockLen); this.blockLen += take; offset += take; if (this.blockLen === 64) { this.processBlock(); this.blockLen = 0; } } return this; } processBlock() { const w = this.words; const b = this.block; for (let i = 0; i < 16; i++) { w[i] = (b[i * 4] << 24) | (b[i * 4 + 1] << 16) | (b[i * 4 + 2] << 8) | b[i * 4 + 3]; } for (let i = 16; i < 80; i++) { const n = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; w[i] = (n << 1) | (n >>> 31); } let a = this.h0; let bb = this.h1; let c = this.h2; let d = this.h3; let e = this.h4; for (let i = 0; i < 80; i++) { let f; let k; if (i < 20) { f = (bb & c) | (~bb & d); k = 0x5a827999; } else if (i < 40) { f = bb ^ c ^ d; k = 0x6ed9eba1; } else if (i < 60) { f = (bb & c) | (bb & d) | (c & d); k = 0x8f1bbcdc; } else { f = bb ^ c ^ d; k = 0xca62c1d6; } const temp = (((a << 5) | (a >>> 27)) + f + e + k + w[i]) | 0; e = d; d = c; c = (bb << 30) | (bb >>> 2); bb = a; a = temp; } this.h0 = (this.h0 + a) | 0; this.h1 = (this.h1 + bb) | 0; this.h2 = (this.h2 + c) | 0; this.h3 = (this.h3 + d) | 0; this.h4 = (this.h4 + e) | 0; } /** @returns {Uint8Array} 20-byte digest; the instance must not be reused */ digest() { const bitLength = this.byteLength * 8; this.update(new Uint8Array([0x80])); while (this.blockLen !== 56) { this.update(new Uint8Array([0])); } const lengthBytes = new Uint8Array(8); new DataView(lengthBytes.buffer).setBigUint64(0, BigInt(bitLength)); // Bypass update()'s length accounting for the final length field this.block.set(lengthBytes, 56); this.blockLen = 64; this.processBlock(); const out = new Uint8Array(20); const view = new DataView(out.buffer); view.setInt32(0, this.h0); view.setInt32(4, this.h1); view.setInt32(8, this.h2); view.setInt32(12, this.h3); view.setInt32(16, this.h4); return out; } }