|
14 | 14 | //! [`VerifyingKey`] types which natively implement ECDSA/secp256k1 signing and |
15 | 15 | //! verification. |
16 | 16 | //! |
17 | | -//! Additionally, this crate contains support for computing ECDSA signatures |
18 | | -//! using either the SHA-256 (standard) or Keccak-256 (Ethereum) digest |
19 | | -//! functions, which are gated under the following Cargo features: |
20 | | -//! |
21 | | -//! - `sha256`: compute signatures using NIST's standard SHA-256 digest |
22 | | -//! function. Unless you are computing signatures for Ethereum, this is |
23 | | -//! almost certainly what you want. |
24 | | -//! - `keccak256`: compute signatures using the Keccak-256 digest function, |
25 | | -//! an incompatible variant of the SHA-3 algorithm used exclusively by |
26 | | -//! Ethereum. |
27 | | -//! |
28 | 17 | //! Most users of this library who want to sign/verify signatures will want to |
29 | 18 | //! enable the `ecdsa` and `sha256` Cargo features. |
30 | 19 | //! |
31 | | -//! ## Ethereum Support |
32 | | -//! |
33 | | -//! This crate natively supports Ethereum-style recoverable signatures. |
34 | | -//! Please see the toplevel documentation of the [`recoverable`] module |
35 | | -//! for more information. |
36 | | -//! |
37 | | -//! ## Signing/Verification Example |
| 20 | +//! ## Signing and Verifying |
38 | 21 | //! |
39 | 22 | //! This example requires the `ecdsa` and `sha256` Cargo features are enabled: |
40 | 23 | //! |
|
63 | 46 | //! assert!(verifying_key.verify(message, &signature).is_ok()); |
64 | 47 | //! # } |
65 | 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 | +//! ### Computing a signature with a [`RecoveryId`]. |
| 62 | +//! |
| 63 | +//! This example shows how to compute a signature and its associated |
| 64 | +//! [`RecoveryId`] in a manner which is byte-for-byte compatible with |
| 65 | +//! Ethereum libraries: |
| 66 | +//! |
| 67 | +#![cfg_attr(feature = "std", doc = "```")] |
| 68 | +#![cfg_attr(not(feature = "std"), doc = "```ignore")] |
| 69 | +//! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 70 | +//! use hex_literal::hex; |
| 71 | +//! use k256::ecdsa::{hazmat::SignPrimitive, RecoveryId, Signature, SigningKey}; |
| 72 | +//! use sha2::Sha256; |
| 73 | +//! use sha3::{Keccak256, Digest}; |
| 74 | +//! |
| 75 | +//! let signing_key = SigningKey::from_bytes(&hex!( |
| 76 | +//! "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" |
| 77 | +//! ))?; |
| 78 | +//! |
| 79 | +//! let msg = hex!("e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080"); |
| 80 | +//! let digest = Keccak256::digest(msg); |
| 81 | +//! let (signature, recid) = signing_key |
| 82 | +//! .as_nonzero_scalar() |
| 83 | +//! .try_sign_prehashed_rfc6979::<Sha256>(digest, b"")?; |
| 84 | +//! |
| 85 | +//! assert_eq!( |
| 86 | +//! signature.to_bytes().as_slice(), |
| 87 | +//! &hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68") |
| 88 | +//! ); |
| 89 | +//! |
| 90 | +//! assert_eq!(recid, RecoveryId::try_from(0u8).ok()); |
| 91 | +//! # Ok(()) |
| 92 | +//! # } |
| 93 | +//! ``` |
| 94 | +//! |
| 95 | +//! ### Recovering a [`VerifyingKey`] from a signature |
| 96 | +//! |
| 97 | +#![cfg_attr(feature = "std", doc = "```ignore")] |
| 98 | +#![cfg_attr(not(feature = "std"), doc = "```ignore")] |
| 99 | +//! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 100 | +//! use hex_literal::hex; |
| 101 | +//! use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; |
| 102 | +//! use sha3::{Keccak256, Digest}; |
| 103 | +//! use elliptic_curve::sec1::ToEncodedPoint; |
| 104 | +//! |
| 105 | +//! let msg = b"example message"; |
| 106 | +//! let signature = Signature::try_from(hex!( |
| 107 | +//! "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51 |
| 108 | +//! 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb" |
| 109 | +//! ).as_slice())?; |
| 110 | +//! let recid = RecoveryId::try_from(1u8)?; |
| 111 | +//! |
| 112 | +//! let recovered_key = VerifyingKey::recover_from_digest( |
| 113 | +//! Keccak256::new_with_prefix(msg), |
| 114 | +//! &signature, |
| 115 | +//! recid |
| 116 | +//! )?; |
| 117 | +//! |
| 118 | +//! let expected_key = VerifyingKey::from_sec1_bytes( |
| 119 | +//! &hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2") |
| 120 | +//! )?; |
| 121 | +//! |
| 122 | +//! assert_eq!(recovered_key, expected_key); |
| 123 | +//! # Ok(()) |
| 124 | +//! # } |
| 125 | +//! ``` |
66 | 126 |
|
67 | | -pub mod recoverable; |
68 | | - |
69 | | -#[cfg(feature = "ecdsa")] |
70 | | -mod normalize; |
71 | | -#[cfg(feature = "ecdsa")] |
72 | | -mod sign; |
73 | | -#[cfg(feature = "ecdsa")] |
74 | | -mod verify; |
75 | | - |
76 | | -pub use ecdsa_core::signature::{self, Error}; |
77 | | - |
78 | | -#[cfg(feature = "digest")] |
79 | | -pub use ecdsa_core::signature::digest; |
| 127 | +use crate::Secp256k1; |
| 128 | +pub use ecdsa_core::{ |
| 129 | + signature::{self, Error}, |
| 130 | + RecoveryId, |
| 131 | +}; |
80 | 132 |
|
81 | 133 | #[cfg(feature = "ecdsa")] |
82 | | -pub use self::{sign::SigningKey, verify::VerifyingKey}; |
83 | | - |
84 | | -use crate::Secp256k1; |
| 134 | +use { |
| 135 | + crate::{AffinePoint, FieldBytes, ProjectivePoint, Scalar, U256}, |
| 136 | + core::borrow::Borrow, |
| 137 | + ecdsa_core::hazmat::{SignPrimitive, VerifyPrimitive}, |
| 138 | + elliptic_curve::{ |
| 139 | + ops::{Invert, Reduce}, |
| 140 | + subtle::CtOption, |
| 141 | + IsHigh, |
| 142 | + }, |
| 143 | +}; |
85 | 144 |
|
86 | 145 | /// ECDSA/secp256k1 signature (fixed-size) |
87 | 146 | pub type Signature = ecdsa_core::Signature<Secp256k1>; |
88 | 147 |
|
89 | 148 | /// ECDSA/secp256k1 signature (ASN.1 DER encoded) |
90 | 149 | pub type DerSignature = ecdsa_core::der::Signature<Secp256k1>; |
91 | 150 |
|
| 151 | +/// ECDSA/secp256k1 signing key |
| 152 | +#[cfg(feature = "ecdsa")] |
| 153 | +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] |
| 154 | +pub type SigningKey = ecdsa_core::SigningKey<Secp256k1>; |
| 155 | + |
| 156 | +/// ECDSA/secp256k1 verification key (i.e. public key) |
| 157 | +#[cfg(feature = "ecdsa")] |
| 158 | +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] |
| 159 | +pub type VerifyingKey = ecdsa_core::VerifyingKey<Secp256k1>; |
| 160 | + |
92 | 161 | #[cfg(feature = "sha256")] |
93 | 162 | #[cfg_attr(docsrs, doc(cfg(feature = "sha256")))] |
94 | 163 | impl ecdsa_core::hazmat::DigestPrimitive for Secp256k1 { |
95 | 164 | type Digest = sha2::Sha256; |
96 | 165 | } |
97 | 166 |
|
| 167 | +#[cfg(feature = "ecdsa")] |
| 168 | +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] |
| 169 | +impl SignPrimitive<Secp256k1> for Scalar { |
| 170 | + #[allow(non_snake_case, clippy::many_single_char_names)] |
| 171 | + fn try_sign_prehashed<K>( |
| 172 | + &self, |
| 173 | + ephemeral_scalar: K, |
| 174 | + z: FieldBytes, |
| 175 | + ) -> Result<(Signature, Option<RecoveryId>), Error> |
| 176 | + where |
| 177 | + K: Borrow<Scalar> + Invert<Output = CtOption<Scalar>>, |
| 178 | + { |
| 179 | + let z = <Self as Reduce<U256>>::from_be_bytes_reduced(z); |
| 180 | + let k_inverse = ephemeral_scalar.invert(); |
| 181 | + let k = ephemeral_scalar.borrow(); |
| 182 | + |
| 183 | + if k_inverse.is_none().into() || k.is_zero().into() { |
| 184 | + return Err(Error::new()); |
| 185 | + } |
| 186 | + |
| 187 | + let k_inverse = k_inverse.unwrap(); |
| 188 | + |
| 189 | + // Compute 𝐑 = 𝑘×𝑮 |
| 190 | + let R = (ProjectivePoint::GENERATOR * k).to_affine(); |
| 191 | + |
| 192 | + // Lift x-coordinate of 𝐑 (element of base field) into a serialized big |
| 193 | + // integer, then reduce it into an element of the scalar field |
| 194 | + let r = <Scalar as Reduce<U256>>::from_be_bytes_reduced(R.x.to_bytes()); |
| 195 | + |
| 196 | + // Compute `s` as a signature over `r` and `z`. |
| 197 | + let s = k_inverse * (z + (r * self)); |
| 198 | + |
| 199 | + if s.is_zero().into() { |
| 200 | + return Err(Error::new()); |
| 201 | + } |
| 202 | + |
| 203 | + let signature = Signature::from_scalars(r, s)?; |
| 204 | + let is_r_odd = R.y.normalize().is_odd(); |
| 205 | + let is_s_high = signature.s().is_high(); |
| 206 | + let is_y_odd = is_r_odd ^ is_s_high; |
| 207 | + let signature_low = signature.normalize_s().unwrap_or(signature); |
| 208 | + let recovery_id = ecdsa_core::RecoveryId::new(is_y_odd.into(), false); |
| 209 | + |
| 210 | + Ok((signature_low, Some(recovery_id))) |
| 211 | + } |
| 212 | +} |
| 213 | + |
| 214 | +#[cfg(feature = "ecdsa")] |
| 215 | +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] |
| 216 | +impl VerifyPrimitive<Secp256k1> for AffinePoint {} |
| 217 | + |
98 | 218 | #[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))] |
99 | 219 | mod tests { |
| 220 | + mod normalize { |
| 221 | + use crate::ecdsa::Signature; |
| 222 | + |
| 223 | + // Test vectors generated using rust-secp256k1 |
| 224 | + #[test] |
| 225 | + #[rustfmt::skip] |
| 226 | + fn s_high() { |
| 227 | + let sig_hi = Signature::try_from([ |
| 228 | + 0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10, |
| 229 | + 0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30, |
| 230 | + 0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c, |
| 231 | + 0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc, |
| 232 | + 0xee, 0x2f, 0x11, 0xef, 0x8c, 0xb0, 0x0a, 0x49, |
| 233 | + 0x61, 0x7d, 0x13, 0x57, 0xf4, 0xd5, 0x56, 0x41, |
| 234 | + 0x09, 0x0a, 0x48, 0xf2, 0x01, 0xe9, 0xb9, 0x59, |
| 235 | + 0xc4, 0x8f, 0x6f, 0x6b, 0xec, 0x6f, 0x93, 0x8f, |
| 236 | + ].as_slice()).unwrap(); |
| 237 | + |
| 238 | + let sig_lo = Signature::try_from([ |
| 239 | + 0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10, |
| 240 | + 0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30, |
| 241 | + 0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c, |
| 242 | + 0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc, |
| 243 | + 0x11, 0xd0, 0xee, 0x10, 0x73, 0x4f, 0xf5, 0xb6, |
| 244 | + 0x9e, 0x82, 0xec, 0xa8, 0x0b, 0x2a, 0xa9, 0xbd, |
| 245 | + 0xb1, 0xa4, 0x93, 0xf4, 0xad, 0x5e, 0xe6, 0xe1, |
| 246 | + 0xfb, 0x42, 0xef, 0x20, 0xe3, 0xc6, 0xad, 0xb2, |
| 247 | + ].as_slice()).unwrap(); |
| 248 | + |
| 249 | + let sig_normalized = sig_hi.normalize_s().unwrap(); |
| 250 | + assert_eq!(sig_lo, sig_normalized); |
| 251 | + } |
| 252 | + |
| 253 | + #[test] |
| 254 | + fn s_low() { |
| 255 | + #[rustfmt::skip] |
| 256 | + let sig = Signature::try_from([ |
| 257 | + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 258 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 259 | + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 260 | + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 261 | + ].as_slice()).unwrap(); |
| 262 | + |
| 263 | + assert_eq!(sig.normalize_s(), None); |
| 264 | + } |
| 265 | + } |
| 266 | + |
100 | 267 | mod wycheproof { |
101 | 268 | use crate::{EncodedPoint, Secp256k1}; |
102 | 269 | use ecdsa_core::{signature::Verifier, Signature}; |
|
0 commit comments