@@ -504,6 +504,23 @@ impl<P: MlDsaParams> SigningKey<P> {
504504 None ,
505505 )
506506 }
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+ ///
516+ /// `SigningKey` implements `signature::Keypair`: this inherent method is
517+ /// retained for convenience, so it is available for callers even when the
518+ /// `signature::Keypair` trait is out-of-scope.
519+ pub fn verifying_key ( & self ) -> VerifyingKey < P > {
520+ let kp: & dyn signature:: Keypair < VerifyingKey = VerifyingKey < P > > = self ;
521+
522+ kp. verifying_key ( )
523+ }
507524}
508525
509526/// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and
@@ -524,6 +541,26 @@ impl<P: MlDsaParams> MultipartSigner<Signature<P>> for SigningKey<P> {
524541 }
525542}
526543
544+ /// The `KeyPair` implementation for `SigningKey` allows to derive a `VerifyingKey` from
545+ /// a bare `SigningKey` (even in the absence of the original seed).
546+ impl < P : MlDsaParams > signature:: Keypair for SigningKey < P > {
547+ type VerifyingKey = VerifyingKey < P > ;
548+
549+ /// This is a utility function that is useful when importing the private key
550+ /// from an external source which does not export the seed and does not
551+ /// provide the precomputed public key associated with the private key
552+ /// itself.
553+ fn verifying_key ( & self ) -> Self :: VerifyingKey {
554+ let As1 = & self . A_hat * & self . s1_hat ;
555+ let t = & As1 . ntt_inverse ( ) + & self . s2 ;
556+
557+ /* Discard t0 */
558+ let ( t1, _) = t. power2round ( ) ;
559+
560+ VerifyingKey :: new ( self . rho . clone ( ) , t1, Some ( self . A_hat . clone ( ) ) , None )
561+ }
562+ }
563+
527564/// The `RandomizedSigner` implementation for `SigningKey` only supports signing with an empty
528565/// context string. If you would like to include a context string, use the [`SigningKey::sign`]
529566/// method.
@@ -947,6 +984,25 @@ mod test {
947984 encode_decode_round_trip_test :: < MlDsa87 > ( ) ;
948985 }
949986
987+ fn public_from_private_test < P > ( )
988+ where
989+ P : MlDsaParams + PartialEq ,
990+ {
991+ let kp = P :: key_gen_internal ( & Array :: default ( ) ) ;
992+ let sk = kp. signing_key ;
993+ let vk = kp. verifying_key ;
994+ let vk_derived = sk. verifying_key ( ) ;
995+
996+ assert ! ( vk == vk_derived) ;
997+ }
998+
999+ #[ test]
1000+ fn public_from_private ( ) {
1001+ public_from_private_test :: < MlDsa44 > ( ) ;
1002+ public_from_private_test :: < MlDsa65 > ( ) ;
1003+ public_from_private_test :: < MlDsa87 > ( ) ;
1004+ }
1005+
9501006 fn sign_verify_round_trip_test < P > ( )
9511007 where
9521008 P : MlDsaParams ,
0 commit comments