@noble/post-quantum 0.6.0 → 0.7.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.
- package/README.md +106 -80
- package/_crystals.d.ts +23 -16
- package/_crystals.js +50 -12
- package/falcon.d.ts +7 -8
- package/falcon.js +109 -74
- package/hybrid.d.ts +21 -32
- package/hybrid.js +157 -78
- package/index.d.ts +0 -1
- package/index.js +8 -1
- package/ml-dsa.d.ts +35 -9
- package/ml-dsa.js +116 -43
- package/ml-kem.d.ts +46 -5
- package/ml-kem.js +175 -67
- package/package.json +10 -18
- package/slh-dsa.d.ts +44 -24
- package/slh-dsa.js +150 -84
- package/src/_crystals.ts +86 -35
- package/src/falcon.ts +230 -169
- package/src/hybrid.ts +243 -129
- package/src/index.ts +8 -0
- package/src/ml-dsa.ts +194 -87
- package/src/ml-kem.ts +283 -122
- package/src/slh-dsa.ts +310 -182
- package/src/utils.ts +244 -48
- package/utils.d.ts +99 -24
- package/utils.js +92 -24
- package/_crystals.d.ts.map +0 -1
- package/_crystals.js.map +0 -1
- package/falcon.d.ts.map +0 -1
- package/falcon.js.map +0 -1
- package/hybrid.d.ts.map +0 -1
- package/hybrid.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/ml-dsa.d.ts.map +0 -1
- package/ml-dsa.js.map +0 -1
- package/ml-kem.d.ts.map +0 -1
- package/ml-kem.js.map +0 -1
- package/slh-dsa.d.ts.map +0 -1
- package/slh-dsa.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
package/hybrid.js
CHANGED
|
@@ -79,15 +79,38 @@ import {} from '@noble/curves/abstract/montgomery.js';
|
|
|
79
79
|
import {} from '@noble/curves/abstract/weierstrass.js';
|
|
80
80
|
import { x25519 } from '@noble/curves/ed25519.js';
|
|
81
81
|
import { p256, p384 } from '@noble/curves/nist.js';
|
|
82
|
-
import { asciiToBytes, bytesToNumberBE, bytesToNumberLE, concatBytes, numberToBytesBE, } from '@noble/curves/utils.js';
|
|
82
|
+
import { abool, afunction, asciiToBytes, bytesToNumberBE, bytesToNumberLE, concatBytes, numberToBytesBE, } from '@noble/curves/utils.js';
|
|
83
83
|
import { expand, extract } from '@noble/hashes/hkdf.js';
|
|
84
84
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
85
85
|
import { sha3_256, shake256 } from '@noble/hashes/sha3.js';
|
|
86
86
|
import { abytes, ahash, anumber } from '@noble/hashes/utils.js';
|
|
87
87
|
import { ml_kem1024, ml_kem768 } from "./ml-kem.js";
|
|
88
|
-
import { cleanBytes, copyBytes, randomBytes, splitCoder, validateSigOpts, validateVerOpts, } from "./utils.js";
|
|
88
|
+
import { aobject, astring, cleanBytes, copyBytes, randomBytes, splitCoder, validateSigOpts, validateVerOpts, } from "./utils.js";
|
|
89
|
+
const validateKEM = (kem, title) => {
|
|
90
|
+
const k = aobject(kem, title);
|
|
91
|
+
aobject(k.lengths, `${title}.lengths`);
|
|
92
|
+
afunction(k.keygen, `${title}.keygen`);
|
|
93
|
+
afunction(k.getPublicKey, `${title}.getPublicKey`);
|
|
94
|
+
afunction(k.encapsulate, `${title}.encapsulate`);
|
|
95
|
+
afunction(k.decapsulate, `${title}.decapsulate`);
|
|
96
|
+
return k;
|
|
97
|
+
};
|
|
98
|
+
const validateSigner = (signer, title) => {
|
|
99
|
+
const s = aobject(signer, title);
|
|
100
|
+
aobject(s.lengths, `${title}.lengths`);
|
|
101
|
+
afunction(s.keygen, `${title}.keygen`);
|
|
102
|
+
afunction(s.getPublicKey, `${title}.getPublicKey`);
|
|
103
|
+
afunction(s.sign, `${title}.sign`);
|
|
104
|
+
afunction(s.verify, `${title}.verify`);
|
|
105
|
+
return s;
|
|
106
|
+
};
|
|
89
107
|
// Can re-use if decide to signatures support, on other hand getSecretKey is specific and ugly
|
|
90
108
|
function ecKeygen(curve, allowZeroKey = false) {
|
|
109
|
+
const c = aobject(curve, 'curve');
|
|
110
|
+
aobject(c.lengths, 'curve.lengths');
|
|
111
|
+
afunction(c.keygen, 'curve.keygen');
|
|
112
|
+
afunction(c.getPublicKey, 'curve.getPublicKey');
|
|
113
|
+
abool(allowZeroKey, 'allowZeroKey');
|
|
91
114
|
const lengths = curve.lengths;
|
|
92
115
|
let keygen = curve.keygen;
|
|
93
116
|
if (allowZeroKey) {
|
|
@@ -110,12 +133,15 @@ function ecKeygen(curve, allowZeroKey = false) {
|
|
|
110
133
|
const seedScalar = Fn.isLE ? bytesToNumberLE(seed) : bytesToNumberBE(seed);
|
|
111
134
|
// Reduce directly into [0, ORDER); scalar 0 still stays invalid.
|
|
112
135
|
const secretKey = Fn.toBytes(Fn.create(seedScalar));
|
|
113
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
secretKey: secretKey,
|
|
138
|
+
publicKey: curve.getPublicKey(secretKey),
|
|
139
|
+
};
|
|
114
140
|
};
|
|
115
141
|
}
|
|
116
142
|
return {
|
|
117
143
|
lengths: { secretKey: lengths.secretKey, publicKey: lengths.publicKey, seed: lengths.seed },
|
|
118
|
-
keygen,
|
|
144
|
+
keygen: (seed) => keygen(seed),
|
|
119
145
|
getPublicKey: (secretKey) => curve.getPublicKey(secretKey),
|
|
120
146
|
};
|
|
121
147
|
}
|
|
@@ -146,6 +172,11 @@ export function ecdhKem(curve, allowZeroKey = false) {
|
|
|
146
172
|
const kg = ecKeygen(curve, allowZeroKey);
|
|
147
173
|
if (!curve.getSharedSecret)
|
|
148
174
|
throw new Error('wrong curve'); // ed25519 doesn't have one!
|
|
175
|
+
// Standalone (not `this.decapsulate`) so encapsulate works even when methods are destructured.
|
|
176
|
+
const decapsulate = (cipherText, secretKey) => {
|
|
177
|
+
const res = curve.getSharedSecret(secretKey, cipherText);
|
|
178
|
+
return (curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res);
|
|
179
|
+
};
|
|
149
180
|
return {
|
|
150
181
|
lengths: { ...kg.lengths, msg: kg.lengths.seed, cipherText: kg.lengths.publicKey },
|
|
151
182
|
keygen: kg.keygen,
|
|
@@ -156,8 +187,8 @@ export function ecdhKem(curve, allowZeroKey = false) {
|
|
|
156
187
|
const seed = copyBytes(rand);
|
|
157
188
|
let ek = undefined;
|
|
158
189
|
try {
|
|
159
|
-
ek =
|
|
160
|
-
const sharedSecret =
|
|
190
|
+
ek = kg.keygen(seed).secretKey;
|
|
191
|
+
const sharedSecret = decapsulate(publicKey, ek);
|
|
161
192
|
const cipherText = curve.getPublicKey(ek);
|
|
162
193
|
return { sharedSecret, cipherText };
|
|
163
194
|
}
|
|
@@ -169,10 +200,7 @@ export function ecdhKem(curve, allowZeroKey = false) {
|
|
|
169
200
|
cleanBytes(ek);
|
|
170
201
|
}
|
|
171
202
|
},
|
|
172
|
-
decapsulate
|
|
173
|
-
const res = curve.getSharedSecret(secretKey, cipherText);
|
|
174
|
-
return curve.lengths.publicKeyHasPrefix ? res.subarray(1) : res;
|
|
175
|
-
},
|
|
203
|
+
decapsulate,
|
|
176
204
|
};
|
|
177
205
|
}
|
|
178
206
|
/**
|
|
@@ -253,10 +281,12 @@ function splitLengths(lst, name) {
|
|
|
253
281
|
export function expandSeedXof(xof) {
|
|
254
282
|
// Forward the caller seed directly: XOFs are expected to treat inputs as read-only, and this
|
|
255
283
|
// adapter only translates the requested byte length into the hash API's `dkLen` option.
|
|
256
|
-
return (seed, seedLen) => xof(seed, { dkLen: seedLen });
|
|
284
|
+
return ((seed, seedLen) => xof(seed, { dkLen: seedLen }));
|
|
257
285
|
}
|
|
258
286
|
function combineKeys(realSeedLen, // how much bytes expandSeed expects
|
|
259
|
-
|
|
287
|
+
expandSeed_, ...ck_) {
|
|
288
|
+
const expandSeed = expandSeed_;
|
|
289
|
+
const ck = ck_;
|
|
260
290
|
const seedCoder = splitLengths(ck, 'seed');
|
|
261
291
|
const pkCoder = splitLengths(ck, 'publicKey');
|
|
262
292
|
// Allows to use identity functions for combiner/expandSeed
|
|
@@ -298,26 +328,43 @@ expandSeed, ...ck) {
|
|
|
298
328
|
cleanBytes(secretKey);
|
|
299
329
|
}
|
|
300
330
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const { publicKey: pk, secretKey } = expandDecapsulationKey(seed);
|
|
331
|
+
// Standalone (not a method) so getPublicKey / destructured usage never depends on `this`.
|
|
332
|
+
const keygen = (seed) => {
|
|
333
|
+
// Detach the root: the exported secretKey must not alias caller-owned seed bytes, so later
|
|
334
|
+
// caller mutation of the seed cannot silently change the secret key (and vice versa).
|
|
335
|
+
const root = seed === undefined ? randomBytes(realSeedLen) : copyBytes(seed);
|
|
336
|
+
let res;
|
|
337
|
+
try {
|
|
338
|
+
const { publicKey: pk, secretKey } = expandDecapsulationKey(root);
|
|
310
339
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
340
|
+
res = {
|
|
341
|
+
secretKey: root,
|
|
342
|
+
publicKey: pkCoder.encode(pk),
|
|
343
|
+
};
|
|
313
344
|
}
|
|
314
345
|
finally {
|
|
315
|
-
|
|
316
|
-
// The exported secretKey is the caller/root seed itself; child secret keys are internal
|
|
346
|
+
// The exported secretKey is the (detached) root seed; child secret keys are internal
|
|
317
347
|
// expansion outputs that are cleaned whether encoding succeeds or throws.
|
|
318
|
-
cleanBytes(secretKey);
|
|
348
|
+
cleanBytes(pk, secretKey);
|
|
319
349
|
}
|
|
350
|
+
return res;
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
if (!res)
|
|
354
|
+
cleanBytes(root);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
return {
|
|
358
|
+
info: { lengths: { seed: realSeedLen, publicKey: pkCoder.bytesLen, secretKey: realSeedLen } },
|
|
359
|
+
// Composite secret keys are root seeds, so public-key derivation reruns key expansion from
|
|
360
|
+
// that seed instead of decoding a packed child-secret-key structure.
|
|
361
|
+
getPublicKey: (secretKey) => {
|
|
362
|
+
const keys = keygen(secretKey);
|
|
363
|
+
// keygen detaches its exported root; getPublicKey discards that half of the result.
|
|
364
|
+
cleanBytes(keys.secretKey);
|
|
365
|
+
return keys.publicKey;
|
|
320
366
|
},
|
|
367
|
+
keygen,
|
|
321
368
|
expandDecapsulationKey,
|
|
322
369
|
realSeedLen,
|
|
323
370
|
};
|
|
@@ -351,20 +398,33 @@ expandSeed, ...ck) {
|
|
|
351
398
|
export function combineKEMS(realSeedLen, // how much bytes expandSeed expects
|
|
352
399
|
realMsgLen, // how much bytes combiner returns
|
|
353
400
|
expandSeed, combiner, ...kems) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
401
|
+
if (realSeedLen !== undefined)
|
|
402
|
+
anumber(realSeedLen, 'realSeedLen');
|
|
403
|
+
if (realMsgLen !== undefined)
|
|
404
|
+
anumber(realMsgLen, 'realMsgLen');
|
|
405
|
+
if (typeof expandSeed !== 'function')
|
|
406
|
+
throw new TypeError('"expandSeed" expected function, got type=' + typeof expandSeed);
|
|
407
|
+
if (typeof combiner !== 'function')
|
|
408
|
+
throw new TypeError('"combiner" expected function, got type=' + typeof combiner);
|
|
409
|
+
const rawCombiner = combiner;
|
|
410
|
+
const rawKems = kems;
|
|
411
|
+
for (let i = 0; i < rawKems.length; i++)
|
|
412
|
+
validateKEM(rawKems[i], `kems[${i}]`);
|
|
413
|
+
const keys = combineKeys(realSeedLen, expandSeed, ...rawKems);
|
|
414
|
+
const ctCoder = splitLengths(rawKems, 'cipherText');
|
|
415
|
+
const pkCoder = splitLengths(rawKems, 'publicKey');
|
|
416
|
+
const msgCoder = splitLengths(rawKems, 'msg');
|
|
358
417
|
if (realMsgLen === undefined)
|
|
359
418
|
realMsgLen = msgCoder.bytesLen;
|
|
360
|
-
anumber(realMsgLen);
|
|
361
|
-
|
|
362
|
-
lengths
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
419
|
+
anumber(realMsgLen, 'realMsgLen');
|
|
420
|
+
const lengths = Object.freeze({
|
|
421
|
+
...keys.info.lengths,
|
|
422
|
+
msg: realMsgLen,
|
|
423
|
+
msgRand: msgCoder.bytesLen,
|
|
424
|
+
cipherText: ctCoder.bytesLen,
|
|
425
|
+
});
|
|
426
|
+
return Object.freeze({
|
|
427
|
+
lengths,
|
|
368
428
|
getPublicKey: keys.getPublicKey,
|
|
369
429
|
keygen: keys.keygen,
|
|
370
430
|
encapsulate(pk, randomness = randomBytes(msgCoder.bytesLen)) {
|
|
@@ -373,15 +433,15 @@ expandSeed, combiner, ...kems) {
|
|
|
373
433
|
const sharedSecret = [];
|
|
374
434
|
const cipherText = [];
|
|
375
435
|
try {
|
|
376
|
-
for (let i = 0; i <
|
|
377
|
-
const enc =
|
|
436
|
+
for (let i = 0; i < rawKems.length; i++) {
|
|
437
|
+
const enc = rawKems[i].encapsulate(pks[i], rand[i]);
|
|
378
438
|
sharedSecret.push(enc.sharedSecret);
|
|
379
439
|
cipherText.push(enc.cipherText);
|
|
380
440
|
}
|
|
381
441
|
return {
|
|
382
442
|
// Detach the combiner result before cleanup: a caller-provided combiner may alias one of
|
|
383
443
|
// the child sharedSecret buffers, and those child buffers are zeroized immediately below.
|
|
384
|
-
sharedSecret: copyBytes(
|
|
444
|
+
sharedSecret: copyBytes(rawCombiner(pks, cipherText, sharedSecret)),
|
|
385
445
|
cipherText: ctCoder.encode(cipherText),
|
|
386
446
|
};
|
|
387
447
|
}
|
|
@@ -394,11 +454,11 @@ expandSeed, combiner, ...kems) {
|
|
|
394
454
|
decapsulate(ct, seed) {
|
|
395
455
|
const cts = ctCoder.decode(ct);
|
|
396
456
|
const { publicKey, secretKey } = keys.expandDecapsulationKey(seed);
|
|
397
|
-
const sharedSecret =
|
|
457
|
+
const sharedSecret = rawKems.map((i, j) => i.decapsulate(cts[j], secretKey[j]));
|
|
398
458
|
try {
|
|
399
459
|
// Detach the decapsulation result before cleanup: the combiner may hand back one of the
|
|
400
460
|
// child shared-secret buffers, and those temporary buffers are zeroized below.
|
|
401
|
-
return copyBytes(
|
|
461
|
+
return copyBytes(rawCombiner(publicKey, cts, sharedSecret));
|
|
402
462
|
}
|
|
403
463
|
finally {
|
|
404
464
|
// Decapsulation only needs the expanded child secret keys and child shared secrets for this
|
|
@@ -406,7 +466,7 @@ expandSeed, combiner, ...kems) {
|
|
|
406
466
|
cleanBytes(secretKey, sharedSecret);
|
|
407
467
|
}
|
|
408
468
|
},
|
|
409
|
-
};
|
|
469
|
+
});
|
|
410
470
|
}
|
|
411
471
|
// There is no specs for this, but can be useful
|
|
412
472
|
// realSeedLen: how much bytes expandSeed expects.
|
|
@@ -423,13 +483,24 @@ expandSeed, combiner, ...kems) {
|
|
|
423
483
|
* import { combineSigners, expandSeedXof } from '@noble/post-quantum/hybrid.js';
|
|
424
484
|
* import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
|
|
425
485
|
* const hybrid = combineSigners(32, expandSeedXof(shake256), ml_dsa44, ml_dsa44);
|
|
426
|
-
* const
|
|
486
|
+
* const seed = new Uint8Array(hybrid.lengths.seed!).fill(1);
|
|
487
|
+
* const { secretKey, publicKey } = hybrid.keygen(seed);
|
|
488
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
489
|
+
* const sig = hybrid.sign(msg, secretKey);
|
|
490
|
+
* const isValid = hybrid.verify(sig, msg, publicKey);
|
|
427
491
|
* ```
|
|
428
492
|
*/
|
|
429
493
|
export function combineSigners(realSeedLen, expandSeed, ...signers) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
494
|
+
if (realSeedLen !== undefined)
|
|
495
|
+
anumber(realSeedLen, 'realSeedLen');
|
|
496
|
+
if (typeof expandSeed !== 'function')
|
|
497
|
+
throw new TypeError('"expandSeed" expected function, got type=' + typeof expandSeed);
|
|
498
|
+
const rawSigners = signers;
|
|
499
|
+
for (let i = 0; i < rawSigners.length; i++)
|
|
500
|
+
validateSigner(rawSigners[i], `signers[${i}]`);
|
|
501
|
+
const keys = combineKeys(realSeedLen, expandSeed, ...rawSigners);
|
|
502
|
+
const sigCoder = splitLengths(rawSigners, 'signature');
|
|
503
|
+
const pkCoder = splitLengths(rawSigners, 'publicKey');
|
|
433
504
|
return {
|
|
434
505
|
lengths: { ...keys.info.lengths, signature: sigCoder.bytesLen, signRand: 0 },
|
|
435
506
|
getPublicKey: keys.getPublicKey,
|
|
@@ -445,7 +516,7 @@ export function combineSigners(realSeedLen, expandSeed, ...signers) {
|
|
|
445
516
|
throw new Error('combineSigners does not support context; use the underlying signer directly');
|
|
446
517
|
const { secretKey } = keys.expandDecapsulationKey(seed);
|
|
447
518
|
try {
|
|
448
|
-
const sigs =
|
|
519
|
+
const sigs = rawSigners.map((i, j) => i.sign(message, secretKey[j]));
|
|
449
520
|
return sigCoder.encode(sigs);
|
|
450
521
|
}
|
|
451
522
|
finally {
|
|
@@ -455,17 +526,24 @@ export function combineSigners(realSeedLen, expandSeed, ...signers) {
|
|
|
455
526
|
}
|
|
456
527
|
},
|
|
457
528
|
/** Verify one combined signature.
|
|
458
|
-
*
|
|
459
|
-
*
|
|
529
|
+
* Wrong-length aggregate signatures return `false` (matching ml-dsa / slh-dsa behavior), as
|
|
530
|
+
* does any failing child verify. Throws on unsupported generic opts or malformed publicKey.
|
|
460
531
|
*/
|
|
461
532
|
verify: (signature, message, publicKey, opts = {}) => {
|
|
462
533
|
validateVerOpts(opts);
|
|
463
534
|
if (opts.context !== undefined)
|
|
464
535
|
throw new Error('combineSigners does not support context; use the underlying signer directly');
|
|
536
|
+
// Malformed signature *length* is a verification failure, not a thrown type error —
|
|
537
|
+
// consistent with ml-dsa / slh-dsa. Must run before sigCoder.decode, which throws.
|
|
538
|
+
// Preserve TypeError for non-byte API arguments before treating byte lengths as invalid.
|
|
539
|
+
abytes(signature, undefined, 'signature');
|
|
540
|
+
// A signature failure must not hide malformed aggregate public-key bytes.
|
|
465
541
|
const pks = pkCoder.decode(publicKey);
|
|
542
|
+
if (signature.length !== sigCoder.bytesLen)
|
|
543
|
+
return false;
|
|
466
544
|
const sigs = sigCoder.decode(signature);
|
|
467
|
-
for (let i = 0; i <
|
|
468
|
-
if (!
|
|
545
|
+
for (let i = 0; i < rawSigners.length; i++) {
|
|
546
|
+
if (!rawSigners[i].verify(sigs[i], message, pks[i]))
|
|
469
547
|
return false;
|
|
470
548
|
}
|
|
471
549
|
return true;
|
|
@@ -497,7 +575,14 @@ export function combineSigners(realSeedLen, expandSeed, ...signers) {
|
|
|
497
575
|
* ```
|
|
498
576
|
*/
|
|
499
577
|
export function QSF(label, pqc, curveKEM, xof, kdf) {
|
|
578
|
+
astring(label, 'label');
|
|
579
|
+
validateKEM(pqc, 'pqc');
|
|
580
|
+
validateKEM(curveKEM, 'curveKEM');
|
|
581
|
+
if (typeof xof !== 'function' || typeof xof.create !== 'function')
|
|
582
|
+
throw new TypeError('"xof" expected hash function, got type=' + typeof xof);
|
|
500
583
|
ahash(xof);
|
|
584
|
+
if (typeof kdf !== 'function' || typeof kdf.create !== 'function')
|
|
585
|
+
throw new TypeError('"kdf" expected hash function, got type=' + typeof kdf);
|
|
501
586
|
ahash(kdf);
|
|
502
587
|
return combineKEMS(32, kdf.outputLen, expandSeedXof(xof), (pk, ct, ss) => kdf(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))), pqc, curveKEM);
|
|
503
588
|
}
|
|
@@ -532,7 +617,14 @@ export const QSF_ml_kem1024_p384 = /* @__PURE__ */ (() => QSF('QSF-KEM(ML-KEM-10
|
|
|
532
617
|
* ```
|
|
533
618
|
*/
|
|
534
619
|
export function createKitchenSink(label, pqc, curveKEM, xof, hash) {
|
|
620
|
+
astring(label, 'label');
|
|
621
|
+
validateKEM(pqc, 'pqc');
|
|
622
|
+
validateKEM(curveKEM, 'curveKEM');
|
|
623
|
+
if (typeof xof !== 'function' || typeof xof.create !== 'function')
|
|
624
|
+
throw new TypeError('"xof" expected hash function, got type=' + typeof xof);
|
|
535
625
|
ahash(xof);
|
|
626
|
+
if (typeof hash !== 'function' || typeof hash.create !== 'function')
|
|
627
|
+
throw new TypeError('"hash" expected hash function, got type=' + typeof hash);
|
|
536
628
|
ahash(hash);
|
|
537
629
|
return combineKEMS(32, 32, expandSeedXof(xof), (pk, ct, ss) => {
|
|
538
630
|
const preimage = concatBytes(ss[0], ss[1], ct[0], pk[0], ct[1], pk[1], asciiToBytes(label));
|
|
@@ -587,6 +679,11 @@ function nistCurveKem(curve, scalarLen, elemLen, nseed) {
|
|
|
587
679
|
const publicKey = curve.getPublicKey(secretKey, false);
|
|
588
680
|
return { secretKey, publicKey };
|
|
589
681
|
}
|
|
682
|
+
// Standalone (not `this.decapsulate`) so encapsulate works even when methods are destructured.
|
|
683
|
+
const decapsulate = (cipherText, secretKey) => {
|
|
684
|
+
const full = curve.getSharedSecret(secretKey, cipherText);
|
|
685
|
+
return full.subarray(1);
|
|
686
|
+
};
|
|
590
687
|
return {
|
|
591
688
|
lengths: {
|
|
592
689
|
secretKey: scalarLen,
|
|
@@ -607,7 +704,7 @@ function nistCurveKem(curve, scalarLen, elemLen, nseed) {
|
|
|
607
704
|
let ek = undefined;
|
|
608
705
|
try {
|
|
609
706
|
ek = rejectionSampling(rand).secretKey;
|
|
610
|
-
const sharedSecret =
|
|
707
|
+
const sharedSecret = decapsulate(publicKey, ek);
|
|
611
708
|
const cipherText = curve.getPublicKey(ek, false);
|
|
612
709
|
return { sharedSecret, cipherText };
|
|
613
710
|
}
|
|
@@ -618,10 +715,7 @@ function nistCurveKem(curve, scalarLen, elemLen, nseed) {
|
|
|
618
715
|
cleanBytes(ek);
|
|
619
716
|
}
|
|
620
717
|
},
|
|
621
|
-
decapsulate
|
|
622
|
-
const full = curve.getSharedSecret(secretKey, cipherText);
|
|
623
|
-
return full.subarray(1);
|
|
624
|
-
},
|
|
718
|
+
decapsulate,
|
|
625
719
|
};
|
|
626
720
|
}
|
|
627
721
|
/**
|
|
@@ -641,29 +735,14 @@ function concreteHybridKem(label, mlkem, curve, nseed) {
|
|
|
641
735
|
const totalSeedLen = mlkemSeedLen + nseed;
|
|
642
736
|
return combineKEMS(32, 32, (seed) => {
|
|
643
737
|
abytes(seed, 32);
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
738
|
+
// One SHAKE256 stream split by the seed coder as mlkemSeed (64) || curveSeed (nseed).
|
|
739
|
+
// Returned directly: the previous concatBytes of two adjacent subarrays produced an
|
|
740
|
+
// identical copy while leaving this original buffer unwiped; expandDecapsulationKey
|
|
741
|
+
// wipes the returned buffer after the child seeds are copied out.
|
|
742
|
+
return shake256(seed, { dkLen: totalSeedLen });
|
|
648
743
|
}, (pk, ct, ss) => sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))), mlkem, curveKem);
|
|
649
744
|
}
|
|
650
745
|
/** P-256 + ML-KEM-768 hybrid preset. */
|
|
651
746
|
export const ml_kem768_p256 = /* @__PURE__ */ (() => concreteHybridKem('MLKEM768-P256', ml_kem768, p256, 128))();
|
|
652
747
|
/** P-384 + ML-KEM-1024 hybrid preset. */
|
|
653
748
|
export const ml_kem1024_p384 = /* @__PURE__ */ (() => concreteHybridKem('MLKEM1024-P384', ml_kem1024, p384, 48))();
|
|
654
|
-
// Legacy aliases
|
|
655
|
-
/** Legacy alias for `ml_kem768_x25519`. */
|
|
656
|
-
export const XWing = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
657
|
-
/** Legacy alias for `ml_kem768_x25519`. */
|
|
658
|
-
export const MLKEM768X25519 = /* @__PURE__ */ (() => ml_kem768_x25519)();
|
|
659
|
-
/** Legacy alias for `ml_kem768_p256`. */
|
|
660
|
-
export const MLKEM768P256 = /* @__PURE__ */ (() => ml_kem768_p256)();
|
|
661
|
-
/** Legacy alias for `ml_kem1024_p384`. */
|
|
662
|
-
export const MLKEM1024P384 = /* @__PURE__ */ (() => ml_kem1024_p384)();
|
|
663
|
-
/** Legacy alias for `QSF_ml_kem768_p256`. */
|
|
664
|
-
export const QSFMLKEM768P256 = /* @__PURE__ */ (() => QSF_ml_kem768_p256)();
|
|
665
|
-
/** Legacy alias for `QSF_ml_kem1024_p384`. */
|
|
666
|
-
export const QSFMLKEM1024P384 = /* @__PURE__ */ (() => QSF_ml_kem1024_p384)();
|
|
667
|
-
/** Legacy alias for `KitchenSink_ml_kem768_x25519`. */
|
|
668
|
-
export const KitchenSinkMLKEM768X25519 = /* @__PURE__ */ (() => KitchenSink_ml_kem768_x25519)();
|
|
669
|
-
//# sourceMappingURL=hybrid.js.map
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -14,8 +14,15 @@ 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');
|
|
20
28
|
export {};
|
|
21
|
-
//# sourceMappingURL=index.js.map
|
package/ml-dsa.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { CHash } from '@noble/hashes/utils.js';
|
|
2
|
+
import { type CryptoKeys, type Signer, type SigOpts, type TArg, type TRet, type VerOpts } from './utils.ts';
|
|
2
3
|
/** Internal ML-DSA options. */
|
|
3
4
|
export type DSAInternalOpts = {
|
|
4
5
|
/**
|
|
@@ -11,12 +12,19 @@ export type DSAInternalOpts = {
|
|
|
11
12
|
/** ML-DSA signer surface with access to the internal message formatting mode. */
|
|
12
13
|
export type DSAInternal = CryptoKeys & {
|
|
13
14
|
lengths: Signer['lengths'];
|
|
14
|
-
sign: (msg: Uint8Array
|
|
15
|
-
verify: (sig: Uint8Array
|
|
15
|
+
sign: (msg: TArg<Uint8Array>, secretKey: TArg<Uint8Array>, opts?: TArg<SigOpts & DSAInternalOpts>) => TRet<Uint8Array>;
|
|
16
|
+
verify: (sig: TArg<Uint8Array>, msg: TArg<Uint8Array>, pubKey: TArg<Uint8Array>, opts?: TArg<VerOpts & DSAInternalOpts>) => boolean;
|
|
16
17
|
};
|
|
17
18
|
/** Public ML-DSA signer surface. */
|
|
18
19
|
export type DSA = Signer & {
|
|
19
|
-
internal: DSAInternal
|
|
20
|
+
internal: TRet<DSAInternal>;
|
|
21
|
+
securityLevel: number;
|
|
22
|
+
/**
|
|
23
|
+
* HashML-DSA (FIPS 204 §5.4) variant which signs a pre-hashed message.
|
|
24
|
+
* @param hash - Approved hash, checked against the parameter set security level.
|
|
25
|
+
* @returns Signer which pre-hashes `msg` before formatting `M'`.
|
|
26
|
+
*/
|
|
27
|
+
prehash: (hash: TArg<CHash>) => TRet<Signer>;
|
|
20
28
|
};
|
|
21
29
|
/** Various lattice params. */
|
|
22
30
|
/** Public ML-DSA parameter-set description. */
|
|
@@ -45,10 +53,28 @@ export type DSAParam = {
|
|
|
45
53
|
* while `C_TILDE_BYTES`, `TR_BYTES`, `CRH_BYTES`, and `securityLevel` live in the preset wrappers.
|
|
46
54
|
*/
|
|
47
55
|
export declare const PARAMS: Record<string, DSAParam>;
|
|
48
|
-
/**
|
|
49
|
-
|
|
56
|
+
/**
|
|
57
|
+
* ML-DSA-44 for 128-bit security level. Not recommended after 2030, as per ASD.
|
|
58
|
+
* @example
|
|
59
|
+
* Generate deterministic ML-DSA-44 keys, sign one message, and verify the signature.
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
62
|
+
* import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
|
|
63
|
+
* const seed = new Uint8Array(ml_dsa44.lengths.seed!);
|
|
64
|
+
* const { secretKey, publicKey } = ml_dsa44.keygen(seed);
|
|
65
|
+
* const msg = new TextEncoder().encode('hello noble');
|
|
66
|
+
* const sig = ml_dsa44.sign(msg, secretKey);
|
|
67
|
+
* const isValid = ml_dsa44.verify(sig, msg, publicKey);
|
|
68
|
+
* const recovered = ml_dsa44.getPublicKey(secretKey);
|
|
69
|
+
* const context = new Uint8Array([1, 2, 3]);
|
|
70
|
+
* const prehash = ml_dsa44.prehash(sha256);
|
|
71
|
+
* const preSig = prehash.sign(msg, secretKey, { context });
|
|
72
|
+
* const preValid = prehash.verify(preSig, msg, publicKey, { context });
|
|
73
|
+
* const internalSig = ml_dsa44.internal.sign(msg, secretKey);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare const ml_dsa44: TRet<DSA>;
|
|
50
77
|
/** ML-DSA-65 for 192-bit security level. Not recommended after 2030, as per ASD. */
|
|
51
|
-
export declare const ml_dsa65: DSA
|
|
78
|
+
export declare const ml_dsa65: TRet<DSA>;
|
|
52
79
|
/** ML-DSA-87 for 256-bit security level. OK after 2030, as per ASD. */
|
|
53
|
-
export declare const ml_dsa87: DSA
|
|
54
|
-
//# sourceMappingURL=ml-dsa.d.ts.map
|
|
80
|
+
export declare const ml_dsa87: TRet<DSA>;
|