diff --git a/Cargo.lock b/Cargo.lock index 6cb440433f204..49b3dd3cf957b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -538,6 +538,7 @@ dependencies = [ "sp-api", "sp-application-crypto", "sp-core", + "sp-io", "sp-keystore", "sp-mmr-primitives", "sp-runtime", diff --git a/client/beefy/src/keystore.rs b/client/beefy/src/keystore.rs index b0259a42075ea..886c00fc5d817 100644 --- a/client/beefy/src/keystore.rs +++ b/client/beefy/src/keystore.rs @@ -19,12 +19,13 @@ use sp_application_crypto::RuntimeAppPublic; use sp_core::keccak_256; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_runtime::traits::Keccak256; use log::warn; use beefy_primitives::{ crypto::{Public, Signature}, - KEY_TYPE, + BeefyVerify, KEY_TYPE, }; use crate::error; @@ -98,11 +99,7 @@ impl BeefyKeystore { /// /// Return `true` if the signature is authentic, `false` otherwise. pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { - let msg = keccak_256(message); - let sig = sig.as_ref(); - let public = public.as_ref(); - - sp_core::ecdsa::Pair::verify_prehashed(sig, &msg, public) + BeefyVerify::::verify(sig, message, public) } } diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 5b82c89ce84b6..0b7fc22cd279b 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -73,12 +73,8 @@ where /// Convert BEEFY secp256k1 public keys into Ethereum addresses pub struct BeefyEcdsaToEthereum; impl Convert> for BeefyEcdsaToEthereum { - fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { - sp_core::ecdsa::Public::try_from(a.as_ref()) - .map_err(|_| { - log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); - }) - .unwrap_or(sp_core::ecdsa::Public::from_raw([0u8; 33])) + fn convert(beefy_id: beefy_primitives::crypto::AuthorityId) -> Vec { + sp_core::ecdsa::Public::from(beefy_id) .to_eth_address() .map(|v| v.to_vec()) .map_err(|_| { diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index fe6ce23337c86..f85e2edf4f442 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } +sp-io = { version = "6.0.0", default-features = false, path = "../io" } sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } @@ -34,6 +35,7 @@ std = [ "sp-api/std", "sp-application-crypto/std", "sp-core/std", + "sp-io/std", "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs index 705366e1b4778..453eb67315d4e 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/beefy/src/lib.rs @@ -41,12 +41,30 @@ pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; use codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; +use sp_application_crypto::RuntimeAppPublic; use sp_core::H256; +use sp_runtime::traits::Hash; use sp_std::prelude::*; /// Key type for BEEFY module. pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"beef"); +/// Trait representing BEEFY authority id. +pub trait BeefyAuthorityId: RuntimeAppPublic {} + +/// Means of verification for a BEEFY authority signature. +/// +/// Accepts custom hashing fn for the message and custom convertor fn for the signer. +pub trait BeefyVerify { + /// Type of the signer. + type Signer: BeefyAuthorityId; + + /// Verify a signature. + /// + /// Return `true` if signature is valid for the value. + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool; +} + /// BEEFY cryptographic types /// /// This module basically introduces three crypto types: @@ -60,7 +78,9 @@ pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::Ke /// The current underlying crypto scheme used is ECDSA. This can be changed, /// without affecting code restricted against the above listed crypto types. pub mod crypto { + use super::{BeefyAuthorityId, BeefyVerify, Hash}; use sp_application_crypto::{app_crypto, ecdsa}; + use sp_core::crypto::Wraps; app_crypto!(ecdsa, crate::KEY_TYPE); /// Identity of a BEEFY authority using ECDSA as its crypto. @@ -68,6 +88,26 @@ pub mod crypto { /// Signature for a BEEFY authority using ECDSA as its crypto. pub type AuthoritySignature = Signature; + + impl BeefyAuthorityId for AuthorityId {} + + impl BeefyVerify for AuthoritySignature + where + ::Output: Into<[u8; 32]>, + { + type Signer = AuthorityId; + + fn verify(&self, msg: &[u8], signer: &Self::Signer) -> bool { + let msg_hash = ::hash(msg).into(); + match sp_io::crypto::secp256k1_ecdsa_recover_compressed( + self.as_inner_ref().as_ref(), + &msg_hash, + ) { + Ok(raw_pubkey) => raw_pubkey.as_ref() == AsRef::<[u8]>::as_ref(signer), + _ => false, + } + } + } } /// The `ConsensusEngineId` of BEEFY. @@ -180,7 +220,8 @@ sp_api::decl_runtime_apis! { mod tests { use super::*; use sp_application_crypto::ecdsa::{self, Public}; - use sp_core::Pair; + use sp_core::{blake2_256, crypto::Wraps, keccak_256, Pair}; + use sp_runtime::traits::{BlakeTwo256, Keccak256}; #[test] fn validator_set() { @@ -194,4 +235,36 @@ mod tests { assert_eq!(validators.id(), set_id); assert_eq!(validators.validators(), &vec![alice.public()]); } + + #[test] + fn beefy_verify_works() { + let msg = &b"test-message"[..]; + let (pair, _) = crypto::Pair::generate(); + + let keccak_256_signature: crypto::Signature = + pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into(); + + let blake2_256_signature: crypto::Signature = + pair.as_inner_ref().sign_prehashed(&blake2_256(msg)).into(); + + // Verification works if same hashing function is used when signing and verifying. + assert!(BeefyVerify::::verify(&keccak_256_signature, msg, &pair.public())); + assert!(BeefyVerify::::verify(&blake2_256_signature, msg, &pair.public())); + // Verification fails if distinct hashing functions are used when signing and verifying. + assert!(!BeefyVerify::::verify(&blake2_256_signature, msg, &pair.public())); + assert!(!BeefyVerify::::verify(&keccak_256_signature, msg, &pair.public())); + + // Other public key doesn't work + let (other_pair, _) = crypto::Pair::generate(); + assert!(!BeefyVerify::::verify( + &keccak_256_signature, + msg, + &other_pair.public() + )); + assert!(!BeefyVerify::::verify( + &blake2_256_signature, + msg, + &other_pair.public() + )); + } } diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index db855620a8f0d..06703acea7202 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -984,6 +984,11 @@ pub trait IsWrappedBy: From + Into { pub trait Wraps: Sized { /// The inner type it is wrapping. type Inner: IsWrappedBy; + + /// Get a reference to the inner type that is wrapped. + fn as_inner_ref(&self) -> &Self::Inner { + Self::Inner::from_ref(self) + } } impl IsWrappedBy for T