diff --git a/.github/workflows/k256.yml b/.github/workflows/k256.yml index f876702e..c5cfc521 100644 --- a/.github/workflows/k256.yml +++ b/.github/workflows/k256.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index ab527852..32fe8f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,7 +318,7 @@ dependencies = [ [[package]] name = "ecdsa" version = "0.15.0-pre" -source = "git+https://github.com/RustCrypto/signatures.git#1578fe31b96cad80afdc1be47e5f0d088bd5bc2b" +source = "git+https://github.com/RustCrypto/signatures.git#beb7271cb0b4a476d3285bb9f755824715536d77" dependencies = [ "der", "elliptic-curve", @@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "rfc6979" version = "0.3.1" -source = "git+https://github.com/RustCrypto/signatures.git#1578fe31b96cad80afdc1be47e5f0d088bd5bc2b" +source = "git+https://github.com/RustCrypto/signatures.git#beb7271cb0b4a476d3285bb9f755824715536d77" dependencies = [ "crypto-bigint", "hmac", diff --git a/k256/Cargo.toml b/k256/Cargo.toml index dcca9bd7..0395083c 100644 --- a/k256/Cargo.toml +++ b/k256/Cargo.toml @@ -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" @@ -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"] @@ -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"] @@ -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]] diff --git a/k256/src/arithmetic/projective.rs b/k256/src/arithmetic/projective.rs index af1ad077..00308f71 100644 --- a/k256/src/arithmetic/projective.rs +++ b/k256/src/arithmetic/projective.rs @@ -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) } diff --git a/k256/src/ecdsa.rs b/k256/src/ecdsa.rs index fe697ebb..ddb59219 100644 --- a/k256/src/ecdsa.rs +++ b/k256/src/ecdsa.rs @@ -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: //! @@ -63,25 +46,105 @@ //! 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> { +//! 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::(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 = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! # fn main() -> Result<(), Box> { +//! 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!("0200866db99873b09fc2fb1e3ba549b156e96d1a567e3284f5f0e859a83320cb8b") +//! )?; +//! +//! assert_eq!(recovered_key, expected_key); +//! # Ok(()) +//! # } +//! ``` -pub mod recoverable; +pub use ecdsa_core::{ + signature::{self, Error}, + RecoveryId, +}; #[cfg(feature = "ecdsa")] -mod normalize; -#[cfg(feature = "ecdsa")] -mod sign; -#[cfg(feature = "ecdsa")] -mod verify; +pub use ecdsa_core::hazmat; -pub use ecdsa_core::signature::{self, Error}; - -#[cfg(feature = "digest")] -pub use ecdsa_core::signature::digest; +use crate::Secp256k1; #[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; @@ -89,14 +152,174 @@ pub type Signature = ecdsa_core::Signature; /// ECDSA/secp256k1 signature (ASN.1 DER encoded) pub type DerSignature = ecdsa_core::der::Signature; +/// ECDSA/secp256k1 signing key +#[cfg(feature = "ecdsa")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] +pub type SigningKey = ecdsa_core::SigningKey; + +/// ECDSA/secp256k1 verification key (i.e. public key) +#[cfg(feature = "ecdsa")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] +pub type VerifyingKey = ecdsa_core::VerifyingKey; + #[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 for Scalar { + #[allow(non_snake_case, clippy::many_single_char_names)] + fn try_sign_prehashed( + &self, + ephemeral_scalar: K, + z: FieldBytes, + ) -> Result<(Signature, Option), Error> + where + K: Borrow + Invert>, + { + let z = >::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 = >::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 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); + } + } + + #[cfg(feature = "sha256")] + mod recovery { + use crate::{ + ecdsa::{RecoveryId, Signature, VerifyingKey}, + EncodedPoint, + }; + use hex_literal::hex; + use sha2::{Digest, Sha256}; + + /// Signature recovery test vectors + struct RecoveryTestVector { + pk: [u8; 33], + msg: &'static [u8], + sig: [u8; 64], + recid: RecoveryId, + } + + const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[ + // Recovery ID 0 + RecoveryTestVector { + pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"), + msg: b"example message", + sig: hex!( + "ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa032 + 3971edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d52" + ), + recid: RecoveryId::new(false, false), + }, + // Recovery ID 1 + RecoveryTestVector { + pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"), + msg: b"example message", + sig: hex!( + "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51 + 35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb" + ), + recid: RecoveryId::new(true, false), + }, + ]; + + #[test] + fn public_key_recovery() { + for vector in RECOVERY_TEST_VECTORS { + let digest = Sha256::new_with_prefix(vector.msg); + let sig = Signature::try_from(vector.sig.as_slice()).unwrap(); + let recid = vector.recid; + let pk = VerifyingKey::recover_from_digest(digest, &sig, recid).unwrap(); + assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes()); + } + } + } + mod wycheproof { use crate::{EncodedPoint, Secp256k1}; use ecdsa_core::{signature::Verifier, Signature}; diff --git a/k256/src/ecdsa/normalize.rs b/k256/src/ecdsa/normalize.rs deleted file mode 100644 index af1a4963..00000000 --- a/k256/src/ecdsa/normalize.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Low-S normalization support as described in [BIP 0062: Dealing with Malleability][1]. -//! -//! [1]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki - -#[cfg(all(test, feature = "ecdsa"))] -mod tests { - use crate::ecdsa::Signature; - - // Test vectors generated using rust-secp256k1 - #[test] - #[rustfmt::skip] - fn normalize_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 normalize_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); - } -} diff --git a/k256/src/ecdsa/recoverable.rs b/k256/src/ecdsa/recoverable.rs deleted file mode 100644 index f7b6a108..00000000 --- a/k256/src/ecdsa/recoverable.rs +++ /dev/null @@ -1,408 +0,0 @@ -//! Ethereum-style "recoverable signatures". -//! -//! These signatures include an additional [`Id`] field which allows for -//! recovery of the [`VerifyingKey`] which can be used to verify them. -//! -//! This is helpful in cases where a hash/fingerprint of a [`VerifyingKey`] -//! for a given signature in known in advance. -//! -//! ## Signing/Recovery Example -//! -//! NOTE: make sure to enable both the `ecdsa` and `keccak256` features of -//! this crate for the example to work. -//! -//! ``` -//! # #[cfg(all(feature = "ecdsa", feature = "keccak256"))] -//! # { -//! use k256::{ -//! ecdsa::{SigningKey, recoverable, signature::Signer}, -//! EncodedPoint -//! }; -//! use rand_core::OsRng; // requires 'getrandom' feature -//! -//! // Signing -//! let signing_key = SigningKey::random(&mut OsRng); // Serialize with `::to_bytes()` -//! let verifying_key = signing_key.verifying_key(); -//! let message = b"ECDSA proves knowledge of a secret number in the context of a single message"; -//! -//! // Note: The signature type must be annotated or otherwise inferable as -//! // `Signer` has many impls of the `Signer` trait (for both regular and -//! // recoverable signature types). -//! let signature: recoverable::Signature = signing_key.sign(message); -//! let recovered_key = signature.recover_verifying_key(message).expect("couldn't recover pubkey"); -//! -//! assert_eq!(&verifying_key, &recovered_key); -//! # } -//! ``` - -use core::fmt::{self, Debug}; -use ecdsa_core::{Error, Result}; -use elliptic_curve::subtle::Choice; - -#[cfg(feature = "ecdsa")] -use crate::{ - ecdsa::{ - signature::{ - digest::{Digest, FixedOutput}, - hazmat::PrehashVerifier, - }, - VerifyingKey, - }, - elliptic_curve::{ - bigint::U256, - consts::U32, - ops::{Invert, LinearCombination, Reduce}, - DecompressPoint, - }, - AffinePoint, FieldBytes, NonZeroScalar, ProjectivePoint, Scalar, -}; - -#[cfg(feature = "keccak256")] -use sha3::Keccak256; - -/// Size of an Ethereum-style recoverable signature in bytes -pub const SIZE: usize = 65; - -/// Ethereum-style "recoverable signatures" which allow for the recovery of -/// the signer's [`VerifyingKey`] from the signature itself. -/// -/// This format consists of [`Signature`] followed by a 1-byte recovery [`Id`] -/// (65-bytes total): -/// -/// - `r`: 32-byte integer, big endian -/// - `s`: 32-byte integer, big endian -/// - `v`: 1-byte recovery [`Id`] -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct Signature { - bytes: [u8; SIZE], -} - -impl Signature { - /// Create a new recoverable ECDSA/secp256k1 signature from a regular - /// fixed-size signature and an associated recovery [`Id`]. - /// - /// This is an "unchecked" conversion and assumes the provided [`Id`] - /// is valid for this signature. - pub fn new(signature: &super::Signature, recovery_id: Id) -> Result { - let mut bytes = [0u8; SIZE]; - bytes[..64].copy_from_slice(&signature.to_bytes()); - bytes[64] = recovery_id.0; - Ok(Self { bytes }) - } - - /// Get the recovery [`Id`] for this signature - pub fn recovery_id(self) -> Id { - self.bytes[64].try_into().expect("invalid recovery ID") - } - - /// Given a public key, message, and signature, use trial recovery - /// to determine if a suitable recovery ID exists, or return an error - /// otherwise. - /// - /// Assumes Keccak256 as the message digest function. Use - /// [`Signature::from_digest_trial_recovery`] to support other - ///digest functions. - #[cfg(all(feature = "ecdsa", feature = "keccak256"))] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - #[cfg_attr(docsrs, doc(cfg(feature = "keccak256")))] - pub fn from_trial_recovery( - public_key: &VerifyingKey, - msg: &[u8], - signature: &super::Signature, - ) -> Result { - Self::from_digest_trial_recovery(public_key, Keccak256::new_with_prefix(msg), signature) - } - - /// Given a public key, message digest, and signature, use trial recovery - /// to determine if a suitable recovery ID exists, or return an error - /// otherwise. - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - pub fn from_digest_trial_recovery( - public_key: &VerifyingKey, - digest: D, - signature: &super::Signature, - ) -> Result - where - D: Clone + Digest + FixedOutput, - { - Self::from_digest_bytes_trial_recovery(public_key, &digest.finalize(), signature) - } - - /// Given a public key, message digest, and signature, use trial recovery - /// to determine if a suitable recovery ID exists, or return an error - /// otherwise. - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - pub fn from_digest_bytes_trial_recovery( - public_key: &VerifyingKey, - digest_bytes: &FieldBytes, - signature: &super::Signature, - ) -> Result { - let signature = signature.normalize_s().unwrap_or(*signature); - - for recovery_id in 0..=1 { - if let Ok(recoverable_signature) = Signature::new(&signature, Id(recovery_id)) { - if let Ok(recovered_key) = - recoverable_signature.recover_verifying_key_from_digest_bytes(digest_bytes) - { - if public_key == &recovered_key - && public_key.verify_prehash(digest_bytes, &signature).is_ok() - { - return Ok(recoverable_signature); - } - } - } - } - - Err(Error::new()) - } - - /// Recover the public key used to create the given signature as a - /// [`VerifyingKey`]. - #[cfg(all(feature = "ecdsa", feature = "keccak256"))] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - #[cfg_attr(docsrs, doc(cfg(feature = "keccak256")))] - pub fn recover_verifying_key(&self, msg: &[u8]) -> Result { - self.recover_verifying_key_from_digest(Keccak256::new_with_prefix(msg)) - } - - /// Recover the public key used to create the given signature as a - /// [`VerifyingKey`] from the provided precomputed [`Digest`]. - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - pub fn recover_verifying_key_from_digest(&self, msg_digest: D) -> Result - where - D: Digest, - { - self.recover_verifying_key_from_digest_bytes(&msg_digest.finalize()) - } - - /// Recover the public key used to create the given signature as a - /// [`VerifyingKey`] from the raw bytes of a message digest. - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - #[allow(non_snake_case, clippy::many_single_char_names)] - pub fn recover_verifying_key_from_digest_bytes( - &self, - digest_bytes: &FieldBytes, - ) -> Result { - let r = self.r(); - let s = self.s(); - let z = >::from_be_bytes_reduced(*digest_bytes); - let R = AffinePoint::decompress(&r.to_bytes(), self.recovery_id().is_y_odd()); - - if R.is_none().into() { - return Err(Error::new()); - } - - let R = ProjectivePoint::from(R.unwrap()); - let r_inv = *r.invert(); - let u1 = -(r_inv * z); - let u2 = r_inv * *s; - let pk = ProjectivePoint::lincomb(&ProjectivePoint::GENERATOR, &u1, &R, &u2); - - // TODO(tarcieri): ensure the signature verifies? - VerifyingKey::try_from(pk) - } - - /// Parse the `r` component of this signature to a [`NonZeroScalar`] - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - pub fn r(&self) -> NonZeroScalar { - NonZeroScalar::try_from(&self.bytes[..32]) - .expect("r-component ensured valid in constructor") - } - - /// Parse the `s` component of this signature to a [`NonZeroScalar`] - #[cfg(feature = "ecdsa")] - #[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] - pub fn s(&self) -> NonZeroScalar { - NonZeroScalar::try_from(&self.bytes[32..64]) - .expect("s-component ensured valid in constructor") - } -} - -impl ecdsa_core::signature::SignatureEncoding for Signature { - type Repr = [u8; SIZE]; - - fn to_bytes(&self) -> Self::Repr { - self.bytes - } -} - -impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - &self.bytes[..] - } -} - -impl Debug for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RecoverableSignature {{ bytes: {:?}) }}", self.as_ref()) - } -} - -impl From for [u8; SIZE] { - fn from(signature: Signature) -> [u8; SIZE] { - signature.bytes - } -} - -impl TryFrom<&[u8]> for Signature { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != SIZE { - return Err(Error::new()); - } - - let signature = super::Signature::try_from(&bytes[..64])?; - let recovery_id = Id::try_from(bytes[64])?; - Self::new(&signature, recovery_id) - } -} - -impl From for super::Signature { - fn from(sig: Signature) -> Self { - Self::try_from(&sig.bytes[..64]).unwrap() - } -} - -#[cfg(feature = "keccak256")] -impl ecdsa_core::signature::PrehashSignature for Signature { - type Digest = Keccak256; -} - -/// Identifier used to compute a [`VerifyingKey`] from a [`Signature`]. -/// -/// In practice these values are always either `0` or `1`, and indicate -/// whether or not the y-coordinate of the original [`VerifyingKey`] is odd. -/// -/// While values `2` and `3` are also defined to capture whether `r` -/// overflowed the curve's order, this crate does *not* support them. -/// -/// There is a vanishingly small chance of these values occurring outside -/// of contrived examples, so for simplicity's sake handling these values -/// is unsupported and will return an `Error` when parsing the `Id`. -#[derive(Copy, Clone, Debug)] -pub struct Id(pub(super) u8); - -impl Id { - /// Create a new [`Id`] from the given byte value - pub fn new(byte: u8) -> Result { - match byte { - 0 | 1 => Ok(Self(byte)), - _ => Err(Error::new()), - } - } - - /// Is `y` odd? - fn is_y_odd(self) -> Choice { - self.0.into() - } -} - -impl TryFrom for Id { - type Error = Error; - - fn try_from(byte: u8) -> Result { - Self::new(byte) - } -} - -impl From for u8 { - fn from(recovery_id: Id) -> u8 { - recovery_id.0 - } -} - -impl TryFrom for Id { - type Error = Error; - - fn try_from(id: ecdsa_core::RecoveryId) -> Result { - if id.is_x_reduced() { - Err(Error::new()) - } else if id.is_y_odd() { - Ok(Id(1)) - } else { - Ok(Id(0)) - } - } -} - -impl From for ecdsa_core::RecoveryId { - fn from(id: Id) -> ecdsa_core::RecoveryId { - ecdsa_core::RecoveryId::new(id.is_y_odd().into(), false) - } -} - -#[cfg(all(test, feature = "ecdsa", feature = "keccak256", feature = "sha256"))] -mod tests { - use super::Signature; - use crate::{ - ecdsa::{signature::Signer, SigningKey}, - EncodedPoint, - }; - use hex_literal::hex; - use sha2::{Digest, Sha256}; - - /// Signature recovery test vectors - struct RecoveryTestVector { - pk: [u8; 33], - sig: [u8; 65], - msg: &'static [u8], - } - - const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[ - // Recovery ID 0 - RecoveryTestVector { - pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"), - sig: hex!( - "ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa03239 - 71edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d5200" - ), - msg: b"example message", - }, - // Recovery ID 1 - RecoveryTestVector { - pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"), - sig: hex!( - "46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb5135 - c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb01" - ), - msg: b"example message", - }, - ]; - - #[test] - fn public_key_recovery() { - for vector in RECOVERY_TEST_VECTORS { - let sig = Signature::try_from(&vector.sig[..]).unwrap(); - let prehash = Sha256::new_with_prefix(vector.msg); - let pk = sig.recover_verifying_key_from_digest(prehash).unwrap(); - assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes()); - } - } - - /// Ensures RFC6979 is implemented in the same way as other Ethereum - /// libraries, using HMAC-DRBG-SHA-256 for RFC6979, and Keccak256 for - /// hashing the message. - /// - /// Test vectors adapted from: - /// - #[test] - fn signing_rfc6979() { - let signing_key = SigningKey::from_bytes(&hex!( - "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318" - )) - .unwrap(); - - let msg = hex!( - "e9808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080018080" - ); - - let sig: Signature = signing_key.sign(&msg); - assert_eq!(sig.as_ref(), &hex!("c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa6800")); - } -} diff --git a/k256/src/ecdsa/sign.rs b/k256/src/ecdsa/sign.rs deleted file mode 100644 index 4bea8cb4..00000000 --- a/k256/src/ecdsa/sign.rs +++ /dev/null @@ -1,345 +0,0 @@ -//! ECDSA signing support. - -use super::{recoverable, Error, Signature, VerifyingKey}; -use crate::{FieldBytes, NonZeroScalar, ProjectivePoint, PublicKey, Scalar, Secp256k1, SecretKey}; -use core::{ - borrow::Borrow, - fmt::{self, Debug}, -}; -use ecdsa_core::{ - hazmat::SignPrimitive, - signature::{ - digest::{Digest, FixedOutput}, - hazmat::PrehashSigner, - DigestSigner, KeypairRef, RandomizedDigestSigner, - }, -}; -use elliptic_curve::{ - bigint::U256, - consts::U32, - ops::{Invert, Reduce}, - rand_core::CryptoRngCore, - subtle::{Choice, ConstantTimeEq, CtOption}, - zeroize::{Zeroize, ZeroizeOnDrop}, - IsHigh, -}; -use sha2::Sha256; - -#[cfg(any(feature = "keccak256", feature = "sha256"))] -use ecdsa_core::signature::{self, PrehashSignature, RandomizedSigner}; - -#[cfg(feature = "pkcs8")] -use crate::pkcs8::{self, DecodePrivateKey}; - -#[cfg(feature = "pem")] -use core::str::FromStr; - -/// ECDSA/secp256k1 signing key -#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] -#[derive(Clone)] -pub struct SigningKey { - /// Secret scalar value (i.e. the private key) - secret_scalar: NonZeroScalar, - - /// Verifying key which corresponds to this signing key. - verifying_key: VerifyingKey, -} - -impl SigningKey { - /// Generate a cryptographically random [`SigningKey`]. - pub fn random(rng: &mut impl CryptoRngCore) -> Self { - NonZeroScalar::random(rng).into() - } - - /// Initialize [`SigningKey`] from a raw scalar value (big endian). - pub fn from_bytes(bytes: &[u8]) -> Result { - SecretKey::from_be_bytes(bytes) - .map(|sk| sk.to_nonzero_scalar().into()) - .map_err(|_| Error::new()) - } - - /// Get the [`VerifyingKey`] which corresponds to this [`SigningKey`]. - pub fn verifying_key(&self) -> VerifyingKey { - self.verifying_key - } - - /// Serialize this [`SigningKey`] as bytes - pub fn to_bytes(&self) -> FieldBytes { - self.secret_scalar.to_bytes() - } -} - -impl AsRef for SigningKey { - fn as_ref(&self) -> &VerifyingKey { - &self.verifying_key - } -} - -#[cfg(any(feature = "keccak256", feature = "sha256"))] -impl signature::Signer for SigningKey -where - S: PrehashSignature, - Self: DigestSigner, -{ - fn try_sign(&self, msg: &[u8]) -> Result { - self.try_sign_digest(S::Digest::new_with_prefix(msg)) - } -} - -#[cfg(any(feature = "keccak256", feature = "sha256"))] -impl RandomizedSigner for SigningKey -where - S: PrehashSignature, - Self: RandomizedDigestSigner, -{ - fn try_sign_with_rng(&self, rng: &mut impl CryptoRngCore, msg: &[u8]) -> signature::Result { - self.try_sign_digest_with_rng(rng, S::Digest::new_with_prefix(msg)) - } -} - -impl DigestSigner for SigningKey -where - D: Digest + FixedOutput, -{ - fn try_sign_digest(&self, msg_digest: D) -> signature::Result { - self.sign_prehash(&msg_digest.finalize_fixed()) - } -} - -impl DigestSigner for SigningKey -where - D: Digest + FixedOutput, -{ - fn try_sign_digest(&self, msg_digest: D) -> signature::Result { - self.sign_prehash(&msg_digest.finalize_fixed()) - } -} - -#[cfg(feature = "sha256")] -#[cfg_attr(docsrs, doc(cfg(feature = "sha256")))] -impl KeypairRef for SigningKey { - type VerifyingKey = VerifyingKey; -} - -impl PrehashSigner for SigningKey { - fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { - let prehash = <[u8; 32]>::try_from(prehash).map_err(|_| Error::new())?; - - Ok(self - .secret_scalar - .try_sign_prehashed_rfc6979::(prehash.into(), &[])? - .0) - } -} - -impl PrehashSigner for SigningKey { - fn sign_prehash(&self, prehash: &[u8]) -> signature::Result { - let prehash = <[u8; 32]>::try_from(prehash).map_err(|_| Error::new())?; - - // Ethereum signatures use SHA-256 for RFC6979, even if the message - // has been hashed with Keccak256 - let (signature, recid) = self - .secret_scalar - .try_sign_prehashed_rfc6979::(prehash.into(), &[])?; - - let recoverable_id = recid.ok_or_else(Error::new)?.try_into()?; - recoverable::Signature::new(&signature, recoverable_id) - } -} - -impl RandomizedDigestSigner for SigningKey -where - D: Digest + FixedOutput, -{ - fn try_sign_digest_with_rng( - &self, - rng: &mut impl CryptoRngCore, - digest: D, - ) -> Result { - RandomizedDigestSigner::::try_sign_digest_with_rng( - self, rng, digest, - ) - .map(Into::into) - } -} - -impl RandomizedDigestSigner for SigningKey -where - D: Digest + FixedOutput, -{ - fn try_sign_digest_with_rng( - &self, - rng: &mut impl CryptoRngCore, - msg_digest: D, - ) -> Result { - let mut ad = FieldBytes::default(); - rng.fill_bytes(&mut ad); - - let digest = msg_digest.finalize_fixed(); - - // Ethereum signatures use SHA-256 for RFC6979, even if the message - // has been hashed with Keccak256 - let (signature, recid) = self - .secret_scalar - .try_sign_prehashed_rfc6979::(digest, &ad)?; - - let recoverable_id = recid.ok_or_else(Error::new)?.try_into()?; - recoverable::Signature::new(&signature, recoverable_id) - } -} - -impl SignPrimitive for Scalar { - #[allow(non_snake_case, clippy::many_single_char_names)] - fn try_sign_prehashed( - &self, - ephemeral_scalar: K, - z: FieldBytes, - ) -> Result<(Signature, Option), Error> - where - K: Borrow + Invert>, - { - let z = >::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 = >::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))) - } -} - -impl ConstantTimeEq for SigningKey { - fn ct_eq(&self, other: &Self) -> Choice { - self.secret_scalar.ct_eq(&other.secret_scalar) - } -} - -impl Debug for SigningKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SigningKey").finish_non_exhaustive() - } -} - -impl Eq for SigningKey {} - -impl PartialEq for SigningKey { - fn eq(&self, other: &SigningKey) -> bool { - self.ct_eq(other).into() - } -} - -impl From for SigningKey { - fn from(secret_key: SecretKey) -> SigningKey { - Self::from(&secret_key) - } -} - -impl From<&SecretKey> for SigningKey { - fn from(secret_key: &SecretKey) -> SigningKey { - secret_key.to_nonzero_scalar().into() - } -} - -impl From for SecretKey { - fn from(signing_key: SigningKey) -> SecretKey { - signing_key.secret_scalar.into() - } -} - -impl From<&SigningKey> for SecretKey { - fn from(signing_key: &SigningKey) -> SecretKey { - signing_key.secret_scalar.into() - } -} - -impl From for VerifyingKey { - fn from(signing_key: SigningKey) -> VerifyingKey { - signing_key.verifying_key() - } -} - -impl From<&SigningKey> for VerifyingKey { - fn from(signing_key: &SigningKey) -> VerifyingKey { - signing_key.verifying_key() - } -} - -impl From for SigningKey { - fn from(secret_scalar: NonZeroScalar) -> Self { - let public_key = PublicKey::from_secret_scalar(&secret_scalar); - - Self { - secret_scalar, - verifying_key: public_key.into(), - } - } -} - -impl From<&NonZeroScalar> for SigningKey { - fn from(secret_scalar: &NonZeroScalar) -> Self { - Self::from(*secret_scalar) - } -} - -impl Drop for SigningKey { - fn drop(&mut self) { - self.secret_scalar.zeroize(); - } -} - -impl ZeroizeOnDrop for SigningKey {} - -#[cfg(feature = "pkcs8")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] -impl TryFrom> for SigningKey { - type Error = pkcs8::Error; - - fn try_from(private_key_info: pkcs8::PrivateKeyInfo<'_>) -> pkcs8::Result { - SecretKey::try_from(private_key_info).map(Into::into) - } -} - -#[cfg(feature = "pkcs8")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] -impl DecodePrivateKey for SigningKey {} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for SigningKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pkcs8_pem(s).map_err(|_| Error::new()) - } -} - -#[cfg(test)] -mod tests { - use crate::{test_vectors::ecdsa::ECDSA_TEST_VECTORS, Secp256k1}; - ecdsa_core::new_signing_test!(Secp256k1, ECDSA_TEST_VECTORS); -} diff --git a/k256/src/ecdsa/verify.rs b/k256/src/ecdsa/verify.rs deleted file mode 100644 index ae0a4e08..00000000 --- a/k256/src/ecdsa/verify.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! ECDSA verification support. - -use super::{recoverable, Error, Signature}; -use crate::{ - AffinePoint, CompressedPoint, EncodedPoint, FieldBytes, ProjectivePoint, PublicKey, Scalar, - Secp256k1, -}; -use ecdsa_core::{ - hazmat::VerifyPrimitive, - signature::{ - self, - digest::{Digest, FixedOutput}, - hazmat::PrehashVerifier, - DigestVerifier, - }, -}; -use elliptic_curve::{ - bigint::U256, - consts::U32, - ops::{Invert, LinearCombination, Reduce}, - sec1::ToEncodedPoint, - IsHigh, -}; - -#[cfg(feature = "sha256")] -use signature::PrehashSignature; - -#[cfg(feature = "pkcs8")] -use crate::pkcs8::{self, DecodePublicKey}; - -#[cfg(feature = "pem")] -use core::str::FromStr; - -#[cfg(all(feature = "pem", feature = "serde"))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "serde"))))] -use serdect::serde::{de, ser, Deserialize, Serialize}; - -/// ECDSA/secp256k1 verification key (i.e. public key) -/// -/// # `serde` support -/// -/// When the `serde` feature of this crate is enabled, the `Serialize` and -/// `Deserialize` traits are impl'd for this type. -/// -/// The serialization is binary-oriented and supports ASN.1 DER-encoded -/// X.509 Subject Public Key Info (SPKI) as the encoding format. -/// -/// For a more text-friendly encoding of public keys, use -/// [`elliptic_curve::JwkEcKey`] instead. -#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct VerifyingKey { - /// Core ECDSA verify key - pub(super) inner: ecdsa_core::VerifyingKey, -} - -impl VerifyingKey { - /// Initialize [`VerifyingKey`] from a SEC1-encoded public key. - pub fn from_sec1_bytes(bytes: &[u8]) -> Result { - ecdsa_core::VerifyingKey::from_sec1_bytes(bytes).map(|key| VerifyingKey { inner: key }) - } - - /// Initialize [`VerifyingKey`] from a SEC1 [`EncodedPoint`]. - // TODO(tarcieri): switch to using `FromEncodedPoint` trait? - pub fn from_encoded_point(public_key: &EncodedPoint) -> Result { - ecdsa_core::VerifyingKey::from_encoded_point(public_key) - .map(|key| VerifyingKey { inner: key }) - } - - /// Serialize this [`VerifyingKey`] as a SEC1-encoded bytestring - /// (with point compression applied) - pub fn to_bytes(&self) -> CompressedPoint { - CompressedPoint::clone_from_slice(EncodedPoint::from(self).as_bytes()) - } -} - -#[cfg(feature = "sha256")] -impl signature::Verifier for VerifyingKey -where - S: PrehashSignature, - Self: DigestVerifier, -{ - fn verify(&self, msg: &[u8], signature: &S) -> signature::Result<()> { - self.verify_digest(S::Digest::new_with_prefix(msg), signature) - } -} - -impl DigestVerifier for VerifyingKey -where - D: Digest + FixedOutput, -{ - fn verify_digest(&self, digest: D, signature: &Signature) -> signature::Result<()> { - self.inner.verify_digest(digest, signature) - } -} - -impl DigestVerifier for VerifyingKey -where - D: Digest + FixedOutput, -{ - fn verify_digest( - &self, - digest: D, - signature: &recoverable::Signature, - ) -> signature::Result<()> { - self.inner - .verify_digest(digest, &Signature::from(*signature)) - } -} - -impl PrehashVerifier for VerifyingKey { - fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> signature::Result<()> { - self.inner.verify_prehash(prehash, signature) - } -} - -impl PrehashVerifier for VerifyingKey { - fn verify_prehash( - &self, - prehash: &[u8], - signature: &recoverable::Signature, - ) -> signature::Result<()> { - self.inner - .verify_prehash(prehash, &Signature::from(*signature)) - } -} - -impl VerifyPrimitive for AffinePoint { - fn verify_prehashed(&self, z: FieldBytes, signature: &Signature) -> signature::Result<()> { - let r = signature.r(); - let s = signature.s(); - let z = >::from_be_bytes_reduced(z); - - // Ensure signature is "low S" normalized ala BIP 0062 - if s.is_high().into() { - return Err(Error::new()); - } - - let s_inv = *s.invert(); - let u1 = z * s_inv; - let u2 = *r * s_inv; - - let x = ProjectivePoint::lincomb( - &ProjectivePoint::GENERATOR, - &u1, - &ProjectivePoint::from(self), - &u2, - ) - .to_affine() - .x; - - if >::from_be_bytes_reduced(x.to_bytes()).eq(&r) { - Ok(()) - } else { - Err(Error::new()) - } - } -} - -impl From for VerifyingKey { - fn from(public_key: PublicKey) -> VerifyingKey { - Self { - inner: public_key.into(), - } - } -} - -impl From<&PublicKey> for VerifyingKey { - fn from(public_key: &PublicKey) -> VerifyingKey { - VerifyingKey::from(*public_key) - } -} - -impl From for PublicKey { - fn from(verifying_key: VerifyingKey) -> PublicKey { - verifying_key.inner.into() - } -} - -impl From<&VerifyingKey> for PublicKey { - fn from(verifying_key: &VerifyingKey) -> PublicKey { - verifying_key.inner.into() - } -} - -impl From> for VerifyingKey { - fn from(verifying_key: ecdsa_core::VerifyingKey) -> VerifyingKey { - VerifyingKey { - inner: verifying_key, - } - } -} - -impl From<&VerifyingKey> for EncodedPoint { - fn from(verifying_key: &VerifyingKey) -> EncodedPoint { - verifying_key.to_encoded_point(true) - } -} - -impl ToEncodedPoint for VerifyingKey { - fn to_encoded_point(&self, compress: bool) -> EncodedPoint { - self.inner.to_encoded_point(compress) - } -} - -impl TryFrom for VerifyingKey { - type Error = Error; - - fn try_from(affine_point: AffinePoint) -> Result { - let inner = PublicKey::try_from(affine_point) - .map_err(|_| Error::new())? - .into(); - - Ok(VerifyingKey { inner }) - } -} - -impl TryFrom<&AffinePoint> for VerifyingKey { - type Error = Error; - - fn try_from(affine_point: &AffinePoint) -> Result { - VerifyingKey::try_from(*affine_point) - } -} - -impl TryFrom<&EncodedPoint> for VerifyingKey { - type Error = Error; - - fn try_from(encoded_point: &EncodedPoint) -> Result { - Self::from_encoded_point(encoded_point) - } -} - -impl From for ProjectivePoint { - fn from(verifying_key: VerifyingKey) -> ProjectivePoint { - PublicKey::from(verifying_key.inner).into() - } -} - -impl From<&VerifyingKey> for ProjectivePoint { - fn from(verifying_key: &VerifyingKey) -> ProjectivePoint { - PublicKey::from(verifying_key.inner).into() - } -} - -impl TryFrom for VerifyingKey { - type Error = Error; - - fn try_from(point: ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -impl TryFrom<&ProjectivePoint> for VerifyingKey { - type Error = Error; - - fn try_from(point: &ProjectivePoint) -> Result { - AffinePoint::from(point).try_into() - } -} - -#[cfg(feature = "pkcs8")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] -impl TryFrom> for VerifyingKey { - type Error = pkcs8::spki::Error; - - fn try_from(spki: pkcs8::SubjectPublicKeyInfo<'_>) -> pkcs8::spki::Result { - PublicKey::try_from(spki).map(|pk| Self { inner: pk.into() }) - } -} - -#[cfg(feature = "pkcs8")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] -impl DecodePublicKey for VerifyingKey {} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for VerifyingKey { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_public_key_pem(s).map_err(|_| Error::new()) - } -} - -#[cfg(all(feature = "pem", feature = "serde"))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "serde"))))] -impl Serialize for VerifyingKey { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - self.inner.serialize(serializer) - } -} - -#[cfg(all(feature = "pem", feature = "serde"))] -#[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "serde"))))] -impl<'de> Deserialize<'de> for VerifyingKey { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - ecdsa_core::VerifyingKey::::deserialize(deserializer).map(Into::into) - } -} - -#[cfg(test)] -mod tests { - use super::VerifyingKey; - use crate::{test_vectors::ecdsa::ECDSA_TEST_VECTORS, Secp256k1}; - use ecdsa_core::signature::Verifier; - use hex_literal::hex; - - ecdsa_core::new_verification_test!(Secp256k1, ECDSA_TEST_VECTORS); - - /// Wycheproof tcId: 304 - #[test] - fn malleability_edge_case_valid() { - let verifying_key_bytes = hex!("043a3150798c8af69d1e6e981f3a45402ba1d732f4be8330c5164f49e10ec555b4221bd842bc5e4d97eff37165f60e3998a424d72a450cf95ea477c78287d0343a"); - let verifying_key = VerifyingKey::from_sec1_bytes(&verifying_key_bytes).unwrap(); - - let msg = hex!("313233343030"); - let sig = Signature::from_der(&hex!("304402207fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a002207fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0")).unwrap(); - assert!(sig.normalize_s().is_none()); // Ensure signature is already normalized - assert!(verifying_key.verify(&msg, &sig).is_ok()); - } -}