| //! Development-related functionality. |
| |
| // TODO(tarcieri): implement full set of tests from ECDSA2VS |
| // <https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss2/ecdsa2vs.pdf> |
| |
| /// ECDSA test vector |
| pub struct TestVector { |
| /// Private scalar |
| pub d: &'static [u8], |
| |
| /// Public key x-coordinate (`Qx`) |
| pub q_x: &'static [u8], |
| |
| /// Public key y-coordinate (`Qy`) |
| pub q_y: &'static [u8], |
| |
| /// Ephemeral scalar (a.k.a. nonce) |
| pub k: &'static [u8], |
| |
| /// Message digest (prehashed) |
| pub m: &'static [u8], |
| |
| /// Signature `r` component |
| pub r: &'static [u8], |
| |
| /// Signature `s` component |
| pub s: &'static [u8], |
| } |
| |
| /// Define ECDSA signing test. |
| #[macro_export] |
| macro_rules! new_signing_test { |
| ($curve:path, $vectors:expr) => { |
| use $crate::{ |
| elliptic_curve::{ |
| bigint::Encoding, |
| generic_array::{typenum::Unsigned, GenericArray}, |
| group::ff::PrimeField, |
| Curve, CurveArithmetic, Scalar, |
| }, |
| hazmat::SignPrimitive, |
| }; |
| |
| fn decode_scalar(bytes: &[u8]) -> Option<Scalar<$curve>> { |
| if bytes.len() == <$curve as Curve>::FieldBytesSize::USIZE { |
| Scalar::<$curve>::from_repr(GenericArray::clone_from_slice(bytes)).into() |
| } else { |
| None |
| } |
| } |
| |
| #[test] |
| fn ecdsa_signing() { |
| for vector in $vectors { |
| let d = decode_scalar(vector.d).expect("invalid vector.d"); |
| let k = decode_scalar(vector.k).expect("invalid vector.m"); |
| |
| assert_eq!( |
| <$curve as Curve>::FieldBytesSize::USIZE, |
| vector.m.len(), |
| "invalid vector.m (must be field-sized digest)" |
| ); |
| let z = GenericArray::clone_from_slice(vector.m); |
| let sig = d.try_sign_prehashed(k, &z).expect("ECDSA sign failed").0; |
| |
| assert_eq!(vector.r, sig.r().to_bytes().as_slice()); |
| assert_eq!(vector.s, sig.s().to_bytes().as_slice()); |
| } |
| } |
| }; |
| } |
| |
| /// Define ECDSA verification test. |
| #[macro_export] |
| macro_rules! new_verification_test { |
| ($curve:path, $vectors:expr) => { |
| use $crate::{ |
| elliptic_curve::{ |
| generic_array::GenericArray, |
| group::ff::PrimeField, |
| sec1::{EncodedPoint, FromEncodedPoint}, |
| AffinePoint, CurveArithmetic, Scalar, |
| }, |
| hazmat::VerifyPrimitive, |
| Signature, |
| }; |
| |
| #[test] |
| fn ecdsa_verify_success() { |
| for vector in $vectors { |
| let q_encoded = EncodedPoint::<$curve>::from_affine_coordinates( |
| GenericArray::from_slice(vector.q_x), |
| GenericArray::from_slice(vector.q_y), |
| false, |
| ); |
| |
| let q = AffinePoint::<$curve>::from_encoded_point(&q_encoded).unwrap(); |
| let z = GenericArray::clone_from_slice(vector.m); |
| |
| let sig = Signature::from_scalars( |
| GenericArray::clone_from_slice(vector.r), |
| GenericArray::clone_from_slice(vector.s), |
| ) |
| .unwrap(); |
| |
| let result = q.verify_prehashed(&z, &sig); |
| assert!(result.is_ok()); |
| } |
| } |
| |
| #[test] |
| fn ecdsa_verify_invalid_s() { |
| for vector in $vectors { |
| let q_encoded = EncodedPoint::<$curve>::from_affine_coordinates( |
| GenericArray::from_slice(vector.q_x), |
| GenericArray::from_slice(vector.q_y), |
| false, |
| ); |
| |
| let q = AffinePoint::<$curve>::from_encoded_point(&q_encoded).unwrap(); |
| let z = GenericArray::clone_from_slice(vector.m); |
| |
| // Flip a bit in `s` |
| let mut s_tweaked = GenericArray::clone_from_slice(vector.s); |
| s_tweaked[0] ^= 1; |
| |
| let sig = |
| Signature::from_scalars(GenericArray::clone_from_slice(vector.r), s_tweaked) |
| .unwrap(); |
| |
| let result = q.verify_prehashed(&z, &sig); |
| assert!(result.is_err()); |
| } |
| } |
| |
| // TODO(tarcieri): test invalid Q, invalid r, invalid m |
| }; |
| } |
| |
| /// Define a Wycheproof verification test. |
| #[macro_export] |
| macro_rules! new_wycheproof_test { |
| ($name:ident, $test_name: expr, $curve:path) => { |
| use $crate::{ |
| elliptic_curve::{bigint::Integer, sec1::EncodedPoint}, |
| signature::Verifier, |
| Signature, |
| }; |
| |
| #[test] |
| fn $name() { |
| use blobby::Blob5Iterator; |
| use elliptic_curve::{bigint::Encoding as _, 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<C: elliptic_curve::Curve>( |
| data: &[u8], |
| ) -> elliptic_curve::FieldBytes<C> { |
| 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::<C>::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::<C>::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::<$curve>(wx); |
| let y = element_from_padded_slice::<$curve>(wy); |
| let q_encoded = EncodedPoint::<$curve>::from_affine_coordinates( |
| &x, &y, /* compress= */ false, |
| ); |
| let verifying_key = |
| $crate::VerifyingKey::<$curve>::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/", $test_name, ".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, |
| ); |
| } |
| } |
| } |
| }; |
| } |