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//! Most users of this library who want to sign/verify signatures will want to
18//! enable the `ecdsa` and `sha256` Cargo features.
19//!
20//! ## Signing and Verifying Signatures
21//!
22//! This example requires the `ecdsa` and `sha256` Cargo features are enabled:
23//!
24//! ```
25//! # #[cfg(all(feature = "ecdsa", feature = "sha256"))]
26//! # {
27//! use k256::{
28//! ecdsa::{SigningKey, Signature, signature::Signer},
29//! SecretKey,
30//! };
31//! use rand_core::OsRng; // requires 'getrandom' feature
32//!
33//! // Signing
34//! let signing_key = SigningKey::random(&mut OsRng); // Serialize with `::to_bytes()`
35//! let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
36//!
37//! // Note: The signature type must be annotated or otherwise inferable as
38//! // `Signer` has many impls of the `Signer` trait (for both regular and
39//! // recoverable signature types).
40//! let signature: Signature = signing_key.sign(message);
41//!
42//! // Verification
43//! use k256::{EncodedPoint, ecdsa::{VerifyingKey, signature::Verifier}};
44//!
45//! let verifying_key = VerifyingKey::from(&signing_key); // Serialize with `::to_encoded_point()`
46//! assert!(verifying_key.verify(message, &signature).is_ok());
47//! # }
48//! ```
49//!
50//! ## Recovering [`VerifyingKey`] from [`Signature`]
51//!
52//! ECDSA makes it possible to recover the public key used to verify a
53//! signature with the assistance of 2-bits of additional information.
54//!
55//! This is helpful when there is already a trust relationship for a particular
56//! key, and it's desirable to omit the full public key used to sign a
57//! particular message.
58//!
59//! One common application of signature recovery with secp256k1 is Ethereum.
60//!
61//! ### Upgrading recoverable signature code from earlier versions of `k256`
62//!
63//! The v0.12 release of `k256` contains a brand new recoverable signature API
64//! from previous releases. Functionality has been upstreamed from `k256` to a
65//! generic implementation in the [`ecdsa`](`ecdsa_core`) crate.
66//!
67//! If you previously used `k256::ecdsa::recoverable::Signature`, the old
68//! functionality now uses a "detached" [`Signature`] and [`RecoveryId`].
69//! Here is where the various functionality went:
70//!
71//! - Signing now requires the use of the [`hazmat::SignPrimitive`] trait
72//! (see examples immediately below).
73//! - Signature recovery is now implemented as methods of the [`VerifyingKey`]
74//! type (i.e. `::recover_from_*`).
75//! - Trial recovery is now defined on the [`RecoveryId`] type
76//! (i.e. `::trial_recovery_from_*`).
77//!
78//! ### Computing a signature with a [`RecoveryId`].
79//!
80//! This example shows how to compute a signature and its associated
81//! [`RecoveryId`] in a manner which is byte-for-byte compatible with
82//! Ethereum libraries, leveraging the [`SigningKey::sign_digest_recoverable`]
83//! API:
84//!
85#![cfg_attr(feature = "std", doc = "```")]
86#![cfg_attr(not(feature = "std"), doc = "```ignore")]
87//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
88//! use hex_literal::hex;
89//! use k256::ecdsa::{hazmat::SignPrimitive, RecoveryId, Signature, SigningKey};
90//! use sha2::Sha256;
91//! use sha3::{Keccak256, Digest};
92//!
93//! let signing_key = SigningKey::from_bytes(&hex!(
94//! "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
95//! ).into())?;
96//!
97//! let msg = hex!("e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080");
98//! let digest = Keccak256::new_with_prefix(msg);
99//! let (signature, recid) = signing_key.sign_digest_recoverable(digest)?;
100//!
101//! assert_eq!(
102//! signature.to_bytes().as_slice(),
103//! &hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68")
104//! );
105//!
106//! assert_eq!(recid, RecoveryId::try_from(0u8).unwrap());
107//! # Ok(())
108//! # }
109//! ```
110//!
111//! ### Recovering a [`VerifyingKey`] from a signature
112//!
113#![cfg_attr(feature = "std", doc = "```")]
114#![cfg_attr(not(feature = "std"), doc = "```ignore")]
115//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
116//! use hex_literal::hex;
117//! use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
118//! use sha3::{Keccak256, Digest};
119//! use elliptic_curve::sec1::ToEncodedPoint;
120//!
121//! let msg = b"example message";
122//!
123//! let signature = Signature::try_from(hex!(
124//! "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
125//! 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
126//! ).as_slice())?;
127//!
128//! let recid = RecoveryId::try_from(1u8)?;
129//!
130//! let recovered_key = VerifyingKey::recover_from_digest(
131//! Keccak256::new_with_prefix(msg),
132//! &signature,
133//! recid
134//! )?;
135//!
136//! let expected_key = VerifyingKey::from_sec1_bytes(
137//! &hex!("0200866db99873b09fc2fb1e3ba549b156e96d1a567e3284f5f0e859a83320cb8b")
138//! )?;
139//!
140//! assert_eq!(recovered_key, expected_key);
141//! # Ok(())
142//! # }
143//! ```
144
145pub use ecdsa_core::{
146 signature::{self, Error},
147 RecoveryId,
148};
149
150#[cfg(any(feature = "ecdsa", feature = "sha256"))]
151pub use ecdsa_core::hazmat;
152
153use crate::Secp256k1;
154
155#[cfg(feature = "ecdsa")]
156use {
157 crate::{AffinePoint, FieldBytes, Scalar},
158 ecdsa_core::hazmat::{SignPrimitive, VerifyPrimitive},
159 elliptic_curve::{ops::Invert, scalar::IsHigh, subtle::CtOption},
160};
161
162/// ECDSA/secp256k1 signature (fixed-size)
163pub type Signature = ecdsa_core::Signature<Secp256k1>;
164
165/// ECDSA/secp256k1 signature (ASN.1 DER encoded)
166pub type DerSignature = ecdsa_core::der::Signature<Secp256k1>;
167
168/// ECDSA/secp256k1 signing key
169#[cfg(feature = "ecdsa")]
170pub type SigningKey = ecdsa_core::SigningKey<Secp256k1>;
171
172/// ECDSA/secp256k1 verification key (i.e. public key)
173#[cfg(feature = "ecdsa")]
174pub type VerifyingKey = ecdsa_core::VerifyingKey<Secp256k1>;
175
176#[cfg(feature = "sha256")]
177impl hazmat::DigestPrimitive for Secp256k1 {
178 type Digest = sha2::Sha256;
179}
180
181#[cfg(feature = "ecdsa")]
182impl SignPrimitive<Secp256k1> for Scalar {
183 #[allow(non_snake_case, clippy::many_single_char_names)]
184 fn try_sign_prehashed<K>(
185 &self,
186 k: K,
187 z: &FieldBytes,
188 ) -> Result<(Signature, Option<RecoveryId>), Error>
189 where
190 K: AsRef<Self> + Invert<Output = CtOption<Self>>,
191 {
192 let (sig, recid) = hazmat::sign_prehashed::<Secp256k1, K>(self, k, z)?;
193 let is_y_odd = recid.is_y_odd() ^ bool::from(sig.s().is_high());
194 let sig_low = sig.normalize_s().unwrap_or(sig);
195 let recid = RecoveryId::new(is_y_odd, recid.is_x_reduced());
196 Ok((sig_low, Some(recid)))
197 }
198}
199
200#[cfg(feature = "ecdsa")]
201impl VerifyPrimitive<Secp256k1> for AffinePoint {
202 fn verify_prehashed(&self, z: &FieldBytes, sig: &Signature) -> Result<(), Error> {
203 if sig.s().is_high().into() {
204 return Err(Error::new());
205 }
206
207 hazmat::verify_prehashed(&self.into(), z, sig)
208 }
209}
210
211#[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))]
212mod tests {
213 mod normalize {
214 use crate::ecdsa::Signature;
215
216 // Test vectors generated using rust-secp256k1
217 #[test]
218 #[rustfmt::skip]
219 fn s_high() {
220 let sig_hi = Signature::try_from([
221 0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
222 0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
223 0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
224 0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
225 0xee, 0x2f, 0x11, 0xef, 0x8c, 0xb0, 0x0a, 0x49,
226 0x61, 0x7d, 0x13, 0x57, 0xf4, 0xd5, 0x56, 0x41,
227 0x09, 0x0a, 0x48, 0xf2, 0x01, 0xe9, 0xb9, 0x59,
228 0xc4, 0x8f, 0x6f, 0x6b, 0xec, 0x6f, 0x93, 0x8f,
229 ].as_slice()).unwrap();
230
231 let sig_lo = Signature::try_from([
232 0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
233 0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
234 0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
235 0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
236 0x11, 0xd0, 0xee, 0x10, 0x73, 0x4f, 0xf5, 0xb6,
237 0x9e, 0x82, 0xec, 0xa8, 0x0b, 0x2a, 0xa9, 0xbd,
238 0xb1, 0xa4, 0x93, 0xf4, 0xad, 0x5e, 0xe6, 0xe1,
239 0xfb, 0x42, 0xef, 0x20, 0xe3, 0xc6, 0xad, 0xb2,
240 ].as_slice()).unwrap();
241
242 let sig_normalized = sig_hi.normalize_s().unwrap();
243 assert_eq!(sig_lo, sig_normalized);
244 }
245
246 #[test]
247 fn s_low() {
248 #[rustfmt::skip]
249 let sig = Signature::try_from([
250 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
251 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
252 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
253 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
254 ].as_slice()).unwrap();
255
256 assert_eq!(sig.normalize_s(), None);
257 }
258 }
259
260 #[cfg(feature = "sha256")]
261 mod recovery {
262 use crate::{
263 ecdsa::{signature::DigestVerifier, RecoveryId, Signature, SigningKey, VerifyingKey},
264 EncodedPoint,
265 };
266 use hex_literal::hex;
267 use sha2::{Digest, Sha256};
268 use sha3::Keccak256;
269
270 /// Signature recovery test vectors
271 struct RecoveryTestVector {
272 pk: [u8; 33],
273 msg: &'static [u8],
274 sig: [u8; 64],
275 recid: RecoveryId,
276 }
277
278 const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[
279 // Recovery ID 0
280 RecoveryTestVector {
281 pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"),
282 msg: b"example message",
283 sig: hex!(
284 "ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa032
285 3971edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d52"
286 ),
287 recid: RecoveryId::new(false, false),
288 },
289 // Recovery ID 1
290 RecoveryTestVector {
291 pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"),
292 msg: b"example message",
293 sig: hex!(
294 "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
295 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
296 ),
297 recid: RecoveryId::new(true, false),
298 },
299 ];
300
301 #[test]
302 fn public_key_recovery() {
303 for vector in RECOVERY_TEST_VECTORS {
304 let digest = Sha256::new_with_prefix(vector.msg);
305 let sig = Signature::try_from(vector.sig.as_slice()).unwrap();
306 let recid = vector.recid;
307 let pk = VerifyingKey::recover_from_digest(digest, &sig, recid).unwrap();
308 assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes());
309 }
310 }
311
312 /// End-to-end example which ensures RFC6979 is implemented in the same
313 /// way as other Ethereum libraries, using HMAC-DRBG-SHA-256 for RFC6979,
314 /// and Keccak256 for hashing the message.
315 ///
316 /// Test vectors adapted from:
317 /// <https://github.com/gakonst/ethers-rs/blob/ba00f549/ethers-signers/src/wallet/private_key.rs#L197>
318 #[test]
319 fn ethereum_end_to_end_example() {
320 let signing_key = SigningKey::from_bytes(
321 &hex!("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318").into(),
322 )
323 .unwrap();
324
325 let msg = hex!(
326 "e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080"
327 );
328 let digest = Keccak256::new_with_prefix(msg);
329
330 let (sig, recid) = signing_key.sign_digest_recoverable(digest.clone()).unwrap();
331 assert_eq!(
332 sig.to_bytes().as_slice(),
333 &hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68")
334 );
335 assert_eq!(recid, RecoveryId::from_byte(0).unwrap());
336
337 let verifying_key =
338 VerifyingKey::recover_from_digest(digest.clone(), &sig, recid).unwrap();
339
340 assert_eq!(signing_key.verifying_key(), &verifying_key);
341 assert!(verifying_key.verify_digest(digest, &sig).is_ok());
342 }
343 }
344
345 mod wycheproof {
346 use crate::{EncodedPoint, Secp256k1};
347 use ecdsa_core::{signature::Verifier, Signature};
348 use elliptic_curve::generic_array::typenum::Unsigned;
349
350 #[test]
351 fn wycheproof() {
352 use blobby::Blob5Iterator;
353
354 // Build a field element but allow for too-short input (left pad with zeros)
355 // or too-long input (check excess leftmost bytes are zeros).
356 fn element_from_padded_slice<C: elliptic_curve::Curve>(
357 data: &[u8],
358 ) -> elliptic_curve::FieldBytes<C> {
359 let point_len = C::FieldBytesSize::USIZE;
360 if data.len() >= point_len {
361 let offset = data.len() - point_len;
362 for v in data.iter().take(offset) {
363 assert_eq!(*v, 0, "EcdsaVerifier: point too large");
364 }
365 elliptic_curve::FieldBytes::<C>::clone_from_slice(&data[offset..])
366 } else {
367 let iter = core::iter::repeat(0)
368 .take(point_len - data.len())
369 .chain(data.iter().cloned());
370 elliptic_curve::FieldBytes::<C>::from_exact_iter(iter).unwrap()
371 }
372 }
373
374 fn run_test(
375 wx: &[u8],
376 wy: &[u8],
377 msg: &[u8],
378 sig: &[u8],
379 pass: bool,
380 ) -> Option<&'static str> {
381 let x = element_from_padded_slice::<Secp256k1>(wx);
382 let y = element_from_padded_slice::<Secp256k1>(wy);
383 let q_encoded =
384 EncodedPoint::from_affine_coordinates(&x, &y, /* compress= */ false);
385 let verifying_key =
386 ecdsa_core::VerifyingKey::from_encoded_point(&q_encoded).unwrap();
387
388 let sig = match Signature::<Secp256k1>::from_der(sig) {
389 Ok(s) => s.normalize_s().unwrap_or(s),
390 Err(_) if !pass => return None,
391 Err(_) => return Some("failed to parse signature ASN.1"),
392 };
393
394 match verifying_key.verify(msg, &sig) {
395 Ok(_) if pass => None,
396 Ok(_) => Some("signature verify unexpectedly succeeded"),
397 Err(_) if !pass => None,
398 Err(_) => Some("signature verify failed"),
399 }
400 }
401
402 let data = include_bytes!(concat!("test_vectors/data/", "wycheproof", ".blb"));
403
404 for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() {
405 let [wx, wy, msg, sig, status] = row.unwrap();
406 let pass = match status[0] {
407 0 => false,
408 1 => true,
409 _ => panic!("invalid value for pass flag"),
410 };
411 if let Some(desc) = run_test(wx, wy, msg, sig, pass) {
412 panic!(
413 "\n\
414 Failed test №{}: {}\n\
415 wx:\t{:?}\n\
416 wy:\t{:?}\n\
417 msg:\t{:?}\n\
418 sig:\t{:?}\n\
419 pass:\t{}\n",
420 i, desc, wx, wy, msg, sig, pass,
421 );
422 }
423 }
424 }
425 }
426}