@noble/post-quantum 0.4.0 → 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 +65 -64
  2. package/_crystals.d.ts +2 -2
  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 +130 -66
  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 -53
  20. package/ml-kem.js.map +1 -1
  21. package/package.json +39 -62
  22. package/slh-dsa.d.ts +5 -4
  23. package/slh-dsa.d.ts.map +1 -1
  24. package/slh-dsa.js +114 -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 +131 -41
  30. package/src/ml-kem.ts +51 -47
  31. package/src/slh-dsa.ts +92 -51
  32. package/src/utils.ts +86 -52
  33. package/utils.d.ts +47 -11
  34. package/utils.d.ts.map +1 -1
  35. package/utils.js +54 -62
  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 -519
  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 -305
  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 -595
  57. package/esm/slh-dsa.js.map +0 -1
  58. package/esm/utils.d.ts +0 -46
  59. package/esm/utils.d.ts.map +0 -1
  60. package/esm/utils.js +0 -135
  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';
25
- import { genCrystals, type XOF, XOF128 } from './_crystals.js';
23
+ import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
24
+ import { type CHash, u32 } from '@noble/hashes/utils.js';
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
- } from './utils.js';
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,10 +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) => {
199
+ abytes(seed, 32);
212
200
  const seedDst = new Uint8Array(33);
213
201
  seedDst.set(seed);
214
202
  seedDst[32] = K;
@@ -252,7 +240,7 @@ const genKPKE = (opts: KyberOpts) => {
252
240
  polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
253
241
  u.push(e1);
254
242
  polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
255
- tmp.fill(0);
243
+ cleanBytes(tmp);
256
244
  }
257
245
  x.clean();
258
246
  const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
@@ -277,16 +265,21 @@ const genKPKE = (opts: KyberOpts) => {
277
265
  function createKyber(opts: KyberOpts) {
278
266
  const KPKE = genKPKE(opts);
279
267
  const { HASH256, HASH512, KDF } = opts;
280
- const { secretCoder: KPKESecretCoder, cipherTextLen } = KPKE;
281
- const publicKeyLen = KPKE.publicKeyLen; // 384*K+32
282
- const secretCoder = splitCoder(KPKE.secretKeyLen, KPKE.publicKeyLen, 32, 32);
283
- const secretKeyLen = secretCoder.bytesLen;
268
+ const { secretCoder: KPKESecretCoder, lengths } = KPKE;
269
+ const secretCoder = splitCoder(lengths.secretKey, lengths.publicKey, 32, 32);
284
270
  const msgLen = 32;
271
+ const seedLen = 64;
285
272
  return {
286
- publicKeyLen,
287
- msgLen,
288
- keygen: (seed = randomBytes(64)) => {
289
- 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);
290
283
  const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
291
284
  const publicKeyHash = HASH256(publicKey);
292
285
  // (dkPKE||ek||H(ek)||z)
@@ -294,13 +287,17 @@ function createKyber(opts: KyberOpts) {
294
287
  cleanBytes(sk, publicKeyHash);
295
288
  return { publicKey, secretKey };
296
289
  },
297
- encapsulate: (publicKey: Uint8Array, msg = randomBytes(32)) => {
298
- ensureBytes(publicKey, publicKeyLen);
299
- 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);
300
297
 
301
298
  // FIPS-203 includes additional verification check for modulus
302
299
  const eke = publicKey.subarray(0, 384 * opts.K);
303
- 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
304
301
  // (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
305
302
  // If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
306
303
  if (!equalBytes(ek, eke)) {
@@ -310,12 +307,19 @@ function createKyber(opts: KyberOpts) {
310
307
  cleanBytes(ek);
311
308
  const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
312
309
  const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
313
- kr.subarray(32).fill(0);
310
+ cleanBytes(kr.subarray(32));
314
311
  return { cipherText, sharedSecret: kr.subarray(0, 32) };
315
312
  },
316
313
  decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => {
317
- ensureBytes(secretKey, secretKeyLen); // 768*k + 96
318
- 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');
319
323
  const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
320
324
  const msg = KPKE.decrypt(cipherText, sk);
321
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,7 +51,10 @@ import {
44
51
  randomBytes,
45
52
  splitCoder,
46
53
  vecCoder,
47
- } from './utils.js';
54
+ type Signer,
55
+ type SigOpts,
56
+ type VerOpts,
57
+ } from './utils.ts';
48
58
 
49
59
  /**
50
60
  * * N: Security parameter (in bytes). W: Winternitz parameter
@@ -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,8 +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,
385
- keygen(seed = randomBytes(seedCoder.bytesLen)) {
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
+ },
404
+ keygen(seed?: Uint8Array) {
405
+ if (seed !== undefined) abytes(seed, seedCoder.bytesLen);
406
+ seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
386
407
  // Set SK.seed, SK.prf, and PK.seed to random n-byte
387
408
  const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
388
409
  const context = getContext(publicSeed, secretSeed);
@@ -397,12 +418,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
397
418
  cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
398
419
  return { publicKey, secretKey };
399
420
  },
400
- 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;
401
428
  const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
402
429
  const [pkSeed, _] = publicCoder.decode(pk);
403
430
  // Set opt_rand to either PK.seed or to a random n-byte string
404
- if (!random) random = pkSeed.slice();
405
- 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);
406
435
  const context = getContext(pkSeed, skSeed);
407
436
  // Generate randomizer
408
437
  const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
@@ -457,7 +486,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
457
486
  root: r,
458
487
  } = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
459
488
  root.set(r);
460
- r.fill(0);
489
+ cleanBytes(r);
461
490
  wots.push([sigWots, sigAuth]);
462
491
  leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
463
492
  }
@@ -466,7 +495,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
466
495
  cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
467
496
  return SIG;
468
497
  },
469
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => {
498
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
470
499
  const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
471
500
  const [random, forsVec, wotsVec] = sigCoder.decode(sig);
472
501
  const pk = publicKey;
@@ -527,33 +556,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
527
556
  },
528
557
  };
529
558
  return {
559
+ info: { type: 'slh-dsa' },
530
560
  internal,
531
- seedLen: seedCoder.bytesLen,
561
+ securityLevel: securityLevel,
562
+ lengths: internal.lengths,
532
563
  keygen: internal.keygen,
533
- signRandBytes: internal.signRandBytes,
534
- sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
535
- const M = getMessage(msg, ctx);
536
- const res = internal.sign(secretKey, M, random);
537
- 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);
538
570
  return res;
539
571
  },
540
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
541
- 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
+ };
542
595
  },
543
- prehash: (hashName: string) => ({
544
- seedLen: seedCoder.bytesLen,
545
- keygen: internal.keygen,
546
- signRandBytes: internal.signRandBytes,
547
- sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
548
- const M = getMessagePrehash(hashName, msg, ctx);
549
- const res = internal.sign(secretKey, M, random);
550
- M.fill(0);
551
- return res;
552
- },
553
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
554
- return internal.verify(publicKey, getMessagePrehash(hashName, msg, ctx), sig);
555
- },
556
- }),
557
596
  };
558
597
  }
559
598
 
@@ -639,16 +678,18 @@ const genSha =
639
678
  const h0tmp = h0ps.clone();
640
679
  const h1tmp = h1ps.clone();
641
680
 
681
+ // https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
642
682
  function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
643
683
  stats.mgf1++;
644
684
  const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
685
+ // NOT 2^32-1
645
686
  if (length > 2 ** 32) throw new Error('mask too long');
646
687
  for (let counter = 0, o = out; o.length; counter++) {
647
688
  counterV.setUint32(0, counter, false);
648
689
  hash.create().update(seed).update(counterB).digestInto(o);
649
690
  o = o.subarray(hash.outputLen);
650
691
  }
651
- out.subarray(length).fill(0);
692
+ cleanBytes(out.subarray(length));
652
693
  return out.subarray(0, length);
653
694
  }
654
695
 
@@ -677,7 +718,7 @@ const genSha =
677
718
  },
678
719
  PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
679
720
  stats.gen_message_random++;
680
- 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);
681
722
  },
682
723
  Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
683
724
  stats.hmsg++;
package/src/utils.ts CHANGED
@@ -3,21 +3,18 @@
3
3
  * @module
4
4
  */
5
5
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
6
- import { abytes } from '@noble/hashes/_assert';
7
- import { sha224, sha256 } from '@noble/hashes/sha256';
8
- import { sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256 } from '@noble/hashes/sha3';
9
- import { sha384, sha512, sha512_224, sha512_256 } from '@noble/hashes/sha512';
10
6
  import {
7
+ type CHash,
11
8
  type TypedArray,
9
+ abytes,
10
+ abytes as abytes_,
12
11
  concatBytes,
13
- hexToBytes,
12
+ isBytes,
14
13
  randomBytes as randb,
15
- utf8ToBytes,
16
- } from '@noble/hashes/utils';
17
-
18
- export const ensureBytes: typeof abytes = abytes;
14
+ } from '@noble/hashes/utils.js';
15
+ export { abytes } from '@noble/hashes/utils.js';
16
+ export { concatBytes };
19
17
  export const randomBytes: typeof randb = randb;
20
- export { concatBytes, utf8ToBytes };
21
18
 
22
19
  // Compares 2 u8a-s in kinda constant time
23
20
  export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
@@ -27,15 +24,60 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
27
24
  return diff === 0;
28
25
  }
29
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
+
30
64
  /** Generic interface for signatures. Has keygen, sign and verify. */
31
- export type Signer = {
32
- signRandBytes: number;
33
- keygen: (seed: Uint8Array) => {
34
- secretKey: Uint8Array;
35
- 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;
36
79
  };
37
- sign: (secretKey: Uint8Array, msg: Uint8Array, random?: Uint8Array) => Uint8Array;
38
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => boolean;
80
+ decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
39
81
  };
40
82
 
41
83
  export interface Coder<F, T> {
@@ -68,7 +110,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
68
110
  const c = lengths[i];
69
111
  const l = getLength(c);
70
112
  const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
71
- ensureBytes(b, l);
113
+ abytes_(b, l);
72
114
  res.set(b, pos);
73
115
  if (typeof c !== 'number') b.fill(0); // clean
74
116
  pos += l;
@@ -76,7 +118,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
76
118
  return res;
77
119
  },
78
120
  decode: (buf: Uint8Array) => {
79
- ensureBytes(buf, bytesLen);
121
+ abytes_(buf, bytesLen);
80
122
  const res = [];
81
123
  for (const c of lengths) {
82
124
  const l = getLength(c);
@@ -106,7 +148,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
106
148
  return res;
107
149
  },
108
150
  decode: (a: Uint8Array): T[] => {
109
- ensureBytes(a, bytesLen);
151
+ abytes_(a, bytesLen);
110
152
  const r: T[] = [];
111
153
  for (let i = 0; i < a.length; i += c.bytesLen)
112
154
  r.push(c.decode(a.subarray(i, i + c.bytesLen)));
@@ -115,7 +157,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
115
157
  };
116
158
  }
117
159
 
118
- // cleanBytes(new Uint8Array(), [new Uint16Array(), new Uint32Array()])
160
+ // cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
119
161
  export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
120
162
  for (const t of list) {
121
163
  if (Array.isArray(t)) for (const b of t) b.fill(0);
@@ -127,48 +169,40 @@ export function getMask(bits: number): number {
127
169
  return (1 << bits) - 1; // 4 -> 0b1111
128
170
  }
129
171
 
130
- export const EMPTY: Uint8Array = new Uint8Array(0);
172
+ export const EMPTY: Uint8Array = Uint8Array.of();
131
173
 
132
174
  export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
133
- ensureBytes(msg);
134
- ensureBytes(ctx);
175
+ abytes_(msg);
176
+ abytes_(ctx);
135
177
  if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
136
178
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
137
179
  }
138
180
 
139
- // OIDS from https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration
140
- // TODO: maybe add 'OID' property to hashes themselves to improve tree-shaking?
141
- const HASHES: Record<string, { oid: Uint8Array; hash: (msg: Uint8Array) => Uint8Array }> = {
142
- 'SHA2-256': { oid: hexToBytes('0609608648016503040201'), hash: sha256 },
143
- 'SHA2-384': { oid: hexToBytes('0609608648016503040202'), hash: sha384 },
144
- 'SHA2-512': { oid: hexToBytes('0609608648016503040203'), hash: sha512 },
145
- 'SHA2-224': { oid: hexToBytes('0609608648016503040204'), hash: sha224 },
146
- 'SHA2-512/224': { oid: hexToBytes('0609608648016503040205'), hash: sha512_224 },
147
- 'SHA2-512/256': { oid: hexToBytes('0609608648016503040206'), hash: sha512_256 },
148
- 'SHA3-224': { oid: hexToBytes('0609608648016503040207'), hash: sha3_224 },
149
- 'SHA3-256': { oid: hexToBytes('0609608648016503040208'), hash: sha3_256 },
150
- 'SHA3-384': { oid: hexToBytes('0609608648016503040209'), hash: sha3_384 },
151
- 'SHA3-512': { oid: hexToBytes('060960864801650304020A'), hash: sha3_512 },
152
- 'SHAKE-128': {
153
- oid: hexToBytes('060960864801650304020B'),
154
- hash: (msg) => shake128(msg, { dkLen: 32 }),
155
- },
156
- 'SHAKE-256': {
157
- oid: hexToBytes('060960864801650304020C'),
158
- hash: (msg) => shake256(msg, { dkLen: 64 }),
159
- },
160
- };
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
+ }
161
197
 
162
198
  export function getMessagePrehash(
163
- hashName: string,
199
+ hash: CHash,
164
200
  msg: Uint8Array,
165
201
  ctx: Uint8Array = EMPTY
166
202
  ): Uint8Array {
167
- ensureBytes(msg);
168
- ensureBytes(ctx);
203
+ abytes_(msg);
204
+ abytes_(ctx);
169
205
  if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
170
- if (!HASHES[hashName]) throw new Error('unknown hash: ' + hashName);
171
- const { oid, hash } = HASHES[hashName];
172
206
  const hashed = hash(msg);
173
- return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid, hashed);
207
+ return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
174
208
  }