From fd1fbffbee37cae2d435682eaecae47f17df51a9 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 4 Feb 2022 16:15:10 +0100 Subject: [PATCH 01/15] Replace libsecp256k1 with secp256k1 --- Cargo.lock | 21 +++- Cargo.toml | 1 + client/executor/Cargo.toml | 1 - primitives/core/Cargo.toml | 3 + primitives/core/src/ecdsa.rs | 204 ++++++++++++++++------------------- primitives/io/Cargo.toml | 1 + primitives/io/src/lib.rs | 64 ++++++----- 7 files changed, 153 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af6cb045f7a6e..bc3f97f4a5fa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8203,7 +8203,6 @@ version = "0.10.0-dev" dependencies = [ "hex-literal", "lazy_static", - "libsecp256k1", "log 0.4.14", "lru 0.6.6", "parity-scale-codec", @@ -8978,6 +8977,24 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secp256k1" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7883017d5b21f011ef8040ea9c6c7ac90834c0df26a69e4c0b06276151f125" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -9611,6 +9628,7 @@ dependencies = [ "regex", "scale-info", "schnorrkel", + "secp256k1", "secrecy", "serde", "serde_json", @@ -9723,6 +9741,7 @@ dependencies = [ "log 0.4.14", "parity-scale-codec", "parking_lot 0.11.2", + "secp256k1", "sp-core", "sp-externalities", "sp-keystore", diff --git a/Cargo.toml b/Cargo.toml index 48a36419eb746..f45cc43be67f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -262,6 +262,7 @@ percent-encoding = { opt-level = 3 } primitive-types = { opt-level = 3 } ring = { opt-level = 3 } rustls = { opt-level = 3 } +secp256k1 = { opt-level = 3 } sha2 = { opt-level = 3 } sha3 = { opt-level = 3 } smallvec = { opt-level = 3 } diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 0d0f2a8bd92ed..31b3c5af2bad7 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -32,7 +32,6 @@ sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } parking_lot = "0.11.2" log = "0.4.8" -libsecp256k1 = "0.7" sp-core-hashing-proc-macro = { version = "4.0.0-dev", path = "../../primitives/core/hashing/proc-macro" } lru = "0.6.6" diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 90138ded4cb19..7a03df5564a9a 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -59,6 +59,7 @@ hex = { version = "0.4", default-features = false, optional = true } twox-hash = { version = "1.6.2", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac", "static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } +secp256k1 = { version = "0.21.2", default-features = false, optional = true, features = ["recovery"] } ss58-registry = { version = "1.11.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "5.0.0", default-features = false, path = "../runtime-interface" } @@ -112,6 +113,7 @@ std = [ "regex", "num-traits/std", "tiny-keccak", + "secp256k1/std", "sp-core-hashing/std", "sp-debug-derive/std", "sp-externalities", @@ -138,6 +140,7 @@ full_crypto = [ "sha2", "twox-hash", "libsecp256k1", + "secp256k1", "sp-core-hashing", "sp-runtime-interface/disable_target_static_assertions", "merlin", diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 4d2cae97ef14a..1d32517d37a57 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -15,9 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] -//! Simple ECDSA API. -// end::description[] +//! Simple ECDSA secp256k1 API. use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -37,14 +35,23 @@ use crate::{ #[cfg(feature = "std")] use bip39::{Language, Mnemonic, MnemonicType}; #[cfg(feature = "full_crypto")] -use core::convert::{TryFrom, TryInto}; +use core::convert::TryFrom; #[cfg(feature = "full_crypto")] -use libsecp256k1::{PublicKey, SecretKey}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, PublicKey, SecretKey, +}; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; +// Secp256k1 context construction is heavy, better to do it once. +#[cfg(feature = "full_crypto")] +lazy_static::lazy_static! { + static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); +} + /// An identifier used to match public keys against ecdsa keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); @@ -57,7 +64,17 @@ type Seed = [u8; 32]; /// The ECDSA compressed public key. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive( - Clone, Encode, Decode, PassByInner, MaxEncodedLen, TypeInfo, Eq, PartialEq, PartialOrd, Ord, + Clone, + Copy, + Encode, + Decode, + PassByInner, + MaxEncodedLen, + TypeInfo, + Eq, + PartialEq, + PartialOrd, + Ord, )] pub struct Public(pub [u8; 33]); @@ -75,10 +92,16 @@ impl Public { /// This will convert the full public key into the compressed format. #[cfg(feature = "std")] pub fn from_full(full: &[u8]) -> Result { - libsecp256k1::PublicKey::parse_slice(full, None) - .map(|k| k.serialize_compressed()) - .map(Self) - .map_err(|_| ()) + let pubkey = if full.len() == 64 { + // Tag it as uncompressed public key. + let mut tagged_full = [0u8; 65]; + tagged_full[0] = 0x04; + tagged_full[1..].copy_from_slice(full); + secp256k1::PublicKey::from_slice(&tagged_full) + } else { + secp256k1::PublicKey::from_slice(full) + }; + pubkey.map(|k| Self(k.serialize())).map_err(|_| ()) } } @@ -305,46 +328,34 @@ impl Signature { /// Recover the public key from this signature and a message. #[cfg(feature = "full_crypto")] pub fn recover>(&self, message: M) -> Option { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let sig: (_, _) = self.try_into().ok()?; - libsecp256k1::recover(&message, &sig.0, &sig.1) - .ok() - .map(|recovered| Public(recovered.serialize_compressed())) + self.recover_prehashed(&blake2_256(message.as_ref())) } /// Recover the public key from this signature and a pre-hashed message. #[cfg(feature = "full_crypto")] pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option { - let message = libsecp256k1::Message::parse(message); - - let sig: (_, _) = self.try_into().ok()?; - - libsecp256k1::recover(&message, &sig.0, &sig.1) + let rid = RecoveryId::from_i32(self.0[64] as i32).ok()?; + let sig = RecoverableSignature::from_compact(&self.0[..64], rid).ok()?; + let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); + SECP256K1 + .recover_ecdsa(&message, &sig) .ok() - .map(|key| Public(key.serialize_compressed())) + .map(|pubkey| Public(pubkey.serialize())) } } #[cfg(feature = "full_crypto")] -impl From<(libsecp256k1::Signature, libsecp256k1::RecoveryId)> for Signature { - fn from(x: (libsecp256k1::Signature, libsecp256k1::RecoveryId)) -> Signature { +impl From for Signature { + fn from(recsig: RecoverableSignature) -> Signature { let mut r = Self::default(); - r.0[0..64].copy_from_slice(&x.0.serialize()[..]); - r.0[64] = x.1.serialize(); + let (recid, sig) = recsig.serialize_compact(); + r.0[..64].copy_from_slice(&sig); + // This is safe due to the limited range of possible valid ids. + r.0[64] = recid.to_i32() as u8; r } } -#[cfg(feature = "full_crypto")] -impl<'a> TryFrom<&'a Signature> for (libsecp256k1::Signature, libsecp256k1::RecoveryId) { - type Error = (); - fn try_from( - x: &'a Signature, - ) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), Self::Error> { - parse_signature_standard(&x.0).map_err(|_| ()) - } -} - /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { @@ -366,7 +377,7 @@ pub enum DeriveError { #[cfg(feature = "full_crypto")] #[derive(Clone)] pub struct Pair { - public: PublicKey, + public: Public, secret: SecretKey, } @@ -420,8 +431,9 @@ impl TraitPair for Pair { /// You should never need to use this; generate(), generate_with_phrase fn from_seed_slice(seed_slice: &[u8]) -> Result { let secret = - SecretKey::parse_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; - let public = PublicKey::from_secret_key(&secret); + SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; + let public = PublicKey::from_secret_key(&SECP256K1, &secret); + let public = Public(public.serialize()); Ok(Pair { public, secret }) } @@ -431,7 +443,7 @@ impl TraitPair for Pair { path: Iter, _seed: Option, ) -> Result<(Pair, Option), DeriveError> { - let mut acc = self.secret.serialize(); + let mut acc = self.seed(); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -443,25 +455,19 @@ impl TraitPair for Pair { /// Get the public key. fn public(&self) -> Public { - Public(self.public.serialize_compressed()) + self.public } /// Sign a message. fn sign(&self, message: &[u8]) -> Signature { - let message = libsecp256k1::Message::parse(&blake2_256(message)); - libsecp256k1::sign(&message, &self.secret).into() + self.sign_prehashed(&blake2_256(message)) } /// Verify a signature on a message. Returns true if the signature is good. fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let sig: (_, _) = match sig.try_into() { - Ok(x) => x, - _ => return false, - }; - match libsecp256k1::recover(&message, &sig.0, &sig.1) { - Ok(actual) => pubkey.0[..] == actual.serialize_compressed()[..], - _ => false, + match sig.recover(message) { + Some(actual) => actual == *pubkey, + None => false, } } @@ -470,16 +476,12 @@ impl TraitPair for Pair { /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct /// size. Use it only if you're coming from byte buffers and need the speed. fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); if sig.len() != 65 { return false } - let (sig, ri) = match parse_signature_standard(&sig) { - Ok(sigri) => sigri, - _ => return false, - }; - match libsecp256k1::recover(&message, &sig, &ri) { - Ok(actual) => pubkey.as_ref() == &actual.serialize()[1..], + let sig = Signature::from_slice(sig); + match sig.recover(message) { + Some(actual) => actual.as_ref() == pubkey.as_ref(), _ => false, } } @@ -494,7 +496,7 @@ impl TraitPair for Pair { impl Pair { /// Get the seed for this key. pub fn seed(&self) -> Seed { - self.secret.serialize() + self.secret.serialize_secret() } /// Exactly as `from_string` except that if no matches are found then, the the first 32 @@ -511,59 +513,42 @@ impl Pair { /// Sign a pre-hashed message pub fn sign_prehashed(&self, message: &[u8; 32]) -> Signature { - let message = libsecp256k1::Message::parse(message); - libsecp256k1::sign(&message, &self.secret).into() + let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); + SECP256K1.sign_ecdsa_recoverable(&message, &self.secret).into() } /// Verify a signature on a pre-hashed message. Return `true` if the signature is valid /// and thus matches the given `public` key. pub fn verify_prehashed(sig: &Signature, message: &[u8; 32], public: &Public) -> bool { - let message = libsecp256k1::Message::parse(message); - - let sig: (_, _) = match sig.try_into() { - Ok(x) => x, - _ => return false, - }; - - match libsecp256k1::recover(&message, &sig.0, &sig.1) { - Ok(actual) => public.0[..] == actual.serialize_compressed()[..], - _ => false, + match sig.recover_prehashed(message) { + Some(actual) => actual == *public, + None => false, } } /// Verify a signature on a message. Returns true if the signature is good. - /// Parses Signature using parse_overflowing_slice + /// Parses Signature using parse_overflowing_slice. + #[deprecated(note = "please use `verify` instead")] pub fn verify_deprecated>(sig: &Signature, message: M, pubkey: &Public) -> bool { let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); - let (sig, ri) = match parse_signature_overflowing(&sig.0) { - Ok(sigri) => sigri, + + let parse_signature_overflowing = |x: [u8; 65]| { + let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64]).ok()?; + let rid = libsecp256k1::RecoveryId::parse(x[64]).ok()?; + Some((sig, rid)) + }; + + let (sig, rid) = match parse_signature_overflowing(sig.0) { + Some(sigri) => sigri, _ => return false, }; - match libsecp256k1::recover(&message, &sig, &ri) { - Ok(actual) => pubkey.0[..] == actual.serialize_compressed()[..], + match libsecp256k1::recover(&message, &sig, &rid) { + Ok(actual) => pubkey.0 == actual.serialize_compressed(), _ => false, } } } -#[cfg(feature = "full_crypto")] -fn parse_signature_standard( - x: &[u8], -) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), libsecp256k1::Error> { - let sig = libsecp256k1::Signature::parse_standard_slice(&x[..64])?; - let ri = libsecp256k1::RecoveryId::parse(x[64])?; - Ok((sig, ri)) -} - -#[cfg(feature = "full_crypto")] -fn parse_signature_overflowing( - x: &[u8], -) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), libsecp256k1::Error> { - let sig = libsecp256k1::Signature::parse_overflowing_slice(&x[..64])?; - let ri = libsecp256k1::RecoveryId::parse(x[64])?; - Ok((sig, ri)) -} - impl CryptoType for Public { #[cfg(feature = "full_crypto")] type Pair = Pair; @@ -582,12 +567,9 @@ impl CryptoType for Pair { #[cfg(test)] mod test { use super::*; - use crate::{ - crypto::{ - set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry, - DEV_PHRASE, - }, - keccak_256, + use crate::crypto::{ + set_default_ss58_version, PublicError, Ss58AddressFormat, Ss58AddressFormatRegistry, + DEV_PHRASE, }; use hex_literal::hex; use serde_json; @@ -806,22 +788,20 @@ mod test { // `msg` shouldn't be mangled let msg = [0u8; 32]; let sig1 = pair.sign_prehashed(&msg); - let sig2: Signature = - libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), &pair.secret).into(); - + let sig2: Signature = { + let message = Message::from_slice(&msg).unwrap(); + SECP256K1.sign_ecdsa_recoverable(&message, &pair.secret).into() + }; assert_eq!(sig1, sig2); // signature is actually different let sig2 = pair.sign(&msg); - assert_ne!(sig1, sig2); // using pre-hashed `msg` works - let msg = keccak_256(b"this should be hashed"); - let sig1 = pair.sign_prehashed(&msg); - let sig2: Signature = - libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), &pair.secret).into(); - + let msg = b"this should be hashed"; + let sig1 = pair.sign_prehashed(&blake2_256(msg)); + let sig2 = pair.sign(msg); assert_eq!(sig1, sig2); } @@ -830,12 +810,12 @@ mod test { let (pair, _, _) = Pair::generate_with_phrase(Some("password")); // `msg` and `sig` match - let msg = keccak_256(b"this should be hashed"); + let msg = blake2_256(b"this should be hashed"); let sig = pair.sign_prehashed(&msg); assert!(Pair::verify_prehashed(&sig, &msg, &pair.public())); // `msg` and `sig` don't match - let msg = keccak_256(b"this is a different message"); + let msg = blake2_256(b"this is a different message"); assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public())); } @@ -844,7 +824,7 @@ mod test { let (pair, _, _) = Pair::generate_with_phrase(Some("password")); // recovered key matches signing key - let msg = keccak_256(b"this should be hashed"); + let msg = blake2_256(b"this should be hashed"); let sig = pair.sign_prehashed(&msg); let key = sig.recover_prehashed(&msg).unwrap(); assert_eq!(pair.public(), key); @@ -853,7 +833,7 @@ mod test { assert!(Pair::verify_prehashed(&sig, &msg, &key)); // recovered key and signing key don't match - let msg = keccak_256(b"this is a different message"); + let msg = blake2_256(b"this is a different message"); let key = sig.recover_prehashed(&msg).unwrap(); assert_ne!(pair.public(), key); } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 207a1a23e81d9..1da98102e4490 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -30,6 +30,7 @@ sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" log = { version = "0.4.8", optional = true } futures = { version = "0.3.1", features = ["thread-pool"], optional = true } parking_lot = { version = "0.11.2", optional = true } +secp256k1 = { version = "0.21.2", default-features = false, features = [ "recovery", "alloc" ] } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.17", default-features = false} diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 76ced407090c3..2179ca2338d6a 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -66,6 +66,11 @@ use sp_runtime_interface::{ use codec::{Decode, Encode}; +use secp256k1::{ + ecdsa::{RecoverableSignature, RecoveryId}, + Message, Secp256k1, +}; + #[cfg(feature = "std")] use sp_externalities::{Externalities, ExternalitiesExt}; @@ -843,6 +848,7 @@ pub trait Crypto { /// /// Returns `true` when the verification was successful. fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { + #[allow(deprecated)] ecdsa::Pair::verify_deprecated(sig, msg, pub_key) } @@ -895,14 +901,15 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) - .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 + let rid = libsecp256k1::RecoveryId::parse( + if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8, ) .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; + let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = libsecp256k1::Message::parse(msg); + let pubkey = + libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; res.copy_from_slice(&pubkey.serialize()[1..65]); Ok(res) @@ -920,16 +927,16 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + let ctx = Secp256k1::new(); // TODO: this can be a static + + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 - ) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; + let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); + let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; - res.copy_from_slice(&pubkey.serialize()[1..65]); + res.copy_from_slice(&pubkey.serialize_uncompressed()[1..]); Ok(res) } @@ -943,14 +950,15 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) - .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 + let rid = libsecp256k1::RecoveryId::parse( + if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8, ) .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; + let sig = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let msg = libsecp256k1::Message::parse(msg); + let pubkey = + libsecp256k1::recover(&msg, &sig, &rid).map_err(|_| EcdsaVerifyError::BadSignature)?; Ok(pubkey.serialize_compressed()) } @@ -965,15 +973,15 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + let ctx = Secp256k1::new(); // TODO: this can be static + + let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) + .map_err(|_| EcdsaVerifyError::BadV)?; + let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = libsecp256k1::RecoveryId::parse( - if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8 - ) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) - .map_err(|_| EcdsaVerifyError::BadSignature)?; - Ok(pubkey.serialize_compressed()) + let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); + let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; + Ok(pubkey.serialize()) } } From e200a83fe9394a82d0dd52d64c1ab8d8c6e186b5 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 4 Feb 2022 17:55:15 +0100 Subject: [PATCH 02/15] Wipe ecdsa secret key from memory on drop --- primitives/core/src/ecdsa.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 1d32517d37a57..a80d296fd236b 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -549,6 +549,19 @@ impl Pair { } } +// The `secp256k1` backend doesn't implement cleanup for their private keys. +// Currently we should take care of wiping the secret from memory. +impl Drop for Pair { + fn drop(&mut self) { + let ptr = self.secret.as_mut_ptr(); + for off in 0..self.secret.len() { + unsafe { + core::ptr::write_volatile(ptr.add(off), 0); + } + } + } +} + impl CryptoType for Public { #[cfg(feature = "full_crypto")] type Pair = Pair; From 0ec138328d33f2be1ed50b12604dc654b948bfef Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 4 Feb 2022 18:35:01 +0100 Subject: [PATCH 03/15] Some comments for a known issue --- primitives/core/src/ecdsa.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index a80d296fd236b..818cc3d53ca40 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -551,6 +551,8 @@ impl Pair { // The `secp256k1` backend doesn't implement cleanup for their private keys. // Currently we should take care of wiping the secret from memory. +// NOTE: this is not effective when `Pair` is moved. The very same problem +// affects the other backends using `zeroize` for secret keys. impl Drop for Pair { fn drop(&mut self) { let ptr = self.secret.as_mut_ptr(); From 7165034296b07f84b4fdf2fbc922aa83914a1803 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 7 Feb 2022 12:38:46 +0100 Subject: [PATCH 04/15] Safer core crypto primitives `from_slice` constructor Previous version panics if slice lenght is not the expected one. --- primitives/core/src/ecdsa.rs | 16 ++++++++-------- primitives/core/src/ed25519.rs | 7 +++++-- primitives/core/src/sr25519.rs | 7 +++++-- primitives/io/src/lib.rs | 6 +++--- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 818cc3d53ca40..dd3c413b8f7f5 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -319,10 +319,13 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 65 { + return None + } let mut r = [0u8; 65]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// Recover the public key from this signature and a message. @@ -476,13 +479,9 @@ impl TraitPair for Pair { /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct /// size. Use it only if you're coming from byte buffers and need the speed. fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - if sig.len() != 65 { - return false - } - let sig = Signature::from_slice(sig); - match sig.recover(message) { + match Signature::from_slice(sig).and_then(|sig| sig.recover(message)) { Some(actual) => actual.as_ref() == pubkey.as_ref(), - _ => false, + None => false, } } @@ -553,6 +552,7 @@ impl Pair { // Currently we should take care of wiping the secret from memory. // NOTE: this is not effective when `Pair` is moved. The very same problem // affects the other backends using `zeroize` for secret keys. +#[cfg(feature = "full_crypto")] impl Drop for Pair { fn drop(&mut self) { let ptr = self.secret.as_mut_ptr(); diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index be7547201e84d..72afe6fa0129f 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -321,10 +321,13 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 64 { + return None + } let mut r = [0u8; 64]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// A new instance from an H512. diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 5e18e02c0ffd8..2f298fa2a2666 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -341,10 +341,13 @@ impl Signature { /// /// NOTE: No checking goes on to ensure this is a real signature. Only use it if /// you are certain that the array actually is a signature. GIGO! - pub fn from_slice(data: &[u8]) -> Self { + pub fn from_slice(data: &[u8]) -> Option { + if data.len() != 64 { + return None + } let mut r = [0u8; 64]; r.copy_from_slice(data); - Signature(r) + Some(Signature(r)) } /// A new instance from an H512. diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 2179ca2338d6a..1b94638f8ca58 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -652,7 +652,7 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| ed25519::Signature::from_slice(sig.as_slice())) + .and_then(|sig| ed25519::Signature::from_slice(&sig)) } /// Verify `ed25519` signature. @@ -776,7 +776,7 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| sr25519::Signature::from_slice(sig.as_slice())) + .and_then(|sig| sr25519::Signature::from_slice(&sig)) } /// Verify an `sr25519` signature. @@ -825,7 +825,7 @@ pub trait Crypto { SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) .ok() .flatten() - .map(|sig| ecdsa::Signature::from_slice(sig.as_slice())) + .and_then(|sig| ecdsa::Signature::from_slice(&sig)) } /// Sign the given a pre-hashed `msg` with the `ecdsa` key that corresponds to the given public From 9e79db3ee2f7439091f3323c2557e133490a2ef7 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 7 Feb 2022 13:19:18 +0100 Subject: [PATCH 05/15] Unit test fix --- client/authority-discovery/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/authority-discovery/src/tests.rs b/client/authority-discovery/src/tests.rs index 3b632d2174002..e9f94b6a186db 100644 --- a/client/authority-discovery/src/tests.rs +++ b/client/authority-discovery/src/tests.rs @@ -105,7 +105,7 @@ fn cryptos_are_compatible() { let sp_core_signature = sp_core_secret.sign(message); // no error expected... assert!(sp_core::ed25519::Pair::verify( - &sp_core::ed25519::Signature::from_slice(&libp2p_signature), + &sp_core::ed25519::Signature::from_slice(&libp2p_signature).unwrap(), message, &sp_core_public )); From 7a8c4fd23d888b92bec3fa4e53e936a72fe09c89 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 7 Feb 2022 15:30:52 +0100 Subject: [PATCH 06/15] Enable use of global secp256k1 context --- Cargo.lock | 1 + primitives/core/Cargo.toml | 4 ++-- primitives/core/src/ecdsa.rs | 10 ++-------- primitives/io/Cargo.toml | 3 ++- primitives/io/src/lib.rs | 15 ++++++++------- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc3f97f4a5fa3..eae42f1a32ae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8983,6 +8983,7 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab7883017d5b21f011ef8040ea9c6c7ac90834c0df26a69e4c0b06276151f125" dependencies = [ + "rand 0.6.5", "secp256k1-sys", ] diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 7a03df5564a9a..734dce63924bc 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -57,9 +57,9 @@ schnorrkel = { version = "0.9.1", features = [ sha2 = { version = "0.10.0", default-features = false, optional = true } hex = { version = "0.4", default-features = false, optional = true } twox-hash = { version = "1.6.2", default-features = false, optional = true } -libsecp256k1 = { version = "0.7", default-features = false, features = ["hmac", "static-context"], optional = true } +libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } -secp256k1 = { version = "0.21.2", default-features = false, optional = true, features = ["recovery"] } +secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "global-context"], optional = true } ss58-registry = { version = "1.11.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "5.0.0", default-features = false, path = "../runtime-interface" } diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index dd3c413b8f7f5..2c96f6915d889 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -39,19 +39,13 @@ use core::convert::TryFrom; #[cfg(feature = "full_crypto")] use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, - Message, PublicKey, SecretKey, + Message, PublicKey, SecretKey, SECP256K1, }; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; -// Secp256k1 context construction is heavy, better to do it once. -#[cfg(feature = "full_crypto")] -lazy_static::lazy_static! { - static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); -} - /// An identifier used to match public keys against ecdsa keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); @@ -435,7 +429,7 @@ impl TraitPair for Pair { fn from_seed_slice(seed_slice: &[u8]) -> Result { let secret = SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; - let public = PublicKey::from_secret_key(&SECP256K1, &secret); + let public = PublicKey::from_secret_key(SECP256K1, &secret); let public = Public(public.serialize()); Ok(Pair { public, secret }) } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 1da98102e4490..cd7561dc5f78f 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -30,7 +30,7 @@ sp-tracing = { version = "4.0.0", default-features = false, path = "../tracing" log = { version = "0.4.8", optional = true } futures = { version = "0.3.1", features = ["thread-pool"], optional = true } parking_lot = { version = "0.11.2", optional = true } -secp256k1 = { version = "0.21.2", default-features = false, features = [ "recovery", "alloc" ] } +secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.17", default-features = false} @@ -45,6 +45,7 @@ std = [ "sp-trie", "sp-state-machine", "libsecp256k1", + "secp256k1", "sp-runtime-interface/std", "sp-externalities", "sp-wasm-interface/std", diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 1b94638f8ca58..a91247c31c989 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -66,9 +66,10 @@ use sp_runtime_interface::{ use codec::{Decode, Encode}; +#[cfg(feature = "std")] use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, - Message, Secp256k1, + Message, SECP256K1, }; #[cfg(feature = "std")] @@ -927,14 +928,14 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let ctx = Secp256k1::new(); // TODO: this can be a static - let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) .map_err(|_| EcdsaVerifyError::BadV)?; let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); - let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; + let pubkey = SECP256K1 + .recover_ecdsa(&msg, &sig) + .map_err(|_| EcdsaVerifyError::BadSignature)?; let mut res = [0u8; 64]; res.copy_from_slice(&pubkey.serialize_uncompressed()[1..]); Ok(res) @@ -973,14 +974,14 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let ctx = Secp256k1::new(); // TODO: this can be static - let rid = RecoveryId::from_i32(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as i32) .map_err(|_| EcdsaVerifyError::BadV)?; let sig = RecoverableSignature::from_compact(&sig[..64], rid) .map_err(|_| EcdsaVerifyError::BadRS)?; let msg = Message::from_slice(msg).expect("Message is 32 bytes; qed"); - let pubkey = ctx.recover_ecdsa(&msg, &sig).map_err(|_| EcdsaVerifyError::BadSignature)?; + let pubkey = SECP256K1 + .recover_ecdsa(&msg, &sig) + .map_err(|_| EcdsaVerifyError::BadSignature)?; Ok(pubkey.serialize()) } } From 6b66969cfaf9ce12e3c3556eb59c85d1f3f3e808 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 8 Feb 2022 13:07:56 +0100 Subject: [PATCH 07/15] Better comments for ecdsa `Pair` drop --- primitives/core/src/ecdsa.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 2c96f6915d889..f6e96b089e395 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -544,8 +544,9 @@ impl Pair { // The `secp256k1` backend doesn't implement cleanup for their private keys. // Currently we should take care of wiping the secret from memory. -// NOTE: this is not effective when `Pair` is moved. The very same problem -// affects the other backends using `zeroize` for secret keys. +// NOTE: this solution is not effective when `Pair` is moved around memory. +// The very same problem affects other cryptographic backends that are just using +// `zeroize`for their secrets. #[cfg(feature = "full_crypto")] impl Drop for Pair { fn drop(&mut self) { From d3183288892096c2807fa97c4d7d462003833a69 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 8 Feb 2022 14:45:03 +0100 Subject: [PATCH 08/15] Replace `libsecp256k1` with `seco256k1` in `beefy-mmr` Used to convert ecdsa public key to ETH address --- Cargo.lock | 2 +- frame/beefy-mmr/Cargo.toml | 4 ++-- frame/beefy-mmr/src/lib.rs | 25 +++++++++++-------------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae42f1a32ae2..2608ebad0a7e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5513,7 +5513,6 @@ dependencies = [ "frame-system", "hex", "hex-literal", - "libsecp256k1", "log 0.4.14", "pallet-beefy", "pallet-mmr", @@ -5521,6 +5520,7 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", + "secp256k1", "serde", "sp-core", "sp-io", diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index cfd18b26f9c7a..7dd529eabac84 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -10,9 +10,9 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } codec = { version = "2.2.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } -libsecp256k1 = { version = "0.7.0", default-features = false } log = { version = "0.4.13", default-features = false } scale-info = { version = "1.0", default-features = false, features = ["derive"] } +secp256k1 = { version = "0.21.2", default-features = false } serde = { version = "1.0.132", optional = true } frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } @@ -43,13 +43,13 @@ std = [ "frame-support/std", "frame-system/std", "hex", - "libsecp256k1/std", "log/std", "pallet-beefy/std", "pallet-mmr-primitives/std", "pallet-mmr/std", "pallet-session/std", "serde", + "secp256k1/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index d66de51d9af21..ad801fd6a3b8c 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -70,23 +70,20 @@ where /// Convert BEEFY secp256k1 public keys into Ethereum addresses pub struct BeefyEcdsaToEthereum; + impl Convert> for BeefyEcdsaToEthereum { fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { use sp_core::crypto::ByteArray; - let compressed_key = a.as_slice(); - - libsecp256k1::PublicKey::parse_slice( - compressed_key, - Some(libsecp256k1::PublicKeyFormat::Compressed), - ) - // uncompress the key - .map(|pub_key| pub_key.serialize().to_vec()) - // now convert to ETH address - .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) - .map_err(|_| { - log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); - }) - .unwrap_or_default() + + secp256k1::PublicKey::from_slice(a.as_slice()) + // uncompress the key + .map(|pub_key| pub_key.serialize_uncompressed().to_vec()) + // now convert to ETH address + .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) + .map_err(|_| { + log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); + }) + .unwrap_or_default() } } From d0f2b0a44f59b6bddb66eb070be532772e8a6f1b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 9 Feb 2022 10:20:37 +0100 Subject: [PATCH 09/15] Replace `libsecp256k1` with `secp256k1` in FRAME `contracts`benchmarks --- Cargo.lock | 2 +- frame/contracts/Cargo.toml | 5 ++--- frame/contracts/src/benchmarking/mod.rs | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2608ebad0a7e7..e29592d88af3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5593,7 +5593,6 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "libsecp256k1", "log 0.4.14", "pallet-balances", "pallet-contracts-primitives", @@ -5606,6 +5605,7 @@ dependencies = [ "rand 0.8.4", "rand_pcg 0.3.1", "scale-info", + "secp256k1", "serde", "smallvec 1.7.0", "sp-core", diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index f24e393daa2ee..8a9dbc8179a3e 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -28,9 +28,9 @@ smallvec = { version = "1", default-features = false, features = [ wasmi-validation = { version = "0.4", default-features = false } # Only used in benchmarking to generate random contract code -libsecp256k1 = { version = "0.7", optional = true, default-features = false, features = ["hmac", "static-context"] } rand = { version = "0.8", optional = true, default-features = false } rand_pcg = { version = "0.3", optional = true } +secp256k1 = { version = "0.21.2", default-features = false, optional = true, features = ["global-context"] } # Substrate Dependencies frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } @@ -77,14 +77,13 @@ std = [ "pallet-contracts-proc-macro/full", "log/std", "rand/std", - "libsecp256k1/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "libsecp256k1", "rand", "rand_pcg", "unstable-interface", + "secp256k1", ] try-runtime = ["frame-support/try-runtime"] # Make contract callable functions marked as __unstable__ available. Do not enable diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 83be846465e47..a7cfe4fae7791 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1812,19 +1812,29 @@ benchmarks! { // It generates different private keys and signatures for the message "Hello world". seal_ecdsa_recover { let r in 0 .. API_BENCHMARK_BATCHES; - use rand::SeedableRng; + use rand::{SeedableRng, RngCore}; + use secp256k1::{SecretKey, SECP256K1, Message}; + let mut rng = rand_pcg::Pcg32::seed_from_u64(123456); let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); + let message = Message::from_slice(&message_hash).unwrap(); + let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| { - use libsecp256k1::{SecretKey, Message, sign}; + let mut random_bytes = [0; 32]; + let private_key = loop { + // It is required that the order of the key is less than the curve group order. + rng.fill_bytes(&mut random_bytes); + if let Ok(key) = SecretKey::from_slice(&random_bytes) { break key } + }; + + let sig = SECP256K1.sign_ecdsa_recoverable(&message, &private_key); + let (rid, raw) = sig.serialize_compact(); - let private_key = SecretKey::random(&mut rng); - let (signature, recovery_id) = sign(&Message::parse(&message_hash), &private_key); let mut full_signature = [0; 65]; - full_signature[..64].copy_from_slice(&signature.serialize()); - full_signature[64] = recovery_id.serialize(); + full_signature[..64].copy_from_slice(&raw); + full_signature[64] = rid.to_i32() as u8; full_signature }) .collect::>(); From 712cf19f9a5b9656a9c94e398a5d53b444a378af Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 10 Feb 2022 09:55:09 +0100 Subject: [PATCH 10/15] Temporary rollback of `beefy-mmr` to libsecp256k1 Check for detected build issues --- Cargo.lock | 2 +- frame/beefy-mmr/Cargo.toml | 6 ++++-- frame/beefy-mmr/src/lib.rs | 33 ++++++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e29592d88af3d..2139f53aca499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5513,6 +5513,7 @@ dependencies = [ "frame-system", "hex", "hex-literal", + "libsecp256k1", "log 0.4.14", "pallet-beefy", "pallet-mmr", @@ -5520,7 +5521,6 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", - "secp256k1", "serde", "sp-core", "sp-io", diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 7dd529eabac84..5e9f42eb4c4ca 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -9,10 +9,11 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } +libsecp256k1 = { version = "0.7.0", default-features = false } codec = { version = "2.2.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } log = { version = "0.4.13", default-features = false } scale-info = { version = "1.0", default-features = false, features = ["derive"] } -secp256k1 = { version = "0.21.2", default-features = false } +#secp256k1 = { version = "0.21.2", default-features = false } serde = { version = "1.0.132", optional = true } frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } @@ -43,13 +44,14 @@ std = [ "frame-support/std", "frame-system/std", "hex", + "libsecp256k1/std", "log/std", "pallet-beefy/std", "pallet-mmr-primitives/std", "pallet-mmr/std", "pallet-session/std", "serde", - "secp256k1/std", + #"secp256k1/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index ad801fd6a3b8c..03468c1e50a3b 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -75,15 +75,30 @@ impl Convert> for BeefyEcdsaToEth fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { use sp_core::crypto::ByteArray; - secp256k1::PublicKey::from_slice(a.as_slice()) - // uncompress the key - .map(|pub_key| pub_key.serialize_uncompressed().to_vec()) - // now convert to ETH address - .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) - .map_err(|_| { - log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); - }) - .unwrap_or_default() + libsecp256k1::PublicKey::parse_slice( + a.as_slice(), + Some(libsecp256k1::PublicKeyFormat::Compressed), + ) + // uncompress the key + .map(|pub_key| pub_key.serialize().to_vec()) + // now convert to ETH address + .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) + .map_err(|_| { + log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); + }) + .unwrap_or_default() + + // secp256k1::PublicKey::from_slice(a.as_slice()) + // // uncompress the key + // .map(|pub_key| pub_key.serialize_uncompressed().to_vec()) + // // now convert to ETH address + // .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) + // .map_err(|_| { + // log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); + // }) + // .unwrap_or_default() + + } } From be0ea83bde0e15c51fd170dd91a159c435c0afb2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 10 Feb 2022 11:58:58 +0100 Subject: [PATCH 11/15] Cargo fmt --- frame/beefy-mmr/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 03468c1e50a3b..c720313944c6e 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -97,8 +97,6 @@ impl Convert> for BeefyEcdsaToEth // log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); // }) // .unwrap_or_default() - - } } From 1348472f3dd743e0428806425f400e0fa0e0f821 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 10 Feb 2022 15:22:39 +0100 Subject: [PATCH 12/15] Rollback of FRAME `contracts` benchmarks to `libsecp256k1` --- Cargo.lock | 2 +- frame/beefy-mmr/Cargo.toml | 2 -- frame/beefy-mmr/src/lib.rs | 10 ---------- frame/contracts/Cargo.toml | 5 +++-- frame/contracts/src/benchmarking/mod.rs | 22 ++++++---------------- 5 files changed, 10 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 266585b457216..359e406295473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5595,6 +5595,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", + "libsecp256k1", "log 0.4.14", "pallet-balances", "pallet-contracts-primitives", @@ -5607,7 +5608,6 @@ dependencies = [ "rand 0.8.4", "rand_pcg 0.3.1", "scale-info", - "secp256k1", "serde", "smallvec 1.7.0", "sp-core", diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 9e292e2161376..1e337ab06d92e 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -13,7 +13,6 @@ libsecp256k1 = { version = "0.7.0", default-features = false } codec = { version = "2.2.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } log = { version = "0.4.13", default-features = false } scale-info = { version = "1.0", default-features = false, features = ["derive"] } -#secp256k1 = { version = "0.21.2", default-features = false } serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } @@ -51,7 +50,6 @@ std = [ "pallet-mmr/std", "pallet-session/std", "serde", - #"secp256k1/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index f0c2fa0a76bc0..a12459f2cfa19 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -87,16 +87,6 @@ impl Convert> for BeefyEcdsaToEth log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); }) .unwrap_or_default() - - // secp256k1::PublicKey::from_slice(a.as_slice()) - // // uncompress the key - // .map(|pub_key| pub_key.serialize_uncompressed().to_vec()) - // // now convert to ETH address - // .map(|uncompressed| sp_io::hashing::keccak_256(&uncompressed[1..])[12..].to_vec()) - // .map_err(|_| { - // log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); - // }) - // .unwrap_or_default() } } diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 8a9dbc8179a3e..f24e393daa2ee 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -28,9 +28,9 @@ smallvec = { version = "1", default-features = false, features = [ wasmi-validation = { version = "0.4", default-features = false } # Only used in benchmarking to generate random contract code +libsecp256k1 = { version = "0.7", optional = true, default-features = false, features = ["hmac", "static-context"] } rand = { version = "0.8", optional = true, default-features = false } rand_pcg = { version = "0.3", optional = true } -secp256k1 = { version = "0.21.2", default-features = false, optional = true, features = ["global-context"] } # Substrate Dependencies frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } @@ -77,13 +77,14 @@ std = [ "pallet-contracts-proc-macro/full", "log/std", "rand/std", + "libsecp256k1/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", + "libsecp256k1", "rand", "rand_pcg", "unstable-interface", - "secp256k1", ] try-runtime = ["frame-support/try-runtime"] # Make contract callable functions marked as __unstable__ available. Do not enable diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 50aa37f725f1f..ed9303adb1242 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1921,29 +1921,19 @@ benchmarks! { // It generates different private keys and signatures for the message "Hello world". seal_ecdsa_recover { let r in 0 .. API_BENCHMARK_BATCHES; - use rand::{SeedableRng, RngCore}; - use secp256k1::{SecretKey, SECP256K1, Message}; + use rand::SeedableRng; + use libsecp256k1::{SecretKey, Message, sign}; let mut rng = rand_pcg::Pcg32::seed_from_u64(123456); - let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); - let message = Message::from_slice(&message_hash).unwrap(); let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| { - let mut random_bytes = [0; 32]; - let private_key = loop { - // It is required that the order of the key is less than the curve group order. - rng.fill_bytes(&mut random_bytes); - if let Ok(key) = SecretKey::from_slice(&random_bytes) { break key } - }; - - let sig = SECP256K1.sign_ecdsa_recoverable(&message, &private_key); - let (rid, raw) = sig.serialize_compact(); - + let private_key = SecretKey::random(&mut rng); + let (signature, recovery_id) = sign(&Message::parse(&message_hash), &private_key); let mut full_signature = [0; 65]; - full_signature[..64].copy_from_slice(&raw); - full_signature[64] = rid.to_i32() as u8; + full_signature[..64].copy_from_slice(&signature.serialize()); + full_signature[64] = recovery_id.serialize(); full_signature }) .collect::>(); From 548e8b66af173e2642b18796868b2e28603eb909 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 14 Feb 2022 09:49:14 +0100 Subject: [PATCH 13/15] Rollback for unrelated changes --- frame/beefy-mmr/Cargo.toml | 2 +- frame/beefy-mmr/src/lib.rs | 4 ++-- frame/contracts/src/benchmarking/mod.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 1e337ab06d92e..f381d4c975776 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -9,8 +9,8 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } -libsecp256k1 = { version = "0.7.0", default-features = false } codec = { version = "2.2.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } +libsecp256k1 = { version = "0.7.0", default-features = false } log = { version = "0.4.13", default-features = false } scale-info = { version = "1.0", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index a12459f2cfa19..7896520c95b7a 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -70,13 +70,13 @@ where /// Convert BEEFY secp256k1 public keys into Ethereum addresses pub struct BeefyEcdsaToEthereum; - impl Convert> for BeefyEcdsaToEthereum { fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { use sp_core::crypto::ByteArray; + let compressed_ley = a.as_slice(); libsecp256k1::PublicKey::parse_slice( - a.as_slice(), + compressed_key, Some(libsecp256k1::PublicKeyFormat::Compressed), ) // uncompress the key diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index ed9303adb1242..88405eba44205 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1922,13 +1922,13 @@ benchmarks! { seal_ecdsa_recover { let r in 0 .. API_BENCHMARK_BATCHES; use rand::SeedableRng; - use libsecp256k1::{SecretKey, Message, sign}; - let mut rng = rand_pcg::Pcg32::seed_from_u64(123456); - let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); + let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) .map(|i| { + use libsecp256k1::{SecretKey, Message, sign}; + let private_key = SecretKey::random(&mut rng); let (signature, recovery_id) = sign(&Message::parse(&message_hash), &private_key); let mut full_signature = [0; 65]; From 8e231061bf211ac0806982aebda3a61a4e50338a Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 14 Feb 2022 09:51:42 +0100 Subject: [PATCH 14/15] Typo fix --- frame/beefy-mmr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 7896520c95b7a..38d0a6ac9a7f8 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -73,7 +73,7 @@ pub struct BeefyEcdsaToEthereum; impl Convert> for BeefyEcdsaToEthereum { fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { use sp_core::crypto::ByteArray; - let compressed_ley = a.as_slice(); + let compressed_key = a.as_slice(); libsecp256k1::PublicKey::parse_slice( compressed_key, From 0ec0bd237954ae8d98be61095f9060f5392c514b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 15 Feb 2022 14:24:02 +0100 Subject: [PATCH 15/15] Add comments for deprecated `ecdsa_verify` and `secp256k1_ecdsa_recover` --- primitives/io/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index e3a51f2a173a9..9f49a098b6add 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -848,6 +848,7 @@ pub trait Crypto { /// Verify `ecdsa` signature. /// /// Returns `true` when the verification was successful. + /// This version is able to handle, non-standard, overflowing signatures. fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { #[allow(deprecated)] ecdsa::Pair::verify_deprecated(sig, msg, pub_key) @@ -898,6 +899,7 @@ pub trait Crypto { /// /// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey /// (doesn't include the 0x04 prefix). + /// This version is able to handle, non-standard, overflowing signatures. fn secp256k1_ecdsa_recover( sig: &[u8; 65], msg: &[u8; 32],