Skip to main content

k256/
ecdsa.rs

1//! Elliptic Curve Digital Signature Algorithm (ECDSA).
2//!
3//! This module contains support for computing and verifying ECDSA signatures.
4//! To use it, you will need to enable one of the two following Cargo features:
5//!
6//! - `ecdsa-core`: provides only the [`Signature`] type (which represents an
7//!   ECDSA/secp256k1 signature). Does not require the `arithmetic` feature.
8//!   This is useful for 3rd-party crates which wish to use the `Signature`
9//!   type for interoperability purposes (particularly in conjunction with the
10//!   [`signature::Signer`] trait). Example use cases for this include other
11//!   software implementations of ECDSA/secp256k1 and wrappers for cloud KMS
12//!   services or hardware devices (HSM or crypto hardware wallet).
13//! - `ecdsa`: provides `ecdsa-core` features plus the [`SigningKey`] and
14//!   [`VerifyingKey`] types which natively implement ECDSA/secp256k1 signing and
15//!   verification.
16//!
17//! ## Signing/Verification Example
18//!
19#![cfg_attr(all(feature = "ecdsa", feature = "getrandom"), doc = "```")]
20#![cfg_attr(not(all(feature = "ecdsa", feature = "getrandom")), doc = "```ignore")]
21//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
22//! // NOTE: requires the `ecdsa` and `getrandom` crate features are enabled
23//! use k256::{
24//!     ecdsa::{SigningKey, Signature, signature::Signer},
25//!     elliptic_curve::Generate,
26//!     SecretKey,
27//! };
28//!
29//! // Signing
30//! let signing_key = SigningKey::generate(); // Serialize with `::to_bytes()`
31//! let verifying_key_bytes = signing_key.verifying_key().to_sec1_point(true); // 33-bytes
32//!
33//! let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
34//! let signature: Signature = signing_key.sign(message);
35//!
36//! // Verification
37//! use k256::{Sec1Point, ecdsa::{VerifyingKey, signature::Verifier}};
38//!
39//! let verifying_key = VerifyingKey::from_sec1_bytes(verifying_key_bytes.as_ref())?;
40//! verifying_key.verify(message, &signature)?;
41//! # Ok(())
42//! # }
43//! ```
44//!
45//! ## Recovering [`VerifyingKey`] from [`Signature`]
46//!
47//! ECDSA makes it possible to recover the public key used to verify a
48//! signature with the assistance of 2-bits of additional information.
49//!
50//! This is helpful when there is already a trust relationship for a particular
51//! key, and it's desirable to omit the full public key used to sign a
52//! particular message.
53//!
54//! One common application of signature recovery with secp256k1 is Ethereum.
55//!
56//! ### Recovering a [`VerifyingKey`] from a signature
57//!
58//! ```
59//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
60//! use hex_literal::hex;
61//! use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
62//! use sha3::{Keccak256, Digest};
63//! use elliptic_curve::sec1::ToSec1Point;
64//!
65//! let msg = b"example message";
66//!
67//! let signature = Signature::try_from(hex!(
68//!     "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
69//!      35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
70//! ).as_slice())?;
71//!
72//! let recid = RecoveryId::try_from(1u8)?;
73//!
74//! let recovered_key = VerifyingKey::recover_from_digest(
75//!     Keccak256::new_with_prefix(msg),
76//!     &signature,
77//!     recid
78//! )?;
79//!
80//! let expected_key = VerifyingKey::from_sec1_bytes(
81//!     &hex!("0200866db99873b09fc2fb1e3ba549b156e96d1a567e3284f5f0e859a83320cb8b")
82//! )?;
83//!
84//! assert_eq!(recovered_key, expected_key);
85//! # Ok(())
86//! # }
87//! ```
88
89pub use ecdsa_core::{
90    EcdsaCurve, RecoveryId,
91    signature::{self, Error},
92};
93
94use crate::Secp256k1;
95#[cfg(feature = "sha256")]
96use ecdsa_core::DigestAlgorithm;
97
98/// ECDSA/secp256k1 signature (fixed-size)
99pub type Signature = ecdsa_core::Signature<Secp256k1>;
100
101/// ECDSA/secp256k1 signature (ASN.1 DER encoded)
102pub type DerSignature = ecdsa_core::der::Signature<Secp256k1>;
103
104impl EcdsaCurve for Secp256k1 {
105    const NORMALIZE_S: bool = true;
106}
107
108/// ECDSA/secp256k1 signing key
109#[cfg(feature = "ecdsa")]
110pub type SigningKey = ecdsa_core::SigningKey<Secp256k1>;
111
112/// ECDSA/secp256k1 verification key (i.e. public key)
113#[cfg(feature = "ecdsa")]
114pub type VerifyingKey = ecdsa_core::VerifyingKey<Secp256k1>;
115
116#[cfg(feature = "sha256")]
117impl DigestAlgorithm for Secp256k1 {
118    type Digest = sha2::Sha256;
119}
120
121#[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))]
122mod tests {
123    mod normalize {
124        use crate::ecdsa::Signature;
125
126        // Test vectors generated using rust-secp256k1
127        #[test]
128        #[rustfmt::skip]
129        fn s_high() {
130            let sig_hi = Signature::try_from([
131                0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
132                0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
133                0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
134                0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
135                0xee, 0x2f, 0x11, 0xef, 0x8c, 0xb0, 0x0a, 0x49,
136                0x61, 0x7d, 0x13, 0x57, 0xf4, 0xd5, 0x56, 0x41,
137                0x09, 0x0a, 0x48, 0xf2, 0x01, 0xe9, 0xb9, 0x59,
138                0xc4, 0x8f, 0x6f, 0x6b, 0xec, 0x6f, 0x93, 0x8f,
139            ].as_slice()).unwrap();
140
141            let sig_lo = Signature::try_from([
142                0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
143                0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
144                0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
145                0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
146                0x11, 0xd0, 0xee, 0x10, 0x73, 0x4f, 0xf5, 0xb6,
147                0x9e, 0x82, 0xec, 0xa8, 0x0b, 0x2a, 0xa9, 0xbd,
148                0xb1, 0xa4, 0x93, 0xf4, 0xad, 0x5e, 0xe6, 0xe1,
149                0xfb, 0x42, 0xef, 0x20, 0xe3, 0xc6, 0xad, 0xb2,
150            ].as_slice()).unwrap();
151
152            let sig_normalized = sig_hi.normalize_s();
153            assert_eq!(sig_lo, sig_normalized);
154        }
155
156        #[test]
157        fn s_low() {
158            #[rustfmt::skip]
159            let sig = Signature::try_from([
160                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
162                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
163                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
164            ].as_slice()).unwrap();
165
166            assert_eq!(sig.normalize_s(), sig);
167        }
168    }
169
170    #[cfg(feature = "sha256")]
171    mod recovery {
172        use crate::{
173            Sec1Point,
174            ecdsa::{
175                RecoveryId, Signature, SigningKey, VerifyingKey, signature::hazmat::PrehashVerifier,
176            },
177        };
178        use hex_literal::hex;
179        use sha2::{Digest, Sha256};
180        use sha3::Keccak256;
181
182        /// Signature recovery test vectors
183        struct RecoveryTestVector {
184            pk: [u8; 33],
185            msg: &'static [u8],
186            sig: [u8; 64],
187            recid: RecoveryId,
188        }
189
190        const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[
191            // Recovery ID 0
192            RecoveryTestVector {
193                pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"),
194                msg: b"example message",
195                sig: hex!(
196                    "ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa032
197                     3971edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d52"
198                ),
199                recid: RecoveryId::new(false, false),
200            },
201            // Recovery ID 1
202            RecoveryTestVector {
203                pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"),
204                msg: b"example message",
205                sig: hex!(
206                    "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
207                     35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
208                ),
209                recid: RecoveryId::new(true, false),
210            },
211        ];
212
213        #[test]
214        fn public_key_recovery() {
215            for vector in RECOVERY_TEST_VECTORS {
216                let digest = Sha256::new_with_prefix(vector.msg);
217                let sig = Signature::try_from(vector.sig.as_slice()).unwrap();
218                let recid = vector.recid;
219                let pk = VerifyingKey::recover_from_digest(digest, &sig, recid).unwrap();
220                assert_eq!(&vector.pk[..], Sec1Point::from(&pk).as_bytes());
221            }
222        }
223
224        /// End-to-end example which ensures RFC6979 is implemented in the same
225        /// way as other Ethereum libraries, using HMAC-DRBG-SHA-256 for RFC6979,
226        /// and Keccak256 for hashing the message.
227        ///
228        /// Test vectors adapted from:
229        /// <https://github.com/gakonst/ethers-rs/blob/ba00f549/ethers-signers/src/wallet/private_key.rs#L197>
230        #[test]
231        fn ethereum_end_to_end_example() {
232            let signing_key = SigningKey::from_bytes(
233                &hex!("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318").into(),
234            )
235            .unwrap();
236
237            let msg = hex!(
238                "e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080"
239            );
240
241            let digest = Keccak256::new_with_prefix(msg);
242            let (sig, recid) = signing_key.sign_digest_recoverable(digest.clone());
243            assert_eq!(
244                sig.to_bytes().as_slice(),
245                &hex!(
246                    "c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68"
247                )
248            );
249            assert_eq!(recid, RecoveryId::from_byte(0).unwrap());
250
251            let verifying_key =
252                VerifyingKey::recover_from_digest(digest.clone(), &sig, recid).unwrap();
253
254            assert_eq!(signing_key.verifying_key(), &verifying_key);
255            assert!(
256                verifying_key
257                    .verify_prehash(&digest.finalize(), &sig)
258                    .is_ok()
259            );
260        }
261    }
262
263    mod wycheproof {
264        use crate::{Sec1Point, Secp256k1};
265        use ecdsa_core::{Signature, signature::Verifier};
266        use elliptic_curve::array::typenum::Unsigned;
267
268        #[test]
269        fn wycheproof() {
270            // Build a field element but allow for too-short input (left pad with zeros)
271            // or too-long input (check excess leftmost bytes are zeros).
272            fn element_from_padded_slice<C: elliptic_curve::Curve>(
273                data: &[u8],
274            ) -> elliptic_curve::FieldBytes<C> {
275                let point_len = C::FieldBytesSize::USIZE;
276                if data.len() >= point_len {
277                    let offset = data.len() - point_len;
278                    for v in data.iter().take(offset) {
279                        assert_eq!(*v, 0, "EcdsaVerifier: point too large");
280                    }
281                    elliptic_curve::FieldBytes::<C>::try_from(&data[offset..])
282                        .expect("length mismatch")
283                } else {
284                    let mut point = elliptic_curve::FieldBytes::<C>::default();
285                    let offset = point_len - data.len();
286                    point[offset..].copy_from_slice(data);
287                    point
288                }
289            }
290
291            fn run_test(
292                wx: &[u8],
293                wy: &[u8],
294                msg: &[u8],
295                sig: &[u8],
296                pass: bool,
297                p1363_sig: bool,
298            ) -> Option<&'static str> {
299                let x = element_from_padded_slice::<Secp256k1>(wx);
300                let y = element_from_padded_slice::<Secp256k1>(wy);
301                let q_encoded =
302                    Sec1Point::from_affine_coordinates(&x, &y, /* compress= */ false);
303                let verifying_key = ecdsa_core::VerifyingKey::from_sec1_point(&q_encoded).unwrap();
304
305                let sig = if p1363_sig {
306                    match Signature::<Secp256k1>::from_slice(sig) {
307                        Ok(s) => s.normalize_s(),
308                        Err(_) if !pass => return None,
309                        Err(_) => return Some("failed to parse signature P1363"),
310                    }
311                } else {
312                    match Signature::<Secp256k1>::from_der(sig) {
313                        Ok(s) => s.normalize_s(),
314                        Err(_) if !pass => return None,
315                        Err(_) => return Some("failed to parse signature ASN.1"),
316                    }
317                };
318
319                match verifying_key.verify(msg, &sig) {
320                    Ok(_) if pass => None,
321                    Ok(_) => Some("signature verify unexpectedly succeeded"),
322                    Err(_) if !pass => None,
323                    Err(_) => Some("signature verify failed"),
324                }
325            }
326
327            #[derive(Debug, Clone, Copy)]
328            struct TestVector {
329                /// X coordinates of the public key
330                pub wx: &'static [u8],
331                /// Y coordinates of the public key
332                pub wy: &'static [u8],
333                /// Payload to verify
334                pub msg: &'static [u8],
335                /// Der encoding of the signature
336                pub sig: &'static [u8],
337                /// Whether the signature should verify (`[1]`) or fail (`[0]`)
338                pub pass_: &'static [u8],
339            }
340
341            impl TestVector {
342                pub fn pass(&self) -> bool {
343                    self.pass_[0] == 1
344                }
345            }
346
347            macro_rules! run_test {
348                ($blob: expr, $p1363_sig: expr) => {
349                    {
350                        ecdsa_core::dev::blobby::parse_into_structs!(
351                            include_bytes!($blob);
352                            static TEST_VECTORS: &[
353                                TestVector { wx, wy, msg, sig, pass_ }
354                            ];
355                        );
356
357
358                        for (i, tv) in TEST_VECTORS.iter().enumerate() {
359                            if let Some(desc) = run_test(tv.wx, tv.wy, tv.msg, tv.sig, tv.pass(), $p1363_sig) {
360                                panic!(
361                                    "\n\
362                                             Failed test №{}: {}\n\
363                                             wx:\t{:?}\n\
364                                             wy:\t{:?}\n\
365                                             msg:\t{:?}\n\
366                                             sig:\t{:?}\n\
367                                             pass:\t{}\n",
368                                    i,
369                                    desc,
370                                    hex::encode(tv.wx),
371                                    hex::encode(tv.wy),
372                                    hex::encode(tv.msg),
373                                    hex::encode(tv.sig),
374                                    tv.pass(),
375                                );
376                            }
377                        }
378                    }
379                }
380            }
381
382            run_test!(concat!("test_vectors/data/", "wycheproof", ".blb"), false);
383            run_test!(
384                concat!("test_vectors/data/", "wycheproof-p1316", ".blb"),
385                true
386            );
387        }
388    }
389}