@@ -345,35 +345,6 @@ if (hasOpenSSL(3, 2)) {
345345 }
346346}
347347
348- // --- ML-DSA external mu (prehashed) ---
349- if ( hasOpenSSL ( 3 , 5 ) ) {
350- // ML-DSA signDigest/verifyDigest treats input as external mu.
351- // mu is the 64-byte SHAKE-256(tr || M') value that the caller computes.
352- const variants = [
353- { alg : 'ml_dsa_44' } ,
354- { alg : 'ml_dsa_65' } ,
355- { alg : 'ml_dsa_87' } ,
356- ] ;
357-
358- for ( const { alg } of variants ) {
359- const privKey = fixtures . readKey ( `${ alg } _private.pem` , 'ascii' ) ;
360- const pubKey = fixtures . readKey ( `${ alg } _public.pem` , 'ascii' ) ;
361-
362- const mu = crypto . randomBytes ( 64 ) ;
363-
364- const sig = crypto . signDigest ( null , mu , privKey ) ;
365- assert ( Buffer . isBuffer ( sig ) ) ;
366- assert ( sig . length > 0 ) ;
367-
368- // Verify with same mu succeeds
369- assert . strictEqual ( crypto . verifyDigest ( null , mu , pubKey , sig ) , true ) ;
370-
371- // Verify with wrong mu fails
372- const wrongMu = crypto . randomBytes ( 64 ) ;
373- assert . strictEqual ( crypto . verifyDigest ( null , wrongMu , pubKey , sig ) , false ) ;
374- }
375- }
376-
377348// --- Async (callback) mode ---
378349{
379350 const privKey = fixtures . readKey ( 'rsa_private_2048.pem' , 'ascii' ) ;
@@ -409,115 +380,8 @@ if (hasOpenSSL(3, 2)) {
409380 } ) ) ;
410381}
411382
412- if ( hasOpenSSL ( 3 , 5 ) ) {
413- // ML-DSA async sign+verify with external mu (64-byte pre-computed value)
414- const mldsaPrivKey = fixtures . readKey ( 'ml_dsa_44_private.pem' , 'ascii' ) ;
415- const mldsaPubKey = fixtures . readKey ( 'ml_dsa_44_public.pem' , 'ascii' ) ;
416- const mu = crypto . randomBytes ( 64 ) ;
417- crypto . signDigest ( null , mu , mldsaPrivKey , common . mustSucceed ( ( sig ) => {
418- assert ( sig . length > 0 ) ;
419- crypto . verifyDigest ( null , mu , mldsaPubKey , sig , common . mustSucceed ( ( ok ) => {
420- assert . strictEqual ( ok , true ) ;
421- } ) ) ;
422- } ) ) ;
423-
424- // Wrong mu length (32 bytes) is rejected asynchronously
425- crypto . signDigest ( null , Buffer . alloc ( 32 ) , mldsaPrivKey , common . mustCall ( ( err ) => {
426- assert ( err ) ;
427- assert . match ( err . message , / p r o v i d e r s i g n a t u r e f a i l u r e / ) ;
428- } ) ) ;
429- }
430-
431383// --- Error: unsupported key type for prehashed signing ---
432384{
433- // ML-DSA rejects wrong mu length (must be exactly 64 bytes).
434- if ( hasOpenSSL ( 3 , 5 ) ) {
435- const privKey = fixtures . readKey ( 'ml_dsa_44_private.pem' , 'ascii' ) ;
436- const pubKey = fixtures . readKey ( 'ml_dsa_44_public.pem' , 'ascii' ) ;
437-
438- assert . throws ( ( ) => {
439- crypto . signDigest ( null , Buffer . alloc ( 32 ) , privKey ) ;
440- } , / p r o v i d e r s i g n a t u r e f a i l u r e / ) ;
441-
442- assert . throws ( ( ) => {
443- crypto . signDigest ( null , Buffer . alloc ( 128 ) , privKey ) ;
444- } , / p r o v i d e r s i g n a t u r e f a i l u r e / ) ;
445-
446- // verifyDigest returns false for wrong mu length (not a throw)
447- assert . strictEqual (
448- crypto . verifyDigest ( null , Buffer . alloc ( 32 ) , pubKey , Buffer . alloc ( 2420 ) ) ,
449- false ,
450- ) ;
451-
452- // Context string is not supported with signDigest/verifyDigest for ML-DSA
453- // since context must already be incorporated into the externally computed mu.
454- assert . throws ( ( ) => {
455- crypto . signDigest ( null , Buffer . alloc ( 64 ) , { key : privKey , context : Buffer . from ( 'ctx' ) } ) ;
456- } , { code : 'ERR_CRYPTO_OPERATION_FAILED' , message : / C o n t e x t p a r a m e t e r i s u n s u p p o r t e d / } ) ;
457- assert . throws ( ( ) => {
458- crypto . verifyDigest ( null , Buffer . alloc ( 64 ) , { key : pubKey , context : Buffer . from ( 'ctx' ) } ,
459- Buffer . alloc ( 2420 ) ) ;
460- } , { code : 'ERR_CRYPTO_OPERATION_FAILED' , message : / C o n t e x t p a r a m e t e r i s u n s u p p o r t e d / } ) ;
461- }
462-
463- // ML-DSA external mu cross-verification with crypto.sign/crypto.verify.
464- // Computes mu = SHAKE-256(tr || M', 64) per FIPS 204, where
465- // tr = SHAKE-256(pk, 64) and M' encodes the context.
466- if ( hasOpenSSL ( 3 , 5 ) ) {
467- const variants = [
468- { alg : 'ml_dsa_44' , sigLen : 2420 } ,
469- { alg : 'ml_dsa_65' , sigLen : 3309 } ,
470- { alg : 'ml_dsa_87' , sigLen : 4627 } ,
471- ] ;
472-
473- for ( const { alg } of variants ) {
474- const privKey = fixtures . readKey ( `${ alg } _private.pem` , 'ascii' ) ;
475- const pubKey = fixtures . readKey ( `${ alg } _public.pem` , 'ascii' ) ;
476-
477- // Get raw public key bytes for tr computation via JWK export.
478- const pubKeyObj = crypto . createPublicKey ( pubKey ) ;
479- const pkBytes = Buffer . from ( pubKeyObj . export ( { format : 'jwk' } ) . pub , 'base64url' ) ;
480- const tr = crypto . createHash ( 'shake256' , { outputLength : 64 } ) . update ( pkBytes ) . digest ( ) ;
481-
482- const msg = Buffer . from ( 'ML-DSA cross-verify test message' ) ;
483-
484- // Without context: M' = 0x00 || 0x00 || M
485- {
486- const mPrime = Buffer . concat ( [ Buffer . from ( [ 0x00 , 0x00 ] ) , msg ] ) ;
487- const mu = crypto . createHash ( 'shake256' , { outputLength : 64 } )
488- . update ( tr ) . update ( mPrime ) . digest ( ) ;
489-
490- const sig = crypto . signDigest ( null , mu , privKey ) ;
491- assert . strictEqual ( crypto . verify ( null , msg , pubKey , sig ) , true ) ;
492-
493- const sig2 = crypto . sign ( null , msg , privKey ) ;
494- assert . strictEqual ( crypto . verifyDigest ( null , mu , pubKey , sig2 ) , true ) ;
495- }
496-
497- // With context: M' = 0x00 || len(ctx) || ctx || M
498- {
499- const ctx = Buffer . from ( 'test context string' ) ;
500- const mPrime = Buffer . concat ( [ Buffer . from ( [ 0x00 , ctx . length ] ) , ctx , msg ] ) ;
501- const mu = crypto . createHash ( 'shake256' , { outputLength : 64 } )
502- . update ( tr ) . update ( mPrime ) . digest ( ) ;
503-
504- const sig = crypto . signDigest ( null , mu , privKey ) ;
505- assert . strictEqual (
506- crypto . verify ( null , msg , { key : pubKey , context : ctx } , sig ) , true ) ;
507-
508- const sig2 = crypto . sign ( null , msg , { key : privKey , context : ctx } ) ;
509- assert . strictEqual ( crypto . verifyDigest ( null , mu , pubKey , sig2 ) , true ) ;
510-
511- // Mismatched context: signDigest with context mu, verify without context
512- assert . strictEqual ( crypto . verify ( null , msg , pubKey , sig ) , false ) ;
513-
514- // Mismatched context: sign without context, verifyDigest with context mu
515- const sig3 = crypto . sign ( null , msg , privKey ) ;
516- assert . strictEqual ( crypto . verifyDigest ( null , mu , pubKey , sig3 ) , false ) ;
517- }
518- }
519- }
520-
521385 // Ed25519ph/Ed448ph require OpenSSL >= 3.2. On older versions, they
522386 // should throw PrehashUnsupported.
523387 if ( ! hasOpenSSL ( 3 , 2 ) ) {
@@ -537,6 +401,22 @@ if (hasOpenSSL(3, 5)) {
537401 assert . throws ( ( ) => {
538402 crypto . signDigest ( 123 , Buffer . alloc ( 32 ) , fixtures . readKey ( 'rsa_private_2048.pem' , 'ascii' ) ) ;
539403 } , { code : 'ERR_INVALID_ARG_TYPE' } ) ;
404+
405+ // ML-DSA keys are not supported with signDigest/verifyDigest.
406+ if ( hasOpenSSL ( 3 , 5 ) ) {
407+ for ( const alg of [ 'ml_dsa_44' , 'ml_dsa_65' , 'ml_dsa_87' ] ) {
408+ const privKey = fixtures . readKey ( `${ alg } _private.pem` , 'ascii' ) ;
409+ const pubKey = fixtures . readKey ( `${ alg } _public.pem` , 'ascii' ) ;
410+
411+ assert . throws ( ( ) => {
412+ crypto . signDigest ( null , Buffer . alloc ( 64 ) , privKey ) ;
413+ } , { code : 'ERR_CRYPTO_OPERATION_FAILED' , message : / P r e h a s h e d s i g n i n g i s n o t s u p p o r t e d / } ) ;
414+
415+ assert . throws ( ( ) => {
416+ crypto . verifyDigest ( null , Buffer . alloc ( 64 ) , pubKey , Buffer . alloc ( 64 ) ) ;
417+ } , { code : 'ERR_CRYPTO_OPERATION_FAILED' , message : / P r e h a s h e d s i g n i n g i s n o t s u p p o r t e d / } ) ;
418+ }
419+ }
540420}
541421
542422// --- Error: non-signing key types (X25519, X448) ---
0 commit comments