_md.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /**
  2. * Internal Merkle-Damgard hash utils.
  3. * @module
  4. */
  5. import { type Input, Hash, abytes, aexists, aoutput, clean, createView, toBytes } from './utils.ts';
  6. /** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
  7. export function setBigUint64(
  8. view: DataView,
  9. byteOffset: number,
  10. value: bigint,
  11. isLE: boolean
  12. ): void {
  13. if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE);
  14. const _32n = BigInt(32);
  15. const _u32_max = BigInt(0xffffffff);
  16. const wh = Number((value >> _32n) & _u32_max);
  17. const wl = Number(value & _u32_max);
  18. const h = isLE ? 4 : 0;
  19. const l = isLE ? 0 : 4;
  20. view.setUint32(byteOffset + h, wh, isLE);
  21. view.setUint32(byteOffset + l, wl, isLE);
  22. }
  23. /** Choice: a ? b : c */
  24. export function Chi(a: number, b: number, c: number): number {
  25. return (a & b) ^ (~a & c);
  26. }
  27. /** Majority function, true if any two inputs is true. */
  28. export function Maj(a: number, b: number, c: number): number {
  29. return (a & b) ^ (a & c) ^ (b & c);
  30. }
  31. /**
  32. * Merkle-Damgard hash construction base class.
  33. * Could be used to create MD5, RIPEMD, SHA1, SHA2.
  34. */
  35. export abstract class HashMD<T extends HashMD<T>> extends Hash<T> {
  36. protected abstract process(buf: DataView, offset: number): void;
  37. protected abstract get(): number[];
  38. protected abstract set(...args: number[]): void;
  39. abstract destroy(): void;
  40. protected abstract roundClean(): void;
  41. readonly blockLen: number;
  42. readonly outputLen: number;
  43. readonly padOffset: number;
  44. readonly isLE: boolean;
  45. // For partial updates less than block size
  46. protected buffer: Uint8Array;
  47. protected view: DataView;
  48. protected finished = false;
  49. protected length = 0;
  50. protected pos = 0;
  51. protected destroyed = false;
  52. constructor(blockLen: number, outputLen: number, padOffset: number, isLE: boolean) {
  53. super();
  54. this.blockLen = blockLen;
  55. this.outputLen = outputLen;
  56. this.padOffset = padOffset;
  57. this.isLE = isLE;
  58. this.buffer = new Uint8Array(blockLen);
  59. this.view = createView(this.buffer);
  60. }
  61. update(data: Input): this {
  62. aexists(this);
  63. data = toBytes(data);
  64. abytes(data);
  65. const { view, buffer, blockLen } = this;
  66. const len = data.length;
  67. for (let pos = 0; pos < len; ) {
  68. const take = Math.min(blockLen - this.pos, len - pos);
  69. // Fast path: we have at least one block in input, cast it to view and process
  70. if (take === blockLen) {
  71. const dataView = createView(data);
  72. for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
  73. continue;
  74. }
  75. buffer.set(data.subarray(pos, pos + take), this.pos);
  76. this.pos += take;
  77. pos += take;
  78. if (this.pos === blockLen) {
  79. this.process(view, 0);
  80. this.pos = 0;
  81. }
  82. }
  83. this.length += data.length;
  84. this.roundClean();
  85. return this;
  86. }
  87. digestInto(out: Uint8Array): void {
  88. aexists(this);
  89. aoutput(out, this);
  90. this.finished = true;
  91. // Padding
  92. // We can avoid allocation of buffer for padding completely if it
  93. // was previously not allocated here. But it won't change performance.
  94. const { buffer, view, blockLen, isLE } = this;
  95. let { pos } = this;
  96. // append the bit '1' to the message
  97. buffer[pos++] = 0b10000000;
  98. clean(this.buffer.subarray(pos));
  99. // we have less than padOffset left in buffer, so we cannot put length in
  100. // current block, need process it and pad again
  101. if (this.padOffset > blockLen - pos) {
  102. this.process(view, 0);
  103. pos = 0;
  104. }
  105. // Pad until full block byte with zeros
  106. for (let i = pos; i < blockLen; i++) buffer[i] = 0;
  107. // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
  108. // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
  109. // So we just write lowest 64 bits of that value.
  110. setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
  111. this.process(view, 0);
  112. const oview = createView(out);
  113. const len = this.outputLen;
  114. // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
  115. if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit');
  116. const outLen = len / 4;
  117. const state = this.get();
  118. if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');
  119. for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
  120. }
  121. digest(): Uint8Array {
  122. const { buffer, outputLen } = this;
  123. this.digestInto(buffer);
  124. const res = buffer.slice(0, outputLen);
  125. this.destroy();
  126. return res;
  127. }
  128. _cloneInto(to?: T): T {
  129. to ||= new (this.constructor as any)() as T;
  130. to.set(...this.get());
  131. const { blockLen, buffer, length, finished, destroyed, pos } = this;
  132. to.destroyed = destroyed;
  133. to.finished = finished;
  134. to.length = length;
  135. to.pos = pos;
  136. if (length % blockLen) to.buffer.set(buffer);
  137. return to;
  138. }
  139. clone(): T {
  140. return this._cloneInto();
  141. }
  142. }
  143. /**
  144. * Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
  145. * Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
  146. */
  147. /** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
  148. export const SHA256_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
  149. 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
  150. ]);
  151. /** Initial SHA224 state. Bits 32..64 of frac part of sqrt of primes 23..53 */
  152. export const SHA224_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
  153. 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4,
  154. ]);
  155. /** Initial SHA384 state. Bits 0..64 of frac part of sqrt of primes 23..53 */
  156. export const SHA384_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
  157. 0xcbbb9d5d, 0xc1059ed8, 0x629a292a, 0x367cd507, 0x9159015a, 0x3070dd17, 0x152fecd8, 0xf70e5939,
  158. 0x67332667, 0xffc00b31, 0x8eb44a87, 0x68581511, 0xdb0c2e0d, 0x64f98fa7, 0x47b5481d, 0xbefa4fa4,
  159. ]);
  160. /** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */
  161. export const SHA512_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
  162. 0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1,
  163. 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179,
  164. ]);