diff --git a/ml-dsa/src/lib.rs b/ml-dsa/src/lib.rs index 94407a0..88d8261 100644 --- a/ml-dsa/src/lib.rs +++ b/ml-dsa/src/lib.rs @@ -504,6 +504,23 @@ impl SigningKey

{ None, ) } + + /// This auxiliary function derives a `VerifyingKey` from a bare + /// `SigningKey` (even in the absence of the original seed). + /// + /// This is a utility function that is useful when importing the private key + /// from an external source which does not export the seed and does not + /// provide the precomputed public key associated with the private key + /// itself. + /// + /// `SigningKey` implements `signature::Keypair`: this inherent method is + /// retained for convenience, so it is available for callers even when the + /// `signature::Keypair` trait is out-of-scope. + pub fn verifying_key(&self) -> VerifyingKey

{ + let kp: &dyn signature::Keypair> = self; + + kp.verifying_key() + } } /// The `Signer` implementation for `SigningKey` uses the optional deterministic variant of ML-DSA, and @@ -524,6 +541,26 @@ impl MultipartSigner> for SigningKey

{ } } +/// The `KeyPair` implementation for `SigningKey` allows to derive a `VerifyingKey` from +/// a bare `SigningKey` (even in the absence of the original seed). +impl signature::Keypair for SigningKey

{ + type VerifyingKey = VerifyingKey

; + + /// This is a utility function that is useful when importing the private key + /// from an external source which does not export the seed and does not + /// provide the precomputed public key associated with the private key + /// itself. + fn verifying_key(&self) -> Self::VerifyingKey { + let As1 = &self.A_hat * &self.s1_hat; + let t = &As1.ntt_inverse() + &self.s2; + + /* Discard t0 */ + let (t1, _) = t.power2round(); + + VerifyingKey::new(self.rho.clone(), t1, Some(self.A_hat.clone()), None) + } +} + /// The `RandomizedSigner` implementation for `SigningKey` only supports signing with an empty /// context string. If you would like to include a context string, use the [`SigningKey::sign`] /// method. @@ -947,6 +984,25 @@ mod test { encode_decode_round_trip_test::(); } + fn public_from_private_test

() + where + P: MlDsaParams + PartialEq, + { + let kp = P::key_gen_internal(&Array::default()); + let sk = kp.signing_key; + let vk = kp.verifying_key; + let vk_derived = sk.verifying_key(); + + assert!(vk == vk_derived); + } + + #[test] + fn public_from_private() { + public_from_private_test::(); + public_from_private_test::(); + public_from_private_test::(); + } + fn sign_verify_round_trip_test

() where P: MlDsaParams,