@noble/post-quantum 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +47 -32
  2. package/_crystals.d.ts +1 -1
  3. package/_crystals.d.ts.map +1 -1
  4. package/_crystals.js +31 -46
  5. package/_crystals.js.map +1 -1
  6. package/hybrid.d.ts +102 -0
  7. package/hybrid.d.ts.map +1 -0
  8. package/hybrid.js +283 -0
  9. package/hybrid.js.map +1 -0
  10. package/index.d.ts +1 -0
  11. package/index.js +4 -4
  12. package/index.js.map +1 -1
  13. package/ml-dsa.d.ts +16 -8
  14. package/ml-dsa.d.ts.map +1 -1
  15. package/ml-dsa.js +126 -68
  16. package/ml-dsa.js.map +1 -1
  17. package/ml-kem.d.ts +1 -14
  18. package/ml-kem.d.ts.map +1 -1
  19. package/ml-kem.js +70 -54
  20. package/ml-kem.js.map +1 -1
  21. package/package.json +39 -85
  22. package/slh-dsa.d.ts +4 -3
  23. package/slh-dsa.d.ts.map +1 -1
  24. package/slh-dsa.js +113 -86
  25. package/slh-dsa.js.map +1 -1
  26. package/src/_crystals.ts +30 -41
  27. package/src/hybrid.ts +372 -0
  28. package/src/index.ts +3 -3
  29. package/src/ml-dsa.ts +125 -39
  30. package/src/ml-kem.ts +49 -46
  31. package/src/slh-dsa.ts +90 -50
  32. package/src/utils.ts +85 -50
  33. package/utils.d.ts +52 -10
  34. package/utils.d.ts.map +1 -1
  35. package/utils.js +54 -60
  36. package/utils.js.map +1 -1
  37. package/esm/_crystals.d.ts +0 -34
  38. package/esm/_crystals.d.ts.map +0 -1
  39. package/esm/_crystals.js +0 -141
  40. package/esm/_crystals.js.map +0 -1
  41. package/esm/index.d.ts +0 -2
  42. package/esm/index.d.ts.map +0 -1
  43. package/esm/index.js +0 -21
  44. package/esm/index.js.map +0 -1
  45. package/esm/ml-dsa.d.ts +0 -25
  46. package/esm/ml-dsa.d.ts.map +0 -1
  47. package/esm/ml-dsa.js +0 -525
  48. package/esm/ml-dsa.js.map +0 -1
  49. package/esm/ml-kem.d.ts +0 -34
  50. package/esm/ml-kem.d.ts.map +0 -1
  51. package/esm/ml-kem.js +0 -306
  52. package/esm/ml-kem.js.map +0 -1
  53. package/esm/package.json +0 -10
  54. package/esm/slh-dsa.d.ts +0 -62
  55. package/esm/slh-dsa.d.ts.map +0 -1
  56. package/esm/slh-dsa.js +0 -596
  57. package/esm/slh-dsa.js.map +0 -1
  58. package/esm/utils.d.ts +0 -40
  59. package/esm/utils.d.ts.map +0 -1
  60. package/esm/utils.js +0 -133
  61. package/esm/utils.js.map +0 -1
  62. package/src/package.json +0 -3
package/src/ml-kem.ts CHANGED
@@ -20,36 +20,22 @@
20
20
  * @module
21
21
  */
22
22
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
23
- import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3';
24
- import { u32, wrapConstructor, wrapConstructorWithOpts } from '@noble/hashes/utils';
23
+ import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
24
+ import { type CHash, u32 } from '@noble/hashes/utils.js';
25
25
  import { genCrystals, type XOF, XOF128 } from './_crystals.ts';
26
26
  import {
27
+ abytes,
27
28
  cleanBytes,
28
29
  type Coder,
29
- ensureBytes,
30
+ copyBytes,
30
31
  equalBytes,
32
+ type KEM,
31
33
  randomBytes,
32
34
  splitCoder,
33
35
  vecCoder,
34
36
  } from './utils.ts';
35
37
 
36
38
  /** Key encapsulation mechanism interface */
37
- export type KEM = {
38
- publicKeyLen: number;
39
- msgLen: number;
40
- keygen: (seed?: Uint8Array) => {
41
- publicKey: Uint8Array;
42
- secretKey: Uint8Array;
43
- };
44
- encapsulate: (
45
- publicKey: Uint8Array,
46
- msg?: Uint8Array
47
- ) => {
48
- cipherText: Uint8Array;
49
- sharedSecret: Uint8Array;
50
- };
51
- decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
52
- };
53
39
 
54
40
  const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std modes were using 256
55
41
  const Q = 3329; // 13*(2**8)+1, modulo prime
@@ -60,7 +46,7 @@ const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
60
46
  Q,
61
47
  F,
62
48
  ROOT_OF_UNITY,
63
- newPoly: (n: number) => new Uint16Array(n),
49
+ newPoly: (n: number): Uint16Array => new Uint16Array(n),
64
50
  brvBits: 7,
65
51
  isKyber: true,
66
52
  });
@@ -106,7 +92,7 @@ const compress = (d: number): Coder<number, number> => {
106
92
  const polyCoder = (d: number) => bitsCoder(d, compress(d));
107
93
 
108
94
  // Poly is mod Q, so 12 bits
109
- type Poly = Uint16Array;
95
+ type Poly = Uint16Array<any>;
110
96
 
111
97
  function polyAdd(a: Poly, b: Poly) {
112
98
  for (let i = 0; i < N; i++) a[i] = mod(a[i] + b[i]); // a += b
@@ -137,14 +123,13 @@ function MultiplyNTTs(f: Poly, g: Poly): Poly {
137
123
 
138
124
  type PRF = (l: number, key: Uint8Array, nonce: number) => Uint8Array;
139
125
 
140
- type Hash = ReturnType<typeof wrapConstructor>;
141
- type HashWOpts = ReturnType<typeof wrapConstructorWithOpts>;
142
126
  type XofGet = ReturnType<ReturnType<XOF>['get']>;
143
127
 
144
128
  type KyberOpts = KEMParam & {
145
- HASH256: Hash;
146
- HASH512: Hash;
147
- KDF: Hash | HashWOpts;
129
+ HASH256: CHash;
130
+ HASH512: CHash;
131
+ // KDF: CHash<Keccak, ShakeOpts>;
132
+ KDF: any;
148
133
  XOF: XOF; // (seed: Uint8Array, len: number, x: number, y: number) => Uint8Array;
149
134
  PRF: PRF;
150
135
  };
@@ -205,11 +190,13 @@ const genKPKE = (opts: KyberOpts) => {
205
190
  const seedCoder = splitCoder(32, 32);
206
191
  return {
207
192
  secretCoder,
208
- secretKeyLen: secretCoder.bytesLen,
209
- publicKeyLen: publicCoder.bytesLen,
210
- cipherTextLen: cipherCoder.bytesLen,
193
+ lengths: {
194
+ secretKey: secretCoder.bytesLen,
195
+ publicKey: publicCoder.bytesLen,
196
+ cipherText: cipherCoder.bytesLen,
197
+ },
211
198
  keygen: (seed: Uint8Array) => {
212
- ensureBytes(seed, 32);
199
+ abytes(seed, 32);
213
200
  const seedDst = new Uint8Array(33);
214
201
  seedDst.set(seed);
215
202
  seedDst[32] = K;
@@ -253,7 +240,7 @@ const genKPKE = (opts: KyberOpts) => {
253
240
  polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
254
241
  u.push(e1);
255
242
  polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
256
- tmp.fill(0);
243
+ cleanBytes(tmp);
257
244
  }
258
245
  x.clean();
259
246
  const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
@@ -278,16 +265,21 @@ const genKPKE = (opts: KyberOpts) => {
278
265
  function createKyber(opts: KyberOpts) {
279
266
  const KPKE = genKPKE(opts);
280
267
  const { HASH256, HASH512, KDF } = opts;
281
- const { secretCoder: KPKESecretCoder, cipherTextLen } = KPKE;
282
- const publicKeyLen = KPKE.publicKeyLen; // 384*K+32
283
- const secretCoder = splitCoder(KPKE.secretKeyLen, KPKE.publicKeyLen, 32, 32);
284
- const secretKeyLen = secretCoder.bytesLen;
268
+ const { secretCoder: KPKESecretCoder, lengths } = KPKE;
269
+ const secretCoder = splitCoder(lengths.secretKey, lengths.publicKey, 32, 32);
285
270
  const msgLen = 32;
271
+ const seedLen = 64;
286
272
  return {
287
- publicKeyLen,
288
- msgLen,
289
- keygen: (seed = randomBytes(64)) => {
290
- ensureBytes(seed, 64);
273
+ info: { type: 'ml-kem' },
274
+ lengths: {
275
+ ...lengths,
276
+ seed: 64,
277
+ msg: msgLen,
278
+ msgRand: msgLen,
279
+ secretKey: secretCoder.bytesLen,
280
+ },
281
+ keygen: (seed = randomBytes(seedLen)) => {
282
+ abytes(seed, seedLen);
291
283
  const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
292
284
  const publicKeyHash = HASH256(publicKey);
293
285
  // (dkPKE||ek||H(ek)||z)
@@ -295,13 +287,17 @@ function createKyber(opts: KyberOpts) {
295
287
  cleanBytes(sk, publicKeyHash);
296
288
  return { publicKey, secretKey };
297
289
  },
298
- encapsulate: (publicKey: Uint8Array, msg = randomBytes(32)) => {
299
- ensureBytes(publicKey, publicKeyLen);
300
- ensureBytes(msg, msgLen);
290
+ getPublicKey: (secretKey: Uint8Array) => {
291
+ const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
292
+ return Uint8Array.from(publicKey);
293
+ },
294
+ encapsulate: (publicKey: Uint8Array, msg = randomBytes(msgLen)) => {
295
+ abytes(publicKey, lengths.publicKey);
296
+ abytes(msg, msgLen);
301
297
 
302
298
  // FIPS-203 includes additional verification check for modulus
303
299
  const eke = publicKey.subarray(0, 384 * opts.K);
304
- const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(eke.slice())); // Copy because of inplace encoding
300
+ const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke))); // Copy because of inplace encoding
305
301
  // (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
306
302
  // If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
307
303
  if (!equalBytes(ek, eke)) {
@@ -311,12 +307,19 @@ function createKyber(opts: KyberOpts) {
311
307
  cleanBytes(ek);
312
308
  const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
313
309
  const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
314
- kr.subarray(32).fill(0);
310
+ cleanBytes(kr.subarray(32));
315
311
  return { cipherText, sharedSecret: kr.subarray(0, 32) };
316
312
  },
317
313
  decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => {
318
- ensureBytes(secretKey, secretKeyLen); // 768*k + 96
319
- ensureBytes(cipherText, cipherTextLen); // 32(du*k + dv)
314
+ abytes(secretKey, secretCoder.bytesLen); // 768*k + 96
315
+ abytes(cipherText, lengths.cipherText); // 32(du*k + dv)
316
+ // test ← H(dk[384𝑘 ∶ 768𝑘 + 32])) .
317
+ const k768 = secretCoder.bytesLen - 96;
318
+ const start = k768 + 32;
319
+ const test = HASH256(secretKey.subarray(k768 / 2, start));
320
+ // If test ≠ dk[768𝑘 + 32 ∶ 768𝑘 + 64], then input checking has failed.
321
+ if (!equalBytes(test, secretKey.subarray(start, start + 32)))
322
+ throw new Error('invalid secretKey: hash check failed');
320
323
  const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
321
324
  const msg = KPKE.decrypt(cipherText, sk);
322
325
  const kr = HASH512.create().update(msg).update(publicKeyHash).digest(); // derive randomness, Khat, rHat = G(mHat || h)
package/src/slh-dsa.ts CHANGED
@@ -27,16 +27,23 @@
27
27
  * @module
28
28
  */
29
29
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
30
- import { setBigUint64 } from '@noble/hashes/_md';
31
- import { HMAC } from '@noble/hashes/hmac';
32
- import { sha256, sha512 } from '@noble/hashes/sha2';
33
- import { shake256 } from '@noble/hashes/sha3';
34
- import { bytesToHex, concatBytes, createView, hexToBytes } from '@noble/hashes/utils';
30
+ import { hmac } from '@noble/hashes/hmac.js';
31
+ import { sha256, sha512 } from '@noble/hashes/sha2.js';
32
+ import { shake256 } from '@noble/hashes/sha3.js';
35
33
  import {
36
- EMPTY,
37
- type Signer,
34
+ bytesToHex,
35
+ concatBytes,
36
+ createView,
37
+ hexToBytes,
38
+ type CHash,
39
+ } from '@noble/hashes/utils.js';
40
+ import {
41
+ abytes,
42
+ checkHash,
43
+ validateSigOpts,
44
+ validateVerOpts,
38
45
  cleanBytes,
39
- ensureBytes,
46
+ copyBytes,
40
47
  equalBytes,
41
48
  getMask,
42
49
  getMessage,
@@ -44,6 +51,9 @@ import {
44
51
  randomBytes,
45
52
  splitCoder,
46
53
  vecCoder,
54
+ type Signer,
55
+ type SigOpts,
56
+ type VerOpts,
47
57
  } from './utils.ts';
48
58
 
49
59
  /**
@@ -58,6 +68,7 @@ export type SphincsOpts = {
58
68
  D: number;
59
69
  K: number;
60
70
  A: number;
71
+ securityLevel: number;
61
72
  };
62
73
 
63
74
  export type SphincsHashOpts = {
@@ -67,12 +78,12 @@ export type SphincsHashOpts = {
67
78
 
68
79
  /** Winternitz signature params. */
69
80
  export const PARAMS: Record<string, SphincsOpts> = {
70
- '128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6 },
71
- '128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12 },
72
- '192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8 },
73
- '192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14 },
74
- '256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9 },
75
- '256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14 },
81
+ '128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 },
82
+ '128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 },
83
+ '192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 },
84
+ '192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 },
85
+ '256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 },
86
+ '256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 },
76
87
  } as const;
77
88
 
78
89
  const AddressType = {
@@ -135,13 +146,14 @@ function getMaskBig(bits: number) {
135
146
  return (1n << BigInt(bits)) - 1n; // 4 -> 0b1111
136
147
  }
137
148
 
138
- export type SphincsSigner = Signer & { seedLen: number } & {
149
+ export type SphincsSigner = Signer & {
139
150
  internal: Signer;
140
- prehash: (hashName: string) => Signer;
151
+ securityLevel: number;
152
+ prehash: (hash: CHash) => Signer;
141
153
  };
142
154
 
143
155
  function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
144
- const { N, W, H, D, K, A } = opts;
156
+ const { N, W, H, D, K, A, securityLevel: securityLevel } = opts;
145
157
  const getContext = hashOpts.getContext(opts);
146
158
  if (W !== 16) throw new Error('Unsupported Winternitz parameter');
147
159
  const WOTS_LOGW = 4;
@@ -197,7 +209,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
197
209
  if (hash !== undefined) addr[OFFSET_HASH_ADDR] = hash;
198
210
  if (index !== undefined) v.setUint32(OFFSET_TREE_INDEX, index, false);
199
211
  if (subtreeAddr) addr.set(subtreeAddr.subarray(0, OFFSET_TREE + 8));
200
- if (tree !== undefined) setBigUint64(v, OFFSET_TREE, tree, false);
212
+ if (tree !== undefined) v.setBigUint64(OFFSET_TREE, tree, false);
201
213
  if (keypair !== undefined) {
202
214
  addr[OFFSET_KP_ADDR1] = keypair;
203
215
  if (TREE_HEIGHT > 8) addr[OFFSET_KP_ADDR2] = keypair >>> 8;
@@ -315,7 +327,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
315
327
  wotsAddr: ADRS,
316
328
  treeAddr: ADRS,
317
329
  leafIdx: number,
318
- prevRoot = new Uint8Array(N)
330
+ prevRoot: Uint8Array = new Uint8Array(N)
319
331
  ) => {
320
332
  setAddr({ type: AddressType.HASHTREE }, treeAddr);
321
333
  // State variables
@@ -381,9 +393,17 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
381
393
  const wotsCoder = vecCoder(splitCoder(WOTS_LEN * N, TREE_HEIGHT * N), D);
382
394
  const sigCoder = splitCoder(N, forsCoder, wotsCoder); // random || fors || wots
383
395
  const internal: Signer = {
384
- signRandBytes: N,
396
+ info: { type: 'internal-slh-dsa' },
397
+ lengths: {
398
+ publicKey: publicCoder.bytesLen,
399
+ secretKey: secretCoder.bytesLen,
400
+ signature: sigCoder.bytesLen,
401
+ seed: seedCoder.bytesLen,
402
+ signRand: N,
403
+ },
385
404
  keygen(seed?: Uint8Array) {
386
- seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : seed.slice();
405
+ if (seed !== undefined) abytes(seed, seedCoder.bytesLen);
406
+ seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
387
407
  // Set SK.seed, SK.prf, and PK.seed to random n-byte
388
408
  const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
389
409
  const context = getContext(publicSeed, secretSeed);
@@ -398,12 +418,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
398
418
  cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
399
419
  return { publicKey, secretKey };
400
420
  },
401
- sign: (sk: Uint8Array, msg: Uint8Array, random?: Uint8Array) => {
421
+ getPublicKey: (secretKey: Uint8Array) => {
422
+ const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
423
+ return Uint8Array.from(pk);
424
+ },
425
+ sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
426
+ validateSigOpts(opts);
427
+ let { extraEntropy: random } = opts;
402
428
  const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
403
429
  const [pkSeed, _] = publicCoder.decode(pk);
404
430
  // Set opt_rand to either PK.seed or to a random n-byte string
405
- if (!random) random = pkSeed.slice();
406
- ensureBytes(random, N);
431
+ if (random === false) random = copyBytes(pkSeed);
432
+ else if (random === undefined) random = randomBytes(N);
433
+ else random = copyBytes(random);
434
+ abytes(random, N);
407
435
  const context = getContext(pkSeed, skSeed);
408
436
  // Generate randomizer
409
437
  const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
@@ -458,7 +486,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
458
486
  root: r,
459
487
  } = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
460
488
  root.set(r);
461
- r.fill(0);
489
+ cleanBytes(r);
462
490
  wots.push([sigWots, sigAuth]);
463
491
  leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
464
492
  }
@@ -467,7 +495,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
467
495
  cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
468
496
  return SIG;
469
497
  },
470
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => {
498
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
471
499
  const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
472
500
  const [random, forsVec, wotsVec] = sigCoder.decode(sig);
473
501
  const pk = publicKey;
@@ -528,33 +556,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
528
556
  },
529
557
  };
530
558
  return {
559
+ info: { type: 'slh-dsa' },
531
560
  internal,
532
- seedLen: seedCoder.bytesLen,
561
+ securityLevel: securityLevel,
562
+ lengths: internal.lengths,
533
563
  keygen: internal.keygen,
534
- signRandBytes: internal.signRandBytes,
535
- sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
536
- const M = getMessage(msg, ctx);
537
- const res = internal.sign(secretKey, M, random);
538
- M.fill(0);
564
+ getPublicKey: internal.getPublicKey,
565
+ sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
566
+ validateSigOpts(opts);
567
+ const M = getMessage(msg, opts.context);
568
+ const res = internal.sign(M, secretKey, opts);
569
+ cleanBytes(M);
539
570
  return res;
540
571
  },
541
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
542
- return internal.verify(publicKey, getMessage(msg, ctx), sig);
572
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
573
+ validateVerOpts(opts);
574
+ return internal.verify(sig, getMessage(msg, opts.context), publicKey);
575
+ },
576
+ prehash: (hash: CHash) => {
577
+ checkHash(hash, securityLevel);
578
+ return {
579
+ info: { type: 'hashslh-dsa' },
580
+ lengths: internal.lengths,
581
+ keygen: internal.keygen,
582
+ getPublicKey: internal.getPublicKey,
583
+ sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
584
+ validateSigOpts(opts);
585
+ const M = getMessagePrehash(hash, msg, opts.context);
586
+ const res = internal.sign(M, secretKey, opts);
587
+ cleanBytes(M);
588
+ return res;
589
+ },
590
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
591
+ validateVerOpts(opts);
592
+ return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
593
+ },
594
+ };
543
595
  },
544
- prehash: (hashName: string) => ({
545
- seedLen: seedCoder.bytesLen,
546
- keygen: internal.keygen,
547
- signRandBytes: internal.signRandBytes,
548
- sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
549
- const M = getMessagePrehash(hashName, msg, ctx);
550
- const res = internal.sign(secretKey, M, random);
551
- M.fill(0);
552
- return res;
553
- },
554
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
555
- return internal.verify(publicKey, getMessagePrehash(hashName, msg, ctx), sig);
556
- },
557
- }),
558
596
  };
559
597
  }
560
598
 
@@ -640,16 +678,18 @@ const genSha =
640
678
  const h0tmp = h0ps.clone();
641
679
  const h1tmp = h1ps.clone();
642
680
 
681
+ // https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
643
682
  function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
644
683
  stats.mgf1++;
645
684
  const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
685
+ // NOT 2^32-1
646
686
  if (length > 2 ** 32) throw new Error('mask too long');
647
687
  for (let counter = 0, o = out; o.length; counter++) {
648
688
  counterV.setUint32(0, counter, false);
649
689
  hash.create().update(seed).update(counterB).digestInto(o);
650
690
  o = o.subarray(hash.outputLen);
651
691
  }
652
- out.subarray(length).fill(0);
692
+ cleanBytes(out.subarray(length));
653
693
  return out.subarray(0, length);
654
694
  }
655
695
 
@@ -678,7 +718,7 @@ const genSha =
678
718
  },
679
719
  PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
680
720
  stats.gen_message_random++;
681
- return new HMAC(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
721
+ return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
682
722
  },
683
723
  Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
684
724
  stats.hmsg++;
package/src/utils.ts CHANGED
@@ -3,20 +3,18 @@
3
3
  * @module
4
4
  */
5
5
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
6
- import { sha224, sha256, sha384, sha512, sha512_224, sha512_256 } from '@noble/hashes/sha2';
7
- import { sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256 } from '@noble/hashes/sha3';
8
6
  import {
7
+ type CHash,
9
8
  type TypedArray,
10
9
  abytes,
10
+ abytes as abytes_,
11
11
  concatBytes,
12
- hexToBytes,
12
+ isBytes,
13
13
  randomBytes as randb,
14
- utf8ToBytes,
15
- } from '@noble/hashes/utils';
16
-
17
- export const ensureBytes: typeof abytes = abytes;
14
+ } from '@noble/hashes/utils.js';
15
+ export { abytes } from '@noble/hashes/utils.js';
16
+ export { concatBytes };
18
17
  export const randomBytes: typeof randb = randb;
19
- export { concatBytes, utf8ToBytes };
20
18
 
21
19
  // Compares 2 u8a-s in kinda constant time
22
20
  export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
@@ -26,15 +24,60 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
26
24
  return diff === 0;
27
25
  }
28
26
 
27
+ // copy bytes to new u8a (aligned). Because Buffer.slice is broken.
28
+ export function copyBytes(bytes: Uint8Array): Uint8Array {
29
+ return Uint8Array.from(bytes);
30
+ }
31
+
32
+ export type CryptoKeys = {
33
+ info?: { type?: string };
34
+ lengths: { seed?: number; publicKey?: number; secretKey?: number };
35
+ keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
36
+ getPublicKey: (secretKey: Uint8Array) => Uint8Array;
37
+ };
38
+
39
+ export type VerOpts = {
40
+ context?: Uint8Array;
41
+ };
42
+ export type SigOpts = VerOpts & {
43
+ // Compatibility with @noble/curves: false to disable, enabled by default, user can pass U8A
44
+ extraEntropy?: Uint8Array | false;
45
+ };
46
+
47
+ export function validateOpts(opts: object): void {
48
+ // We try to catch u8a, since it was previously valid argument at this position
49
+ if (typeof opts !== 'object' || opts === null || isBytes(opts))
50
+ throw new Error('expected opts to be an object');
51
+ }
52
+
53
+ export function validateVerOpts(opts: VerOpts): void {
54
+ validateOpts(opts);
55
+ if (opts.context !== undefined) abytes(opts.context, undefined, 'opts.context');
56
+ }
57
+
58
+ export function validateSigOpts(opts: SigOpts): void {
59
+ validateVerOpts(opts);
60
+ if (opts.extraEntropy !== false && opts.extraEntropy !== undefined)
61
+ abytes(opts.extraEntropy, undefined, 'opts.extraEntropy');
62
+ }
63
+
29
64
  /** Generic interface for signatures. Has keygen, sign and verify. */
30
- export type Signer = {
31
- signRandBytes: number;
32
- keygen: (seed: Uint8Array) => {
33
- secretKey: Uint8Array;
34
- publicKey: Uint8Array;
65
+ export type Signer = CryptoKeys & {
66
+ lengths: { signRand?: number; signature?: number };
67
+ sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts) => Uint8Array;
68
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts?: VerOpts) => boolean;
69
+ };
70
+
71
+ export type KEM = CryptoKeys & {
72
+ lengths: { cipherText?: number; msg?: number; msgRand?: number };
73
+ encapsulate: (
74
+ publicKey: Uint8Array,
75
+ msg?: Uint8Array
76
+ ) => {
77
+ cipherText: Uint8Array;
78
+ sharedSecret: Uint8Array;
35
79
  };
36
- sign: (secretKey: Uint8Array, msg: Uint8Array, random?: Uint8Array) => Uint8Array;
37
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => boolean;
80
+ decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
38
81
  };
39
82
 
40
83
  export interface Coder<F, T> {
@@ -67,7 +110,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
67
110
  const c = lengths[i];
68
111
  const l = getLength(c);
69
112
  const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
70
- ensureBytes(b, l);
113
+ abytes_(b, l);
71
114
  res.set(b, pos);
72
115
  if (typeof c !== 'number') b.fill(0); // clean
73
116
  pos += l;
@@ -75,7 +118,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
75
118
  return res;
76
119
  },
77
120
  decode: (buf: Uint8Array) => {
78
- ensureBytes(buf, bytesLen);
121
+ abytes_(buf, bytesLen);
79
122
  const res = [];
80
123
  for (const c of lengths) {
81
124
  const l = getLength(c);
@@ -105,7 +148,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
105
148
  return res;
106
149
  },
107
150
  decode: (a: Uint8Array): T[] => {
108
- ensureBytes(a, bytesLen);
151
+ abytes_(a, bytesLen);
109
152
  const r: T[] = [];
110
153
  for (let i = 0; i < a.length; i += c.bytesLen)
111
154
  r.push(c.decode(a.subarray(i, i + c.bytesLen)));
@@ -114,7 +157,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
114
157
  };
115
158
  }
116
159
 
117
- // cleanBytes(new Uint8Array(), [new Uint16Array(), new Uint32Array()])
160
+ // cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
118
161
  export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
119
162
  for (const t of list) {
120
163
  if (Array.isArray(t)) for (const b of t) b.fill(0);
@@ -126,48 +169,40 @@ export function getMask(bits: number): number {
126
169
  return (1 << bits) - 1; // 4 -> 0b1111
127
170
  }
128
171
 
129
- export const EMPTY: Uint8Array = new Uint8Array(0);
172
+ export const EMPTY: Uint8Array = Uint8Array.of();
130
173
 
131
174
  export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
132
- ensureBytes(msg);
133
- ensureBytes(ctx);
175
+ abytes_(msg);
176
+ abytes_(ctx);
134
177
  if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
135
178
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
136
179
  }
137
180
 
138
- // OIDS from https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration
139
- // TODO: maybe add 'OID' property to hashes themselves to improve tree-shaking?
140
- const HASHES: Record<string, { oid: Uint8Array; hash: (msg: Uint8Array) => Uint8Array }> = {
141
- 'SHA2-256': { oid: hexToBytes('0609608648016503040201'), hash: sha256 },
142
- 'SHA2-384': { oid: hexToBytes('0609608648016503040202'), hash: sha384 },
143
- 'SHA2-512': { oid: hexToBytes('0609608648016503040203'), hash: sha512 },
144
- 'SHA2-224': { oid: hexToBytes('0609608648016503040204'), hash: sha224 },
145
- 'SHA2-512/224': { oid: hexToBytes('0609608648016503040205'), hash: sha512_224 },
146
- 'SHA2-512/256': { oid: hexToBytes('0609608648016503040206'), hash: sha512_256 },
147
- 'SHA3-224': { oid: hexToBytes('0609608648016503040207'), hash: sha3_224 },
148
- 'SHA3-256': { oid: hexToBytes('0609608648016503040208'), hash: sha3_256 },
149
- 'SHA3-384': { oid: hexToBytes('0609608648016503040209'), hash: sha3_384 },
150
- 'SHA3-512': { oid: hexToBytes('060960864801650304020A'), hash: sha3_512 },
151
- 'SHAKE-128': {
152
- oid: hexToBytes('060960864801650304020B'),
153
- hash: (msg) => shake128(msg, { dkLen: 32 }),
154
- },
155
- 'SHAKE-256': {
156
- oid: hexToBytes('060960864801650304020C'),
157
- hash: (msg) => shake256(msg, { dkLen: 64 }),
158
- },
159
- };
181
+ // 06 09 60 86 48 01 65 03 04 02
182
+ const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
183
+
184
+ export function checkHash(hash: CHash, requiredStrength: number = 0): void {
185
+ if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
186
+ throw new Error('hash.oid is invalid: expected NIST hash');
187
+ const collisionResistance = (hash.outputLen * 8) / 2;
188
+ if (requiredStrength > collisionResistance) {
189
+ throw new Error(
190
+ 'Pre-hash security strength too low: ' +
191
+ collisionResistance +
192
+ ', required: ' +
193
+ requiredStrength
194
+ );
195
+ }
196
+ }
160
197
 
161
198
  export function getMessagePrehash(
162
- hashName: string,
199
+ hash: CHash,
163
200
  msg: Uint8Array,
164
201
  ctx: Uint8Array = EMPTY
165
202
  ): Uint8Array {
166
- ensureBytes(msg);
167
- ensureBytes(ctx);
203
+ abytes_(msg);
204
+ abytes_(ctx);
168
205
  if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
169
- if (!HASHES[hashName]) throw new Error('unknown hash: ' + hashName);
170
- const { oid, hash } = HASHES[hashName];
171
206
  const hashed = hash(msg);
172
- return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid, hashed);
207
+ return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
173
208
  }