feat(ml-dsa): Implement verifying_key method for SigningKey (#1008)

Implement the `signature::Keypair` trait for `SigningKey`, enabling
derivation of a `VerifyingKey` from a bare signing key. Also adds
inherent `verifying_key` method to act as a convenience shim around the
trait implementation, ensuring a single source of logic and improving
interoperability.

Signed-off-by: Francesco Rollo <eferollo@gmail.com>
This commit is contained in:
Nicola Tuveri
2025-07-28 15:48:34 +03:00
committed by GitHub
parent e6d52c2ae0
commit 289c868ba7
+56
View File
@@ -504,6 +504,23 @@ impl<P: MlDsaParams> SigningKey<P> {
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<P> {
let kp: &dyn signature::Keypair<VerifyingKey = VerifyingKey<P>> = self;
kp.verifying_key()
}
}
/// 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> {
}
}
/// The `KeyPair` implementation for `SigningKey` allows to derive a `VerifyingKey` from
/// a bare `SigningKey` (even in the absence of the original seed).
impl<P: MlDsaParams> signature::Keypair for SigningKey<P> {
type VerifyingKey = VerifyingKey<P>;
/// 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::<MlDsa87>();
}
fn public_from_private_test<P>()
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::<MlDsa44>();
public_from_private_test::<MlDsa65>();
public_from_private_test::<MlDsa87>();
}
fn sign_verify_round_trip_test<P>()
where
P: MlDsaParams,