223 lines
8.6 KiB
Plaintext
223 lines
8.6 KiB
Plaintext
/**
|
|
* Internal Merkle-Damgard hash utils.
|
|
* @module
|
|
*/
|
|
import {
|
|
abytes,
|
|
aexists,
|
|
aoutput,
|
|
clean,
|
|
createView,
|
|
type Hash,
|
|
type TArg,
|
|
type TRet,
|
|
} from './utils.ts';
|
|
|
|
/**
|
|
* Shared 32-bit conditional boolean primitive reused by SHA-256, SHA-1, and MD5 `F`.
|
|
* Returns bits from `b` when `a` is set, otherwise from `c`.
|
|
* The XOR form is equivalent to MD5's `F(X,Y,Z) = XY v not(X)Z` because the masked terms never
|
|
* set the same bit.
|
|
* @param a - selector word
|
|
* @param b - word chosen when selector bit is set
|
|
* @param c - word chosen when selector bit is clear
|
|
* @returns Mixed 32-bit word.
|
|
* @example
|
|
* Combine three words with the shared 32-bit choice primitive.
|
|
* ```ts
|
|
* Chi(0xffffffff, 0x12345678, 0x87654321);
|
|
* ```
|
|
*/
|
|
export function Chi(a: number, b: number, c: number): number {
|
|
return (a & b) ^ (~a & c);
|
|
}
|
|
|
|
/**
|
|
* Shared 32-bit majority primitive reused by SHA-256 and SHA-1.
|
|
* Returns bits shared by at least two inputs.
|
|
* @param a - first input word
|
|
* @param b - second input word
|
|
* @param c - third input word
|
|
* @returns Mixed 32-bit word.
|
|
* @example
|
|
* Combine three words with the shared 32-bit majority primitive.
|
|
* ```ts
|
|
* Maj(0xffffffff, 0x12345678, 0x87654321);
|
|
* ```
|
|
*/
|
|
export function Maj(a: number, b: number, c: number): number {
|
|
return (a & b) ^ (a & c) ^ (b & c);
|
|
}
|
|
|
|
/**
|
|
* Merkle-Damgard hash construction base class.
|
|
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
|
|
* Accepts only byte-aligned `Uint8Array` input, even when the underlying spec describes bit
|
|
* strings with partial-byte tails.
|
|
* @param blockLen - internal block size in bytes
|
|
* @param outputLen - digest size in bytes
|
|
* @param padOffset - trailing length field size in bytes
|
|
* @param isLE - whether length and state words are encoded in little-endian
|
|
* @example
|
|
* Use a concrete subclass to get the shared Merkle-Damgard update/digest flow.
|
|
* ```ts
|
|
* import { _SHA1 } from '@noble/hashes/legacy.js';
|
|
* const hash = new _SHA1();
|
|
* hash.update(new Uint8Array([97, 98, 99]));
|
|
* hash.digest();
|
|
* ```
|
|
*/
|
|
export abstract class HashMD<T extends HashMD<T>> implements Hash<T> {
|
|
// Subclasses must treat `buf` as read-only: `update()` may pass a direct view over caller input
|
|
// when it can process whole blocks without buffering first.
|
|
protected abstract process(buf: DataView, offset: number): void;
|
|
protected abstract get(): number[];
|
|
protected abstract set(...args: number[]): void;
|
|
abstract destroy(): void;
|
|
protected abstract roundClean(): void;
|
|
|
|
readonly blockLen: number;
|
|
readonly outputLen: number;
|
|
readonly canXOF = false;
|
|
readonly padOffset: number;
|
|
readonly isLE: boolean;
|
|
|
|
// For partial updates less than block size
|
|
protected buffer: Uint8Array;
|
|
protected view: DataView;
|
|
protected finished = false;
|
|
protected length = 0;
|
|
protected pos = 0;
|
|
protected destroyed = false;
|
|
|
|
constructor(blockLen: number, outputLen: number, padOffset: number, isLE: boolean) {
|
|
this.blockLen = blockLen;
|
|
this.outputLen = outputLen;
|
|
this.padOffset = padOffset;
|
|
this.isLE = isLE;
|
|
this.buffer = new Uint8Array(blockLen);
|
|
this.view = createView(this.buffer);
|
|
}
|
|
update(data: TArg<Uint8Array>): this {
|
|
aexists(this);
|
|
abytes(data);
|
|
const { view, buffer, blockLen } = this;
|
|
const len = data.length;
|
|
for (let pos = 0; pos < len; ) {
|
|
const take = Math.min(blockLen - this.pos, len - pos);
|
|
// Fast path only when there is no buffered partial block: `take === blockLen` implies
|
|
// `this.pos === 0`, so we can process full blocks directly from the input view.
|
|
if (take === blockLen) {
|
|
const dataView = createView(data);
|
|
for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
|
|
continue;
|
|
}
|
|
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
this.pos += take;
|
|
pos += take;
|
|
if (this.pos === blockLen) {
|
|
this.process(view, 0);
|
|
this.pos = 0;
|
|
}
|
|
}
|
|
this.length += data.length;
|
|
this.roundClean();
|
|
return this;
|
|
}
|
|
digestInto(out: TArg<Uint8Array>): void {
|
|
aexists(this);
|
|
aoutput(out, this);
|
|
this.finished = true;
|
|
// Padding
|
|
// We can avoid allocation of buffer for padding completely if it
|
|
// was previously not allocated here. But it won't change performance.
|
|
const { buffer, view, blockLen, isLE } = this;
|
|
let { pos } = this;
|
|
// append the bit '1' to the message
|
|
buffer[pos++] = 0b10000000;
|
|
clean(this.buffer.subarray(pos));
|
|
// we have less than padOffset left in buffer, so we cannot put length in
|
|
// current block, need process it and pad again
|
|
if (this.padOffset > blockLen - pos) {
|
|
this.process(view, 0);
|
|
pos = 0;
|
|
}
|
|
// Pad until full block byte with zeros
|
|
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
|
|
// `padOffset` reserves the whole length field. For SHA-384/512 the high 64 bits stay zero from
|
|
// the padding fill above, and JS will overflow before user input can make that half non-zero.
|
|
// So we only need to write the low 64 bits here.
|
|
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
|
|
this.process(view, 0);
|
|
const oview = createView(out);
|
|
const len = this.outputLen;
|
|
// NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT
|
|
if (len % 4) throw new Error('_sha2: outputLen must be aligned to 32bit');
|
|
const outLen = len / 4;
|
|
const state = this.get();
|
|
if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');
|
|
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
|
|
}
|
|
digest(): TRet<Uint8Array> {
|
|
const { buffer, outputLen } = this;
|
|
this.digestInto(buffer);
|
|
// Copy before destroy(): subclasses wipe `buffer` during cleanup, but `digest()` must return
|
|
// fresh bytes to the caller.
|
|
const res = buffer.slice(0, outputLen);
|
|
this.destroy();
|
|
return res as TRet<Uint8Array>;
|
|
}
|
|
_cloneInto(to?: T): T {
|
|
to ||= new (this.constructor as any)() as T;
|
|
to.set(...this.get());
|
|
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
to.destroyed = destroyed;
|
|
to.finished = finished;
|
|
to.length = length;
|
|
to.pos = pos;
|
|
// Only partial-block bytes need copying: when `length % blockLen === 0`, `pos === 0` and
|
|
// later `update()` / `digestInto()` overwrite `to.buffer` from the start before reading it.
|
|
if (length % blockLen) to.buffer.set(buffer);
|
|
return to as unknown as any;
|
|
}
|
|
clone(): T {
|
|
return this._cloneInto();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
|
|
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
|
|
*/
|
|
|
|
/** Initial SHA256 state from RFC 6234 §6.1: the first 32 bits of the fractional parts of the
|
|
* square roots of the first eight prime numbers. Exported as a shared table; callers must treat
|
|
* it as read-only because constructors copy words from it by index. */
|
|
export const SHA256_IV: TRet<Uint32Array> = /* @__PURE__ */ Uint32Array.from([
|
|
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
]);
|
|
|
|
/** Initial SHA224 state `H(0)` from RFC 6234 §6.1. Exported as a shared table; callers must
|
|
* treat it as read-only because constructors copy words from it by index. */
|
|
export const SHA224_IV: TRet<Uint32Array> = /* @__PURE__ */ Uint32Array.from([
|
|
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
|
|
]);
|
|
|
|
/** Initial SHA384 state from RFC 6234 §6.3: eight RFC 64-bit `H(0)` words stored as sixteen
|
|
* big-endian 32-bit halves. Derived from the fractional parts of the square roots of the ninth
|
|
* through sixteenth prime numbers. Exported as a shared table; callers must treat it as read-only
|
|
* because constructors copy halves from it by index. */
|
|
export const SHA384_IV: TRet<Uint32Array> = /* @__PURE__ */ Uint32Array.from([
|
|
0xcbbb9d5d, 0xc1059ed8, 0x629a292a, 0x367cd507, 0x9159015a, 0x3070dd17, 0x152fecd8, 0xf70e5939,
|
|
0x67332667, 0xffc00b31, 0x8eb44a87, 0x68581511, 0xdb0c2e0d, 0x64f98fa7, 0x47b5481d, 0xbefa4fa4,
|
|
]);
|
|
|
|
/** Initial SHA512 state from RFC 6234 §6.3: eight RFC 64-bit `H(0)` words stored as sixteen
|
|
* big-endian 32-bit halves. Derived from the fractional parts of the square roots of the first
|
|
* eight prime numbers. Exported as a shared table; callers must treat it as read-only because
|
|
* constructors copy halves from it by index. */
|
|
export const SHA512_IV: TRet<Uint32Array> = /* @__PURE__ */ Uint32Array.from([
|
|
0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1,
|
|
0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179,
|
|
]);
|