diff --git a/Cargo.lock b/Cargo.lock index eb55f6db2d7cd..71714eff5b73f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "Inflector" version = "0.11.4" @@ -1862,6 +1864,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -2329,6 +2341,17 @@ dependencies = [ "hmac 0.7.1", ] +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.4", + "hmac 0.8.1", +] + [[package]] name = "honggfuzz" version = "0.5.52" @@ -2984,7 +3007,7 @@ dependencies = [ "futures 0.3.12", "futures-timer 3.0.2", "lazy_static", - "libsecp256k1", + "libsecp256k1 0.3.5", "log", "multihash", "multistream-select", @@ -3360,13 +3383,61 @@ dependencies = [ "arrayref", "crunchy", "digest 0.8.1", - "hmac-drbg", + "hmac-drbg 0.2.0", "rand 0.7.3", "sha2 0.8.2", "subtle 2.4.0", "typenum", ] +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg 0.3.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.3", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle 2.4.0", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libz-sys" version = "1.1.2" @@ -5332,12 +5403,13 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111e193c96758d476d272093a853882668da17489f76bf4361b8decae0b6c515" +checksum = "2e337f62db341435f0da05b8f6b97e984ef4ea5800510cd07c2d624688c40b47" dependencies = [ "blake2-rfc", "crc32fast", + "fs2", "hex", "libc", "log", @@ -7004,7 +7076,7 @@ dependencies = [ "derive_more", "hex-literal", "lazy_static", - "libsecp256k1", + "libsecp256k1 0.6.0", "log", "parity-scale-codec", "parity-wasm 0.41.0", @@ -8395,7 +8467,7 @@ dependencies = [ "hex-literal", "impl-serde", "lazy_static", - "libsecp256k1", + "libsecp256k1 0.6.0", "log", "merlin", "num-traits", @@ -8498,7 +8570,7 @@ version = "3.0.0" dependencies = [ "futures 0.3.12", "hash-db", - "libsecp256k1", + "libsecp256k1 0.6.0", "log", "parity-scale-codec", "parking_lot 0.11.1", diff --git a/Cargo.toml b/Cargo.toml index 38b3a2bdcf296..4bc97c7f64208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -208,8 +208,6 @@ members = [ # # This list is ordered alphabetically. [profile.dev.package] -aes-soft = { opt-level = 3 } -aesni = { opt-level = 3 } blake2 = { opt-level = 3 } blake2-rfc = { opt-level = 3 } blake2b_simd = { opt-level = 3 } diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index e0b21b7fb6652..85e38708e6a38 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -35,7 +35,7 @@ sc-executor-wasmi = { version = "0.9.0", path = "wasmi" } sc-executor-wasmtime = { version = "0.9.0", path = "wasmtime", optional = true } parking_lot = "0.11.1" log = "0.4.8" -libsecp256k1 = "0.3.4" +libsecp256k1 = "0.6" [dev-dependencies] assert_matches = "1.3.0" diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 3d9cf1287e051..2ca61447e1d58 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -49,7 +49,7 @@ schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backen sha2 = { version = "0.9.2", default-features = false, optional = true } hex = { version = "0.4", default-features = false, optional = true } twox-hash = { version = "1.5.0", default-features = false, optional = true } -libsecp256k1 = { version = "0.3.2", default-features = false, features = ["hmac"], optional = true } +libsecp256k1 = { version = "0.6", default-features = false, features = ["hmac", "static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } sp-runtime-interface = { version = "3.0.0", default-features = false, path = "../runtime-interface" } diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index fc9b16beedd13..98cc1866b47c4 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -23,7 +23,7 @@ use sp_std::vec::Vec; use sp_std::cmp::Ordering; -use codec::{Encode, Decode}; +use codec::{Decode, Encode}; #[cfg(feature = "full_crypto")] use core::convert::{TryFrom, TryInto}; @@ -40,7 +40,7 @@ use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; use sp_runtime_interface::pass_by::PassByInner; #[cfg(feature = "full_crypto")] -use secp256k1::{PublicKey, SecretKey}; +use libsecp256k1::{PublicKey, SecretKey}; /// An identifier used to match public keys against ecdsa keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); @@ -103,7 +103,7 @@ impl Public { /// This will convert the full public key into the compressed format. #[cfg(feature = "std")] pub fn from_full(full: &[u8]) -> Result { - secp256k1::PublicKey::parse_slice(full, None) + libsecp256k1::PublicKey::parse_slice(full, None) .map(|k| k.serialize_compressed()) .map(Self) .map_err(|_| ()) @@ -348,17 +348,29 @@ 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 = secp256k1::Message::parse(&blake2_256(message.as_ref())); + let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); let sig: (_, _) = self.try_into().ok()?; - secp256k1::recover(&message, &sig.0, &sig.1) + libsecp256k1::recover(&message, &sig.0, &sig.1) .ok() .map(|recovered| Public(recovered.serialize_compressed())) } + + /// 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) + .ok() + .map(|key| Public(key.serialize_compressed())) + } } #[cfg(feature = "full_crypto")] -impl From<(secp256k1::Signature, secp256k1::RecoveryId)> for Signature { - fn from(x: (secp256k1::Signature, secp256k1::RecoveryId)) -> Signature { +impl From<(libsecp256k1::Signature, libsecp256k1::RecoveryId)> for Signature { + fn from(x: (libsecp256k1::Signature, libsecp256k1::RecoveryId)) -> Signature { let mut r = Self::default(); r.0[0..64].copy_from_slice(&x.0.serialize()[..]); r.0[64] = x.1.serialize(); @@ -367,13 +379,10 @@ impl From<(secp256k1::Signature, secp256k1::RecoveryId)> for Signature { } #[cfg(feature = "full_crypto")] -impl<'a> TryFrom<&'a Signature> for (secp256k1::Signature, secp256k1::RecoveryId) { +impl<'a> TryFrom<&'a Signature> for (libsecp256k1::Signature, libsecp256k1::RecoveryId) { type Error = (); - fn try_from(x: &'a Signature) -> Result<(secp256k1::Signature, secp256k1::RecoveryId), Self::Error> { - Ok(( - secp256k1::Signature::parse_slice(&x.0[0..64]).expect("hardcoded to 64 bytes; qed"), - secp256k1::RecoveryId::parse(x.0[64]).map_err(|_| ())?, - )) + fn try_from(x: &'a Signature) -> Result<(libsecp256k1::Signature, libsecp256k1::RecoveryId), Self::Error> { + parse_signature_standard(&x.0).map_err(|_| ()) } } @@ -428,7 +437,7 @@ impl TraitPair for Pair { /// Generate key pair from given recovery phrase and password. #[cfg(feature = "std")] fn from_phrase(phrase: &str, password: Option<&str>) -> Result<(Pair, Seed), SecretStringError> { - let big_seed = seed_from_entropy( + let big_seed = substrate_bip39::seed_from_entropy( Mnemonic::from_phrase(phrase, Language::English) .map_err(|_| SecretStringError::InvalidPhrase)?.entropy(), password.unwrap_or(""), @@ -478,16 +487,16 @@ impl TraitPair for Pair { /// Sign a message. fn sign(&self, message: &[u8]) -> Signature { - let message = secp256k1::Message::parse(&blake2_256(message)); - secp256k1::sign(&message, &self.secret).into() + let message = libsecp256k1::Message::parse(&blake2_256(message)); + libsecp256k1::sign(&message, &self.secret).into() } /// 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 = secp256k1::Message::parse(&blake2_256(message.as_ref())); + let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); let sig: (_, _) = match sig.try_into() { Ok(x) => x, _ => return false }; - match secp256k1::recover(&message, &sig.0, &sig.1) { - Ok(actual) => &pubkey.0[..] == &actual.serialize_compressed()[..], + match libsecp256k1::recover(&message, &sig.0, &sig.1) { + Ok(actual) => pubkey.0[..] == actual.serialize_compressed()[..], _ => false, } } @@ -497,11 +506,13 @@ 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 = secp256k1::Message::parse(&blake2_256(message.as_ref())); + let message = libsecp256k1::Message::parse(&blake2_256(message.as_ref())); if sig.len() != 65 { return false } - let ri = match secp256k1::RecoveryId::parse(sig[64]) { Ok(x) => x, _ => return false }; - let sig = match secp256k1::Signature::parse_slice(&sig[0..64]) { Ok(x) => x, _ => return false }; - match secp256k1::recover(&message, &sig, &ri) { + 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..], _ => false, } @@ -531,6 +542,60 @@ impl Pair { Self::from_seed(&padded_seed) }) } + + /// 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() + } + + /// 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, + } + } + + /// Verify a signature on a message. Returns true if the signature is good. + /// Parses Signature using parse_overflowing_slice + 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, + _ => return false, + }; + match libsecp256k1::recover(&message, &sig, &ri) { + 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 { @@ -761,4 +826,63 @@ mod test { // Poorly-sized assert!(deserialize_signature("\"abc123\"").is_err()); } + + #[test] + fn sign_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // `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(); + + 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(); + + assert_eq!(sig1, sig2); + } + + #[test] + fn verify_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // `msg` and `sig` match + let msg = keccak_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"); + assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public())); + } + + #[test] + fn recover_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // recovered key matches signing key + let msg = keccak_256(b"this should be hashed"); + let sig = pair.sign_prehashed(&msg); + let key = sig.recover_prehashed(&msg).unwrap(); + assert_eq!(pair.public(), key); + + // recovered key is useable + 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 key = sig.recover_prehashed(&msg).unwrap(); + assert_ne!(pair.public(), key); + } } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index f87711b17234f..2c43e1e2096ef 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -20,7 +20,7 @@ hash-db = { version = "0.15.2", default-features = false } sp-core = { version = "3.0.0", default-features = false, path = "../core" } sp-keystore = { version = "0.9.0", default-features = false, optional = true, path = "../keystore" } sp-std = { version = "3.0.0", default-features = false, path = "../std" } -libsecp256k1 = { version = "0.3.4", optional = true } +libsecp256k1 = { version = "0.6", optional = true } sp-state-machine = { version = "0.9.0", optional = true, path = "../state-machine" } sp-wasm-interface = { version = "3.0.0", path = "../wasm-interface", default-features = false } sp-runtime-interface = { version = "3.0.0", default-features = false, path = "../runtime-interface" } diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 397dd3c21712a..1fa16c975e22c 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -653,11 +653,15 @@ pub trait Crypto { /// Verify `ecdsa` signature. /// /// Returns `true` when the verification was successful. - fn ecdsa_verify( - sig: &ecdsa::Signature, - msg: &[u8], - pub_key: &ecdsa::Public, - ) -> bool { + fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { + ecdsa::Pair::verify_deprecated(sig, msg, pub_key) + } + + /// Verify `ecdsa` signature. + /// + /// Returns `true` when the verification was successful. + #[version(2)] + fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { ecdsa::Pair::verify(sig, msg, pub_key) } @@ -691,32 +695,83 @@ pub trait Crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 64], EcdsaVerifyError> { - let rs = secp256k1::Signature::parse_slice(&sig[0..64]) + let rs = libsecp256k1::Signature::parse_overflowing_slice(&sig[0..64]) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = secp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = secp256k1::recover(&secp256k1::Message::parse(msg), &rs, &v) + 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 mut res = [0u8; 64]; res.copy_from_slice(&pubkey.serialize()[1..65]); Ok(res) } + /// Verify and recover a SECP256k1 ECDSA signature. + /// + /// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`. + /// - `msg` is the blake2-256 hash of the message. + /// + /// Returns `Err` if the signature is bad, otherwise the 64-byte pubkey + /// (doesn't include the 0x04 prefix). + #[version(2)] + fn secp256k1_ecdsa_recover( + sig: &[u8; 65], + msg: &[u8; 32], + ) -> Result<[u8; 64], EcdsaVerifyError> { + let rs = libsecp256k1::Signature::parse_standard_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 + ) + .map_err(|_| EcdsaVerifyError::BadV)?; + let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + .map_err(|_| EcdsaVerifyError::BadSignature)?; + let mut res = [0u8; 64]; + res.copy_from_slice(&pubkey.serialize()[1..65]); + Ok(res) + } + + /// Verify and recover a SECP256k1 ECDSA signature. + /// + /// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`. + /// - `msg` is the blake2-256 hash of the message. + /// + /// Returns `Err` if the signature is bad, otherwise the 33-byte compressed pubkey. + fn secp256k1_ecdsa_recover_compressed( + 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 + ) + .map_err(|_| EcdsaVerifyError::BadV)?; + let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + .map_err(|_| EcdsaVerifyError::BadSignature)?; + Ok(pubkey.serialize_compressed()) + } + /// Verify and recover a SECP256k1 ECDSA signature. /// /// - `sig` is passed in RSV format. V should be either `0/1` or `27/28`. /// - `msg` is the blake2-256 hash of the message. /// /// Returns `Err` if the signature is bad, otherwise the 33-byte compressed pubkey. + #[version(2)] fn secp256k1_ecdsa_recover_compressed( sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - let rs = secp256k1::Signature::parse_slice(&sig[0..64]) + let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) .map_err(|_| EcdsaVerifyError::BadRS)?; - let v = secp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8) - .map_err(|_| EcdsaVerifyError::BadV)?; - let pubkey = secp256k1::recover(&secp256k1::Message::parse(msg), &rs, &v) + 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()) }