@noble/post-quantum 0.6.0 → 0.6.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.
package/src/hybrid.ts CHANGED
@@ -101,6 +101,8 @@ import {
101
101
  type CryptoKeys,
102
102
  type KEM,
103
103
  type Signer,
104
+ type TArg,
105
+ type TRet,
104
106
  } from './utils.ts';
105
107
 
106
108
  type CurveAll = ECDSA | EdDSA | MontgomeryECDH;
@@ -126,18 +128,26 @@ function ecKeygen(curve: CurveAll, allowZeroKey: boolean = false) {
126
128
  // Unlike noble-curves' seeded Weierstrass keygen, this path removes the post-reduction +1.
127
129
  // That is enough to match exact reduced-vector bytes, but an all-zero seed still reduces to
128
130
  // scalar 0 here and getPublicKey(secretKey) throws instead of "allowing zero".
129
- keygen = (seed: Uint8Array = randomBytes(lengths.seed)) => {
131
+ keygen = (seed: TArg<Uint8Array> = randomBytes(lengths.seed)) => {
130
132
  abytes(seed, lengths.seed!, 'seed');
131
133
  const seedScalar = Fn.isLE ? bytesToNumberLE(seed) : bytesToNumberBE(seed);
132
134
  // Reduce directly into [0, ORDER); scalar 0 still stays invalid.
133
135
  const secretKey = Fn.toBytes(Fn.create(seedScalar));
134
- return { secretKey, publicKey: curve.getPublicKey(secretKey) };
136
+ return {
137
+ secretKey: secretKey as TRet<Uint8Array>,
138
+ publicKey: curve.getPublicKey(secretKey) as TRet<Uint8Array>,
139
+ };
135
140
  };
136
141
  }
137
142
  return {
138
143
  lengths: { secretKey: lengths.secretKey, publicKey: lengths.publicKey, seed: lengths.seed },
139
- keygen,
140
- getPublicKey: (secretKey: Uint8Array) => curve.getPublicKey(secretKey),
144
+ keygen: (seed?: TArg<Uint8Array>) =>
145
+ keygen(seed) as TRet<{
146
+ secretKey: Uint8Array;
147
+ publicKey: Uint8Array;
148
+ }>,
149
+ getPublicKey: (secretKey: TArg<Uint8Array>) =>
150
+ curve.getPublicKey(secretKey) as TRet<Uint8Array>,
141
151
  };
142
152
  }
143
153
 
@@ -164,14 +174,17 @@ function ecKeygen(curve: CurveAll, allowZeroKey: boolean = false) {
164
174
  * const publicKeyLen = kem.lengths.publicKey;
165
175
  * ```
166
176
  */
167
- export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
177
+ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): TRet<KEM> {
168
178
  const kg = ecKeygen(curve, allowZeroKey);
169
179
  if (!curve.getSharedSecret) throw new Error('wrong curve'); // ed25519 doesn't have one!
170
180
  return {
171
181
  lengths: { ...kg.lengths, msg: kg.lengths.seed, cipherText: kg.lengths.publicKey },
172
182
  keygen: kg.keygen,
173
183
  getPublicKey: kg.getPublicKey,
174
- encapsulate(publicKey: Uint8Array, rand: Uint8Array = randomBytes(curve.lengths.seed)) {
184
+ encapsulate(
185
+ publicKey: TArg<Uint8Array>,
186
+ rand: TArg<Uint8Array> = randomBytes(curve.lengths.seed)
187
+ ) {
175
188
  // Some curve.keygen(seed) paths reuse the provided seed buffer as secretKey; detach caller
176
189
  // randomness first so cleanBytes() only wipes wrapper-owned material.
177
190
  const seed = copyBytes(rand);
@@ -179,7 +192,7 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
179
192
  try {
180
193
  ek = this.keygen(seed).secretKey;
181
194
  const sharedSecret = this.decapsulate(publicKey, ek);
182
- const cipherText = curve.getPublicKey(ek);
195
+ const cipherText = curve.getPublicKey(ek) as TRet<Uint8Array>;
183
196
  return { sharedSecret, cipherText };
184
197
  } finally {
185
198
  // Invalid peer public keys can make decapsulation throw; wipe both the detached seed and
@@ -188,9 +201,9 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
188
201
  if (ek) cleanBytes(ek);
189
202
  }
190
203
  },
191
- decapsulate(cipherText: Uint8Array, secretKey: Uint8Array) {
204
+ decapsulate(cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>) {
192
205
  const res = curve.getSharedSecret(secretKey, cipherText);
193
- return curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res;
206
+ return (curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res) as TRet<Uint8Array>;
194
207
  },
195
208
  };
196
209
  }
@@ -216,7 +229,7 @@ export function ecdhKem(curve: CurveECDH, allowZeroKey: boolean = false): KEM {
216
229
  * const sigLen = signer.lengths.signature;
217
230
  * ```
218
231
  */
219
- export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): Signer {
232
+ export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): TRet<Signer> {
220
233
  const kg = ecKeygen(curve, allowZeroKey);
221
234
  if (!curve.sign || !curve.verify) throw new Error('wrong curve'); // ed25519 doesn't have one!
222
235
  return {
@@ -234,7 +247,7 @@ export function ecSigner(curve: CurveSign, allowZeroKey: boolean = false): Signe
234
247
  );
235
248
  if (opts.context !== undefined)
236
249
  throw new Error('ecSigner does not support context; use the underlying curve directly');
237
- return curve.sign(message, secretKey);
250
+ return curve.sign(message, secretKey) as TRet<Uint8Array>;
238
251
  },
239
252
  /** Verify one wrapped curve signature.
240
253
  * Returns the wrapped curve's `verify()` result for well-formed inputs. Throws on unsupported
@@ -264,7 +277,7 @@ function splitLengths<K extends string, T extends { lengths: Partial<Record<K, n
264
277
  }
265
278
 
266
279
  /** Seed-expansion callback used by the hybrid combiners. */
267
- export type ExpandSeed = (seed: Uint8Array, len: number) => Uint8Array;
280
+ export type ExpandSeed = (seed: TArg<Uint8Array>, len: number) => TRet<Uint8Array>;
268
281
  type XOF = CHashXOF<any, { dkLen: number }>;
269
282
 
270
283
  // It is XOF for most cases, but can be more complex!
@@ -282,30 +295,36 @@ type XOF = CHashXOF<any, { dkLen: number }>;
282
295
  * const seed = expandSeed(new Uint8Array([1]), 4);
283
296
  * ```
284
297
  */
285
- export function expandSeedXof(xof: XOF): ExpandSeed {
298
+ export function expandSeedXof(xof: TArg<XOF>): TRet<ExpandSeed> {
286
299
  // Forward the caller seed directly: XOFs are expected to treat inputs as read-only, and this
287
300
  // adapter only translates the requested byte length into the hash API's `dkLen` option.
288
- return (seed: Uint8Array, seedLen: number) => xof(seed, { dkLen: seedLen });
301
+ return ((seed: TArg<Uint8Array>, seedLen: number): TRet<Uint8Array> =>
302
+ (xof as XOF)(seed, { dkLen: seedLen }) as TRet<Uint8Array>) as TRet<ExpandSeed>;
289
303
  }
290
304
 
291
305
  /** Combines public keys, ciphertexts, and shared secrets into one shared secret. */
292
306
  export type Combiner = (
293
- publicKeys: Uint8Array[],
294
- cipherTexts: Uint8Array[],
295
- sharedSecrets: Uint8Array[]
296
- ) => Uint8Array;
307
+ publicKeys: TArg<Uint8Array[]>,
308
+ cipherTexts: TArg<Uint8Array[]>,
309
+ sharedSecrets: TArg<Uint8Array[]>
310
+ ) => TRet<Uint8Array>;
297
311
 
298
312
  function combineKeys(
299
313
  realSeedLen: number | undefined, // how much bytes expandSeed expects
300
- expandSeed: ExpandSeed,
301
- ...ck: CryptoKeys[]
314
+ expandSeed_: TArg<ExpandSeed>,
315
+ ...ck_: TArg<CryptoKeys[]>
302
316
  ) {
317
+ const expandSeed = expandSeed_ as ExpandSeed;
318
+ const ck = ck_ as CryptoKeys[];
303
319
  const seedCoder = splitLengths(ck, 'seed');
304
320
  const pkCoder = splitLengths(ck, 'publicKey');
305
321
  // Allows to use identity functions for combiner/expandSeed
306
322
  if (realSeedLen === undefined) realSeedLen = seedCoder.bytesLen;
307
323
  anumber(realSeedLen);
308
- function expandDecapsulationKey(seed: Uint8Array) {
324
+ function expandDecapsulationKey(seed: TArg<Uint8Array>): TRet<{
325
+ secretKey: Uint8Array[];
326
+ publicKey: Uint8Array[];
327
+ }> {
309
328
  abytes(seed, realSeedLen!);
310
329
  const expandedRaw = expandSeed(seed, seedCoder.bytesLen);
311
330
  // Identity/subarray expanders can hand back caller-owned seed storage. Detach those outputs so
@@ -328,7 +347,10 @@ function combineKeys(
328
347
  publicKey.push(keys.publicKey);
329
348
  }
330
349
  ok = true;
331
- return { secretKey, publicKey };
350
+ return { secretKey, publicKey } as TRet<{
351
+ secretKey: Uint8Array[];
352
+ publicKey: Uint8Array[];
353
+ }>;
332
354
  } finally {
333
355
  // Child keygen() can throw after deriving only a prefix of the composite key schedule. Keep
334
356
  // the exported copies on success, but wipe all temporary and partially built secret material
@@ -339,16 +361,16 @@ function combineKeys(
339
361
  }
340
362
  return {
341
363
  info: { lengths: { seed: realSeedLen, publicKey: pkCoder.bytesLen, secretKey: realSeedLen } },
342
- getPublicKey(secretKey: Uint8Array) {
364
+ getPublicKey(secretKey: TArg<Uint8Array>) {
343
365
  // Composite secret keys are root seeds, so public-key derivation reruns key expansion from
344
366
  // that seed instead of decoding a packed child-secret-key structure.
345
- return this.keygen(secretKey).publicKey;
367
+ return this.keygen(secretKey).publicKey as TRet<Uint8Array>;
346
368
  },
347
- keygen(seed: Uint8Array = randomBytes(realSeedLen)) {
369
+ keygen(seed: TArg<Uint8Array> = randomBytes(realSeedLen)) {
348
370
  const { publicKey: pk, secretKey } = expandDecapsulationKey(seed);
349
371
  try {
350
- const publicKey = pkCoder.encode(pk);
351
- return { secretKey: seed, publicKey };
372
+ const publicKey = pkCoder.encode(pk) as TRet<Uint8Array>;
373
+ return { secretKey: seed as TRet<Uint8Array>, publicKey };
352
374
  } finally {
353
375
  cleanBytes(pk);
354
376
  // The exported secretKey is the caller/root seed itself; child secret keys are internal
@@ -390,41 +412,47 @@ function combineKeys(
390
412
  export function combineKEMS(
391
413
  realSeedLen: number | undefined, // how much bytes expandSeed expects
392
414
  realMsgLen: number | undefined, // how much bytes combiner returns
393
- expandSeed: ExpandSeed,
394
- combiner: Combiner,
395
- ...kems: KEM[]
396
- ): KEM {
397
- const keys = combineKeys(realSeedLen, expandSeed, ...kems);
398
- const ctCoder = splitLengths(kems, 'cipherText');
399
- const pkCoder = splitLengths(kems, 'publicKey');
400
- const msgCoder = splitLengths(kems, 'msg');
415
+ expandSeed: TArg<ExpandSeed>,
416
+ combiner: TArg<Combiner>,
417
+ ...kems: TArg<KEM[]>
418
+ ): TRet<KEM> {
419
+ const rawCombiner = combiner as Combiner;
420
+ const rawKems = kems as KEM[];
421
+ const keys = combineKeys(realSeedLen, expandSeed, ...rawKems);
422
+ const ctCoder = splitLengths(rawKems, 'cipherText');
423
+ const pkCoder = splitLengths(rawKems, 'publicKey');
424
+ const msgCoder = splitLengths(rawKems, 'msg');
401
425
  if (realMsgLen === undefined) realMsgLen = msgCoder.bytesLen;
402
426
  anumber(realMsgLen);
403
- return {
404
- lengths: {
405
- ...keys.info.lengths,
406
- msg: realMsgLen,
407
- msgRand: msgCoder.bytesLen,
408
- cipherText: ctCoder.bytesLen,
409
- },
427
+ const lengths = Object.freeze({
428
+ ...keys.info.lengths,
429
+ msg: realMsgLen,
430
+ msgRand: msgCoder.bytesLen,
431
+ cipherText: ctCoder.bytesLen,
432
+ });
433
+ return Object.freeze({
434
+ lengths,
410
435
  getPublicKey: keys.getPublicKey,
411
436
  keygen: keys.keygen,
412
- encapsulate(pk: Uint8Array, randomness: Uint8Array = randomBytes(msgCoder.bytesLen)) {
437
+ encapsulate(
438
+ pk: TArg<Uint8Array>,
439
+ randomness: TArg<Uint8Array> = randomBytes(msgCoder.bytesLen)
440
+ ) {
413
441
  const pks = pkCoder.decode(pk);
414
442
  const rand = msgCoder.decode(randomness);
415
443
  const sharedSecret: Uint8Array[] = [];
416
444
  const cipherText: Uint8Array[] = [];
417
445
  try {
418
- for (let i = 0; i < kems.length; i++) {
419
- const enc = kems[i].encapsulate(pks[i], rand[i]);
446
+ for (let i = 0; i < rawKems.length; i++) {
447
+ const enc = rawKems[i].encapsulate(pks[i], rand[i]);
420
448
  sharedSecret.push(enc.sharedSecret);
421
449
  cipherText.push(enc.cipherText);
422
450
  }
423
451
  return {
424
452
  // Detach the combiner result before cleanup: a caller-provided combiner may alias one of
425
453
  // the child sharedSecret buffers, and those child buffers are zeroized immediately below.
426
- sharedSecret: copyBytes(combiner(pks, cipherText, sharedSecret)),
427
- cipherText: ctCoder.encode(cipherText),
454
+ sharedSecret: copyBytes(rawCombiner(pks, cipherText, sharedSecret)),
455
+ cipherText: ctCoder.encode(cipherText) as TRet<Uint8Array>,
428
456
  };
429
457
  } finally {
430
458
  // Child encapsulation or combiner failures can happen after some components already
@@ -432,21 +460,21 @@ export function combineKEMS(
432
460
  cleanBytes(sharedSecret, cipherText);
433
461
  }
434
462
  },
435
- decapsulate(ct: Uint8Array, seed: Uint8Array) {
463
+ decapsulate(ct: TArg<Uint8Array>, seed: TArg<Uint8Array>) {
436
464
  const cts = ctCoder.decode(ct);
437
465
  const { publicKey, secretKey } = keys.expandDecapsulationKey(seed);
438
- const sharedSecret = kems.map((i, j) => i.decapsulate(cts[j], secretKey[j]));
466
+ const sharedSecret = rawKems.map((i, j) => i.decapsulate(cts[j], secretKey[j]));
439
467
  try {
440
468
  // Detach the decapsulation result before cleanup: the combiner may hand back one of the
441
469
  // child shared-secret buffers, and those temporary buffers are zeroized below.
442
- return copyBytes(combiner(publicKey, cts, sharedSecret));
470
+ return copyBytes(rawCombiner(publicKey, cts, sharedSecret));
443
471
  } finally {
444
472
  // Decapsulation only needs the expanded child secret keys and child shared secrets for this
445
473
  // call; keep the caller/root seed intact, but wipe all derived material even on errors.
446
474
  cleanBytes(secretKey, sharedSecret);
447
475
  }
448
476
  },
449
- };
477
+ });
450
478
  }
451
479
  // There is no specs for this, but can be useful
452
480
  // realSeedLen: how much bytes expandSeed expects.
@@ -468,12 +496,13 @@ export function combineKEMS(
468
496
  */
469
497
  export function combineSigners(
470
498
  realSeedLen: number | undefined,
471
- expandSeed: ExpandSeed,
472
- ...signers: Signer[]
473
- ): Signer {
474
- const keys = combineKeys(realSeedLen, expandSeed, ...signers);
475
- const sigCoder = splitLengths(signers, 'signature');
476
- const pkCoder = splitLengths(signers, 'publicKey');
499
+ expandSeed: TArg<ExpandSeed>,
500
+ ...signers: TArg<Signer[]>
501
+ ): TRet<Signer> {
502
+ const rawSigners = signers as Signer[];
503
+ const keys = combineKeys(realSeedLen, expandSeed, ...rawSigners);
504
+ const sigCoder = splitLengths(rawSigners, 'signature');
505
+ const pkCoder = splitLengths(rawSigners, 'publicKey');
477
506
  return {
478
507
  lengths: { ...keys.info.lengths, signature: sigCoder.bytesLen, signRand: 0 },
479
508
  getPublicKey: keys.getPublicKey,
@@ -493,8 +522,8 @@ export function combineSigners(
493
522
  );
494
523
  const { secretKey } = keys.expandDecapsulationKey(seed);
495
524
  try {
496
- const sigs = signers.map((i, j) => i.sign(message, secretKey[j]));
497
- return sigCoder.encode(sigs);
525
+ const sigs = rawSigners.map((i, j) => i.sign(message, secretKey[j]));
526
+ return sigCoder.encode(sigs) as TRet<Uint8Array>;
498
527
  } finally {
499
528
  // Composite secret keys are root seeds; the per-signer child secret keys are temporary
500
529
  // expansion outputs and must not stay live after the combined signature is produced.
@@ -513,8 +542,8 @@ export function combineSigners(
513
542
  );
514
543
  const pks = pkCoder.decode(publicKey);
515
544
  const sigs = sigCoder.decode(signature);
516
- for (let i = 0; i < signers.length; i++) {
517
- if (!signers[i].verify(sigs[i], message, pks[i])) return false;
545
+ for (let i = 0; i < rawSigners.length; i++) {
546
+ if (!rawSigners[i].verify(sigs[i], message, pks[i])) return false;
518
547
  }
519
548
  return true;
520
549
  },
@@ -545,21 +574,28 @@ export function combineSigners(
545
574
  * const publicKeyLen = kem.lengths.publicKey;
546
575
  * ```
547
576
  */
548
- export function QSF(label: string, pqc: KEM, curveKEM: KEM, xof: XOF, kdf: CHash): KEM {
577
+ export function QSF(
578
+ label: string,
579
+ pqc: TArg<KEM>,
580
+ curveKEM: TArg<KEM>,
581
+ xof: TArg<XOF>,
582
+ kdf: CHash
583
+ ): TRet<KEM> {
549
584
  ahash(xof);
550
585
  ahash(kdf);
551
586
  return combineKEMS(
552
587
  32,
553
588
  kdf.outputLen,
554
589
  expandSeedXof(xof),
555
- (pk, ct, ss) => kdf(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
590
+ (pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
591
+ kdf(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
556
592
  pqc,
557
593
  curveKEM
558
594
  );
559
595
  }
560
596
 
561
597
  /** QSF preset combining ML-KEM-768 with P-256. */
562
- export const QSF_ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
598
+ export const QSF_ml_kem768_p256: TRet<KEM> = /* @__PURE__ */ (() =>
563
599
  QSF(
564
600
  'QSF-KEM(ML-KEM-768,P-256)-XOF(SHAKE256)-KDF(SHA3-256)',
565
601
  ml_kem768,
@@ -568,7 +604,7 @@ export const QSF_ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
568
604
  sha3_256
569
605
  ))();
570
606
  /** QSF preset combining ML-KEM-1024 with P-384. */
571
- export const QSF_ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
607
+ export const QSF_ml_kem1024_p384: TRet<KEM> = /* @__PURE__ */ (() =>
572
608
  QSF(
573
609
  'QSF-KEM(ML-KEM-1024,P-384)-XOF(SHAKE256)-KDF(SHA3-256)',
574
610
  ml_kem1024,
@@ -605,18 +641,18 @@ export const QSF_ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
605
641
  */
606
642
  export function createKitchenSink(
607
643
  label: string,
608
- pqc: KEM,
609
- curveKEM: KEM,
610
- xof: XOF,
644
+ pqc: TArg<KEM>,
645
+ curveKEM: TArg<KEM>,
646
+ xof: TArg<XOF>,
611
647
  hash: CHash
612
- ): KEM {
648
+ ): TRet<KEM> {
613
649
  ahash(xof);
614
650
  ahash(hash);
615
651
  return combineKEMS(
616
652
  32,
617
653
  32,
618
654
  expandSeedXof(xof),
619
- (pk, ct, ss) => {
655
+ (pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) => {
620
656
  const preimage = concatBytes(ss[0], ss[1], ct[0], pk[0], ct[1], pk[1], asciiToBytes(label));
621
657
  const len = 32;
622
658
  const ikm = concatBytes(asciiToBytes('hybrid_prk'), preimage);
@@ -641,7 +677,7 @@ const x25519kem = /* @__PURE__ */ ecdhKem(x25519);
641
677
  /** KitchenSink preset combining ML-KEM-768 with X25519.
642
678
  * Caller randomness splits into 32 ML-KEM coins plus a 32-byte X25519 ephemeral-secret seed.
643
679
  */
644
- export const KitchenSink_ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
680
+ export const KitchenSink_ml_kem768_x25519: TRet<KEM> = /* @__PURE__ */ (() =>
645
681
  createKitchenSink(
646
682
  'KitchenSink-KEM(ML-KEM-768,X25519)-XOF(SHAKE256)-KDF(HKDF-SHA-256)',
647
683
  ml_kem768,
@@ -655,13 +691,14 @@ export const KitchenSink_ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
655
691
  * Uses the hard-coded domain-separation label `\\.//^\\` and hashes only `ct1 || pk1`
656
692
  * from the X25519 side in addition to the two component shared secrets.
657
693
  */
658
- export const ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
694
+ export const ml_kem768_x25519: TRet<KEM> = /* @__PURE__ */ (() =>
659
695
  combineKEMS(
660
696
  32,
661
697
  32,
662
698
  expandSeedXof(shake256),
663
699
  // Awesome label, so much escaping hell in a single line.
664
- (pk, ct, ss) => sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes('\\.//^\\'))),
700
+ (pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
701
+ sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes('\\.//^\\'))),
665
702
  ml_kem768,
666
703
  x25519kem
667
704
  ))();
@@ -674,12 +711,15 @@ export const ml_kem768_x25519: KEM = /* @__PURE__ */ (() =>
674
711
  * prefix, not the SEC 1 `x_P`-only primitive output, because current hybrid combiners hash
675
712
  * both coordinates.
676
713
  */
677
- function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: number): KEM {
714
+ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: number): TRet<KEM> {
678
715
  const Fn = curve.Point.Fn;
679
716
  if (!Fn) throw new Error('no Point.Fn');
680
717
  // Scan scalar-sized windows until one decodes to a nonzero scalar in `[1, n-1]`; if every
681
718
  // window is zero or out of range, fail instead of silently reducing modulo `n`.
682
- function rejectionSampling(seed: Uint8Array): { secretKey: Uint8Array; publicKey: Uint8Array } {
719
+ function rejectionSampling(seed: TArg<Uint8Array>): TRet<{
720
+ secretKey: Uint8Array;
721
+ publicKey: Uint8Array;
722
+ }> {
683
723
  let sk: bigint;
684
724
  for (let start = 0, end = scalarLen; ; start = end, end += scalarLen) {
685
725
  if (end > seed.length) throw new Error('rejection sampling failed');
@@ -688,7 +728,10 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
688
728
  }
689
729
  const secretKey = Fn.toBytes(Fn.create(sk));
690
730
  const publicKey = curve.getPublicKey(secretKey, false);
691
- return { secretKey, publicKey };
731
+ return { secretKey, publicKey } as TRet<{
732
+ secretKey: Uint8Array;
733
+ publicKey: Uint8Array;
734
+ }>;
692
735
  }
693
736
 
694
737
  return {
@@ -699,20 +742,20 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
699
742
  msg: nseed,
700
743
  cipherText: elemLen,
701
744
  },
702
- keygen(seed: Uint8Array = randomBytes(nseed)) {
745
+ keygen(seed: TArg<Uint8Array> = randomBytes(nseed)) {
703
746
  abytes(seed, nseed, 'seed');
704
747
  return rejectionSampling(seed);
705
748
  },
706
- getPublicKey(secretKey: Uint8Array) {
707
- return curve.getPublicKey(secretKey, false);
749
+ getPublicKey(secretKey: TArg<Uint8Array>) {
750
+ return curve.getPublicKey(secretKey, false) as TRet<Uint8Array>;
708
751
  },
709
- encapsulate(publicKey: Uint8Array, rand: Uint8Array = randomBytes(nseed)) {
752
+ encapsulate(publicKey: TArg<Uint8Array>, rand: TArg<Uint8Array> = randomBytes(nseed)) {
710
753
  abytes(rand, nseed, 'rand');
711
754
  let ek: Uint8Array | undefined = undefined;
712
755
  try {
713
756
  ek = rejectionSampling(rand).secretKey;
714
757
  const sharedSecret = this.decapsulate(publicKey, ek);
715
- const cipherText = curve.getPublicKey(ek, false);
758
+ const cipherText = curve.getPublicKey(ek, false) as TRet<Uint8Array>;
716
759
  return { sharedSecret, cipherText };
717
760
  } finally {
718
761
  // Rejection-sampled NIST-curve ephemeral secret keys are temporary encapsulation state and
@@ -720,9 +763,9 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
720
763
  if (ek) cleanBytes(ek);
721
764
  }
722
765
  },
723
- decapsulate(cipherText: Uint8Array, secretKey: Uint8Array) {
766
+ decapsulate(cipherText: TArg<Uint8Array>, secretKey: TArg<Uint8Array>) {
724
767
  const full = curve.getSharedSecret(secretKey, cipherText);
725
- return full.subarray(1);
768
+ return full.subarray(1) as TRet<Uint8Array>;
726
769
  },
727
770
  };
728
771
  }
@@ -735,7 +778,12 @@ function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: n
735
778
  * `shake256(seed, { dkLen: 64 + nseed })`,
736
779
  * and the combiner hashes `ss0 || ss1 || ct1 || pk1 || label`.
737
780
  */
738
- function concreteHybridKem(label: string, mlkem: KEM, curve: ECDSA, nseed: number): KEM {
781
+ function concreteHybridKem(
782
+ label: string,
783
+ mlkem: TArg<KEM>,
784
+ curve: ECDSA,
785
+ nseed: number
786
+ ): TRet<KEM> {
739
787
  const { secretKey: scalarLen, publicKeyUncompressed: elemLen } = curve.lengths;
740
788
  if (!scalarLen || !elemLen) throw new Error('wrong curve');
741
789
  const curveKem = nistCurveKem(curve, scalarLen, elemLen, nseed);
@@ -745,40 +793,41 @@ function concreteHybridKem(label: string, mlkem: KEM, curve: ECDSA, nseed: numbe
745
793
  return combineKEMS(
746
794
  32,
747
795
  32,
748
- (seed: Uint8Array) => {
796
+ (seed: TArg<Uint8Array>): TRet<Uint8Array> => {
749
797
  abytes(seed, 32);
750
798
  const expanded = shake256(seed, { dkLen: totalSeedLen });
751
799
  const mlkemSeed = expanded.subarray(0, mlkemSeedLen);
752
800
  const curveSeed = expanded.subarray(mlkemSeedLen, totalSeedLen);
753
- return concatBytes(mlkemSeed, curveSeed);
801
+ return concatBytes(mlkemSeed, curveSeed) as TRet<Uint8Array>;
754
802
  },
755
- (pk, ct, ss) => sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
803
+ (pk: TArg<Uint8Array[]>, ct: TArg<Uint8Array[]>, ss: TArg<Uint8Array[]>) =>
804
+ sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
756
805
  mlkem,
757
806
  curveKem
758
807
  );
759
808
  }
760
809
 
761
810
  /** P-256 + ML-KEM-768 hybrid preset. */
762
- export const ml_kem768_p256: KEM = /* @__PURE__ */ (() =>
811
+ export const ml_kem768_p256: TRet<KEM> = /* @__PURE__ */ (() =>
763
812
  concreteHybridKem('MLKEM768-P256', ml_kem768, p256, 128))();
764
813
 
765
814
  /** P-384 + ML-KEM-1024 hybrid preset. */
766
- export const ml_kem1024_p384: KEM = /* @__PURE__ */ (() =>
815
+ export const ml_kem1024_p384: TRet<KEM> = /* @__PURE__ */ (() =>
767
816
  concreteHybridKem('MLKEM1024-P384', ml_kem1024, p384, 48))();
768
817
 
769
818
  // Legacy aliases
770
819
  /** Legacy alias for `ml_kem768_x25519`. */
771
- export const XWing: KEM = /* @__PURE__ */ (() => ml_kem768_x25519)();
820
+ export const XWing: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_x25519)();
772
821
  /** Legacy alias for `ml_kem768_x25519`. */
773
- export const MLKEM768X25519: KEM = /* @__PURE__ */ (() => ml_kem768_x25519)();
822
+ export const MLKEM768X25519: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_x25519)();
774
823
  /** Legacy alias for `ml_kem768_p256`. */
775
- export const MLKEM768P256: KEM = /* @__PURE__ */ (() => ml_kem768_p256)();
824
+ export const MLKEM768P256: TRet<KEM> = /* @__PURE__ */ (() => ml_kem768_p256)();
776
825
  /** Legacy alias for `ml_kem1024_p384`. */
777
- export const MLKEM1024P384: KEM = /* @__PURE__ */ (() => ml_kem1024_p384)();
826
+ export const MLKEM1024P384: TRet<KEM> = /* @__PURE__ */ (() => ml_kem1024_p384)();
778
827
  /** Legacy alias for `QSF_ml_kem768_p256`. */
779
- export const QSFMLKEM768P256: KEM = /* @__PURE__ */ (() => QSF_ml_kem768_p256)();
828
+ export const QSFMLKEM768P256: TRet<KEM> = /* @__PURE__ */ (() => QSF_ml_kem768_p256)();
780
829
  /** Legacy alias for `QSF_ml_kem1024_p384`. */
781
- export const QSFMLKEM1024P384: KEM = /* @__PURE__ */ (() => QSF_ml_kem1024_p384)();
830
+ export const QSFMLKEM1024P384: TRet<KEM> = /* @__PURE__ */ (() => QSF_ml_kem1024_p384)();
782
831
  /** Legacy alias for `KitchenSink_ml_kem768_x25519`. */
783
- export const KitchenSinkMLKEM768X25519: KEM = /* @__PURE__ */ (() =>
832
+ export const KitchenSinkMLKEM768X25519: TRet<KEM> = /* @__PURE__ */ (() =>
784
833
  KitchenSink_ml_kem768_x25519)();
package/src/index.ts CHANGED
@@ -14,6 +14,14 @@ import {
14
14
  slh_dsa_shake_192f, slh_dsa_shake_192s,
15
15
  slh_dsa_shake_256f, slh_dsa_shake_256s,
16
16
  } from '@noble/post-quantum/slh-dsa.js';
17
+ import {
18
+ falcon512, falcon512padded, falcon1024, falcon1024padded,
19
+ } from '@noble/post-quantum/falcon.js';
20
+ import {
21
+ ml_kem768_x25519, ml_kem768_p256, ml_kem1024_p384,
22
+ KitchenSink_ml_kem768_x25519, XWing,
23
+ QSF_ml_kem768_p256, QSF_ml_kem1024_p384,
24
+ } from '@noble/post-quantum/hybrid.js';
17
25
  ```
18
26
  */
19
27
  throw new Error('root module cannot be imported: import submodules instead. Check out README');