_blake.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import { number, exists, output } from './_assert.js';
  2. import { Hash, Input, toBytes, u32, isLE, byteSwap32, byteSwapIfBE } from './utils.js';
  3. // Blake is based on ChaCha permutation.
  4. // For BLAKE2b, the two extra permutations for rounds 10 and 11 are SIGMA[10..11] = SIGMA[0..1].
  5. // prettier-ignore
  6. export const SIGMA = /* @__PURE__ */ new Uint8Array([
  7. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
  8. 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
  9. 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
  10. 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
  11. 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
  12. 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
  13. 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
  14. 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
  15. 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
  16. 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,
  17. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
  18. 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
  19. ]);
  20. export type BlakeOpts = {
  21. dkLen?: number;
  22. key?: Input;
  23. salt?: Input;
  24. personalization?: Input;
  25. };
  26. export abstract class BLAKE<T extends BLAKE<T>> extends Hash<T> {
  27. protected abstract compress(msg: Uint32Array, offset: number, isLast: boolean): void;
  28. protected abstract get(): number[];
  29. protected abstract set(...args: number[]): void;
  30. abstract destroy(): void;
  31. protected buffer: Uint8Array;
  32. protected buffer32: Uint32Array;
  33. protected length: number = 0;
  34. protected pos: number = 0;
  35. protected finished = false;
  36. protected destroyed = false;
  37. constructor(
  38. readonly blockLen: number,
  39. public outputLen: number,
  40. opts: BlakeOpts = {},
  41. keyLen: number,
  42. saltLen: number,
  43. persLen: number
  44. ) {
  45. super();
  46. number(blockLen);
  47. number(outputLen);
  48. number(keyLen);
  49. if (outputLen < 0 || outputLen > keyLen) throw new Error('outputLen bigger than keyLen');
  50. if (opts.key !== undefined && (opts.key.length < 1 || opts.key.length > keyLen))
  51. throw new Error(`key must be up 1..${keyLen} byte long or undefined`);
  52. if (opts.salt !== undefined && opts.salt.length !== saltLen)
  53. throw new Error(`salt must be ${saltLen} byte long or undefined`);
  54. if (opts.personalization !== undefined && opts.personalization.length !== persLen)
  55. throw new Error(`personalization must be ${persLen} byte long or undefined`);
  56. this.buffer32 = u32((this.buffer = new Uint8Array(blockLen)));
  57. }
  58. update(data: Input) {
  59. exists(this);
  60. // Main difference with other hashes: there is flag for last block,
  61. // so we cannot process current block before we know that there
  62. // is the next one. This significantly complicates logic and reduces ability
  63. // to do zero-copy processing
  64. const { blockLen, buffer, buffer32 } = this;
  65. data = toBytes(data);
  66. const len = data.length;
  67. const offset = data.byteOffset;
  68. const buf = data.buffer;
  69. for (let pos = 0; pos < len; ) {
  70. // If buffer is full and we still have input (don't process last block, same as blake2s)
  71. if (this.pos === blockLen) {
  72. if (!isLE) byteSwap32(buffer32);
  73. this.compress(buffer32, 0, false);
  74. if (!isLE) byteSwap32(buffer32);
  75. this.pos = 0;
  76. }
  77. const take = Math.min(blockLen - this.pos, len - pos);
  78. const dataOffset = offset + pos;
  79. // full block && aligned to 4 bytes && not last in input
  80. if (take === blockLen && !(dataOffset % 4) && pos + take < len) {
  81. const data32 = new Uint32Array(buf, dataOffset, Math.floor((len - pos) / 4));
  82. if (!isLE) byteSwap32(data32);
  83. for (let pos32 = 0; pos + blockLen < len; pos32 += buffer32.length, pos += blockLen) {
  84. this.length += blockLen;
  85. this.compress(data32, pos32, false);
  86. }
  87. if (!isLE) byteSwap32(data32);
  88. continue;
  89. }
  90. buffer.set(data.subarray(pos, pos + take), this.pos);
  91. this.pos += take;
  92. this.length += take;
  93. pos += take;
  94. }
  95. return this;
  96. }
  97. digestInto(out: Uint8Array) {
  98. exists(this);
  99. output(out, this);
  100. const { pos, buffer32 } = this;
  101. this.finished = true;
  102. // Padding
  103. this.buffer.subarray(pos).fill(0);
  104. if (!isLE) byteSwap32(buffer32);
  105. this.compress(buffer32, 0, true);
  106. if (!isLE) byteSwap32(buffer32);
  107. const out32 = u32(out);
  108. this.get().forEach((v, i) => (out32[i] = byteSwapIfBE(v)));
  109. }
  110. digest() {
  111. const { buffer, outputLen } = this;
  112. this.digestInto(buffer);
  113. const res = buffer.slice(0, outputLen);
  114. this.destroy();
  115. return res;
  116. }
  117. _cloneInto(to?: T): T {
  118. const { buffer, length, finished, destroyed, outputLen, pos } = this;
  119. to ||= new (this.constructor as any)({ dkLen: outputLen }) as T;
  120. to.set(...this.get());
  121. to.length = length;
  122. to.finished = finished;
  123. to.destroyed = destroyed;
  124. to.outputLen = outputLen;
  125. to.buffer.set(buffer);
  126. to.pos = pos;
  127. return to;
  128. }
  129. }