@@ -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 ) ;
0 commit comments