diff --git a/Cargo.lock b/Cargo.lock index b70f2b25..17ed3526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -779,6 +779,7 @@ name = "p521" version = "0.13.0" dependencies = [ "base16ct", + "blobby", "ecdsa", "elliptic-curve", "hex-literal", diff --git a/p521/Cargo.toml b/p521/Cargo.toml index bd3b9a68..513421dc 100644 --- a/p521/Cargo.toml +++ b/p521/Cargo.toml @@ -27,6 +27,7 @@ rand_core = { version = "0.6", optional = true, default-features = false } sha2 = { version = "0.10", optional = true, default-features = false } [dev-dependencies] +blobby = "0.3" ecdsa-core = { version = "0.16", package = "ecdsa", default-features = false, features = ["dev"] } hex-literal = "0.4" primeorder = { version = "0.13.3", features = ["dev"], path = "../primeorder" } diff --git a/p521/src/ecdsa.rs b/p521/src/ecdsa.rs index aafa4695..659aeed1 100644 --- a/p521/src/ecdsa.rs +++ b/p521/src/ecdsa.rs @@ -241,9 +241,89 @@ mod tests { ecdsa_core::new_verification_test!(NistP521, ECDSA_TEST_VECTORS); } - // TODO(tarcieri): wycheproof test vectors - // mod wycheproof { - // use crate::NistP521; - // ecdsa_core::new_wycheproof_test!(wycheproof, "wycheproof", NistP521); - // } + mod wycheproof { + use crate::{ + ecdsa::{Signature, Verifier, VerifyingKey}, + EncodedPoint, NistP521, + }; + + // TODO: use ecdsa_core::new_wycheproof_test!(wycheproof, "wycheproof", NistP521); + #[test] + fn wycheproof() { + use blobby::Blob5Iterator; + use elliptic_curve::generic_array::typenum::Unsigned; + + // Build a field element but allow for too-short input (left pad with zeros) + // or too-long input (check excess leftmost bytes are zeros). + fn element_from_padded_slice( + data: &[u8], + ) -> elliptic_curve::FieldBytes { + let point_len = C::FieldBytesSize::USIZE; + if data.len() >= point_len { + let offset = data.len() - point_len; + for v in data.iter().take(offset) { + assert_eq!(*v, 0, "EcdsaVerifier: point too large"); + } + elliptic_curve::FieldBytes::::clone_from_slice(&data[offset..]) + } else { + // Provided slice is too short and needs to be padded with zeros + // on the left. Build a combined exact iterator to do this. + let iter = core::iter::repeat(0) + .take(point_len - data.len()) + .chain(data.iter().cloned()); + elliptic_curve::FieldBytes::::from_exact_iter(iter).unwrap() + } + } + + fn run_test( + wx: &[u8], + wy: &[u8], + msg: &[u8], + sig: &[u8], + pass: bool, + ) -> Option<&'static str> { + let x = element_from_padded_slice::(wx); + let y = element_from_padded_slice::(wy); + let q_encoded = + EncodedPoint::from_affine_coordinates(&x, &y, /* compress= */ false); + let verifying_key = VerifyingKey::from_encoded_point(&q_encoded).unwrap(); + + let sig = match Signature::from_der(sig) { + Ok(s) => s, + Err(_) if !pass => return None, + Err(_) => return Some("failed to parse signature ASN.1"), + }; + + match verifying_key.verify(msg, &sig) { + Ok(_) if pass => None, + Ok(_) => Some("signature verify unexpectedly succeeded"), + Err(_) if !pass => None, + Err(_) => Some("signature verify failed"), + } + } + + let data = include_bytes!(concat!("test_vectors/data/wycheproof.blb")); + + for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() { + let [wx, wy, msg, sig, status] = row.unwrap(); + let pass = match status[0] { + 0 => false, + 1 => true, + _ => panic!("invalid value for pass flag"), + }; + if let Some(desc) = run_test(wx, wy, msg, sig, pass) { + panic!( + "\n\ + Failed test №{}: {}\n\ + wx:\t{:?}\n\ + wy:\t{:?}\n\ + msg:\t{:?}\n\ + sig:\t{:?}\n\ + pass:\t{}\n", + i, desc, wx, wy, msg, sig, pass, + ); + } + } + } + } } diff --git a/p521/src/test_vectors/data/wycheproof.blb b/p521/src/test_vectors/data/wycheproof.blb new file mode 100644 index 00000000..2ab50eab Binary files /dev/null and b/p521/src/test_vectors/data/wycheproof.blb differ