@@ -504,6 +504,23 @@ impl<P: MlDsaParams> SigningKey<P> {
504
504
None ,
505
505
)
506
506
}
507
+
508
+ /// This auxiliary function derives a `VerifyingKey` from a bare
509
+ /// `SigningKey` (even in the absence of the original seed)
510
+ ///
511
+ /// This is a utility function that is useful when importing the private key
512
+ /// from an external source which does not export the seed and does not
513
+ /// provide the precomputed public key associated with the private key
514
+ /// itself.
515
+ pub fn verifying_key ( & self ) -> VerifyingKey < P > {
516
+ let As1 = & self . A_hat * & self . s1_hat ;
517
+ let t = & As1 . ntt_inverse ( ) + & self . s2 ;
518
+
519
+ /* Discard t0 */
520
+ let ( t1, _) = t. power2round ( ) ;
521
+
522
+ VerifyingKey :: new ( self . rho . clone ( ) , t1, Some ( self . A_hat . clone ( ) ) , None )
523
+ }
507
524
}
508
525
509
526
/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and
@@ -947,6 +964,25 @@ mod test {
947
964
encode_decode_round_trip_test :: < MlDsa87 > ( ) ;
948
965
}
949
966
967
+ fn public_from_private_test < P > ( )
968
+ where
969
+ P : MlDsaParams + PartialEq ,
970
+ {
971
+ let kp = P :: key_gen_internal ( & Array :: default ( ) ) ;
972
+ let sk = kp. signing_key ;
973
+ let vk = kp. verifying_key ;
974
+ let vk_derived = sk. verifying_key ( ) ;
975
+
976
+ assert ! ( vk == vk_derived) ;
977
+ }
978
+
979
+ #[ test]
980
+ fn public_from_private ( ) {
981
+ public_from_private_test :: < MlDsa44 > ( ) ;
982
+ public_from_private_test :: < MlDsa65 > ( ) ;
983
+ public_from_private_test :: < MlDsa87 > ( ) ;
984
+ }
985
+
950
986
fn sign_verify_round_trip_test < P > ( )
951
987
where
952
988
P : MlDsaParams ,
0 commit comments