sha3.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import { bytes, exists, number, output } from './_assert.js';
  2. import { rotlBH, rotlBL, rotlSH, rotlSL, split } from './_u64.js';
  3. import {
  4. Hash,
  5. u32,
  6. Input,
  7. toBytes,
  8. wrapConstructor,
  9. wrapXOFConstructorWithOpts,
  10. HashXOF,
  11. isLE,
  12. byteSwap32,
  13. } from './utils.js';
  14. // SHA3 (keccak) is based on a new design: basically, the internal state is bigger than output size.
  15. // It's called a sponge function.
  16. // Various per round constants calculations
  17. const SHA3_PI: number[] = [];
  18. const SHA3_ROTL: number[] = [];
  19. const _SHA3_IOTA: bigint[] = [];
  20. const _0n = /* @__PURE__ */ BigInt(0);
  21. const _1n = /* @__PURE__ */ BigInt(1);
  22. const _2n = /* @__PURE__ */ BigInt(2);
  23. const _7n = /* @__PURE__ */ BigInt(7);
  24. const _256n = /* @__PURE__ */ BigInt(256);
  25. const _0x71n = /* @__PURE__ */ BigInt(0x71);
  26. for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
  27. // Pi
  28. [x, y] = [y, (2 * x + 3 * y) % 5];
  29. SHA3_PI.push(2 * (5 * y + x));
  30. // Rotational
  31. SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
  32. // Iota
  33. let t = _0n;
  34. for (let j = 0; j < 7; j++) {
  35. R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;
  36. if (R & _2n) t ^= _1n << ((_1n << /* @__PURE__ */ BigInt(j)) - _1n);
  37. }
  38. _SHA3_IOTA.push(t);
  39. }
  40. const [SHA3_IOTA_H, SHA3_IOTA_L] = /* @__PURE__ */ split(_SHA3_IOTA, true);
  41. // Left rotation (without 0, 32, 64)
  42. const rotlH = (h: number, l: number, s: number) => (s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s));
  43. const rotlL = (h: number, l: number, s: number) => (s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s));
  44. // Same as keccakf1600, but allows to skip some rounds
  45. export function keccakP(s: Uint32Array, rounds: number = 24) {
  46. const B = new Uint32Array(5 * 2);
  47. // NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)
  48. for (let round = 24 - rounds; round < 24; round++) {
  49. // Theta θ
  50. for (let x = 0; x < 10; x++) B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
  51. for (let x = 0; x < 10; x += 2) {
  52. const idx1 = (x + 8) % 10;
  53. const idx0 = (x + 2) % 10;
  54. const B0 = B[idx0];
  55. const B1 = B[idx0 + 1];
  56. const Th = rotlH(B0, B1, 1) ^ B[idx1];
  57. const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
  58. for (let y = 0; y < 50; y += 10) {
  59. s[x + y] ^= Th;
  60. s[x + y + 1] ^= Tl;
  61. }
  62. }
  63. // Rho (ρ) and Pi (π)
  64. let curH = s[2];
  65. let curL = s[3];
  66. for (let t = 0; t < 24; t++) {
  67. const shift = SHA3_ROTL[t];
  68. const Th = rotlH(curH, curL, shift);
  69. const Tl = rotlL(curH, curL, shift);
  70. const PI = SHA3_PI[t];
  71. curH = s[PI];
  72. curL = s[PI + 1];
  73. s[PI] = Th;
  74. s[PI + 1] = Tl;
  75. }
  76. // Chi (χ)
  77. for (let y = 0; y < 50; y += 10) {
  78. for (let x = 0; x < 10; x++) B[x] = s[y + x];
  79. for (let x = 0; x < 10; x++) s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
  80. }
  81. // Iota (ι)
  82. s[0] ^= SHA3_IOTA_H[round];
  83. s[1] ^= SHA3_IOTA_L[round];
  84. }
  85. B.fill(0);
  86. }
  87. export class Keccak extends Hash<Keccak> implements HashXOF<Keccak> {
  88. protected state: Uint8Array;
  89. protected pos = 0;
  90. protected posOut = 0;
  91. protected finished = false;
  92. protected state32: Uint32Array;
  93. protected destroyed = false;
  94. // NOTE: we accept arguments in bytes instead of bits here.
  95. constructor(
  96. public blockLen: number,
  97. public suffix: number,
  98. public outputLen: number,
  99. protected enableXOF = false,
  100. protected rounds: number = 24
  101. ) {
  102. super();
  103. // Can be passed from user as dkLen
  104. number(outputLen);
  105. // 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes
  106. if (0 >= this.blockLen || this.blockLen >= 200)
  107. throw new Error('Sha3 supports only keccak-f1600 function');
  108. this.state = new Uint8Array(200);
  109. this.state32 = u32(this.state);
  110. }
  111. protected keccak() {
  112. if (!isLE) byteSwap32(this.state32);
  113. keccakP(this.state32, this.rounds);
  114. if (!isLE) byteSwap32(this.state32);
  115. this.posOut = 0;
  116. this.pos = 0;
  117. }
  118. update(data: Input) {
  119. exists(this);
  120. const { blockLen, state } = this;
  121. data = toBytes(data);
  122. const len = data.length;
  123. for (let pos = 0; pos < len; ) {
  124. const take = Math.min(blockLen - this.pos, len - pos);
  125. for (let i = 0; i < take; i++) state[this.pos++] ^= data[pos++];
  126. if (this.pos === blockLen) this.keccak();
  127. }
  128. return this;
  129. }
  130. protected finish() {
  131. if (this.finished) return;
  132. this.finished = true;
  133. const { state, suffix, pos, blockLen } = this;
  134. // Do the padding
  135. state[pos] ^= suffix;
  136. if ((suffix & 0x80) !== 0 && pos === blockLen - 1) this.keccak();
  137. state[blockLen - 1] ^= 0x80;
  138. this.keccak();
  139. }
  140. protected writeInto(out: Uint8Array): Uint8Array {
  141. exists(this, false);
  142. bytes(out);
  143. this.finish();
  144. const bufferOut = this.state;
  145. const { blockLen } = this;
  146. for (let pos = 0, len = out.length; pos < len; ) {
  147. if (this.posOut >= blockLen) this.keccak();
  148. const take = Math.min(blockLen - this.posOut, len - pos);
  149. out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
  150. this.posOut += take;
  151. pos += take;
  152. }
  153. return out;
  154. }
  155. xofInto(out: Uint8Array): Uint8Array {
  156. // Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF
  157. if (!this.enableXOF) throw new Error('XOF is not possible for this instance');
  158. return this.writeInto(out);
  159. }
  160. xof(bytes: number): Uint8Array {
  161. number(bytes);
  162. return this.xofInto(new Uint8Array(bytes));
  163. }
  164. digestInto(out: Uint8Array) {
  165. output(out, this);
  166. if (this.finished) throw new Error('digest() was already called');
  167. this.writeInto(out);
  168. this.destroy();
  169. return out;
  170. }
  171. digest() {
  172. return this.digestInto(new Uint8Array(this.outputLen));
  173. }
  174. destroy() {
  175. this.destroyed = true;
  176. this.state.fill(0);
  177. }
  178. _cloneInto(to?: Keccak): Keccak {
  179. const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
  180. to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
  181. to.state32.set(this.state32);
  182. to.pos = this.pos;
  183. to.posOut = this.posOut;
  184. to.finished = this.finished;
  185. to.rounds = rounds;
  186. // Suffix can change in cSHAKE
  187. to.suffix = suffix;
  188. to.outputLen = outputLen;
  189. to.enableXOF = enableXOF;
  190. to.destroyed = this.destroyed;
  191. return to;
  192. }
  193. }
  194. const gen = (suffix: number, blockLen: number, outputLen: number) =>
  195. wrapConstructor(() => new Keccak(blockLen, suffix, outputLen));
  196. export const sha3_224 = /* @__PURE__ */ gen(0x06, 144, 224 / 8);
  197. /**
  198. * SHA3-256 hash function
  199. * @param message - that would be hashed
  200. */
  201. export const sha3_256 = /* @__PURE__ */ gen(0x06, 136, 256 / 8);
  202. export const sha3_384 = /* @__PURE__ */ gen(0x06, 104, 384 / 8);
  203. export const sha3_512 = /* @__PURE__ */ gen(0x06, 72, 512 / 8);
  204. export const keccak_224 = /* @__PURE__ */ gen(0x01, 144, 224 / 8);
  205. /**
  206. * keccak-256 hash function. Different from SHA3-256.
  207. * @param message - that would be hashed
  208. */
  209. export const keccak_256 = /* @__PURE__ */ gen(0x01, 136, 256 / 8);
  210. export const keccak_384 = /* @__PURE__ */ gen(0x01, 104, 384 / 8);
  211. export const keccak_512 = /* @__PURE__ */ gen(0x01, 72, 512 / 8);
  212. export type ShakeOpts = { dkLen?: number };
  213. const genShake = (suffix: number, blockLen: number, outputLen: number) =>
  214. wrapXOFConstructorWithOpts<HashXOF<Keccak>, ShakeOpts>(
  215. (opts: ShakeOpts = {}) =>
  216. new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true)
  217. );
  218. export const shake128 = /* @__PURE__ */ genShake(0x1f, 168, 128 / 8);
  219. export const shake256 = /* @__PURE__ */ genShake(0x1f, 136, 256 / 8);