/** * sha1-uint8array.ts */ // Self-reference via the package name so `tsc --noEmit` resolves these // types through `package.json` `exports` — the same path an external // consumer would take. If the `exports.types` mapping ever breaks, // the build fails here. import type * as types from "sha1-uint8array" // Round constants, one per 20-round stage. const K0 = 0x5a827999 | 0 const K1 = 0x6ed9eba1 | 0 const K2 = 0x8f1bbcdc | 0 const K3 = 0xca62c1d6 | 0 // Hash block/allocation sizes. Plain consts work as-is under Node's // type-strip, and minifiers inline them as literals. const N_inputBytes = 64 const N_inputWords = N_inputBytes / 4 const N_highIndex = N_inputWords - 2 const N_lowIndex = N_inputWords - 1 const N_workWords = 80 const N_allocBytes = 80 const N_allocWords = N_allocBytes / 4 const N_allocTotal = N_allocBytes * 100 const algorithms: {[algorithm: string]: number} = { sha1: 1, } export const createHash: typeof types.createHash = (algorithm?: string) => { if (algorithm && !algorithms[algorithm] && !algorithms[algorithm.toLowerCase()]) { throw new Error("Digest method not supported") } return new Hash() } export class Hash { private A = 0x67452301 | 0; private B = 0xefcdab89 | 0; private C = 0x98badcfe | 0; private D = 0x10325476 | 0; private E = 0xc3d2e1f0 | 0; private _byte: Uint8Array; private _word: Int32Array; private _size = 0; private _sp = 0; // surrogate pair constructor() { if (!sharedBuffer || sharedOffset >= N_allocTotal) { sharedBuffer = new ArrayBuffer(N_allocTotal) sharedOffset = 0 } this._byte = new Uint8Array(sharedBuffer, sharedOffset, N_allocBytes) this._word = new Int32Array(sharedBuffer, sharedOffset, N_allocWords) sharedOffset += N_allocBytes } update(data: string, encoding?: string): this; update(data: Uint8Array): this; update(data: ArrayBufferView): this; update(data: string | Uint8Array | ArrayBufferView): this { // data: string if ("string" === typeof data) { return this._utf8(data) } // data: undefined if (data == null) { throw new TypeError("Invalid type: " + typeof data) } const byteOffset = data.byteOffset const length = data.byteLength let blocks = (length / N_inputBytes) | 0 let offset = 0 // longer than 1 block if (blocks && !(byteOffset & 3) && !(this._size % N_inputBytes)) { const block = new Int32Array(data.buffer, byteOffset, blocks * N_inputWords) while (blocks--) { this._int32(block, offset >> 2) offset += N_inputBytes } this._size += offset } // data: TypedArray | DataView const BYTES_PER_ELEMENT = (data as Uint8Array).BYTES_PER_ELEMENT if (BYTES_PER_ELEMENT !== 1 && data.buffer) { const rest = new Uint8Array(data.buffer, byteOffset + offset, length - offset) return this._uint8(rest) } // no more bytes if (offset === length) return this // data: Uint8Array | Int8Array return this._uint8(data as any, offset) } private _uint8(data: Uint8Array, offset?: number) { const {_byte, _word} = this const length = data.length offset = offset!! | 0 while (offset < length) { const start = this._size % N_inputBytes let index = start while (offset < length && index < N_inputBytes) { _byte[index++] = data[offset++] } if (index >= N_inputBytes) { this._int32(_word) } this._size += index - start } return this } private _utf8(text: string): this { const {_byte, _word} = this const length = text.length let surrogate = this._sp for (let offset = 0; offset < length; ) { const start = this._size % N_inputBytes let index = start while (offset < length && index < N_inputBytes) { // Runs of ASCII land as whole words while the write stays // word-aligned: four code units fold into one int32 store, // replacing four byte stores and three loop trips. The // first non-ASCII unit falls through to the scalar path, // which may unalign the cursor until the next block. if (!(index & 3)) { while (offset + 4 <= length && index + 4 <= N_inputBytes) { const c0 = text.charCodeAt(offset) if (c0 >= 0x80) break const c1 = text.charCodeAt(offset + 1) const c2 = text.charCodeAt(offset + 2) const c3 = text.charCodeAt(offset + 3) if ((c1 | c2 | c3) >= 0x80) break _word[index >> 2] = pack32(c0, c1, c2, c3) offset += 4 index += 4 } if (offset >= length || index >= N_inputBytes) break } let code = text.charCodeAt(offset++) | 0 if (code < 0x80) { // ASCII characters _byte[index++] = code } else if (code < 0x800) { // 2 bytes _byte[index++] = 0xC0 | (code >>> 6) _byte[index++] = 0x80 | (code & 0x3F) } else if (code < 0xD800 || code > 0xDFFF) { // 3 bytes _byte[index++] = 0xE0 | (code >>> 12) _byte[index++] = 0x80 | ((code >>> 6) & 0x3F) _byte[index++] = 0x80 | (code & 0x3F) } else if (surrogate) { // 4 bytes - surrogate pair code = ((surrogate & 0x3FF) << 10) + (code & 0x3FF) + 0x10000 _byte[index++] = 0xF0 | (code >>> 18) _byte[index++] = 0x80 | ((code >>> 12) & 0x3F) _byte[index++] = 0x80 | ((code >>> 6) & 0x3F) _byte[index++] = 0x80 | (code & 0x3F) surrogate = 0 } else { surrogate = code } } if (index >= N_inputBytes) { this._int32(_word) _word[0] = _word[N_inputWords] } this._size += index - start } this._sp = surrogate return this } private _int32(data: Int32Array, offset?: number): void { let {A, B, C, D, E} = this let i = 0 offset = offset!! | 0 // The 80 rounds run as one loop per 20-round stage, so the round // body carries no per-round stage division, function dispatch or // constant lookup. The first stage splits again where loading // input words turns into extending the schedule; the flat W // keeps every extension index direct and in bounds. // Rounds go in pairs: the second reads the first through fresh // bindings, halving the a..e state rotation assignments. for (i = 0; i < N_inputWords; i += 2) { const w0 = W[i] = swap32(data[offset++]) const T1 = (rotate5(A) + ((B & C) | ((~B) & D)) + E + w0 + K0) | 0 const B30 = rotate30(B) const w1 = W[i + 1] = swap32(data[offset++]) const T2 = (rotate5(T1) + ((A & B30) | ((~A) & C)) + D + w1 + K0) | 0 E = C D = B30 C = rotate30(A) B = T1 A = T2 } for (i = N_inputWords; i < 20; i += 2) { const w0 = W[i] = rotate1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]) const T1 = (rotate5(A) + ((B & C) | ((~B) & D)) + E + w0 + K0) | 0 const B30 = rotate30(B) const w1 = W[i + 1] = rotate1(W[i - 2] ^ W[i - 7] ^ W[i - 13] ^ W[i - 15]) const T2 = (rotate5(T1) + ((A & B30) | ((~A) & C)) + D + w1 + K0) | 0 E = C D = B30 C = rotate30(A) B = T1 A = T2 } for (i = 20; i < 40; i += 2) { const w0 = W[i] = rotate1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]) const T1 = (rotate5(A) + (B ^ C ^ D) + E + w0 + K1) | 0 const B30 = rotate30(B) const w1 = W[i + 1] = rotate1(W[i - 2] ^ W[i - 7] ^ W[i - 13] ^ W[i - 15]) const T2 = (rotate5(T1) + (A ^ B30 ^ C) + D + w1 + K1) | 0 E = C D = B30 C = rotate30(A) B = T1 A = T2 } for (i = 40; i < 60; i += 2) { const w0 = W[i] = rotate1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]) const T1 = (rotate5(A) + ((B & C) | (B & D) | (C & D)) + E + w0 + K2) | 0 const B30 = rotate30(B) const w1 = W[i + 1] = rotate1(W[i - 2] ^ W[i - 7] ^ W[i - 13] ^ W[i - 15]) const T2 = (rotate5(T1) + ((A & B30) | (A & C) | (B30 & C)) + D + w1 + K2) | 0 E = C D = B30 C = rotate30(A) B = T1 A = T2 } for (i = 60; i < N_workWords; i += 2) { const w0 = W[i] = rotate1(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]) const T1 = (rotate5(A) + (B ^ C ^ D) + E + w0 + K3) | 0 const B30 = rotate30(B) const w1 = W[i + 1] = rotate1(W[i - 2] ^ W[i - 7] ^ W[i - 13] ^ W[i - 15]) const T2 = (rotate5(T1) + (A ^ B30 ^ C) + D + w1 + K3) | 0 E = C D = B30 C = rotate30(A) B = T1 A = T2 } this.A = (A + this.A) | 0 this.B = (B + this.B) | 0 this.C = (C + this.C) | 0 this.D = (D + this.D) | 0 this.E = (E + this.E) | 0 } digest(): Uint8Array; digest(encoding: string): string; digest(encoding?: string) { const {_byte, _word} = this let i = (this._size % N_inputBytes) | 0 _byte[i++] = 0x80 // pad 0 for current word while (i & 3) { _byte[i++] = 0 } i >>= 2 if (i > N_highIndex) { while (i < N_inputWords) { _word[i++] = 0 } i = 0 this._int32(_word) } // pad 0 for rest words while (i < N_inputWords) { _word[i++] = 0 } // input size const bits64 = this._size * 8 const low32 = (bits64 & 0xffffffff) >>> 0 const high32 = (bits64 - low32) / 0x100000000 if (high32) _word[N_highIndex] = swap32(high32) if (low32) _word[N_lowIndex] = swap32(low32) this._int32(_word) return (encoding === "hex") ? this._hex() : this._bin() } private _hex(): string { const {A, B, C, D, E} = this return hex32(A) + hex32(B) + hex32(C) + hex32(D) + hex32(E) } private _bin(): Uint8Array { const {A, B, C, D, E, _byte, _word} = this _word[0] = swap32(A) _word[1] = swap32(B) _word[2] = swap32(C) _word[3] = swap32(D) _word[4] = swap32(E) return _byte.slice(0, 20) } } type NS = (num: number) => string type NN = (num: number) => number // Full 80-word message schedule. The flat layout costs 256 bytes over // a 16-word ring but frees the hot loop from masking every index. const W = new Int32Array(N_workWords) let sharedBuffer: ArrayBuffer let sharedOffset: number = 0 // Two hex digits per byte value, built once at load. Concatenating four // prebuilt pairs avoids Number#toString(16), which runs on the slower // double path here because the int32 word is offset beyond 2^32 first. const HEX: string[] = [] for (let i = 0; i < 256; i++) { HEX.push((i + 0x100).toString(16).substr(-2)) } const hex32: NS = num => HEX[(num >>> 24)] + HEX[(num >>> 16) & 0xFF] + HEX[(num >>> 8) & 0xFF] + HEX[num & 0xFF] const swapLE: NN = (c => (((c << 24) & 0xff000000) | ((c << 8) & 0xff0000) | ((c >> 8) & 0xff00) | ((c >> 24) & 0xff))) const swapBE: NN = (c => c) const swap32: NN = isBE() ? swapBE : swapLE // Folds four ASCII bytes into one int32 in the platform's byte order, // so a word store lays them out exactly as four byte stores would. type N4N = (c0: number, c1: number, c2: number, c3: number) => number const packLE: N4N = (c0, c1, c2, c3) => (c0 | (c1 << 8) | (c2 << 16) | (c3 << 24)) const packBE: N4N = (c0, c1, c2, c3) => ((c0 << 24) | (c1 << 16) | (c2 << 8) | c3) const pack32: N4N = isBE() ? packBE : packLE const rotate1: NN = num => (num << 1) | (num >>> 31) const rotate5: NN = num => (num << 5) | (num >>> 27) const rotate30: NN = num => (num << 30) | (num >>> 2) function isBE(): boolean { const buf = new Uint8Array(new Uint16Array([0xFEFF]).buffer) // BOM return (buf[0] === 0xFE) }