Skip to content

Commit

Permalink
k256: add keccak256 feature (#142)
Browse files Browse the repository at this point in the history
Ethereum-style signatures (`k256::ecdsa::recoverable::Signature`) use
Keccak256.

This commit adds a `keccak256` feature which can be used to compute
these signatures using Keccak256 automatically.
  • Loading branch information
tarcieri authored Aug 26, 2020
1 parent 572ccbb commit dd701d0
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 16 deletions.
26 changes: 26 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cfg-if = "0.1"
ecdsa-core = { version = "0.7", package = "ecdsa", optional = true, default-features = false }
elliptic-curve = { version = "0.5", default-features = false, features = ["weierstrass"] }
sha2 = { version = "0.9", optional = true, default-features = false }
sha3 = { version = "0.9", optional = true, default-features = false }

[dev-dependencies]
criterion = "0.3"
Expand All @@ -32,10 +33,11 @@ default = ["arithmetic", "oid", "std"]
arithmetic = []
digest = ["ecdsa-core/digest"]
ecdh = ["elliptic-curve/ecdh", "rand", "zeroize"]
ecdsa = ["arithmetic", "ecdsa-core/signer", "ecdsa-core/verifier", "rand", "sha256", "zeroize"]
ecdsa = ["arithmetic", "digest", "ecdsa-core/signer", "ecdsa-core/verifier", "rand", "zeroize"]
endomorphism-mul = []
field-montgomery = []
force-32-bit = []
keccak256 = ["digest", "sha3"]
oid = ["elliptic-curve/oid"]
rand = ["elliptic-curve/rand"]
sha256 = ["digest", "sha2"]
Expand Down
3 changes: 3 additions & 0 deletions k256/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ mod verifier;

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

#[cfg(feature = "digest")]
pub use ecdsa_core::signature::digest;

#[cfg(feature = "ecdsa")]
pub use self::{signer::Signer, verifier::Verifier};

Expand Down
45 changes: 34 additions & 11 deletions k256/src/ecdsa/recoverable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
//! // `Signer` has many impls of the `RandomizedSigner` trait (for both
//! // regular and recoverable signature types).
//! let signature: recoverable::Signature = signer.sign_with_rng(&mut OsRng, message);
//! let recovered_pubkey = signature.recover_pubkey(message).expect("couldn't recover pubkey");
//! let recovered_pubkey = signature.recover_public_key(message).expect("couldn't recover pubkey");
//!
//! assert_eq!(&public_key, &recovered_pubkey);
//! # }
Expand All @@ -40,8 +40,12 @@ use core::{
use ecdsa_core::{signature::Signature as _, Error};

#[cfg(feature = "ecdsa")]
use crate::arithmetic::{
field::FieldElement, scalar::Scalar, AffinePoint, ProjectivePoint, CURVE_EQUATION_B,
use crate::{
arithmetic::{
field::FieldElement, scalar::Scalar, AffinePoint, ProjectivePoint, CURVE_EQUATION_B,
},
ecdsa::digest::Digest,
elliptic_curve::consts::U32,
};

#[cfg(feature = "ecdsa")]
Expand All @@ -53,8 +57,8 @@ use elliptic_curve::{
#[cfg(any(feature = "ecdsa", docsrs))]
use crate::EncodedPoint;

#[cfg(feature = "ecdsa")]
use sha2::{Digest, Sha256};
#[cfg(feature = "keccak256")]
use sha3::Keccak256;

/// Size of an Ethereum-style recoverable signature in bytes
pub const SIZE: usize = 65;
Expand Down Expand Up @@ -107,7 +111,7 @@ impl Signature {
for recovery_id in 0..=1 {
let recoverable_signature = Signature::new(&signature, Id(recovery_id));

if let Ok(recovered_key) = recoverable_signature.recover_pubkey(msg) {
if let Ok(recovered_key) = recoverable_signature.recover_public_key(msg) {
if public_key == &recovered_key {
return Ok(recoverable_signature);
}
Expand All @@ -117,14 +121,26 @@ impl Signature {
Err(Error::new())
}

/// Recover the [`EncodedPoint`] used to create the given signature
/// Recover the public key used to create the given signature as an
/// [`EncodedPoint`].
#[cfg(all(feature = "ecdsa", feature = "keccak256"))]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")), doc(cfg(feature = "keccak256")))]
pub fn recover_public_key(&self, msg: &[u8]) -> Result<EncodedPoint, Error> {
self.recover_public_key_from_prehash(Keccak256::new().chain(msg))
}

/// Recover the public key used to create the given signature as an
/// [`EncodedPoint`] from the provided precomputed [`Digest`].
#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
#[allow(non_snake_case, clippy::many_single_char_names)]
pub fn recover_pubkey(&self, msg: &[u8]) -> Result<EncodedPoint, Error> {
pub fn recover_public_key_from_prehash<D>(&self, msg_prehash: D) -> Result<EncodedPoint, Error>
where
D: Digest<OutputSize = U32>,
{
let r = self.r()?;
let s = self.s()?;
let z = Scalar::from_digest(Sha256::new().chain(msg));
let z = Scalar::from_digest(msg_prehash);
let x = FieldElement::from_bytes(&r.to_bytes());

let pk = x.and_then(|x| {
Expand Down Expand Up @@ -234,6 +250,11 @@ impl From<Signature> for super::Signature {
}
}

#[cfg(feature = "keccak256")]
impl ecdsa_core::signature::PrehashSignature for Signature {
type Digest = Keccak256;
}

/// Identifier used to compute a [`EncodedPoint`] from a [`Signature`].
///
/// In practice these values are always either `0` or `1`, and indicate
Expand Down Expand Up @@ -278,11 +299,12 @@ impl From<Id> for u8 {
}
}

#[cfg(all(test, feature = "ecdsa"))]
#[cfg(all(test, feature = "ecdsa", feature = "sha256"))]
mod tests {
use super::Signature;
use core::convert::TryFrom;
use hex_literal::hex;
use sha2::{Digest, Sha256};

/// Signature recovery test vectors
struct TestVector {
Expand Down Expand Up @@ -316,7 +338,8 @@ mod tests {
fn public_key_recovery() {
for vector in VECTORS {
let sig = Signature::try_from(&vector.sig[..]).unwrap();
let pk = sig.recover_pubkey(vector.msg).unwrap();
let prehash = Sha256::new().chain(vector.msg);
let pk = sig.recover_public_key_from_prehash(prehash).unwrap();
assert_eq!(&vector.pk[..], pk.as_bytes());
}
}
Expand Down
15 changes: 11 additions & 4 deletions k256/src/ecdsa/signer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! ECDSA signer
use super::{recoverable, Error, Signature};
use crate::{ElementBytes, EncodedPoint, ProjectivePoint, Scalar, Secp256k1, SecretKey};
use crate::{
ecdsa::digest::Digest, ElementBytes, EncodedPoint, ProjectivePoint, Scalar, Secp256k1,
SecretKey,
};
use core::borrow::Borrow;
use ecdsa_core::{hazmat::RecoverableSignPrimitive, signature::RandomizedSigner};
use elliptic_curve::{
Expand All @@ -10,7 +13,9 @@ use elliptic_curve::{
zeroize::Zeroizing,
FromBytes, Generate,
};
use sha2::{Digest, Sha256};

#[cfg(feature = "keccak256")]
use sha3::Keccak256;

#[cfg(debug_assertions)]
use crate::{ecdsa::signature::Verifier as _, ecdsa::Verifier};
Expand Down Expand Up @@ -42,6 +47,7 @@ impl Signer {
}
}

#[cfg(feature = "sha256")]
impl RandomizedSigner<Signature> for Signer {
fn try_sign_with_rng(
&self,
Expand All @@ -61,6 +67,7 @@ impl RandomizedSigner<Signature> for Signer {
}
}

#[cfg(feature = "keccak256")]
impl RandomizedSigner<recoverable::Signature> for Signer {
fn try_sign_with_rng(
&self,
Expand All @@ -69,13 +76,13 @@ impl RandomizedSigner<recoverable::Signature> for Signer {
) -> Result<recoverable::Signature, Error> {
let d = Scalar::from_bytes(self.secret_key.as_bytes()).unwrap();
let k = Zeroizing::new(Scalar::generate(rng));
let z = Sha256::digest(msg);
let z = Keccak256::digest(msg);
let signature = d.try_sign_recoverable_prehashed(&*k, &z)?;

#[cfg(debug_assertions)]
assert_eq!(
self.public_key,
signature.recover_pubkey(msg).expect("recovery failed")
signature.recover_public_key(msg).expect("recovery failed")
);

Ok(signature)
Expand Down

0 comments on commit dd701d0

Please sign in to comment.