Skip to content

Commit 289c868

Browse files
authored
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 <[email protected]>
1 parent e6d52c2 commit 289c868

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

ml-dsa/src/lib.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)