@noble/post-quantum 0.4.1 → 0.5.1

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 +53 -33
  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 +38 -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 +373 -0
  28. package/src/index.ts +3 -3
  29. package/src/ml-dsa.ts +129 -42
  30. package/src/ml-kem.ts +52 -49
  31. package/src/slh-dsa.ts +97 -56
  32. package/src/utils.ts +86 -50
  33. package/utils.d.ts +53 -11
  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
  };
@@ -199,17 +184,19 @@ const genKPKE = (opts: KyberOpts) => {
199
184
  const poly1 = polyCoder(1);
200
185
  const polyV = polyCoder(dv);
201
186
  const polyU = polyCoder(du);
202
- const publicCoder = splitCoder(vecCoder(polyCoder(12), K), 32);
187
+ const publicCoder = splitCoder('publicKey', vecCoder(polyCoder(12), K), 32);
203
188
  const secretCoder = vecCoder(polyCoder(12), K);
204
- const cipherCoder = splitCoder(vecCoder(polyU, K), polyV);
205
- const seedCoder = splitCoder(32, 32);
189
+ const cipherCoder = splitCoder('ciphertext', vecCoder(polyU, K), polyV);
190
+ const seedCoder = splitCoder('seed', 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, 'seed');
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('secretKey', 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, 'seed');
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, 'publicKey');
296
+ abytes(msg, msgLen, 'message');
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, 'secretKey'); // 768*k + 96
315
+ abytes(cipherText, lengths.cipherText, '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,23 +27,33 @@
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,
38
43
  cleanBytes,
39
- ensureBytes,
44
+ copyBytes,
40
45
  equalBytes,
41
46
  getMask,
42
47
  getMessage,
43
48
  getMessagePrehash,
44
49
  randomBytes,
45
50
  splitCoder,
51
+ validateSigOpts,
52
+ validateVerOpts,
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;
@@ -229,6 +241,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
229
241
  const TREE_BITS = TREE_HEIGHT * (D - 1);
230
242
  const LEAF_BITS = TREE_HEIGHT;
231
243
  const hashMsgCoder = splitCoder(
244
+ 'hashedMessage',
232
245
  Math.ceil((A * K) / 8),
233
246
  Math.ceil(TREE_BITS / 8),
234
247
  Math.ceil(TREE_HEIGHT / 8)
@@ -315,7 +328,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
315
328
  wotsAddr: ADRS,
316
329
  treeAddr: ADRS,
317
330
  leafIdx: number,
318
- prevRoot = new Uint8Array(N)
331
+ prevRoot: Uint8Array = new Uint8Array(N)
319
332
  ) => {
320
333
  setAddr({ type: AddressType.HASHTREE }, treeAddr);
321
334
  // State variables
@@ -374,16 +387,24 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
374
387
  return context.thashN(2, buffer, addr);
375
388
  };
376
389
 
377
- const seedCoder = splitCoder(N, N, N);
378
- const publicCoder = splitCoder(N, N);
379
- const secretCoder = splitCoder(N, N, publicCoder.bytesLen);
380
- const forsCoder = vecCoder(splitCoder(N, N * A), K);
381
- const wotsCoder = vecCoder(splitCoder(WOTS_LEN * N, TREE_HEIGHT * N), D);
382
- const sigCoder = splitCoder(N, forsCoder, wotsCoder); // random || fors || wots
390
+ const seedCoder = splitCoder('seed', N, N, N);
391
+ const publicCoder = splitCoder('publicKey', N, N);
392
+ const secretCoder = splitCoder('secretKey', N, N, publicCoder.bytesLen);
393
+ const forsCoder = vecCoder(splitCoder('fors', N, N * A), K);
394
+ const wotsCoder = vecCoder(splitCoder('wots', WOTS_LEN * N, TREE_HEIGHT * N), D);
395
+ const sigCoder = splitCoder('signature', N, forsCoder, wotsCoder); // random || fors || wots
383
396
  const internal: Signer = {
384
- signRandBytes: N,
397
+ info: { type: 'internal-slh-dsa' },
398
+ lengths: {
399
+ publicKey: publicCoder.bytesLen,
400
+ secretKey: secretCoder.bytesLen,
401
+ signature: sigCoder.bytesLen,
402
+ seed: seedCoder.bytesLen,
403
+ signRand: N,
404
+ },
385
405
  keygen(seed?: Uint8Array) {
386
- seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : seed.slice();
406
+ if (seed !== undefined) abytes(seed, seedCoder.bytesLen, 'seed');
407
+ seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
387
408
  // Set SK.seed, SK.prf, and PK.seed to random n-byte
388
409
  const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
389
410
  const context = getContext(publicSeed, secretSeed);
@@ -398,12 +419,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
398
419
  cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
399
420
  return { publicKey, secretKey };
400
421
  },
401
- sign: (sk: Uint8Array, msg: Uint8Array, random?: Uint8Array) => {
422
+ getPublicKey: (secretKey: Uint8Array) => {
423
+ const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
424
+ return Uint8Array.from(pk);
425
+ },
426
+ sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
427
+ validateSigOpts(opts);
428
+ let { extraEntropy: random } = opts;
402
429
  const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
403
430
  const [pkSeed, _] = publicCoder.decode(pk);
404
431
  // Set opt_rand to either PK.seed or to a random n-byte string
405
- if (!random) random = pkSeed.slice();
406
- ensureBytes(random, N);
432
+ if (random === false) random = copyBytes(pkSeed);
433
+ else if (random === undefined) random = randomBytes(N);
434
+ else random = copyBytes(random);
435
+ abytes(random, N);
407
436
  const context = getContext(pkSeed, skSeed);
408
437
  // Generate randomizer
409
438
  const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
@@ -458,7 +487,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
458
487
  root: r,
459
488
  } = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
460
489
  root.set(r);
461
- r.fill(0);
490
+ cleanBytes(r);
462
491
  wots.push([sigWots, sigAuth]);
463
492
  leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
464
493
  }
@@ -467,7 +496,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
467
496
  cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
468
497
  return SIG;
469
498
  },
470
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => {
499
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
471
500
  const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
472
501
  const [random, forsVec, wotsVec] = sigCoder.decode(sig);
473
502
  const pk = publicKey;
@@ -528,33 +557,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
528
557
  },
529
558
  };
530
559
  return {
560
+ info: { type: 'slh-dsa' },
531
561
  internal,
532
- seedLen: seedCoder.bytesLen,
562
+ securityLevel: securityLevel,
563
+ lengths: internal.lengths,
533
564
  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);
565
+ getPublicKey: internal.getPublicKey,
566
+ sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
567
+ validateSigOpts(opts);
568
+ const M = getMessage(msg, opts.context);
569
+ const res = internal.sign(M, secretKey, opts);
570
+ cleanBytes(M);
539
571
  return res;
540
572
  },
541
- verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
542
- return internal.verify(publicKey, getMessage(msg, ctx), sig);
573
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
574
+ validateVerOpts(opts);
575
+ return internal.verify(sig, getMessage(msg, opts.context), publicKey);
576
+ },
577
+ prehash: (hash: CHash) => {
578
+ checkHash(hash, securityLevel);
579
+ return {
580
+ info: { type: 'hashslh-dsa' },
581
+ lengths: internal.lengths,
582
+ keygen: internal.keygen,
583
+ getPublicKey: internal.getPublicKey,
584
+ sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
585
+ validateSigOpts(opts);
586
+ const M = getMessagePrehash(hash, msg, opts.context);
587
+ const res = internal.sign(M, secretKey, opts);
588
+ cleanBytes(M);
589
+ return res;
590
+ },
591
+ verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
592
+ validateVerOpts(opts);
593
+ return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
594
+ },
595
+ };
543
596
  },
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
597
  };
559
598
  }
560
599
 
@@ -640,16 +679,18 @@ const genSha =
640
679
  const h0tmp = h0ps.clone();
641
680
  const h1tmp = h1ps.clone();
642
681
 
682
+ // https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
643
683
  function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
644
684
  stats.mgf1++;
645
685
  const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
686
+ // NOT 2^32-1
646
687
  if (length > 2 ** 32) throw new Error('mask too long');
647
688
  for (let counter = 0, o = out; o.length; counter++) {
648
689
  counterV.setUint32(0, counter, false);
649
690
  hash.create().update(seed).update(counterB).digestInto(o);
650
691
  o = o.subarray(hash.outputLen);
651
692
  }
652
- out.subarray(length).fill(0);
693
+ cleanBytes(out.subarray(length));
653
694
  return out.subarray(0, length);
654
695
  }
655
696
 
@@ -678,7 +719,7 @@ const genSha =
678
719
  },
679
720
  PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
680
721
  stats.gen_message_random++;
681
- return new HMAC(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
722
+ return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
682
723
  },
683
724
  Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
684
725
  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> {
@@ -55,6 +98,7 @@ type SplitOut<T extends (number | BytesCoderLen<any>)[]> = {
55
98
  [K in keyof T]: T[K] extends number ? Uint8Array : UnCoder<T[K]>;
56
99
  };
57
100
  export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
101
+ label: string,
58
102
  ...lengths: T
59
103
  ): BytesCoder<SplitOut<T>> & { bytesLen: number } {
60
104
  const getLength = (c: number | BytesCoderLen<any>) => (typeof c === 'number' ? c : c.bytesLen);
@@ -67,7 +111,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
67
111
  const c = lengths[i];
68
112
  const l = getLength(c);
69
113
  const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
70
- ensureBytes(b, l);
114
+ abytes_(b, l, label);
71
115
  res.set(b, pos);
72
116
  if (typeof c !== 'number') b.fill(0); // clean
73
117
  pos += l;
@@ -75,7 +119,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
75
119
  return res;
76
120
  },
77
121
  decode: (buf: Uint8Array) => {
78
- ensureBytes(buf, bytesLen);
122
+ abytes_(buf, bytesLen, label);
79
123
  const res = [];
80
124
  for (const c of lengths) {
81
125
  const l = getLength(c);
@@ -105,7 +149,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
105
149
  return res;
106
150
  },
107
151
  decode: (a: Uint8Array): T[] => {
108
- ensureBytes(a, bytesLen);
152
+ abytes_(a, bytesLen);
109
153
  const r: T[] = [];
110
154
  for (let i = 0; i < a.length; i += c.bytesLen)
111
155
  r.push(c.decode(a.subarray(i, i + c.bytesLen)));
@@ -114,7 +158,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
114
158
  };
115
159
  }
116
160
 
117
- // cleanBytes(new Uint8Array(), [new Uint16Array(), new Uint32Array()])
161
+ // cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
118
162
  export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
119
163
  for (const t of list) {
120
164
  if (Array.isArray(t)) for (const b of t) b.fill(0);
@@ -126,48 +170,40 @@ export function getMask(bits: number): number {
126
170
  return (1 << bits) - 1; // 4 -> 0b1111
127
171
  }
128
172
 
129
- export const EMPTY: Uint8Array = new Uint8Array(0);
173
+ export const EMPTY: Uint8Array = Uint8Array.of();
130
174
 
131
175
  export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
132
- ensureBytes(msg);
133
- ensureBytes(ctx);
176
+ abytes_(msg);
177
+ abytes_(ctx);
134
178
  if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
135
179
  return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
136
180
  }
137
181
 
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
- };
182
+ // 06 09 60 86 48 01 65 03 04 02
183
+ const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
184
+
185
+ export function checkHash(hash: CHash, requiredStrength: number = 0): void {
186
+ if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
187
+ throw new Error('hash.oid is invalid: expected NIST hash');
188
+ const collisionResistance = (hash.outputLen * 8) / 2;
189
+ if (requiredStrength > collisionResistance) {
190
+ throw new Error(
191
+ 'Pre-hash security strength too low: ' +
192
+ collisionResistance +
193
+ ', required: ' +
194
+ requiredStrength
195
+ );
196
+ }
197
+ }
160
198
 
161
199
  export function getMessagePrehash(
162
- hashName: string,
200
+ hash: CHash,
163
201
  msg: Uint8Array,
164
202
  ctx: Uint8Array = EMPTY
165
203
  ): Uint8Array {
166
- ensureBytes(msg);
167
- ensureBytes(ctx);
204
+ abytes_(msg);
205
+ abytes_(ctx);
168
206
  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
207
  const hashed = hash(msg);
172
- return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid, hashed);
208
+ return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
173
209
  }