diff --git a/ml-dsa/tests/wycheproof.rs b/ml-dsa/tests/wycheproof.rs index 94274e3..9bfad99 100644 --- a/ml-dsa/tests/wycheproof.rs +++ b/ml-dsa/tests/wycheproof.rs @@ -2,9 +2,9 @@ // Implementation is based in part on `rsa` which is in turn based on Graviola. -use ml_dsa::{KeyGen, MlDsa44, MlDsa65, MlDsa87}; +use ml_dsa::{KeyGen, MlDsa44, MlDsa65, MlDsa87, Signature, VerifyingKey}; use serde::Deserialize; -use signature::{SignatureEncoding, Signer}; +use signature::{SignatureEncoding, Signer, Verifier}; use std::fs::File; #[derive(Deserialize, Debug)] @@ -53,18 +53,25 @@ enum ExpectedResult { Acceptable, } -macro_rules! mldsa_sign_from_seed_test { +macro_rules! load_json_file { + ($json_file:expr) => {{ + let path = format!("../thirdparty/wycheproof/testvectors_v1/{}", $json_file); + let data_file = File::open(&path) + .expect("failed to open data file (try running `git submodule update --init`)"); + + println!("Loading file: {path}"); + + let tests: TestFile = serde_json::from_reader(data_file).expect("invalid test JSON"); + println!("{}:\n{}\n", tests.algorithm, tests.header.join("")); + tests + }}; +} + +macro_rules! mldsa_sign_seed_test { ($name:ident, $json_file:expr, $keypair:ident) => { #[test] fn $name() { - let path = format!("../thirdparty/wycheproof/testvectors_v1/{}", $json_file); - let data_file = File::open(&path) - .expect("failed to open data file (try running `git submodule update --init`)"); - - println!("Loading file: {path}"); - - let tests: TestFile = serde_json::from_reader(data_file).expect("invalid test JSON"); - println!("{}:\n{}\n", tests.algorithm, tests.header.join("")); + let tests = load_json_file!($json_file); for group in tests.groups { let sk = $keypair::from_seed(&group.private_seed.as_slice().try_into().unwrap()); @@ -80,10 +87,10 @@ macro_rules! mldsa_sign_from_seed_test { match test.result { ExpectedResult::Valid => { - assert_eq!(&*result.unwrap().to_bytes(), test.sig.as_slice()); + assert_eq!(&*result.unwrap().to_bytes(), test.sig.as_slice()) } ExpectedResult::Invalid => { - assert!(result.is_err()); + assert!(result.is_err()) } other => todo!("{:?}", other), } @@ -94,18 +101,82 @@ macro_rules! mldsa_sign_from_seed_test { }; } -mldsa_sign_from_seed_test!( +macro_rules! mldsa_verify_test { + ($name:ident, $json_file:expr, $keypair:ident) => { + mldsa_verify_test!($name, $json_file, $keypair, []); + }; + ($name:ident, $json_file:expr, $keypair:ident, $skip:expr) => { + #[test] + fn $name() { + let tests = load_json_file!($json_file); + + for group in &tests.groups { + if let Ok(encoded_vk) = group.public_key.as_slice().try_into() { + let vk = VerifyingKey::::decode(&encoded_vk); + for test in &group.tests { + println!("Test #{}: {} ({:?})", test.id, &test.comment, &test.result); + + if $skip.contains(&test.id) { + println!("Test #{} is in skip list, skipping!", test.id); + continue; + } + + if let Some(sig) = test + .sig + .as_slice() + .try_into() + .ok() + .and_then(|sig| Signature::::decode(&sig)) + { + if test.ctx.is_empty() { + let result = vk.verify(&test.msg, &sig); + + match test.result { + ExpectedResult::Valid => assert!(result.is_ok()), + ExpectedResult::Invalid => assert!(result.is_err()), + other => todo!("{:?}", other), + } + } else { + // TODO(test) contexts + } + } else { + println!("error decoding signature (length: {})", test.sig.len(),); + assert_eq!(test.result, ExpectedResult::Invalid); + } + } + } + } + } + }; +} + +// TODO(tarcieri): debug these test failures +const SIGNATURE_WITH_A_REPEATED_HINT_TEST_ID: usize = 18; +const PUBLIC_KEY_WITH_T1_COMPONENT_SET_TO_ZERO_TEST_ID: usize = 68; + +mldsa_sign_seed_test!( mldsa_44_sign_seed_test, "mldsa_44_sign_seed_test.json", MlDsa44 ); -mldsa_sign_from_seed_test!( +mldsa_sign_seed_test!( mldsa_65_sign_seed_test, "mldsa_65_sign_seed_test.json", MlDsa65 ); -mldsa_sign_from_seed_test!( +mldsa_sign_seed_test!( mldsa_87_sign_seed_test, "mldsa_87_sign_seed_test.json", MlDsa87 ); +mldsa_verify_test!( + mldsa_44_verify_test, + "mldsa_44_verify_test.json", + MlDsa44, + [ + SIGNATURE_WITH_A_REPEATED_HINT_TEST_ID, + PUBLIC_KEY_WITH_T1_COMPONENT_SET_TO_ZERO_TEST_ID + ] +); +mldsa_verify_test!(mldsa_65_verify_test, "mldsa_65_verify_test.json", MlDsa65); +mldsa_verify_test!(mldsa_87_verify_test, "mldsa_87_verify_test.json", MlDsa87);