Skip to content

Commit 0fc6501

Browse files
authored
Merge pull request #34 from panva/concrete-hybrid-kems
implement concrete-hybrid-kems
2 parents 33bea34 + f352b2c commit 0fc6501

6 files changed

Lines changed: 359 additions & 3 deletions

File tree

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ import {
6969
import {
7070
XWing,
7171
KitchenSinkMLKEM768X25519,
72-
QSFMLKEM768P256, QSFMLKEM1024P384
72+
QSFMLKEM768P256, QSFMLKEM1024P384,
73+
MLKEM768P256, MLKEM768X25519, MLKEM1024P384,
7374
} from '@noble/post-quantum/hybrids.js';
7475
```
7576

@@ -175,17 +176,24 @@ SLH-DSA is slow: see [benchmarks](#speed) for key size & speed.
175176
import {
176177
XWing,
177178
KitchenSinkMLKEM768X25519,
178-
QSFMLKEM768P256, QSFMLKEM1024P384
179+
QSFMLKEM768P256, QSFMLKEM1024P384,
180+
MLKEM768P256, MLKEM768X25519, MLKEM1024P384,
179181
} from '@noble/post-quantum/hybrids.js';
180182
```
181183

182-
XWing is x25519+mlkem768, just like kitchensink.
184+
- **XWing** / **MLKEM768X25519**: ML-KEM-768 + X25519 (CG Framework)
185+
- **KitchenSinkMLKEM768X25519**: ML-KEM-768 + X25519 with HKDF-SHA256 combiner
186+
- **QSFMLKEM768P256**: ML-KEM-768 + P-256 (QSF construction)
187+
- **QSFMLKEM1024P384**: ML-KEM-1024 + P-384 (QSF construction)
188+
- **MLKEM768P256**: ML-KEM-768 + P-256 (CG Framework)
189+
- **MLKEM1024P384**: ML-KEM-1024 + P-384 (CG Framework)
183190

184191
The following spec drafts are matched:
185192

186193
- [irtf-cfrg-hybrid-kems](https://datatracker.ietf.org/doc/draft-irtf-cfrg-hybrid-kems/)
187194
- [connolly-cfrg-xwing-kem](https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/)
188195
- [tls-westerbaan-xyber768d00](https://datatracker.ietf.org/doc/draft-tls-westerbaan-xyber768d00/)
196+
- [irtf-cfrg-concrete-hybrid-kems](https://datatracker.ietf.org/doc/draft-irtf-cfrg-concrete-hybrid-kems/)
189197

190198
### What should I use?
191199

src/hybrid.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,90 @@ export const XWing: KEM = combineKEMS(
371371
ml_kem768,
372372
x25519kem
373373
);
374+
375+
export const MLKEM768X25519: KEM = XWing;
376+
377+
function nistCurveKem(curve: ECDSA, scalarLen: number, elemLen: number, nseed: number): KEM {
378+
const Fn = curve.Point.Fn;
379+
if (!Fn) throw new Error('No Point.Fn');
380+
const order = Fn.ORDER;
381+
382+
function rejectionSampling(seed: Uint8Array): { secretKey: Uint8Array; publicKey: Uint8Array } {
383+
let start = 0;
384+
let end = scalarLen;
385+
let sk = Fn.isLE
386+
? bytesToNumberLE(seed.subarray(start, end))
387+
: bytesToNumberBE(seed.subarray(start, end));
388+
389+
while (sk === 0n || sk >= order) {
390+
start = end;
391+
end = end + scalarLen;
392+
if (end > seed.length) {
393+
throw new Error('Rejection sampling failed');
394+
}
395+
sk = Fn.isLE
396+
? bytesToNumberLE(seed.subarray(start, end))
397+
: bytesToNumberBE(seed.subarray(start, end));
398+
}
399+
400+
const secretKey = Fn.toBytes(Fn.create(sk));
401+
const publicKey = curve.getPublicKey(secretKey, false);
402+
return { secretKey, publicKey };
403+
}
404+
405+
return {
406+
lengths: {
407+
secretKey: scalarLen,
408+
publicKey: elemLen,
409+
seed: nseed,
410+
msg: nseed,
411+
cipherText: elemLen,
412+
},
413+
keygen(seed: Uint8Array = randomBytes(nseed)) {
414+
abytes(seed, nseed, 'seed');
415+
return rejectionSampling(seed);
416+
},
417+
getPublicKey(secretKey: Uint8Array) {
418+
return curve.getPublicKey(secretKey, false);
419+
},
420+
encapsulate(publicKey: Uint8Array, rand: Uint8Array = randomBytes(nseed)) {
421+
abytes(rand, nseed, 'rand');
422+
const { secretKey: ek } = rejectionSampling(rand);
423+
const sharedSecret = this.decapsulate(publicKey, ek);
424+
const cipherText = curve.getPublicKey(ek, false);
425+
cleanBytes(ek);
426+
return { sharedSecret, cipherText };
427+
},
428+
decapsulate(cipherText: Uint8Array, secretKey: Uint8Array) {
429+
const fullSecret = curve.getSharedSecret(secretKey, cipherText);
430+
return fullSecret.subarray(1);
431+
},
432+
};
433+
}
434+
435+
function concreteHybridKem(label: string, mlkem: KEM, curve: ECDSA, nseed: number): KEM {
436+
const { secretKey: scalarLen, publicKeyUncompressed: elemLen } = curve.lengths;
437+
if (!scalarLen || !elemLen) throw new Error('wrong curve');
438+
const curveKem = nistCurveKem(curve, scalarLen, elemLen, nseed);
439+
const mlkemSeedLen = 64;
440+
const totalSeedLen = mlkemSeedLen + nseed;
441+
442+
return combineKEMS(
443+
32,
444+
32,
445+
(seed: Uint8Array) => {
446+
abytes(seed, 32);
447+
const expanded = shake256(seed, { dkLen: totalSeedLen });
448+
const mlkemSeed = expanded.subarray(0, mlkemSeedLen);
449+
const curveSeed = expanded.subarray(mlkemSeedLen, totalSeedLen);
450+
return concatBytes(mlkemSeed, curveSeed);
451+
},
452+
(pk, ct, ss) => sha3_256(concatBytes(ss[0], ss[1], ct[1], pk[1], asciiToBytes(label))),
453+
mlkem,
454+
curveKem
455+
);
456+
}
457+
458+
export const MLKEM768P256: KEM = concreteHybridKem('MLKEM768-P256', ml_kem768, p256, 128);
459+
460+
export const MLKEM1024P384: KEM = concreteHybridKem('MLKEM1024-P384', ml_kem1024, p384, 48);

test/hybrid.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
KitchenSinkMLKEM768X25519,
88
QSFMLKEM1024P384,
99
QSFMLKEM768P256,
10+
MLKEM1024P384,
11+
MLKEM768P256,
12+
MLKEM768X25519,
1013
XWing,
1114
combineSigners,
1215
ecSigner,
@@ -35,6 +38,18 @@ const VECTORS = {
3538
'./vectors/hybrids/test-vectors-KitchenSink-KEM(ML-KEM-768,X25519)-XOF(SHAKE256)-KDF(HKDF-SHA-256).json'
3639
),
3740
},
41+
'MLKEM1024-P384': {
42+
lib: MLKEM1024P384,
43+
tests: jsonGZ('./vectors/hybrids/test-vectors-MLKEM1024P384.json'),
44+
},
45+
'MLKEM768-P256': {
46+
lib: MLKEM768P256,
47+
tests: jsonGZ('./vectors/hybrids/test-vectors-MLKEM768P256.json'),
48+
},
49+
'MLKEM768-X25519': {
50+
lib: MLKEM768X25519,
51+
tests: jsonGZ('./vectors/hybrids/test-vectors-MLKEM768X25519.json'),
52+
},
3853
XWing: {
3954
lib: XWing,
4055
// https://github.com/RustCrypto/KEMs/blob/master/x-wing/src/test-vectors.json

0 commit comments

Comments
 (0)