123 lines
4.5 KiB
JavaScript
123 lines
4.5 KiB
JavaScript
import { exists, output } from './_assert.js';
|
|
import { Hash, createView, toBytes } from './utils.js';
|
|
// Polyfill for Safari 14
|
|
function setBigUint64(view, byteOffset, value, isLE) {
|
|
if (typeof view.setBigUint64 === 'function')
|
|
return view.setBigUint64(byteOffset, value, isLE);
|
|
const _32n = BigInt(32);
|
|
const _u32_max = BigInt(0xffffffff);
|
|
const wh = Number((value >> _32n) & _u32_max);
|
|
const wl = Number(value & _u32_max);
|
|
const h = isLE ? 4 : 0;
|
|
const l = isLE ? 0 : 4;
|
|
view.setUint32(byteOffset + h, wh, isLE);
|
|
view.setUint32(byteOffset + l, wl, isLE);
|
|
}
|
|
// Choice: a ? b : c
|
|
export const Chi = (a, b, c) => (a & b) ^ (~a & c);
|
|
// Majority function, true if any two inpust is true
|
|
export const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
|
|
/**
|
|
* Merkle-Damgard hash construction base class.
|
|
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
|
|
*/
|
|
export class HashMD extends Hash {
|
|
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
super();
|
|
this.blockLen = blockLen;
|
|
this.outputLen = outputLen;
|
|
this.padOffset = padOffset;
|
|
this.isLE = isLE;
|
|
this.finished = false;
|
|
this.length = 0;
|
|
this.pos = 0;
|
|
this.destroyed = false;
|
|
this.buffer = new Uint8Array(blockLen);
|
|
this.view = createView(this.buffer);
|
|
}
|
|
update(data) {
|
|
exists(this);
|
|
const { view, buffer, blockLen } = this;
|
|
data = toBytes(data);
|
|
const len = data.length;
|
|
for (let pos = 0; pos < len;) {
|
|
const take = Math.min(blockLen - this.pos, len - pos);
|
|
// Fast path: we have at least one block in input, cast it to view and process
|
|
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) {
|
|
exists(this);
|
|
output(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;
|
|
this.buffer.subarray(pos).fill(0);
|
|
// 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;
|
|
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
|
|
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
|
|
// So we just write lowest 64 bits of that value.
|
|
setBigUint64(view, 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 should be fused in single op with modulo by JIT
|
|
if (len % 4)
|
|
throw new Error('_sha2: outputLen should 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() {
|
|
const { buffer, outputLen } = this;
|
|
this.digestInto(buffer);
|
|
const res = buffer.slice(0, outputLen);
|
|
this.destroy();
|
|
return res;
|
|
}
|
|
_cloneInto(to) {
|
|
to || (to = new this.constructor());
|
|
to.set(...this.get());
|
|
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
to.length = length;
|
|
to.pos = pos;
|
|
to.finished = finished;
|
|
to.destroyed = destroyed;
|
|
if (length % blockLen)
|
|
to.buffer.set(buffer);
|
|
return to;
|
|
}
|
|
}
|
|
//# sourceMappingURL=_md.js.map
|