Skip to main content

ecdsa/
dev.rs

1//! Development-related functionality.
2
3// TODO(tarcieri): implement full set of tests from ECDSA2VS
4// <https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss2/ecdsa2vs.pdf>
5
6pub use digest::dev::blobby;
7
8use crate::EcdsaCurve;
9use elliptic_curve::dev::mock_curve::MockCurve;
10
11/// Write a series of `criterion`-based benchmarks for ECDSA signing and verification.
12#[macro_export]
13macro_rules! bench_ecdsa {
14    ($name:ident, $desc:expr, $signing_key:expr, $signature:ty) => {
15        fn bench_sign<M: ::criterion::measurement::Measurement>(
16            group: &mut ::criterion::BenchmarkGroup<'_, M>,
17        ) {
18            use $crate::signature::Signer as _;
19            let sk = core::hint::black_box($signing_key);
20            let msg = core::hint::black_box(b"example message");
21            group.bench_function("sign", |b| {
22                b.iter(|| {
23                    let sig: Signature = sk.sign(msg);
24                    core::hint::black_box(sig)
25                })
26            });
27        }
28
29        fn bench_verify<M: ::criterion::measurement::Measurement>(
30            group: &mut ::criterion::BenchmarkGroup<'_, M>,
31        ) {
32            use $crate::signature::{Signer as _, Verifier as _};
33            let sk = $signing_key;
34            let vk = sk.verifying_key();
35            let msg = core::hint::black_box(b"example message");
36            let sig: Signature = core::hint::black_box(sk.sign(msg));
37            group.bench_function("verify", |b| b.iter(|| vk.verify(msg, &sig)));
38        }
39
40        fn $name(c: &mut ::criterion::Criterion) {
41            let mut group = c.benchmark_group($desc);
42            bench_sign(&mut group);
43            bench_verify(&mut group);
44            group.finish();
45        }
46    };
47}
48
49/// Define ECDSA signing test.
50#[macro_export]
51macro_rules! new_signing_test {
52    ($curve:path, $vectors:expr) => {
53        use $crate::{
54            elliptic_curve::{
55                Curve, CurveArithmetic, FieldBytes, NonZeroScalar, Scalar,
56                array::{Array, typenum::Unsigned},
57                bigint::Encoding,
58                group::ff::PrimeField,
59            },
60            hazmat::sign_prehashed,
61        };
62
63        fn decode_scalar(bytes: &[u8]) -> Option<NonZeroScalar<$curve>> {
64            if bytes.len() == <$curve as Curve>::FieldBytesSize::USIZE {
65                NonZeroScalar::<$curve>::from_repr(bytes.try_into().unwrap()).into()
66            } else {
67                None
68            }
69        }
70
71        #[test]
72        fn ecdsa_signing() {
73            for vector in $vectors {
74                let d = decode_scalar(vector.d).expect("invalid vector.d");
75                let k = decode_scalar(vector.k).expect("invalid vector.k");
76
77                assert_eq!(
78                    <$curve as Curve>::FieldBytesSize::USIZE,
79                    vector.m.len(),
80                    "invalid vector.m (must be field-sized digest)"
81                );
82                let z = FieldBytes::<$curve>::try_from(vector.m).unwrap();
83                let sig = sign_prehashed::<$curve>(&d, &k, &z)
84                    .expect("ECDSA sign failed")
85                    .0;
86
87                assert_eq!(vector.r, sig.r().to_bytes().as_slice());
88                assert_eq!(vector.s, sig.s().to_bytes().as_slice());
89            }
90        }
91    };
92}
93
94/// Define ECDSA verification test.
95#[macro_export]
96macro_rules! new_verification_test {
97    ($curve:path, $vectors:expr) => {
98        use $crate::{
99            Signature, VerifyingKey,
100            elliptic_curve::{
101                AffinePoint, CurveArithmetic, Scalar,
102                array::Array,
103                group::ff::PrimeField,
104                sec1::{FromSec1Point, Sec1Point},
105            },
106            signature::hazmat::PrehashVerifier,
107        };
108
109        #[test]
110        fn ecdsa_verify_success() {
111            for vector in $vectors {
112                let q_encoded = Sec1Point::<$curve>::from_affine_coordinates(
113                    &Array::try_from(vector.q_x).unwrap(),
114                    &Array::try_from(vector.q_y).unwrap(),
115                    false,
116                );
117
118                let q = VerifyingKey::<$curve>::from_sec1_point(&q_encoded).unwrap();
119
120                let sig = Signature::from_scalars(
121                    Array::try_from(vector.r).unwrap(),
122                    Array::try_from(vector.s).unwrap(),
123                )
124                .unwrap();
125
126                let result = q.verify_prehash(vector.m, &sig);
127                assert!(result.is_ok());
128            }
129        }
130
131        #[test]
132        fn ecdsa_verify_invalid_s() {
133            for vector in $vectors {
134                let q_encoded = Sec1Point::<$curve>::from_affine_coordinates(
135                    &Array::try_from(vector.q_x).unwrap(),
136                    &Array::try_from(vector.q_y).unwrap(),
137                    false,
138                );
139
140                let q = VerifyingKey::<$curve>::from_sec1_point(&q_encoded).unwrap();
141                let r = Array::try_from(vector.r).unwrap();
142
143                // Flip a bit in `s`
144                let mut s_tweaked = Array::try_from(vector.s).unwrap();
145                s_tweaked[0] ^= 1;
146
147                let sig = Signature::from_scalars(r, s_tweaked).unwrap();
148                let result = q.verify_prehash(vector.m, &sig);
149                assert!(result.is_err());
150            }
151        }
152
153        // TODO(tarcieri): test invalid Q, invalid r, invalid m
154    };
155}
156
157/// Define a Wycheproof verification test.
158#[macro_export]
159macro_rules! new_wycheproof_test {
160    ($name:ident, $test_name: expr, $curve:path) => {
161        use $crate::{
162            Signature,
163            elliptic_curve::sec1::Sec1Point,
164            signature::Verifier,
165        };
166
167        #[test]
168        fn $name() {
169            use $crate::elliptic_curve::{self, array::typenum::Unsigned};
170
171            // Build a field element but allow for too-short input (left pad with zeros)
172            // or too-long input (check excess leftmost bytes are zeros).
173            fn element_from_padded_slice<C: elliptic_curve::Curve>(
174                data: &[u8],
175            ) -> elliptic_curve::FieldBytes<C> {
176                let point_len = C::FieldBytesSize::USIZE;
177                if data.len() >= point_len {
178                    let offset = data.len() - point_len;
179                    for v in data.iter().take(offset) {
180                        assert_eq!(*v, 0, "EcdsaVerifier: point too large");
181                    }
182                    elliptic_curve::FieldBytes::<C>::try_from(&data[offset..]).unwrap()
183                } else {
184                    // Provided slice is too short and needs to be padded with zeros
185                    // on the left.  Build a combined exact iterator to do this.
186                    let iter = core::iter::repeat(0)
187                        .take(point_len - data.len())
188                        .chain(data.iter().cloned());
189                    elliptic_curve::FieldBytes::<C>::from_iter(iter)
190                }
191            }
192
193            fn run_test(
194                wx: &[u8],
195                wy: &[u8],
196                msg: &[u8],
197                sig: &[u8],
198                pass: bool,
199            ) -> Option<&'static str> {
200                let x = element_from_padded_slice::<$curve>(wx);
201                let y = element_from_padded_slice::<$curve>(wy);
202                let q_encoded = Sec1Point::<$curve>::from_affine_coordinates(
203                    &x, &y, /* compress= */ false,
204                );
205                let verifying_key =
206                    $crate::VerifyingKey::<$curve>::from_sec1_point(&q_encoded).unwrap();
207
208                let sig = match Signature::from_der(sig) {
209                    Ok(s) => s,
210                    Err(_) if !pass => return None,
211                    Err(_) => return Some("failed to parse signature ASN.1"),
212                };
213
214                match verifying_key.verify(msg, &sig) {
215                    Ok(_) if pass => None,
216                    Ok(_) => Some("signature verify unexpectedly succeeded"),
217                    Err(_) if !pass => None,
218                    Err(_) => Some("signature verify failed"),
219                }
220            }
221
222            #[derive(Debug,Clone,Copy)]
223            struct TestVector {
224                /// X coordinates of the public key
225                pub wx: &'static [u8],
226                /// Y coordinates of the public key
227                pub wy: &'static [u8],
228                /// Payload to verify
229                pub msg: &'static [u8],
230                /// Der encoding of the signature
231                pub sig: &'static [u8],
232                /// Whether the signature should verify (`[1]`) or fail (`[0]`)
233                pub pass_: &'static [u8],
234            }
235
236            impl TestVector {
237                pub(crate) fn pass(&self) -> bool {
238                    match self.pass_ {
239                        &[0] => false,
240                        &[1] => true,
241                        other => panic!(
242                            concat!(
243                                "Unsupported value for pass in `",
244                                $test_name,
245                                "`.\n",
246                                "found=`{other:?}`,\n",
247                                "expected=[0] or [1]"
248                            ),
249                            other=other
250                        ),
251                    }
252                }
253            }
254
255            $crate::dev::blobby::parse_into_structs!(
256                include_bytes!(concat!("test_vectors/data/", $test_name, ".blb"));
257                static TEST_VECTORS: &[
258                    TestVector { wx, wy, msg, sig, pass_ }
259                ];
260            );
261
262            for (i, tv) in TEST_VECTORS.iter().enumerate() {
263                if let Some(desc) = run_test(tv.wx, tv.wy, tv.msg, tv.sig, tv.pass()) {
264                    panic!(
265                        "\n\
266                                 Failed test №{}: {}\n\
267                                 wx:\t{:?}\n\
268                                 wy:\t{:?}\n\
269                                 msg:\t{:?}\n\
270                                 sig:\t{:?}\n\
271                                 pass:\t{}\n",
272                        i, desc, tv.wx, tv.wy, tv.msg, tv.sig, tv.pass(),
273                    );
274                }
275            }
276        }
277    };
278}
279
280impl EcdsaCurve for MockCurve {
281    const NORMALIZE_S: bool = false;
282}
283
284/// ECDSA test vector
285#[derive(Clone, Copy, Debug)]
286pub struct TestVector {
287    /// Private scalar
288    pub d: &'static [u8],
289
290    /// Public key x-coordinate (`Qx`)
291    pub q_x: &'static [u8],
292
293    /// Public key y-coordinate (`Qy`)
294    pub q_y: &'static [u8],
295
296    /// Ephemeral scalar (a.k.a. nonce)
297    pub k: &'static [u8],
298
299    /// Message digest (prehashed)
300    pub m: &'static [u8],
301
302    /// Signature `r` component
303    pub r: &'static [u8],
304
305    /// Signature `s` component
306    pub s: &'static [u8],
307}
308
309#[cfg(test)]
310#[allow(clippy::integer_division_remainder_used, reason = "tests")]
311mod tests {
312    use super::*;
313
314    impl crate::DigestAlgorithm for MockCurve {
315        type Digest = sha2::Sha256;
316    }
317
318    new_wycheproof_test!(wycheproof_mock, "wycheproof-mock", MockCurve);
319}