Skip to content

Commit

Permalink
k256: remove ecdsa::recoverable module; add replacement docs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tarcieri committed Nov 6, 2022
1 parent ec35dc7 commit e94e834
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 1,175 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/k256.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ jobs:
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features hash2curve
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features keccak256
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features schnorr
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features serde
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sha256
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa,keccak256
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdsa,sha256
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,bits,ecdh,ecdsa,hash2curve,jwk,keccak256,pem,pkcs8,schnorr,serde,sha256
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,bits,ecdh,ecdsa,hash2curve,jwk,pem,pkcs8,schnorr,serde,sha256

benches:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ ecdsa-core = { version = "=0.15.0-pre", package = "ecdsa", optional = true, defa
hex-literal = { version = "0.3", optional = true }
serdect = { version = "0.1", optional = true, default-features = false }
sha2 = { version = "0.10", optional = true, default-features = false }
sha3 = { version = "0.10", optional = true, default-features = false }

[dev-dependencies]
blobby = "0.3"
Expand All @@ -37,6 +36,7 @@ num-bigint = "0.4"
num-traits = "0.2"
proptest = "1.0"
rand_core = { version = "0.6", features = ["getrandom"] }
sha3 = { version = "0.10", default-features = false }

[features]
default = ["arithmetic", "ecdsa", "pkcs8", "schnorr", "std"]
Expand All @@ -51,7 +51,6 @@ ecdsa = ["arithmetic", "ecdsa-core/sign", "ecdsa-core/verify", "sha256"]
expose-field = ["arithmetic"]
hash2curve = ["arithmetic", "elliptic-curve/hash2curve"]
jwk = ["elliptic-curve/jwk"]
keccak256 = ["digest", "sha3"]
pem = ["ecdsa-core/pem", "elliptic-curve/pem", "pkcs8"]
pkcs8 = ["ecdsa-core/pkcs8", "elliptic-curve/pkcs8"]
schnorr = ["arithmetic", "sha256"]
Expand All @@ -60,7 +59,7 @@ sha256 = ["digest", "sha2"]
test-vectors = ["hex-literal"]

[package.metadata.docs.rs]
features = ["ecdh", "ecdsa", "keccak256", "schnorr"]
features = ["ecdh", "ecdsa", "schnorr"]
rustdoc-args = ["--cfg", "docsrs"]

[[bench]]
Expand Down
6 changes: 5 additions & 1 deletion k256/src/arithmetic/projective.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ impl ProjectivePoint {
pub fn to_affine(&self) -> AffinePoint {
self.z
.invert()
.map(|zinv| AffinePoint::new(self.x * &zinv, self.y * &zinv))
.map(|zinv| {
let x = self.x * &zinv;
let y = self.y * &zinv;
AffinePoint::new(x.normalize(), y.normalize())
})
.unwrap_or_else(|| AffinePoint::IDENTITY)
}

Expand Down
235 changes: 201 additions & 34 deletions k256/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,10 @@
//! [`VerifyingKey`] types which natively implement ECDSA/secp256k1 signing and
//! verification.
//!
//! Additionally, this crate contains support for computing ECDSA signatures
//! using either the SHA-256 (standard) or Keccak-256 (Ethereum) digest
//! functions, which are gated under the following Cargo features:
//!
//! - `sha256`: compute signatures using NIST's standard SHA-256 digest
//! function. Unless you are computing signatures for Ethereum, this is
//! almost certainly what you want.
//! - `keccak256`: compute signatures using the Keccak-256 digest function,
//! an incompatible variant of the SHA-3 algorithm used exclusively by
//! Ethereum.
//!
//! Most users of this library who want to sign/verify signatures will want to
//! enable the `ecdsa` and `sha256` Cargo features.
//!
//! ## Ethereum Support
//!
//! This crate natively supports Ethereum-style recoverable signatures.
//! Please see the toplevel documentation of the [`recoverable`] module
//! for more information.
//!
//! ## Signing/Verification Example
//! ## Signing and Verifying
//!
//! This example requires the `ecdsa` and `sha256` Cargo features are enabled:
//!
Expand Down Expand Up @@ -63,40 +46,224 @@
//! assert!(verifying_key.verify(message, &signature).is_ok());
//! # }
//! ```
//!
//! ## Recovering [`VerifyingKey`] from [`Signature`]
//!
//! ECDSA makes it possible to recover the public key used to verify a
//! signature with the assistance of 2-bits of additional information.
//!
//! This is helpful when there is already a trust relationship for a particular
//! key, and it's desirable to omit the full public key used to sign a
//! particular message.
//!
//! One common application of signature recovery with secp256k1 is Ethereum.
//!
//! ### Computing a signature with a [`RecoveryId`].
//!
//! This example shows how to compute a signature and its associated
//! [`RecoveryId`] in a manner which is byte-for-byte compatible with
//! Ethereum libraries:
//!
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use hex_literal::hex;
//! use k256::ecdsa::{hazmat::SignPrimitive, RecoveryId, Signature, SigningKey};
//! use sha2::Sha256;
//! use sha3::{Keccak256, Digest};
//!
//! let signing_key = SigningKey::from_bytes(&hex!(
//! "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
//! ))?;
//!
//! let msg = hex!("e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080");
//! let digest = Keccak256::digest(msg);
//! let (signature, recid) = signing_key
//! .as_nonzero_scalar()
//! .try_sign_prehashed_rfc6979::<Sha256>(digest, b"")?;
//!
//! assert_eq!(
//! signature.to_bytes().as_slice(),
//! &hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68")
//! );
//!
//! assert_eq!(recid, RecoveryId::try_from(0u8).ok());
//! # Ok(())
//! # }
//! ```
//!
//! ### Recovering a [`VerifyingKey`] from a signature
//!
#![cfg_attr(feature = "std", doc = "```ignore")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use hex_literal::hex;
//! use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
//! use sha3::{Keccak256, Digest};
//! use elliptic_curve::sec1::ToEncodedPoint;
//!
//! let msg = b"example message";
//! let signature = Signature::try_from(hex!(
//! "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
//! 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
//! ).as_slice())?;
//! let recid = RecoveryId::try_from(1u8)?;
//!
//! let recovered_key = VerifyingKey::recover_from_digest(
//! Keccak256::new_with_prefix(msg),
//! &signature,
//! recid
//! )?;
//!
//! let expected_key = VerifyingKey::from_sec1_bytes(
//! &hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2")
//! )?;
//!
//! assert_eq!(recovered_key, expected_key);
//! # Ok(())
//! # }
//! ```

pub mod recoverable;

#[cfg(feature = "ecdsa")]
mod normalize;
#[cfg(feature = "ecdsa")]
mod sign;
#[cfg(feature = "ecdsa")]
mod verify;

pub use ecdsa_core::signature::{self, Error};

#[cfg(feature = "digest")]
pub use ecdsa_core::signature::digest;
use crate::Secp256k1;
pub use ecdsa_core::{
signature::{self, Error},
RecoveryId,
};

#[cfg(feature = "ecdsa")]
pub use self::{sign::SigningKey, verify::VerifyingKey};

use crate::Secp256k1;
use {
crate::{AffinePoint, FieldBytes, ProjectivePoint, Scalar, U256},
core::borrow::Borrow,
ecdsa_core::hazmat::{SignPrimitive, VerifyPrimitive},
elliptic_curve::{
ops::{Invert, Reduce},
subtle::CtOption,
IsHigh,
},
};

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

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

/// ECDSA/secp256k1 signing key
#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
pub type SigningKey = ecdsa_core::SigningKey<Secp256k1>;

/// ECDSA/secp256k1 verification key (i.e. public key)
#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
pub type VerifyingKey = ecdsa_core::VerifyingKey<Secp256k1>;

#[cfg(feature = "sha256")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha256")))]
impl ecdsa_core::hazmat::DigestPrimitive for Secp256k1 {
type Digest = sha2::Sha256;
}

#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
impl SignPrimitive<Secp256k1> for Scalar {
#[allow(non_snake_case, clippy::many_single_char_names)]
fn try_sign_prehashed<K>(
&self,
ephemeral_scalar: K,
z: FieldBytes,
) -> Result<(Signature, Option<RecoveryId>), Error>
where
K: Borrow<Scalar> + Invert<Output = CtOption<Scalar>>,
{
let z = <Self as Reduce<U256>>::from_be_bytes_reduced(z);
let k_inverse = ephemeral_scalar.invert();
let k = ephemeral_scalar.borrow();

if k_inverse.is_none().into() || k.is_zero().into() {
return Err(Error::new());
}

let k_inverse = k_inverse.unwrap();

// Compute 𝐑 = 𝑘×𝑮
let R = (ProjectivePoint::GENERATOR * k).to_affine();

// Lift x-coordinate of 𝐑 (element of base field) into a serialized big
// integer, then reduce it into an element of the scalar field
let r = <Scalar as Reduce<U256>>::from_be_bytes_reduced(R.x.to_bytes());

// Compute `s` as a signature over `r` and `z`.
let s = k_inverse * (z + (r * self));

if s.is_zero().into() {
return Err(Error::new());
}

let signature = Signature::from_scalars(r, s)?;
let is_r_odd = R.y.normalize().is_odd();
let is_s_high = signature.s().is_high();
let is_y_odd = is_r_odd ^ is_s_high;
let signature_low = signature.normalize_s().unwrap_or(signature);
let recovery_id = ecdsa_core::RecoveryId::new(is_y_odd.into(), false);

Ok((signature_low, Some(recovery_id)))
}
}

#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
impl VerifyPrimitive<Secp256k1> for AffinePoint {}

#[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))]
mod tests {
mod normalize {
use crate::ecdsa::Signature;

// Test vectors generated using rust-secp256k1
#[test]
#[rustfmt::skip]
fn s_high() {
let sig_hi = Signature::try_from([
0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
0xee, 0x2f, 0x11, 0xef, 0x8c, 0xb0, 0x0a, 0x49,
0x61, 0x7d, 0x13, 0x57, 0xf4, 0xd5, 0x56, 0x41,
0x09, 0x0a, 0x48, 0xf2, 0x01, 0xe9, 0xb9, 0x59,
0xc4, 0x8f, 0x6f, 0x6b, 0xec, 0x6f, 0x93, 0x8f,
].as_slice()).unwrap();

let sig_lo = Signature::try_from([
0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10,
0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30,
0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c,
0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc,
0x11, 0xd0, 0xee, 0x10, 0x73, 0x4f, 0xf5, 0xb6,
0x9e, 0x82, 0xec, 0xa8, 0x0b, 0x2a, 0xa9, 0xbd,
0xb1, 0xa4, 0x93, 0xf4, 0xad, 0x5e, 0xe6, 0xe1,
0xfb, 0x42, 0xef, 0x20, 0xe3, 0xc6, 0xad, 0xb2,
].as_slice()).unwrap();

let sig_normalized = sig_hi.normalize_s().unwrap();
assert_eq!(sig_lo, sig_normalized);
}

#[test]
fn s_low() {
#[rustfmt::skip]
let sig = Signature::try_from([
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
].as_slice()).unwrap();

assert_eq!(sig.normalize_s(), None);
}
}

mod wycheproof {
use crate::{EncodedPoint, Secp256k1};
use ecdsa_core::{signature::Verifier, Signature};
Expand Down
Loading

0 comments on commit e94e834

Please sign in to comment.