pub use digest::dev::blobby;
use crate::EcdsaCurve;
use elliptic_curve::dev::mock_curve::MockCurve;
#[macro_export]
macro_rules! bench_ecdsa {
($name:ident, $desc:expr, $signing_key:expr, $signature:ty) => {
fn bench_sign<M: ::criterion::measurement::Measurement>(
group: &mut ::criterion::BenchmarkGroup<'_, M>,
) {
use $crate::signature::Signer as _;
let sk = core::hint::black_box($signing_key);
let msg = core::hint::black_box(b"example message");
group.bench_function("sign", |b| {
b.iter(|| {
let sig: Signature = sk.sign(msg);
core::hint::black_box(sig)
})
});
}
fn bench_verify<M: ::criterion::measurement::Measurement>(
group: &mut ::criterion::BenchmarkGroup<'_, M>,
) {
use $crate::signature::{Signer as _, Verifier as _};
let sk = $signing_key;
let vk = sk.verifying_key();
let msg = core::hint::black_box(b"example message");
let sig: Signature = core::hint::black_box(sk.sign(msg));
group.bench_function("verify", |b| b.iter(|| vk.verify(msg, &sig)));
}
fn $name(c: &mut ::criterion::Criterion) {
let mut group = c.benchmark_group($desc);
bench_sign(&mut group);
bench_verify(&mut group);
group.finish();
}
};
}
#[macro_export]
macro_rules! new_signing_test {
($curve:path, $vectors:expr) => {
use $crate::{
elliptic_curve::{
Curve, CurveArithmetic, FieldBytes, NonZeroScalar, Scalar,
array::{Array, typenum::Unsigned},
bigint::Encoding,
group::ff::PrimeField,
},
hazmat::sign_prehashed,
};
fn decode_scalar(bytes: &[u8]) -> Option<NonZeroScalar<$curve>> {
if bytes.len() == <$curve as Curve>::FieldBytesSize::USIZE {
NonZeroScalar::<$curve>::from_repr(bytes.try_into().unwrap()).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.k");
assert_eq!(
<$curve as Curve>::FieldBytesSize::USIZE,
vector.m.len(),
"invalid vector.m (must be field-sized digest)"
);
let z = FieldBytes::<$curve>::try_from(vector.m).unwrap();
let sig = sign_prehashed::<$curve>(&d, &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());
}
}
};
}
#[macro_export]
macro_rules! new_verification_test {
($curve:path, $vectors:expr) => {
use $crate::{
Signature, VerifyingKey,
elliptic_curve::{
AffinePoint, CurveArithmetic, Scalar,
array::Array,
group::ff::PrimeField,
sec1::{FromSec1Point, Sec1Point},
},
signature::hazmat::PrehashVerifier,
};
#[test]
fn ecdsa_verify_success() {
for vector in $vectors {
let q_encoded = Sec1Point::<$curve>::from_affine_coordinates(
&Array::try_from(vector.q_x).unwrap(),
&Array::try_from(vector.q_y).unwrap(),
false,
);
let q = VerifyingKey::<$curve>::from_sec1_point(&q_encoded).unwrap();
let sig = Signature::from_scalars(
Array::try_from(vector.r).unwrap(),
Array::try_from(vector.s).unwrap(),
)
.unwrap();
let result = q.verify_prehash(vector.m, &sig);
assert!(result.is_ok());
}
}
#[test]
fn ecdsa_verify_invalid_s() {
for vector in $vectors {
let q_encoded = Sec1Point::<$curve>::from_affine_coordinates(
&Array::try_from(vector.q_x).unwrap(),
&Array::try_from(vector.q_y).unwrap(),
false,
);
let q = VerifyingKey::<$curve>::from_sec1_point(&q_encoded).unwrap();
let r = Array::try_from(vector.r).unwrap();
let mut s_tweaked = Array::try_from(vector.s).unwrap();
s_tweaked[0] ^= 1;
let sig = Signature::from_scalars(r, s_tweaked).unwrap();
let result = q.verify_prehash(vector.m, &sig);
assert!(result.is_err());
}
}
};
}
#[macro_export]
macro_rules! new_wycheproof_test {
($name:ident, $test_name: expr, $curve:path) => {
use $crate::{
Signature,
elliptic_curve::sec1::Sec1Point,
signature::Verifier,
};
#[test]
fn $name() {
use $crate::elliptic_curve::{self, array::typenum::Unsigned};
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>::try_from(&data[offset..]).unwrap()
} else {
let iter = core::iter::repeat(0)
.take(point_len - data.len())
.chain(data.iter().cloned());
elliptic_curve::FieldBytes::<C>::from_iter(iter)
}
}
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 = Sec1Point::<$curve>::from_affine_coordinates(
&x, &y, false,
);
let verifying_key =
$crate::VerifyingKey::<$curve>::from_sec1_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"),
}
}
#[derive(Debug,Clone,Copy)]
struct TestVector {
pub wx: &'static [u8],
pub wy: &'static [u8],
pub msg: &'static [u8],
pub sig: &'static [u8],
pub pass_: &'static [u8],
}
impl TestVector {
pub(crate) fn pass(&self) -> bool {
match self.pass_ {
&[0] => false,
&[1] => true,
other => panic!(
concat!(
"Unsupported value for pass in `",
$test_name,
"`.\n",
"found=`{other:?}`,\n",
"expected=[0] or [1]"
),
other=other
),
}
}
}
$crate::dev::blobby::parse_into_structs!(
include_bytes!(concat!("test_vectors/data/", $test_name, ".blb"));
static TEST_VECTORS: &[
TestVector { wx, wy, msg, sig, pass_ }
];
);
for (i, tv) in TEST_VECTORS.iter().enumerate() {
if let Some(desc) = run_test(tv.wx, tv.wy, tv.msg, tv.sig, tv.pass()) {
panic!(
"\n\
Failed test №{}: {}\n\
wx:\t{:?}\n\
wy:\t{:?}\n\
msg:\t{:?}\n\
sig:\t{:?}\n\
pass:\t{}\n",
i, desc, tv.wx, tv.wy, tv.msg, tv.sig, tv.pass(),
);
}
}
}
};
}
impl EcdsaCurve for MockCurve {
const NORMALIZE_S: bool = false;
}
pub struct TestVector {
pub d: &'static [u8],
pub q_x: &'static [u8],
pub q_y: &'static [u8],
pub k: &'static [u8],
pub m: &'static [u8],
pub r: &'static [u8],
pub s: &'static [u8],
}
#[cfg(test)]
mod tests {
use super::*;
impl crate::hazmat::DigestAlgorithm for MockCurve {
type Digest = sha2::Sha256;
}
new_wycheproof_test!(wycheproof_mock, "wycheproof-mock", MockCurve);
}