Skip to content

Commit e94e834

Browse files
committed
k256: remove ecdsa::recoverable module; add replacement docs
Support for recovering `VerifyingKey`s from signatures has been upstreamed into the `ecdsa` crate in RustCrypto/signatures#576. This commit removes the `ecdsa::recoverable` module and the custom impls of `SigningKey`/`VerifyingKey` needed to support it. In its places are docs about how to use the generic APIs to accomplish equivalent functionality.
1 parent ec35dc7 commit e94e834

9 files changed

Lines changed: 212 additions & 1175 deletions

File tree

.github/workflows/k256.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ jobs:
4646
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa
4747
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features hash2curve
4848
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk
49-
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features keccak256
5049
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem
5150
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8
5251
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features schnorr
5352
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features serde
5453
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sha256
55-
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa,keccak256
54+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa
5655
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa,sha256
57-
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,bits,ecdh,ecdsa,hash2curve,jwk,keccak256,pem,pkcs8,schnorr,serde,sha256
56+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,bits,ecdh,ecdsa,hash2curve,jwk,pem,pkcs8,schnorr,serde,sha256
5857

5958
benches:
6059
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

k256/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ ecdsa-core = { version = "=0.15.0-pre", package = "ecdsa", optional = true, defa
2626
hex-literal = { version = "0.3", optional = true }
2727
serdect = { version = "0.1", optional = true, default-features = false }
2828
sha2 = { version = "0.10", optional = true, default-features = false }
29-
sha3 = { version = "0.10", optional = true, default-features = false }
3029

3130
[dev-dependencies]
3231
blobby = "0.3"
@@ -37,6 +36,7 @@ num-bigint = "0.4"
3736
num-traits = "0.2"
3837
proptest = "1.0"
3938
rand_core = { version = "0.6", features = ["getrandom"] }
39+
sha3 = { version = "0.10", default-features = false }
4040

4141
[features]
4242
default = ["arithmetic", "ecdsa", "pkcs8", "schnorr", "std"]
@@ -51,7 +51,6 @@ ecdsa = ["arithmetic", "ecdsa-core/sign", "ecdsa-core/verify", "sha256"]
5151
expose-field = ["arithmetic"]
5252
hash2curve = ["arithmetic", "elliptic-curve/hash2curve"]
5353
jwk = ["elliptic-curve/jwk"]
54-
keccak256 = ["digest", "sha3"]
5554
pem = ["ecdsa-core/pem", "elliptic-curve/pem", "pkcs8"]
5655
pkcs8 = ["ecdsa-core/pkcs8", "elliptic-curve/pkcs8"]
5756
schnorr = ["arithmetic", "sha256"]
@@ -60,7 +59,7 @@ sha256 = ["digest", "sha2"]
6059
test-vectors = ["hex-literal"]
6160

6261
[package.metadata.docs.rs]
63-
features = ["ecdh", "ecdsa", "keccak256", "schnorr"]
62+
features = ["ecdh", "ecdsa", "schnorr"]
6463
rustdoc-args = ["--cfg", "docsrs"]
6564

6665
[[bench]]

k256/src/arithmetic/projective.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ impl ProjectivePoint {
7878
pub fn to_affine(&self) -> AffinePoint {
7979
self.z
8080
.invert()
81-
.map(|zinv| AffinePoint::new(self.x * &zinv, self.y * &zinv))
81+
.map(|zinv| {
82+
let x = self.x * &zinv;
83+
let y = self.y * &zinv;
84+
AffinePoint::new(x.normalize(), y.normalize())
85+
})
8286
.unwrap_or_else(|| AffinePoint::IDENTITY)
8387
}
8488

k256/src/ecdsa.rs

Lines changed: 201 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,10 @@
1414
//! [`VerifyingKey`] types which natively implement ECDSA/secp256k1 signing and
1515
//! verification.
1616
//!
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-
//!
2817
//! Most users of this library who want to sign/verify signatures will want to
2918
//! enable the `ecdsa` and `sha256` Cargo features.
3019
//!
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
3821
//!
3922
//! This example requires the `ecdsa` and `sha256` Cargo features are enabled:
4023
//!
@@ -63,40 +46,224 @@
6346
//! assert!(verifying_key.verify(message, &signature).is_ok());
6447
//! # }
6548
//! ```
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+
//! ```
66126
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+
};
80132

81133
#[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+
};
85144

86145
/// ECDSA/secp256k1 signature (fixed-size)
87146
pub type Signature = ecdsa_core::Signature<Secp256k1>;
88147

89148
/// ECDSA/secp256k1 signature (ASN.1 DER encoded)
90149
pub type DerSignature = ecdsa_core::der::Signature<Secp256k1>;
91150

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+
92161
#[cfg(feature = "sha256")]
93162
#[cfg_attr(docsrs, doc(cfg(feature = "sha256")))]
94163
impl ecdsa_core::hazmat::DigestPrimitive for Secp256k1 {
95164
type Digest = sha2::Sha256;
96165
}
97166

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+
98218
#[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))]
99219
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+
100267
mod wycheproof {
101268
use crate::{EncodedPoint, Secp256k1};
102269
use ecdsa_core::{signature::Verifier, Signature};

0 commit comments

Comments
 (0)