ecdsa_flow/
dev.rs

1//! Development-related functionality.
2
3use crate::hazmat::FromDigest;
4use elliptic_curve_flow::{
5    bigint::{ArrayEncoding, Encoding},
6    consts::U32,
7    dev::{MockCurve, Scalar},
8    group::ff::PrimeField,
9    subtle::{ConditionallySelectable, ConstantTimeLess},
10    Curve,
11};
12use signature_flow::digest::Digest;
13
14type UInt = <MockCurve as Curve>::UInt;
15
16impl FromDigest<MockCurve> for Scalar {
17    fn from_digest<D>(digest: D) -> Self
18    where
19        D: Digest<OutputSize = U32>,
20    {
21        let uint = UInt::from_be_bytes(digest.finalize().into());
22        let overflow = !uint.ct_lt(&MockCurve::ORDER);
23        let scalar = uint.wrapping_add(&UInt::conditional_select(
24            &UInt::ZERO,
25            &MockCurve::ORDER,
26            overflow,
27        ));
28
29        Self::from_repr(scalar.to_be_byte_array()).unwrap()
30    }
31}
32
33// TODO(tarcieri): implement full set of tests from ECDSA2VS
34// <https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/dss2/ecdsa2vs.pdf>
35
36/// ECDSA test vector
37pub struct TestVector {
38    /// Private scalar
39    pub d: &'static [u8],
40
41    /// Public key x-coordinate (`Qx`)
42    pub q_x: &'static [u8],
43
44    /// Public key y-coordinate (`Qy`)
45    pub q_y: &'static [u8],
46
47    /// Ephemeral scalar (a.k.a. nonce)
48    pub k: &'static [u8],
49
50    /// Message digest (prehashed)
51    pub m: &'static [u8],
52
53    /// Signature `r` component
54    pub r: &'static [u8],
55
56    /// Signature `s` component
57    pub s: &'static [u8],
58}
59
60/// Define ECDSA signing test.
61#[macro_export]
62#[cfg_attr(docsrs, doc(cfg(feature = "dev")))]
63macro_rules! new_signing_test {
64    ($curve:path, $vectors:expr) => {
65        use core::convert::TryInto;
66        use $crate::{
67            elliptic_curve_flow::{
68                bigint::Encoding, generic_array::GenericArray, group::ff::PrimeField, Curve,
69                ProjectiveArithmetic, Scalar,
70            },
71            hazmat::SignPrimitive,
72        };
73
74        fn decode_scalar(bytes: &[u8]) -> Option<Scalar<$curve>> {
75            if bytes.len() == <$curve as Curve>::UInt::BYTE_SIZE {
76                Scalar::<$curve>::from_repr(GenericArray::clone_from_slice(bytes)).into()
77            } else {
78                None
79            }
80        }
81
82        #[test]
83        fn ecdsa_signing() {
84            for vector in $vectors {
85                let d = decode_scalar(vector.d).expect("invalid vector.d");
86                let k = decode_scalar(vector.k).expect("invalid vector.m");
87                let z = decode_scalar(vector.m).expect("invalid vector.z");
88                let sig = d.try_sign_prehashed(&k, &z).expect("ECDSA sign failed");
89
90                assert_eq!(vector.r, sig.r().to_bytes().as_slice());
91                assert_eq!(vector.s, sig.s().to_bytes().as_slice());
92            }
93        }
94    };
95}
96
97/// Define ECDSA verification test.
98#[macro_export]
99#[cfg_attr(docsrs, doc(cfg(feature = "dev")))]
100macro_rules! new_verification_test {
101    ($curve:path, $vectors:expr) => {
102        use core::convert::TryInto;
103        use $crate::{
104            elliptic_curve_flow::{
105                generic_array::GenericArray, group::ff::PrimeField, sec1::EncodedPoint,
106                AffinePoint, ProjectiveArithmetic, Scalar,
107            },
108            hazmat::VerifyPrimitive,
109            Signature,
110        };
111
112        #[test]
113        fn ecdsa_verify_success() {
114            for vector in $vectors {
115                let q_encoded = EncodedPoint::from_affine_coordinates(
116                    GenericArray::from_slice(vector.q_x),
117                    GenericArray::from_slice(vector.q_y),
118                    false,
119                );
120
121                let q: AffinePoint<$curve> = q_encoded.decode().unwrap();
122
123                let maybe_z = Scalar::<$curve>::from_repr(GenericArray::clone_from_slice(vector.m));
124                assert!(bool::from(maybe_z.is_some()), "invalid vector.m");
125                let z = maybe_z.unwrap();
126
127                let sig = Signature::from_scalars(
128                    GenericArray::clone_from_slice(vector.r),
129                    GenericArray::clone_from_slice(vector.s),
130                )
131                .unwrap();
132
133                let result = q.verify_prehashed(&z, &sig);
134                assert!(result.is_ok());
135            }
136        }
137
138        #[test]
139        fn ecdsa_verify_invalid_s() {
140            for vector in $vectors {
141                let q_encoded = EncodedPoint::from_affine_coordinates(
142                    GenericArray::from_slice(vector.q_x),
143                    GenericArray::from_slice(vector.q_y),
144                    false,
145                );
146
147                let q: AffinePoint<$curve> = q_encoded.decode().unwrap();
148
149                let maybe_z = Scalar::<$curve>::from_repr(GenericArray::clone_from_slice(vector.m));
150                assert!(bool::from(maybe_z.is_some()), "invalid vector.m");
151                let z = maybe_z.unwrap();
152
153                // Flip a bit in `s`
154                let mut s_tweaked = GenericArray::clone_from_slice(vector.s);
155                s_tweaked[0] ^= 1;
156
157                let sig =
158                    Signature::from_scalars(GenericArray::clone_from_slice(vector.r), s_tweaked)
159                        .unwrap();
160
161                let result = q.verify_prehashed(&z, &sig);
162                assert!(result.is_err());
163            }
164        }
165
166        // TODO(tarcieri): test invalid Q, invalid r, invalid m
167    };
168}
169
170/// Define a Wycheproof verification test.
171#[macro_export]
172#[cfg_attr(docsrs, doc(cfg(feature = "dev")))]
173macro_rules! new_wycheproof_test {
174    ($name:ident, $test_name: expr, $curve:path) => {
175        use $crate::{elliptic_curve_flow::sec1::EncodedPoint, signature_flow::Verifier, Signature};
176
177        #[test]
178        fn $name() {
179            use blobby::Blob5Iterator;
180            use elliptic_curve_flow::{bigint::Encoding as _, generic_array::typenum::Unsigned};
181
182            // Build a field element but allow for too-short input (left pad with zeros)
183            // or too-long input (check excess leftmost bytes are zeros).
184            fn element_from_padded_slice<C: elliptic_curve_flow::Curve>(
185                data: &[u8],
186            ) -> elliptic_curve_flow::FieldBytes<C> {
187                let point_len = C::UInt::BYTE_SIZE;
188                if data.len() >= point_len {
189                    let offset = data.len() - point_len;
190                    for v in data.iter().take(offset) {
191                        assert_eq!(*v, 0, "EcdsaVerifier: point too large");
192                    }
193                    elliptic_curve_flow::FieldBytes::<C>::clone_from_slice(&data[offset..])
194                } else {
195                    // Provided slice is too short and needs to be padded with zeros
196                    // on the left.  Build a combined exact iterator to do this.
197                    let iter = core::iter::repeat(0)
198                        .take(point_len - data.len())
199                        .chain(data.iter().cloned());
200                    elliptic_curve_flow::FieldBytes::<C>::from_exact_iter(iter).unwrap()
201                }
202            }
203
204            fn run_test(
205                wx: &[u8],
206                wy: &[u8],
207                msg: &[u8],
208                sig: &[u8],
209                pass: bool,
210            ) -> Option<&'static str> {
211                let x = element_from_padded_slice::<$curve>(wx);
212                let y = element_from_padded_slice::<$curve>(wy);
213                let q_encoded: EncodedPoint<$curve> =
214                    EncodedPoint::from_affine_coordinates(&x, &y, /* compress= */ false);
215                let verifying_key = $crate::VerifyingKey::from_encoded_point(&q_encoded).unwrap();
216
217                let sig = match Signature::from_der(sig) {
218                    Ok(s) => s,
219                    Err(_) if !pass => return None,
220                    Err(_) => return Some("failed to parse signature ASN.1"),
221                };
222
223                match verifying_key.verify(msg, &sig) {
224                    Ok(_) if pass => None,
225                    Ok(_) => Some("signature verify unexpectedly succeeded"),
226                    Err(_) if !pass => None,
227                    Err(_) => Some("signature verify failed"),
228                }
229            }
230
231            let data = include_bytes!(concat!("test_vectors/data/", $test_name, ".blb"));
232
233            for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() {
234                let [wx, wy, msg, sig, status] = row.unwrap();
235                let pass = match status[0] {
236                    0 => false,
237                    1 => true,
238                    _ => panic!("invalid value for pass flag"),
239                };
240                if let Some(desc) = run_test(wx, wy, msg, sig, pass) {
241                    panic!(
242                        "\n\
243                                 Failed test №{}: {}\n\
244                                 wx:\t{:?}\n\
245                                 wy:\t{:?}\n\
246                                 msg:\t{:?}\n\
247                                 sig:\t{:?}\n\
248                                 pass:\t{}\n",
249                        i, desc, wx, wy, msg, sig, pass,
250                    );
251                }
252            }
253        }
254    };
255}