_md.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { exists, output } from './_assert.js';
  2. import { Hash, createView, Input, toBytes } from './utils.js';
  3. // Polyfill for Safari 14
  4. function setBigUint64(view: DataView, byteOffset: number, value: bigint, isLE: boolean): void {
  5. if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE);
  6. const _32n = BigInt(32);
  7. const _u32_max = BigInt(0xffffffff);
  8. const wh = Number((value >> _32n) & _u32_max);
  9. const wl = Number(value & _u32_max);
  10. const h = isLE ? 4 : 0;
  11. const l = isLE ? 0 : 4;
  12. view.setUint32(byteOffset + h, wh, isLE);
  13. view.setUint32(byteOffset + l, wl, isLE);
  14. }
  15. // Choice: a ? b : c
  16. export const Chi = (a: number, b: number, c: number) => (a & b) ^ (~a & c);
  17. // Majority function, true if any two inpust is true
  18. export const Maj = (a: number, b: number, c: number) => (a & b) ^ (a & c) ^ (b & c);
  19. /**
  20. * Merkle-Damgard hash construction base class.
  21. * Could be used to create MD5, RIPEMD, SHA1, SHA2.
  22. */
  23. export abstract class HashMD<T extends HashMD<T>> extends Hash<T> {
  24. protected abstract process(buf: DataView, offset: number): void;
  25. protected abstract get(): number[];
  26. protected abstract set(...args: number[]): void;
  27. abstract destroy(): void;
  28. protected abstract roundClean(): void;
  29. // For partial updates less than block size
  30. protected buffer: Uint8Array;
  31. protected view: DataView;
  32. protected finished = false;
  33. protected length = 0;
  34. protected pos = 0;
  35. protected destroyed = false;
  36. constructor(
  37. readonly blockLen: number,
  38. public outputLen: number,
  39. readonly padOffset: number,
  40. readonly isLE: boolean
  41. ) {
  42. super();
  43. this.buffer = new Uint8Array(blockLen);
  44. this.view = createView(this.buffer);
  45. }
  46. update(data: Input): this {
  47. exists(this);
  48. const { view, buffer, blockLen } = this;
  49. data = toBytes(data);
  50. const len = data.length;
  51. for (let pos = 0; pos < len; ) {
  52. const take = Math.min(blockLen - this.pos, len - pos);
  53. // Fast path: we have at least one block in input, cast it to view and process
  54. if (take === blockLen) {
  55. const dataView = createView(data);
  56. for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
  57. continue;
  58. }
  59. buffer.set(data.subarray(pos, pos + take), this.pos);
  60. this.pos += take;
  61. pos += take;
  62. if (this.pos === blockLen) {
  63. this.process(view, 0);
  64. this.pos = 0;
  65. }
  66. }
  67. this.length += data.length;
  68. this.roundClean();
  69. return this;
  70. }
  71. digestInto(out: Uint8Array) {
  72. exists(this);
  73. output(out, this);
  74. this.finished = true;
  75. // Padding
  76. // We can avoid allocation of buffer for padding completely if it
  77. // was previously not allocated here. But it won't change performance.
  78. const { buffer, view, blockLen, isLE } = this;
  79. let { pos } = this;
  80. // append the bit '1' to the message
  81. buffer[pos++] = 0b10000000;
  82. this.buffer.subarray(pos).fill(0);
  83. // we have less than padOffset left in buffer, so we cannot put length in
  84. // current block, need process it and pad again
  85. if (this.padOffset > blockLen - pos) {
  86. this.process(view, 0);
  87. pos = 0;
  88. }
  89. // Pad until full block byte with zeros
  90. for (let i = pos; i < blockLen; i++) buffer[i] = 0;
  91. // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
  92. // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
  93. // So we just write lowest 64 bits of that value.
  94. setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
  95. this.process(view, 0);
  96. const oview = createView(out);
  97. const len = this.outputLen;
  98. // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
  99. if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit');
  100. const outLen = len / 4;
  101. const state = this.get();
  102. if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');
  103. for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
  104. }
  105. digest() {
  106. const { buffer, outputLen } = this;
  107. this.digestInto(buffer);
  108. const res = buffer.slice(0, outputLen);
  109. this.destroy();
  110. return res;
  111. }
  112. _cloneInto(to?: T): T {
  113. to ||= new (this.constructor as any)() as T;
  114. to.set(...this.get());
  115. const { blockLen, buffer, length, finished, destroyed, pos } = this;
  116. to.length = length;
  117. to.pos = pos;
  118. to.finished = finished;
  119. to.destroyed = destroyed;
  120. if (length % blockLen) to.buffer.set(buffer);
  121. return to;
  122. }
  123. }