Skip to content

Commit 6fff296

Browse files
committed
Add proptests with round trip check
- Ensures we can verify signatures we've produced - Ensures we can decode signatures we've encoded
1 parent b01c3b7 commit 6fff296

File tree

7 files changed

+89
-6
lines changed

7 files changed

+89
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ml-dsa/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ criterion = "0.5.1"
3737
hex = { version = "0.4.3", features = ["serde"] }
3838
hex-literal = "0.4.1"
3939
pkcs8 = { version = "=0.11.0-rc.1", features = ["pem"] }
40+
proptest = "1"
4041
rand = "0.8.5"
4142
serde = { version = "1.0.215", features = ["derive"] }
4243
serde_json = "1.0.132"

ml-dsa/src/lib.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
//! # Quickstart
1818
//!
1919
//! ```
20-
//! use ml_dsa::{MlDsa65, KeyGen};
21-
//! use signature::{Keypair, Signer, Verifier};
20+
//! use ml_dsa::{MlDsa65, KeyGen, signature::{Keypair, Signer, Verifier}};
2221
//!
2322
//! let mut rng = rand::thread_rng();
2423
//! let kp = MlDsa65::key_gen(&mut rng);
@@ -83,10 +82,11 @@ use crate::ntt::{Ntt, NttInverse};
8382
use crate::param::{ParameterSet, QMinus1, SamplingSize, SpecQ};
8483
use crate::sampling::{expand_a, expand_mask, expand_s, sample_in_ball};
8584
use crate::util::B64;
85+
use core::fmt;
8686

8787
pub use crate::param::{EncodedSignature, EncodedSigningKey, EncodedVerifyingKey, MlDsaParams};
8888
pub use crate::util::B32;
89-
pub use signature::Error;
89+
pub use signature::{self, Error};
9090

9191
/// An ML-DSA signature
9292
#[derive(Clone, PartialEq, Debug)]
@@ -192,6 +192,11 @@ impl<P: MlDsaParams> KeyPair<P> {
192192
pub fn signing_key(&self) -> &SigningKey<P> {
193193
&self.signing_key
194194
}
195+
196+
/// The verifying key of the key pair
197+
pub fn verifying_key(&self) -> &VerifyingKey<P> {
198+
&self.verifying_key
199+
}
195200
}
196201

197202
impl<P: MlDsaParams> AsRef<VerifyingKey<P>> for KeyPair<P> {
@@ -200,6 +205,14 @@ impl<P: MlDsaParams> AsRef<VerifyingKey<P>> for KeyPair<P> {
200205
}
201206
}
202207

208+
impl<P: MlDsaParams> fmt::Debug for KeyPair<P> {
209+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210+
f.debug_struct("KeyPair")
211+
.field("verifying_key", &self.verifying_key)
212+
.finish_non_exhaustive()
213+
}
214+
}
215+
203216
impl<P: MlDsaParams> signature::KeypairRef for KeyPair<P> {
204217
type VerifyingKey = VerifyingKey<P>;
205218
}
@@ -276,6 +289,12 @@ pub struct SigningKey<P: MlDsaParams> {
276289
A_hat: NttMatrix<P::K, P::L>,
277290
}
278291

292+
impl<P: MlDsaParams> fmt::Debug for SigningKey<P> {
293+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294+
f.debug_struct("SigningKey").finish_non_exhaustive()
295+
}
296+
}
297+
279298
#[cfg(feature = "zeroize")]
280299
impl<P: MlDsaParams> Drop for SigningKey<P> {
281300
fn drop(&mut self) {

ml-dsa/tests/key-gen.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use ml_dsa::*;
22

33
use hybrid_array::Array;
4-
use signature::Keypair;
54
use std::{fs::read_to_string, path::PathBuf};
65

76
#[test]

ml-dsa/tests/pkcs8.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use pkcs8::{
77
spki::AssociatedAlgorithmIdentifier,
88
DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey,
99
};
10-
use signature::Keypair;
1110

1211
#[test]
1312
fn private_key_serialization() {
@@ -34,7 +33,7 @@ fn private_key_serialization() {
3433
public_bytes
3534
);
3635

37-
assert_eq!(kp.verifying_key(), pk);
36+
assert_eq!(kp.verifying_key(), &pk);
3837
}
3938

4039
test_roundtrip::<MlDsa44>(

ml-dsa/tests/proptests.proptest-regressions

Lines changed: 8 additions & 0 deletions
Large diffs are not rendered by default.

ml-dsa/tests/proptests.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use ml_dsa::{
2+
signature::{Signer, Verifier},
3+
KeyGen, KeyPair, MlDsa44, MlDsa65, MlDsa87, Signature,
4+
};
5+
use proptest::prelude::*;
6+
7+
/// Example message
8+
const MSG: &[u8] = b"Hello world";
9+
10+
// Keypairs
11+
prop_compose! {
12+
fn mldsa44_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa44> {
13+
MlDsa44::key_gen_internal(seed_bytes.as_ref())
14+
}
15+
}
16+
prop_compose! {
17+
fn mldsa65_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa65> {
18+
MlDsa65::key_gen_internal(seed_bytes.as_ref())
19+
}
20+
}
21+
prop_compose! {
22+
fn mldsa87_keypair()(seed_bytes in any::<[u8; 32]>()) -> KeyPair<MlDsa87> {
23+
MlDsa87::key_gen_internal(seed_bytes.as_ref())
24+
}
25+
}
26+
27+
macro_rules! round_trip_test {
28+
($params:path, $keypair:expr) => {
29+
let sig = $keypair.signing_key().sign(MSG);
30+
31+
// Check signature verification
32+
let verify_result = $keypair.verifying_key().verify(MSG, &sig);
33+
prop_assert!(verify_result.is_ok());
34+
35+
// Check signature encoding round trip
36+
let sig_decoded = Signature::<$params>::decode(&sig.encode());
37+
prop_assert_eq!(Some(sig), sig_decoded);
38+
};
39+
}
40+
41+
proptest! {
42+
#[test]
43+
fn mldsa44_round_trip(keypair in mldsa44_keypair()) {
44+
round_trip_test!(MlDsa44, keypair);
45+
}
46+
47+
#[test]
48+
fn mldsa65_round_trip(keypair in mldsa65_keypair()) {
49+
round_trip_test!(MlDsa65, keypair);
50+
}
51+
52+
#[test]
53+
fn mldsa87_round_trip(keypair in mldsa87_keypair()) {
54+
round_trip_test!(MlDsa87, keypair);
55+
}
56+
}

0 commit comments

Comments
 (0)