sha3-addons.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import { number as assertNumber } from './_assert.js';
  2. import {
  3. Input,
  4. toBytes,
  5. wrapConstructorWithOpts,
  6. u32,
  7. Hash,
  8. HashXOF,
  9. wrapXOFConstructorWithOpts,
  10. } from './utils.js';
  11. import { Keccak, ShakeOpts } from './sha3.js';
  12. // cSHAKE && KMAC (NIST SP800-185)
  13. function leftEncode(n: number): Uint8Array {
  14. const res = [n & 0xff];
  15. n >>= 8;
  16. for (; n > 0; n >>= 8) res.unshift(n & 0xff);
  17. res.unshift(res.length);
  18. return new Uint8Array(res);
  19. }
  20. function rightEncode(n: number): Uint8Array {
  21. const res = [n & 0xff];
  22. n >>= 8;
  23. for (; n > 0; n >>= 8) res.unshift(n & 0xff);
  24. res.push(res.length);
  25. return new Uint8Array(res);
  26. }
  27. function chooseLen(opts: ShakeOpts, outputLen: number): number {
  28. return opts.dkLen === undefined ? outputLen : opts.dkLen;
  29. }
  30. const toBytesOptional = (buf?: Input) => (buf !== undefined ? toBytes(buf) : new Uint8Array([]));
  31. // NOTE: second modulo is necessary since we don't need to add padding if current element takes whole block
  32. const getPadding = (len: number, block: number) => new Uint8Array((block - (len % block)) % block);
  33. export type cShakeOpts = ShakeOpts & { personalization?: Input; NISTfn?: Input };
  34. // Personalization
  35. function cshakePers(hash: Keccak, opts: cShakeOpts = {}): Keccak {
  36. if (!opts || (!opts.personalization && !opts.NISTfn)) return hash;
  37. // Encode and pad inplace to avoid unneccesary memory copies/slices (so we don't need to zero them later)
  38. // bytepad(encode_string(N) || encode_string(S), 168)
  39. const blockLenBytes = leftEncode(hash.blockLen);
  40. const fn = toBytesOptional(opts.NISTfn);
  41. const fnLen = leftEncode(8 * fn.length); // length in bits
  42. const pers = toBytesOptional(opts.personalization);
  43. const persLen = leftEncode(8 * pers.length); // length in bits
  44. if (!fn.length && !pers.length) return hash;
  45. hash.suffix = 0x04;
  46. hash.update(blockLenBytes).update(fnLen).update(fn).update(persLen).update(pers);
  47. let totalLen = blockLenBytes.length + fnLen.length + fn.length + persLen.length + pers.length;
  48. hash.update(getPadding(totalLen, hash.blockLen));
  49. return hash;
  50. }
  51. const gencShake = (suffix: number, blockLen: number, outputLen: number) =>
  52. wrapXOFConstructorWithOpts<Keccak, cShakeOpts>((opts: cShakeOpts = {}) =>
  53. cshakePers(new Keccak(blockLen, suffix, chooseLen(opts, outputLen), true), opts)
  54. );
  55. export const cshake128 = /* @__PURE__ */ (() => gencShake(0x1f, 168, 128 / 8))();
  56. export const cshake256 = /* @__PURE__ */ (() => gencShake(0x1f, 136, 256 / 8))();
  57. class KMAC extends Keccak implements HashXOF<KMAC> {
  58. constructor(
  59. blockLen: number,
  60. outputLen: number,
  61. enableXOF: boolean,
  62. key: Input,
  63. opts: cShakeOpts = {}
  64. ) {
  65. super(blockLen, 0x1f, outputLen, enableXOF);
  66. cshakePers(this, { NISTfn: 'KMAC', personalization: opts.personalization });
  67. key = toBytes(key);
  68. // 1. newX = bytepad(encode_string(K), 168) || X || right_encode(L).
  69. const blockLenBytes = leftEncode(this.blockLen);
  70. const keyLen = leftEncode(8 * key.length);
  71. this.update(blockLenBytes).update(keyLen).update(key);
  72. const totalLen = blockLenBytes.length + keyLen.length + key.length;
  73. this.update(getPadding(totalLen, this.blockLen));
  74. }
  75. protected finish() {
  76. if (!this.finished) this.update(rightEncode(this.enableXOF ? 0 : this.outputLen * 8)); // outputLen in bits
  77. super.finish();
  78. }
  79. _cloneInto(to?: KMAC): KMAC {
  80. // Create new instance without calling constructor since key already in state and we don't know it.
  81. // Force "to" to be instance of KMAC instead of Sha3.
  82. if (!to) {
  83. to = Object.create(Object.getPrototypeOf(this), {}) as KMAC;
  84. to.state = this.state.slice();
  85. to.blockLen = this.blockLen;
  86. to.state32 = u32(to.state);
  87. }
  88. return super._cloneInto(to) as KMAC;
  89. }
  90. clone(): KMAC {
  91. return this._cloneInto();
  92. }
  93. }
  94. function genKmac(blockLen: number, outputLen: number, xof = false) {
  95. const kmac = (key: Input, message: Input, opts?: cShakeOpts): Uint8Array =>
  96. kmac.create(key, opts).update(message).digest();
  97. kmac.create = (key: Input, opts: cShakeOpts = {}) =>
  98. new KMAC(blockLen, chooseLen(opts, outputLen), xof, key, opts);
  99. return kmac;
  100. }
  101. export const kmac128 = /* @__PURE__ */ (() => genKmac(168, 128 / 8))();
  102. export const kmac256 = /* @__PURE__ */ (() => genKmac(136, 256 / 8))();
  103. export const kmac128xof = /* @__PURE__ */ (() => genKmac(168, 128 / 8, true))();
  104. export const kmac256xof = /* @__PURE__ */ (() => genKmac(136, 256 / 8, true))();
  105. // TupleHash
  106. // Usage: tuple(['ab', 'cd']) != tuple(['a', 'bcd'])
  107. class TupleHash extends Keccak implements HashXOF<TupleHash> {
  108. constructor(blockLen: number, outputLen: number, enableXOF: boolean, opts: cShakeOpts = {}) {
  109. super(blockLen, 0x1f, outputLen, enableXOF);
  110. cshakePers(this, { NISTfn: 'TupleHash', personalization: opts.personalization });
  111. // Change update after cshake processed
  112. this.update = (data: Input) => {
  113. data = toBytes(data);
  114. super.update(leftEncode(data.length * 8));
  115. super.update(data);
  116. return this;
  117. };
  118. }
  119. protected finish() {
  120. if (!this.finished) super.update(rightEncode(this.enableXOF ? 0 : this.outputLen * 8)); // outputLen in bits
  121. super.finish();
  122. }
  123. _cloneInto(to?: TupleHash): TupleHash {
  124. to ||= new TupleHash(this.blockLen, this.outputLen, this.enableXOF);
  125. return super._cloneInto(to) as TupleHash;
  126. }
  127. clone(): TupleHash {
  128. return this._cloneInto();
  129. }
  130. }
  131. function genTuple(blockLen: number, outputLen: number, xof = false) {
  132. const tuple = (messages: Input[], opts?: cShakeOpts): Uint8Array => {
  133. const h = tuple.create(opts);
  134. for (const msg of messages) h.update(msg);
  135. return h.digest();
  136. };
  137. tuple.create = (opts: cShakeOpts = {}) =>
  138. new TupleHash(blockLen, chooseLen(opts, outputLen), xof, opts);
  139. return tuple;
  140. }
  141. export const tuplehash128 = /* @__PURE__ */ (() => genTuple(168, 128 / 8))();
  142. export const tuplehash256 = /* @__PURE__ */ (() => genTuple(136, 256 / 8))();
  143. export const tuplehash128xof = /* @__PURE__ */ (() => genTuple(168, 128 / 8, true))();
  144. export const tuplehash256xof = /* @__PURE__ */ (() => genTuple(136, 256 / 8, true))();
  145. // ParallelHash (same as K12/M14, but without speedup for inputs less 8kb, reduced number of rounds and more simple)
  146. type ParallelOpts = cShakeOpts & { blockLen?: number };
  147. class ParallelHash extends Keccak implements HashXOF<ParallelHash> {
  148. private leafHash?: Hash<Keccak>;
  149. private chunkPos = 0; // Position of current block in chunk
  150. private chunksDone = 0; // How many chunks we already have
  151. private chunkLen: number;
  152. constructor(
  153. blockLen: number,
  154. outputLen: number,
  155. protected leafCons: () => Hash<Keccak>,
  156. enableXOF: boolean,
  157. opts: ParallelOpts = {}
  158. ) {
  159. super(blockLen, 0x1f, outputLen, enableXOF);
  160. cshakePers(this, { NISTfn: 'ParallelHash', personalization: opts.personalization });
  161. let { blockLen: B } = opts;
  162. B ||= 8;
  163. assertNumber(B);
  164. this.chunkLen = B;
  165. super.update(leftEncode(B));
  166. // Change update after cshake processed
  167. this.update = (data: Input) => {
  168. data = toBytes(data);
  169. const { chunkLen, leafCons } = this;
  170. for (let pos = 0, len = data.length; pos < len; ) {
  171. if (this.chunkPos == chunkLen || !this.leafHash) {
  172. if (this.leafHash) {
  173. super.update(this.leafHash.digest());
  174. this.chunksDone++;
  175. }
  176. this.leafHash = leafCons();
  177. this.chunkPos = 0;
  178. }
  179. const take = Math.min(chunkLen - this.chunkPos, len - pos);
  180. this.leafHash.update(data.subarray(pos, pos + take));
  181. this.chunkPos += take;
  182. pos += take;
  183. }
  184. return this;
  185. };
  186. }
  187. protected finish() {
  188. if (this.finished) return;
  189. if (this.leafHash) {
  190. super.update(this.leafHash.digest());
  191. this.chunksDone++;
  192. }
  193. super.update(rightEncode(this.chunksDone));
  194. super.update(rightEncode(this.enableXOF ? 0 : this.outputLen * 8)); // outputLen in bits
  195. super.finish();
  196. }
  197. _cloneInto(to?: ParallelHash): ParallelHash {
  198. to ||= new ParallelHash(this.blockLen, this.outputLen, this.leafCons, this.enableXOF);
  199. if (this.leafHash) to.leafHash = this.leafHash._cloneInto(to.leafHash as Keccak);
  200. to.chunkPos = this.chunkPos;
  201. to.chunkLen = this.chunkLen;
  202. to.chunksDone = this.chunksDone;
  203. return super._cloneInto(to) as ParallelHash;
  204. }
  205. destroy() {
  206. super.destroy.call(this);
  207. if (this.leafHash) this.leafHash.destroy();
  208. }
  209. clone(): ParallelHash {
  210. return this._cloneInto();
  211. }
  212. }
  213. function genPrl(
  214. blockLen: number,
  215. outputLen: number,
  216. leaf: ReturnType<typeof gencShake>,
  217. xof = false
  218. ) {
  219. const parallel = (message: Input, opts?: ParallelOpts): Uint8Array =>
  220. parallel.create(opts).update(message).digest();
  221. parallel.create = (opts: ParallelOpts = {}) =>
  222. new ParallelHash(
  223. blockLen,
  224. chooseLen(opts, outputLen),
  225. () => leaf.create({ dkLen: 2 * outputLen }),
  226. xof,
  227. opts
  228. );
  229. return parallel;
  230. }
  231. export const parallelhash128 = /* @__PURE__ */ (() => genPrl(168, 128 / 8, cshake128))();
  232. export const parallelhash256 = /* @__PURE__ */ (() => genPrl(136, 256 / 8, cshake256))();
  233. export const parallelhash128xof = /* @__PURE__ */ (() => genPrl(168, 128 / 8, cshake128, true))();
  234. export const parallelhash256xof = /* @__PURE__ */ (() => genPrl(136, 256 / 8, cshake256, true))();
  235. // Should be simple 'shake with 12 rounds', but no, we got whole new spec about Turbo SHAKE Pro MAX.
  236. export type TurboshakeOpts = ShakeOpts & {
  237. D?: number; // Domain separation byte
  238. };
  239. const genTurboshake = (blockLen: number, outputLen: number) =>
  240. wrapXOFConstructorWithOpts<HashXOF<Keccak>, TurboshakeOpts>((opts: TurboshakeOpts = {}) => {
  241. const D = opts.D === undefined ? 0x1f : opts.D;
  242. // Section 2.1 of https://datatracker.ietf.org/doc/draft-irtf-cfrg-kangarootwelve/
  243. if (!Number.isSafeInteger(D) || D < 0x01 || D > 0x7f)
  244. throw new Error(`turboshake: wrong domain separation byte: ${D}, should be 0x01..0x7f`);
  245. return new Keccak(blockLen, D, opts.dkLen === undefined ? outputLen : opts.dkLen, true, 12);
  246. });
  247. export const turboshake128 = /* @__PURE__ */ genTurboshake(168, 256 / 8);
  248. export const turboshake256 = /* @__PURE__ */ genTurboshake(136, 512 / 8);
  249. // Kangaroo
  250. // Same as NIST rightEncode, but returns [0] for zero string
  251. function rightEncodeK12(n: number): Uint8Array {
  252. const res = [];
  253. for (; n > 0; n >>= 8) res.unshift(n & 0xff);
  254. res.push(res.length);
  255. return new Uint8Array(res);
  256. }
  257. export type KangarooOpts = { dkLen?: number; personalization?: Input };
  258. const EMPTY = new Uint8Array([]);
  259. class KangarooTwelve extends Keccak implements HashXOF<KangarooTwelve> {
  260. readonly chunkLen = 8192;
  261. private leafHash?: Keccak;
  262. private personalization: Uint8Array;
  263. private chunkPos = 0; // Position of current block in chunk
  264. private chunksDone = 0; // How many chunks we already have
  265. constructor(
  266. blockLen: number,
  267. protected leafLen: number,
  268. outputLen: number,
  269. rounds: number,
  270. opts: KangarooOpts
  271. ) {
  272. super(blockLen, 0x07, outputLen, true, rounds);
  273. const { personalization } = opts;
  274. this.personalization = toBytesOptional(personalization);
  275. }
  276. update(data: Input) {
  277. data = toBytes(data);
  278. const { chunkLen, blockLen, leafLen, rounds } = this;
  279. for (let pos = 0, len = data.length; pos < len; ) {
  280. if (this.chunkPos == chunkLen) {
  281. if (this.leafHash) super.update(this.leafHash.digest());
  282. else {
  283. this.suffix = 0x06; // Its safe to change suffix here since its used only in digest()
  284. super.update(new Uint8Array([3, 0, 0, 0, 0, 0, 0, 0]));
  285. }
  286. this.leafHash = new Keccak(blockLen, 0x0b, leafLen, false, rounds);
  287. this.chunksDone++;
  288. this.chunkPos = 0;
  289. }
  290. const take = Math.min(chunkLen - this.chunkPos, len - pos);
  291. const chunk = data.subarray(pos, pos + take);
  292. if (this.leafHash) this.leafHash.update(chunk);
  293. else super.update(chunk);
  294. this.chunkPos += take;
  295. pos += take;
  296. }
  297. return this;
  298. }
  299. protected finish() {
  300. if (this.finished) return;
  301. const { personalization } = this;
  302. this.update(personalization).update(rightEncodeK12(personalization.length));
  303. // Leaf hash
  304. if (this.leafHash) {
  305. super.update(this.leafHash.digest());
  306. super.update(rightEncodeK12(this.chunksDone));
  307. super.update(new Uint8Array([0xff, 0xff]));
  308. }
  309. super.finish.call(this);
  310. }
  311. destroy() {
  312. super.destroy.call(this);
  313. if (this.leafHash) this.leafHash.destroy();
  314. // We cannot zero personalization buffer since it is user provided and we don't want to mutate user input
  315. this.personalization = EMPTY;
  316. }
  317. _cloneInto(to?: KangarooTwelve): KangarooTwelve {
  318. const { blockLen, leafLen, leafHash, outputLen, rounds } = this;
  319. to ||= new KangarooTwelve(blockLen, leafLen, outputLen, rounds, {});
  320. super._cloneInto(to);
  321. if (leafHash) to.leafHash = leafHash._cloneInto(to.leafHash);
  322. to.personalization.set(this.personalization);
  323. to.leafLen = this.leafLen;
  324. to.chunkPos = this.chunkPos;
  325. to.chunksDone = this.chunksDone;
  326. return to;
  327. }
  328. clone(): KangarooTwelve {
  329. return this._cloneInto();
  330. }
  331. }
  332. // Default to 32 bytes, so it can be used without opts
  333. export const k12 = /* @__PURE__ */ (() =>
  334. wrapConstructorWithOpts<KangarooTwelve, KangarooOpts>(
  335. (opts: KangarooOpts = {}) => new KangarooTwelve(168, 32, chooseLen(opts, 32), 12, opts)
  336. ))();
  337. // MarsupilamiFourteen
  338. export const m14 = /* @__PURE__ */ (() =>
  339. wrapConstructorWithOpts<KangarooTwelve, KangarooOpts>(
  340. (opts: KangarooOpts = {}) => new KangarooTwelve(136, 64, chooseLen(opts, 64), 14, opts)
  341. ))();
  342. // https://keccak.team/files/CSF-0.1.pdf
  343. // + https://github.com/XKCP/XKCP/tree/master/lib/high/Keccak/PRG
  344. class KeccakPRG extends Keccak {
  345. protected rate: number;
  346. constructor(capacity: number) {
  347. assertNumber(capacity);
  348. // Rho should be full bytes
  349. if (capacity < 0 || capacity > 1600 - 10 || (1600 - capacity - 2) % 8)
  350. throw new Error('KeccakPRG: Invalid capacity');
  351. // blockLen = rho in bytes
  352. super((1600 - capacity - 2) / 8, 0, 0, true);
  353. this.rate = 1600 - capacity;
  354. this.posOut = Math.floor((this.rate + 7) / 8);
  355. }
  356. keccak() {
  357. // Duplex padding
  358. this.state[this.pos] ^= 0x01;
  359. this.state[this.blockLen] ^= 0x02; // Rho is full bytes
  360. super.keccak();
  361. this.pos = 0;
  362. this.posOut = 0;
  363. }
  364. update(data: Input) {
  365. super.update(data);
  366. this.posOut = this.blockLen;
  367. return this;
  368. }
  369. feed(data: Input) {
  370. return this.update(data);
  371. }
  372. protected finish() {}
  373. digestInto(_out: Uint8Array): Uint8Array {
  374. throw new Error('KeccakPRG: digest is not allowed, please use .fetch instead.');
  375. }
  376. fetch(bytes: number): Uint8Array {
  377. return this.xof(bytes);
  378. }
  379. // Ensure irreversibility (even if state leaked previous outputs cannot be computed)
  380. forget() {
  381. if (this.rate < 1600 / 2 + 1) throw new Error('KeccakPRG: rate too low to use forget');
  382. this.keccak();
  383. for (let i = 0; i < this.blockLen; i++) this.state[i] = 0;
  384. this.pos = this.blockLen;
  385. this.keccak();
  386. this.posOut = this.blockLen;
  387. }
  388. _cloneInto(to?: KeccakPRG): KeccakPRG {
  389. const { rate } = this;
  390. to ||= new KeccakPRG(1600 - rate);
  391. super._cloneInto(to);
  392. to.rate = rate;
  393. return to;
  394. }
  395. clone(): KeccakPRG {
  396. return this._cloneInto();
  397. }
  398. }
  399. export const keccakprg = (capacity = 254) => new KeccakPRG(capacity);