diff --git a/Cargo.lock b/Cargo.lock index db9a2ca35643..49507ecd4f93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18225,6 +18225,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", + "sp-keystore", "sp-mmr-primitives", "sp-runtime", "sp-std 14.0.0", diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 352269742787..56c38bf2e479 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -52,3 +52,12 @@ sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } sp-keyring = { path = "../../../primitives/keyring" } sp-tracing = { path = "../../../primitives/tracing" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" } + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-application-crypto/bls-experimental", + "sp-consensus-beefy/bls-experimental", + "sp-core/bls-experimental", +] diff --git a/substrate/client/consensus/beefy/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 645a10b2a1d4..8a0b0a74308f 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -485,8 +485,8 @@ pub(crate) mod tests { use sc_network_test::Block; use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE; use sp_consensus_beefy::{ - ecdsa_crypto::Signature, known_payloads, Commitment, Keyring, MmrRootHash, Payload, - SignedCommitment, VoteMessage, + ecdsa_crypto::Signature, known_payloads, test_utils::Keyring, Commitment, MmrRootHash, + Payload, SignedCommitment, VoteMessage, }; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -507,10 +507,13 @@ pub(crate) mod tests { } } - pub fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + pub fn sign_commitment( + who: &Keyring, + commitment: &Commitment, + ) -> Signature { let store = MemoryKeystore::new(); store.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store.into()).into(); + let beefy_keystore: BeefyKeystore = Some(store.into()).into(); beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() } @@ -538,7 +541,10 @@ pub(crate) mod tests { .validators() .iter() .map(|validator: &AuthorityId| { - Some(sign_commitment(&Keyring::from_public(validator).unwrap(), &commitment)) + Some(sign_commitment( + &Keyring::::from_public(validator).unwrap(), + &commitment, + )) }) .collect(); @@ -547,7 +553,7 @@ pub(crate) mod tests { #[test] fn should_validate_messages() { - let keys = vec![Keyring::Alice.public()]; + let keys = vec![Keyring::::Alice.public()]; let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); let (gv, mut report_stream) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); diff --git a/substrate/client/consensus/beefy/src/justification.rs b/substrate/client/consensus/beefy/src/justification.rs index 483184e2374a..7f1b9e5237c3 100644 --- a/substrate/client/consensus/beefy/src/justification.rs +++ b/substrate/client/consensus/beefy/src/justification.rs @@ -76,7 +76,7 @@ pub(crate) fn verify_with_validator_set( .as_ref() .map(|sig| { signatures_checked += 1; - BeefyKeystore::verify(id, sig, &message[..]) + BeefyKeystore::verify(*id, sig, &message[..]) }) .unwrap_or(false) }) @@ -93,7 +93,8 @@ pub(crate) fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { use sp_consensus_beefy::{ - known_payloads, Commitment, Keyring, Payload, SignedCommitment, VersionedFinalityProof, + known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment, + VersionedFinalityProof, }; use substrate_test_runtime_client::runtime::Block; @@ -103,7 +104,7 @@ pub(crate) mod tests { pub(crate) fn new_finality_proof( block_num: NumberFor, validator_set: &ValidatorSet, - keys: &[Keyring], + keys: &[Keyring], ) -> BeefyVersionedFinalityProof { let commitment = Commitment { payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), @@ -174,7 +175,7 @@ pub(crate) mod tests { }; // change a signature to a different key *bad_signed_commitment.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); + Some(Keyring::::Dave.sign(&bad_signed_commitment.commitment.encode())); match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { Err((ConsensusError::InvalidJustification, 3)) => (), e => assert!(false, "Got unexpected {:?}", e), diff --git a/substrate/client/consensus/beefy/src/keystore.rs b/substrate/client/consensus/beefy/src/keystore.rs index 75c44de3324c..2ddc938fbc6c 100644 --- a/substrate/client/consensus/beefy/src/keystore.rs +++ b/substrate/client/consensus/beefy/src/keystore.rs @@ -16,41 +16,45 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, RuntimeAppPublic}; +use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, AppCrypto, RuntimeAppPublic}; +use sp_consensus_beefy::{AuthorityIdBound, BeefyAuthorityId, BeefySignatureHasher}; +use sp_core::ecdsa; +#[cfg(feature = "bls-experimental")] +use sp_core::ecdsa_bls377; use sp_crypto_hashing::keccak_256; use sp_keystore::KeystorePtr; +use codec::Decode; use log::warn; - -use sp_consensus_beefy::{ - ecdsa_crypto::{Public, Signature}, - BeefyAuthorityId, -}; +use std::marker::PhantomData; use crate::{error, LOG_TARGET}; -/// Hasher used for BEEFY signatures. -pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256; - /// A BEEFY specific keystore implemented as a `Newtype`. This is basically a /// wrapper around [`sp_keystore::Keystore`] and allows to customize /// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); +pub(crate) struct BeefyKeystore( + Option, + PhantomData AuthorityId>, +); -impl BeefyKeystore { +impl BeefyKeystore { /// Check if the keystore contains a private key for one of the public keys /// contained in `keys`. A public key with a matching private key is known /// as a local authority id. /// /// Return the public key for which we also do have a private key. If no /// matching private key is found, `None` will be returned. - pub fn authority_id(&self, keys: &[Public]) -> Option { + pub fn authority_id(&self, keys: &[AuthorityId]) -> Option { let store = self.0.clone()?; // we do check for multiple private keys as a key store sanity check. - let public: Vec = keys + let public: Vec = keys .iter() - .filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)])) + .filter(|k| { + store + .has_keys(&[(::to_raw_vec(k), BEEFY_KEY_TYPE)]) + }) .cloned() .collect(); @@ -71,55 +75,125 @@ impl BeefyKeystore { /// Note that `message` usually will be pre-hashed before being signed. /// /// Return the message signature or an error in case of failure. - pub fn sign(&self, public: &Public, message: &[u8]) -> Result { + pub fn sign( + &self, + public: &AuthorityId, + message: &[u8], + ) -> Result<::Signature, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let msg = keccak_256(message); - let public = public.as_ref(); - - let sig = store - .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, public, &msg) - .map_err(|e| error::Error::Keystore(e.to_string()))? - .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; - - // check that `sig` has the expected result type - let sig = sig.clone().try_into().map_err(|_| { - error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) + // ECDSA should use ecdsa_sign_prehashed since it needs to be hashed by keccak_256 instead + // of blake2. As such we need to deal with producing the signatures case-by-case + let signature_byte_array: Vec = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let msg_hash = keccak_256(message); + let public: ecdsa::Public = ecdsa::Public::try_from(public.as_slice()).unwrap(); + + let sig = store + .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| { + error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()) + })?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let public: ecdsa_bls377::Public = + ecdsa_bls377::Public::try_from(public.as_slice()).unwrap(); + let sig = store + .ecdsa_bls377_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &message) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| error::Error::Signature("bls377_sign() failed".to_string()))?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into()))?, + }; + + //check that `sig` has the expected result type + let signature = ::Signature::decode( + &mut signature_byte_array.as_slice(), + ) + .map_err(|_| { + error::Error::Signature(format!( + "invalid signature {:?} for key {:?}", + signature_byte_array, public + )) })?; - Ok(sig) + Ok(signature) } /// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently /// supported (i.e. found in the keystore). - pub fn public_keys(&self) -> Result, error::Error> { + pub fn public_keys(&self) -> Result, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let pk: Vec = - store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect(); - - Ok(pk) + let pk = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => store + .ecdsa_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => store + .ecdsa_bls377_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into())), + }; + + pk } /// Use the `public` key to verify that `sig` is a valid signature for `message`. /// /// Return `true` if the signature is authentic, `false` otherwise. - pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { + pub fn verify( + public: &AuthorityId, + sig: &::Signature, + message: &[u8], + ) -> bool { BeefyAuthorityId::::verify(public, sig, message) } } -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { - BeefyKeystore(store) +impl From> for BeefyKeystore +where + ::Signature: Send + Sync, +{ + fn from(store: Option) -> BeefyKeystore { + BeefyKeystore(store, PhantomData) } } #[cfg(test)] pub mod tests { - use sp_consensus_beefy::{ecdsa_crypto, Keyring}; - use sp_core::{ecdsa, Pair}; - use sp_keystore::testing::MemoryKeystore; + #[cfg(feature = "bls-experimental")] + use sp_consensus_beefy::ecdsa_bls_crypto; + use sp_consensus_beefy::{ + ecdsa_crypto, + test_utils::{BeefySignerAuthority, Keyring}, + }; + use sp_core::Pair as PairT; + use sp_keystore::{testing::MemoryKeystore, Keystore}; use super::*; use crate::error::Error; @@ -128,152 +202,265 @@ pub mod tests { MemoryKeystore::new().into() } - #[test] - fn verify_should_work() { - let msg = keccak_256(b"I am Alice!"); - let sig = Keyring::Alice.sign(b"I am Alice!"); - - assert!(ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Alice.public().into(), + fn pair_verify_should_work< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let msg = b"I am Alice!"; + let sig = Keyring::::Alice.sign(b"I am Alice!"); + + assert!(>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), )); // different public key -> fail - assert!(!ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Bob.public().into(), + assert!(!>::verify( + &Keyring::Bob.public(), + &sig, + &msg.as_slice(), )); - let msg = keccak_256(b"I am not Alice!"); + let msg = b"I am not Alice!"; // different msg -> fail - assert!( - !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) - ); + assert!(!>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), + )); + } + + /// Generate key pair in the given store using the provided seed + fn generate_in_store( + store: KeystorePtr, + key_type: sp_application_crypto::KeyTypeId, + owner: Option>, + ) -> AuthorityId + where + AuthorityId: + AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + { + let optional_seed: Option = owner.map(|owner| owner.to_seed()); + + match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let pk = store.ecdsa_generate_new(key_type, optional_seed.as_deref()).ok().unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let pk = store + .ecdsa_bls377_generate_new(key_type, optional_seed.as_deref()) + .ok() + .unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + _ => panic!("Requested CRYPTO_ID is not supported by the BEEFY Keyring"), + } } #[test] - fn pair_works() { - let want = ecdsa_crypto::Pair::from_string("//Alice", None) + fn pair_verify_should_work_ecdsa() { + pair_verify_should_work::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn pair_verify_should_work_ecdsa_n_bls() { + pair_verify_should_work::(); + } + + fn pair_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let want = ::Pair::from_string("//Alice", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Alice.pair().to_raw_vec(); + let got = Keyring::::Alice.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Bob", None) + let want = ::Pair::from_string("//Bob", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Bob.pair().to_raw_vec(); + let got = Keyring::::Bob.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Charlie", None) + let want = ::Pair::from_string("//Charlie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Charlie.pair().to_raw_vec(); + let got = Keyring::::Charlie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Dave", None) + let want = ::Pair::from_string("//Dave", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Dave.pair().to_raw_vec(); + let got = Keyring::::Dave.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Eve", None) + let want = ::Pair::from_string("//Eve", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Eve.pair().to_raw_vec(); + let got = Keyring::::Eve.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Ferdie", None) + let want = ::Pair::from_string("//Ferdie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Ferdie.pair().to_raw_vec(); + let got = Keyring::::Ferdie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//One", None) + let want = ::Pair::from_string("//One", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::One.pair().to_raw_vec(); + let got = Keyring::::One.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Two", None) + let want = ::Pair::from_string("//Two", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Two.pair().to_raw_vec(); + let got = Keyring::::Two.pair().to_raw_vec(); assert_eq!(want, got); } #[test] - fn authority_id_works() { + fn ecdsa_pair_works() { + pair_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn ecdsa_n_bls_pair_works() { + pair_works::(); + } + + fn authority_id_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); + + let alice = Keyring::::Alice.public(); let bob = Keyring::Bob.public(); let charlie = Keyring::Charlie.public(); - let store: BeefyKeystore = Some(store).into(); + let beefy_store: BeefyKeystore = Some(store).into(); let mut keys = vec![bob, charlie]; - let id = store.authority_id(keys.as_slice()); + let id = beefy_store.authority_id(keys.as_slice()); assert!(id.is_none()); keys.push(alice.clone()); - let id = store.authority_id(keys.as_slice()).unwrap(); + let id = beefy_store.authority_id(keys.as_slice()).unwrap(); assert_eq!(id, alice); } #[test] - fn sign_works() { + fn authority_id_works_for_ecdsa() { + authority_id_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn authority_id_works_for_ecdsa_n_bls() { + authority_id_works::(); + } + + fn sign_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let alice = Keyring::Alice.public(); + + let store: BeefyKeystore = Some(store).into(); let msg = b"are you involved or commited?"; let sig1 = store.sign(&alice, msg).unwrap(); - let sig2 = Keyring::Alice.sign(msg); + let sig2 = Keyring::::Alice.sign(msg); assert_eq!(sig1, sig2); } #[test] - fn sign_error() { + fn sign_works_for_ecdsa() { + sign_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_works_for_ecdsa_n_bls() { + sign_works::(); + } + + fn sign_error< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >( + expected_error_message: &str, + ) where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited?"; let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); + let err = Error::Signature(expected_error_message.to_string()); assert_eq!(sig, err); } + #[test] + fn sign_error_for_ecdsa() { + sign_error::("ecdsa_sign_prehashed() failed"); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_error_for_ecdsa_n_bls() { + sign_error::("bls377_sign() failed"); + } + #[test] fn sign_no_keystore() { - let store: BeefyKeystore = None.into(); + let store: BeefyKeystore = None.into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited"; @@ -283,17 +470,21 @@ pub mod tests { assert_eq!(sig, err); } - #[test] - fn verify_works() { + fn verify_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); + + let alice = Keyring::Alice.public(); // `msg` and `sig` match let msg = b"are you involved or commited?"; @@ -305,32 +496,48 @@ pub mod tests { assert!(!BeefyKeystore::verify(&alice, &sig, msg)); } - // Note that we use keys with and without a seed for this test. #[test] - fn public_keys_works() { + fn verify_works_for_ecdsa() { + verify_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn verify_works_for_ecdsa_n_bls() { + verify_works::(); + } + + // Note that we use keys with and without a seed for this test. + fn public_keys_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { const TEST_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"test"); let store = keystore(); - let add_key = - |key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap(); - // test keys - let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); - let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); - - let _ = add_key(TEST_TYPE, None); - let _ = add_key(TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Alice)); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Bob)); // BEEFY keys - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); + let _ = + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave)); + let _ = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve)); + + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); - let key1: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); - let key2: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); + let key1 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); + let key2 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let keys = store.public_keys().ok().unwrap(); @@ -340,4 +547,16 @@ pub mod tests { assert!(keys.contains(&key1)); assert!(keys.contains(&key2)); } + + #[test] + fn public_keys_works_for_ecdsa_keystore() { + public_keys_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn public_keys_works_for_ecdsa_n_bls() { + public_keys_works::(); + } } diff --git a/substrate/client/consensus/beefy/src/round.rs b/substrate/client/consensus/beefy/src/round.rs index 47414c60fdb5..0045dc70c260 100644 --- a/substrate/client/consensus/beefy/src/round.rs +++ b/substrate/client/consensus/beefy/src/round.rs @@ -207,7 +207,7 @@ mod tests { use sc_network_test::Block; use sp_consensus_beefy::{ - known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload, + known_payloads::MMR_ROOT_ID, test_utils::Keyring, Commitment, EquivocationProof, Payload, SignedCommitment, ValidatorSet, VoteMessage, }; @@ -226,7 +226,7 @@ mod tests { #[test] fn round_tracker() { let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let bob_vote = (Keyring::Bob.public(), Keyring::::Bob.sign(b"I am committed")); let threshold = 2; // adding new vote allowed @@ -237,7 +237,8 @@ mod tests { // vote is not done assert!(!rt.is_done(threshold)); - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + let alice_vote = + (Keyring::Alice.public(), Keyring::::Alice.sign(b"I am committed")); // adding new vote (self vote this time) allowed assert!(rt.add_vote(alice_vote)); @@ -271,7 +272,11 @@ mod tests { assert_eq!(42, rounds.validator_set_id()); assert_eq!(1, rounds.session_start()); assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + &vec![ + Keyring::::Alice.public(), + Keyring::::Bob.public(), + Keyring::::Charlie.public() + ], rounds.validators() ); } @@ -301,7 +306,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add 1st good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); @@ -310,26 +315,26 @@ mod tests { assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Dave.public(); - vote.signature = Keyring::Dave.sign(b"I am committed"); + vote.signature = Keyring::::Dave.sign(b"I am committed"); // invalid vote (Dave is not a validator) assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid); vote.id = Keyring::Bob.public(); - vote.signature = Keyring::Bob.sign(b"I am committed"); + vote.signature = Keyring::::Bob.sign(b"I am committed"); // add 2nd good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Charlie.public(); - vote.signature = Keyring::Charlie.sign(b"I am committed"); + vote.signature = Keyring::::Charlie.sign(b"I am committed"); // add 3rd good vote -> round concluded -> signatures present assert_eq!( rounds.add_vote(vote.clone()), VoteImportResult::RoundConcluded(SignedCommitment { commitment, signatures: vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), None, ] }) @@ -337,7 +342,7 @@ mod tests { rounds.conclude(block_number); vote.id = Keyring::Eve.public(); - vote.signature = Keyring::Eve.sign(b"I am committed"); + vote.signature = Keyring::::Eve.sign(b"I am committed"); // Eve is a validator, but round was concluded, adding vote disallowed assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale); } @@ -364,7 +369,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add vote for previous session, should fail assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); @@ -407,22 +412,22 @@ mod tests { let mut alice_vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut bob_vote = VoteMessage { id: Keyring::Bob.public(), commitment: commitment.clone(), - signature: Keyring::Bob.sign(b"I am committed"), + signature: Keyring::::Bob.sign(b"I am committed"), }; let mut charlie_vote = VoteMessage { id: Keyring::Charlie.public(), commitment, - signature: Keyring::Charlie.sign(b"I am committed"), + signature: Keyring::::Charlie.sign(b"I am committed"), }; let expected_signatures = vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), ]; // round 1 - only 2 out of 3 vote @@ -484,7 +489,7 @@ mod tests { let alice_vote1 = VoteMessage { id: Keyring::Alice.public(), commitment: commitment1, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut alice_vote2 = alice_vote1.clone(); alice_vote2.commitment = commitment2; diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 7e61e877c1dd..0e6ff210b158 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -57,9 +57,10 @@ use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, known_payloads, mmr::{find_mmr_root_digest, MmrRootProvider}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash, - OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + test_utils::Keyring as BeefyKeyring, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, MmrRootHash, OpaqueKeyOwnershipProof, + Payload, SignedCommitment, ValidatorSet, ValidatorSetId, VersionedFinalityProof, VoteMessage, + BEEFY_ENGINE_ID, }; use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; @@ -349,11 +350,11 @@ fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: Beef .unwrap(); } -pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { - keys.iter().map(|&key| key.public().into()).collect() +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|key| key.public().into()).collect() } -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> KeystorePtr { +pub(crate) fn create_beefy_keystore(authority: &BeefyKeyring) -> KeystorePtr { let keystore = MemoryKeystore::new(); keystore .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&authority.to_seed())) @@ -393,7 +394,7 @@ async fn voter_init_setup( // Spawns beefy voters. Returns a future to spawn on the runtime. fn initialize_beefy( net: &mut BeefyTestNet, - peers: Vec<(usize, &BeefyKeyring, Arc)>, + peers: Vec<(usize, &BeefyKeyring, Arc)>, min_block_delta: u32, ) -> impl Future where @@ -413,7 +414,7 @@ where for (peer_id, key, api) in peers.into_iter() { let peer = &net.peers[peer_id]; - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (_, _, peer_data) = net.make_block_import(peer.client().clone()); let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data; @@ -471,7 +472,7 @@ async fn run_for(duration: Duration, net: &Arc>) { pub(crate) fn get_beefy_streams( net: &mut BeefyTestNet, // peer index and key - peers: impl Iterator, + peers: impl Iterator)>, ) -> (Vec>, Vec>>) { let mut best_block_streams = Vec::new(); @@ -569,7 +570,7 @@ async fn streams_empty_after_timeout( async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key - peers: impl Iterator + Clone, + peers: impl Iterator)> + Clone, finalize_target: &H256, expected_beefy: &[u64], ) { diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index e67e3e0f76ad..19a24b9bc1a5 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -26,7 +26,7 @@ use crate::{ error::Error, expect_validator_set, justification::BeefyVersionedFinalityProof, - keystore::{BeefyKeystore, BeefySignatureHasher}, + keystore::BeefyKeystore, metric_inc, metric_set, metrics::VoterMetrics, round::{Rounds, VoteImportResult}, @@ -45,8 +45,8 @@ use sp_consensus::SyncOracle; use sp_consensus_beefy::{ check_equivocation_proof, ecdsa_crypto::{AuthorityId, Signature}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, ValidatorSet, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + BeefyApi, BeefySignatureHasher, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, + ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, }; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, @@ -340,7 +340,7 @@ pub(crate) struct BeefyWorkerBase { // utilities pub backend: Arc, pub runtime: Arc, - pub key_store: BeefyKeystore, + pub key_store: BeefyKeystore, /// BEEFY client metrics. pub metrics: Option, @@ -1278,8 +1278,11 @@ pub(crate) mod tests { use sc_network_test::TestNetFactory; use sp_blockchain::Backend as BlockchainBackendT; use sp_consensus_beefy::{ - generate_equivocation_proof, known_payloads, known_payloads::MMR_ROOT_ID, - mmr::MmrRootProvider, Keyring, Payload, SignedCommitment, + known_payloads, + known_payloads::MMR_ROOT_ID, + mmr::MmrRootProvider, + test_utils::{generate_equivocation_proof, Keyring}, + Payload, SignedCommitment, }; use sp_runtime::traits::{Header as HeaderT, One}; use substrate_test_runtime_client::{ @@ -1309,7 +1312,7 @@ pub(crate) mod tests { fn create_beefy_worker( peer: &mut BeefyPeer, - key: &Keyring, + key: &Keyring, min_block_delta: u32, genesis_validator_set: ValidatorSet, ) -> BeefyWorker< @@ -1319,7 +1322,7 @@ pub(crate) mod tests { TestApi, Arc>, > { - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (to_rpc_justif_sender, from_voter_justif_stream) = BeefyVersionedFinalityProofStream::::channel(); diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 3b29f435e2a9..ca4a87ef383e 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -37,7 +37,7 @@ use sp_core::bandersnatch; } sp_keystore::bls_experimental_enabled! { -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; } use crate::{Error, Result}; @@ -391,6 +391,20 @@ impl Keystore for LocalKeystore { self.sign::(key_type, public, msg) } + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + let sig = self.0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + + } } diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index bf5ae19510ce..453cf19a4fe1 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -15,21 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::vec; - use codec::Encode; -use sp_consensus_beefy::{ - check_equivocation_proof, generate_equivocation_proof, known_payloads::MMR_ROOT_ID, - Keyring as BeefyKeyring, Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, -}; - -use sp_runtime::DigestItem; +use std::vec; use frame_support::{ assert_err, assert_ok, dispatch::{GetDispatchInfo, Pays}, traits::{Currency, KeyOwnerProofSystem, OnInitialize}, }; +use sp_consensus_beefy::{ + check_equivocation_proof, + known_payloads::MMR_ROOT_ID, + test_utils::{generate_equivocation_proof, Keyring as BeefyKeyring}, + Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, +}; +use sp_runtime::DigestItem; use crate::{mock::*, Call, Config, Error, Weight, WeightInfo}; diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 6232eca5ff89..9fd9ce5d123b 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -25,6 +25,7 @@ sp-crypto-hashing = { path = "../../crypto/hashing", default-features = false } sp-io = { path = "../../io", default-features = false } sp-mmr-primitives = { path = "../../merkle-mountain-range", default-features = false } sp-runtime = { path = "../../runtime", default-features = false } +sp-keystore = { path = "../../keystore", default-features = false } sp-std = { path = "../../std", default-features = false } strum = { version = "0.24.1", features = ["derive"], default-features = false } lazy_static = { version = "1.4.0", optional = true } @@ -45,6 +46,7 @@ std = [ "sp-core/std", "sp-crypto-hashing/std", "sp-io/std", + "sp-keystore/std", "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", diff --git a/substrate/primitives/consensus/beefy/src/commitment.rs b/substrate/primitives/consensus/beefy/src/commitment.rs index 37be1a4f6fc3..1f0fb34ebf10 100644 --- a/substrate/primitives/consensus/beefy/src/commitment.rs +++ b/substrate/primitives/consensus/beefy/src/commitment.rs @@ -398,7 +398,7 @@ mod tests { assert_eq!( encoded, array_bytes::hex2bytes_unchecked( - "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01667603fc041cf9d7147d22bf54b15e5778893d6986b71a929747befd3b4d233fbe668bc480e8865116b94db46ca25a01e03c71955f2582604e415da68f2c3c406b9d5f4ad416230ec5453f05ac16a50d8d0923dfb0413cc956ae3fa6334465bd1f2cacec8e9cd606438390fe2a29dc052d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00df61d3b2be0963eb6caa243cc505d327aec73e1bb7ffe9a14b1354b0c406792ac6d6f47c06987c15dec9993f43eefa001d866fe0850d986702c414840f0d9ec0fdc04832ef91ae37c8d49e2f573ca50cb37f152801d489a19395cb04e5fc8f2ab6954b58a3bcc40ef9b6409d2ff7ef07" + "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba015dd1c9b2237e54baa93d232cdf83a430b58a5efbc2f86ca1bab173a315ff6f15bef161425750c028055e9a23947b73002889a8b22168628438875a8ef25d76db998a80187b50719471286f054f3b3809b77a0cd87d7fe9c1a9d5d562683e25a70610f0804e92340549a43a7159b77b0c2d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a001074884b6998c82331bd57ffa0a02cbfd02483c765b9216eab6a1fc119206236bf7971be68acaebff7400edee943240006a6096c9cfa65e9eb4e67f025c27112d14b4574fb208c439500f45cf3a8060f6cf009044f3141cce0364a7c2710a19b1bdf4abf27f86e5e3db08bddd35a7d12" ) ); } diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index 68eeeb3c6800..1c3801e3a506 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -32,20 +32,22 @@ //! while GRANDPA uses `ed25519`. mod commitment; -pub mod mmr; mod payload; -#[cfg(feature = "std")] -mod test_utils; + +pub mod mmr; pub mod witness; +/// Test utilities +#[cfg(feature = "std")] +pub mod test_utils; + pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; -#[cfg(feature = "std")] -pub use test_utils::*; use codec::{Codec, Decode, Encode}; +use core::fmt::{Debug, Display}; use scale_info::TypeInfo; -use sp_application_crypto::RuntimeAppPublic; +use sp_application_crypto::{AppCrypto, AppPublic, ByteArray, RuntimeAppPublic}; use sp_core::H256; use sp_runtime::traits::{Hash, Keccak256, NumberFor}; use sp_std::prelude::*; @@ -63,6 +65,25 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool; } +/// Hasher used for BEEFY signatures. +pub type BeefySignatureHasher = sp_runtime::traits::Keccak256; + +/// A trait bound which lists all traits which are required to be implemented by +/// a BEEFY AuthorityId type in order to be able to be used in BEEFY Keystore +pub trait AuthorityIdBound: + Codec + + Debug + + Clone + + AsRef<[u8]> + + ByteArray + + AppPublic + + AppCrypto + + RuntimeAppPublic + + Display + + BeefyAuthorityId +{ +} + /// BEEFY cryptographic types for ECDSA crypto /// /// This module basically introduces four crypto types: @@ -74,7 +95,7 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { /// Your code should use the above types as concrete types for all crypto related /// functionality. pub mod ecdsa_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa}; use sp_core::crypto::Wraps; @@ -101,6 +122,7 @@ pub mod ecdsa_crypto { } } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for BLS crypto @@ -116,7 +138,7 @@ pub mod ecdsa_crypto { #[cfg(feature = "bls-experimental")] pub mod bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, bls377}; use sp_core::{bls377::Pair as BlsPair, crypto::Wraps, Pair as _}; @@ -134,13 +156,14 @@ pub mod bls_crypto { { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool { // `w3f-bls` library uses IETF hashing standard and as such does not expose - // a choice of hash to field function. + // a choice of hash-to-field function. // We are directly calling into the library to avoid introducing new host call. // and because BeefyAuthorityId::verify is being called in the runtime so we don't have BlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref()) } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for (ECDSA,BLS) crypto pair @@ -155,7 +178,7 @@ pub mod bls_crypto { /// functionality. #[cfg(feature = "bls-experimental")] pub mod ecdsa_bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa_bls377}; use sp_core::{crypto::Wraps, ecdsa_bls377::Pair as EcdsaBlsPair}; @@ -187,6 +210,8 @@ pub mod ecdsa_bls_crypto { ) } } + + impl AuthorityIdBound for AuthorityId {} } /// The `ConsensusEngineId` of BEEFY. diff --git a/substrate/primitives/consensus/beefy/src/test_utils.rs b/substrate/primitives/consensus/beefy/src/test_utils.rs index a6e65e5bff0b..ec13c9c69004 100644 --- a/substrate/primitives/consensus/beefy/src/test_utils.rs +++ b/substrate/primitives/consensus/beefy/src/test_utils.rs @@ -15,18 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(feature = "std")] +#[cfg(feature = "bls-experimental")] +use crate::ecdsa_bls_crypto; +use crate::{ + ecdsa_crypto, AuthorityIdBound, BeefySignatureHasher, Commitment, EquivocationProof, Payload, + ValidatorSetId, VoteMessage, +}; +use sp_application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps}; +use sp_core::{ecdsa, Pair}; +use sp_runtime::traits::Hash; -use crate::{ecdsa_crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage}; use codec::Encode; -use sp_core::{ecdsa, Pair}; -use std::collections::HashMap; +use std::{collections::HashMap, marker::PhantomData}; use strum::IntoEnumIterator; /// Set of test accounts using [`crate::ecdsa_crypto`] types. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] -pub enum Keyring { +pub enum Keyring { Alice, Bob, Charlie, @@ -35,71 +41,110 @@ pub enum Keyring { Ferdie, One, Two, + _Marker(PhantomData), +} + +/// Trait representing BEEFY specific generation and signing behavior of authority id +/// +/// Accepts custom hashing fn for the message and custom convertor fn for the signer. +pub trait BeefySignerAuthority: AppPair { + /// Generate and return signature for `message` using custom hashing `MsgHash` + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature; +} + +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + let hashed_message = ::hash(message).into(); + self.as_inner_ref().sign_prehashed(&hashed_message).into() + } +} + +#[cfg(feature = "bls-experimental")] +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + self.as_inner_ref().sign_with_hasher::(&message).into() + } } -impl Keyring { +/// Implement Keyring functionalities generically over AuthorityId +impl Keyring +where + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, +{ /// Sign `msg`. - pub fn sign(self, msg: &[u8]) -> ecdsa_crypto::Signature { - // todo: use custom signature hashing type - let msg = sp_crypto_hashing::keccak_256(msg); - ecdsa::Pair::from(self).sign_prehashed(&msg).into() + pub fn sign(&self, msg: &[u8]) -> ::Signature { + let key_pair: ::Pair = self.pair(); + key_pair.sign_with_hasher(&msg).into() } /// Return key pair. - pub fn pair(self) -> ecdsa_crypto::Pair { - ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() + pub fn pair(&self) -> ::Pair { + ::Pair::from_string(self.to_seed().as_str(), None) + .unwrap() + .into() } /// Return public key. - pub fn public(self) -> ecdsa_crypto::Public { - self.pair().public() + pub fn public(&self) -> AuthorityId { + self.pair().public().into() } /// Return seed string. - pub fn to_seed(self) -> String { + pub fn to_seed(&self) -> String { format!("//{}", self) } /// Get Keyring from public key. - pub fn from_public(who: &ecdsa_crypto::Public) -> Option { - Self::iter().find(|&k| &ecdsa_crypto::Public::from(k) == who) + pub fn from_public(who: &AuthorityId) -> Option> { + Self::iter().find(|k| k.public() == *who) } } lazy_static::lazy_static! { - static ref PRIVATE_KEYS: HashMap = - Keyring::iter().map(|i| (i, i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap = - PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); + static ref PRIVATE_KEYS: HashMap, ecdsa_crypto::Pair> = + Keyring::iter().map(|i| (i.clone(), i.pair())).collect(); + static ref PUBLIC_KEYS: HashMap, ecdsa_crypto::Public> = + PRIVATE_KEYS.iter().map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))).collect(); } -impl From for ecdsa_crypto::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Pair { + fn from(k: Keyring) -> Self { k.pair() } } -impl From for ecdsa::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa::Pair { + fn from(k: Keyring) -> Self { k.pair().into() } } -impl From for ecdsa_crypto::Public { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Public { + fn from(k: Keyring) -> Self { (*PUBLIC_KEYS).get(&k).cloned().unwrap() } } /// Create a new `EquivocationProof` based on given arguments. pub fn generate_equivocation_proof( - vote1: (u64, Payload, ValidatorSetId, &Keyring), - vote2: (u64, Payload, ValidatorSetId, &Keyring), + vote1: (u64, Payload, ValidatorSetId, &Keyring), + vote2: (u64, Payload, ValidatorSetId, &Keyring), ) -> EquivocationProof { let signed_vote = |block_number: u64, payload: Payload, validator_set_id: ValidatorSetId, - keyring: &Keyring| { + keyring: &Keyring| { let commitment = Commitment { validator_set_id, block_number, payload }; let signature = keyring.sign(&commitment.encode()); VoteMessage { commitment, id: keyring.public(), signature } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 96b0ff19e561..61e7162544a6 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -985,6 +985,19 @@ mod tests { assert!(res.is_err()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + } + #[test] fn sign_verify() { let pair = Pair::from_seed(DEV_SEED); diff --git a/substrate/primitives/core/src/bls.rs b/substrate/primitives/core/src/bls.rs index 452c6372d16b..0c84d0ba8e6c 100644 --- a/substrate/primitives/core/src/bls.rs +++ b/substrate/primitives/core/src/bls.rs @@ -15,7 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Simple BLS (Boneh–Lynn–Shacham) Signature API. +//! BLS (Boneh–Lynn–Shacham) Signature along with efficiently verifiable Chaum-Pedersen proof API. +//! Signatures are implemented according to +//! [Efficient Aggregatable BLS Signatures with Chaum-Pedersen Proofs](https://eprint.iacr.org/2022/1611) +//! Hash-to-BLS-curve is using Simplified SWU for AB == 0 +//! [RFC 9380](https://datatracker.ietf.org/doc/rfc9380/) Sect 6.6.3. +//! Chaum-Pedersen proof uses the same hash-to-field specified in RFC 9380 for the field of the BLS +//! curve. #[cfg(feature = "serde")] use crate::crypto::Ss58Codec; @@ -452,11 +458,12 @@ impl TraitPair for Pair { fn derive>( &self, path: Iter, - _seed: Option, + seed: Option, ) -> Result<(Self, Option), DeriveError> { - let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = self.0.secret.to_bytes().try_into().expect( - "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", - ); + let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = + seed.unwrap_or(self.0.secret.to_bytes().try_into().expect( + "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", + )); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -544,7 +551,7 @@ mod test { "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -588,12 +595,12 @@ mod test { assert_eq!( public, Public::unchecked_from(array_bytes::hex2array_unchecked( - "6dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + "7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" )) ); let message = b""; let signature = - array_bytes::hex2array_unchecked("bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let expected_signature = Signature::unchecked_from(signature); println!("signature is {:?}", pair.sign(&message[..])); @@ -647,12 +654,30 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 1b63db7af7f2..f172b3a7d02c 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -644,12 +644,29 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.secret, repair_seed.secret); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.secret, repair_phrase.secret); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.secret, repair_string.secret); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.secret, pair2.secret); } #[test] diff --git a/substrate/primitives/core/src/ed25519.rs b/substrate/primitives/core/src/ed25519.rs index aa0d77510bd8..60ebd93e12d4 100644 --- a/substrate/primitives/core/src/ed25519.rs +++ b/substrate/primitives/core/src/ed25519.rs @@ -501,6 +501,22 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn test_vector_should_work() { let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( @@ -590,6 +606,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs index 960b8469249e..20b32c339bd7 100644 --- a/substrate/primitives/core/src/paired_crypto.rs +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -460,10 +460,11 @@ where path: Iter, seed: Option, ) -> Result<(Self, Option), DeriveError> { - let path: Vec<_> = path.collect(); + let left_path: Vec<_> = path.collect(); + let right_path: Vec<_> = left_path.clone(); - let left = self.left.derive(path.iter().cloned(), seed.map(|s| s.into()))?; - let right = self.right.derive(path.into_iter(), seed.map(|s| s.into()))?; + let left = self.left.derive(left_path.into_iter(), seed.map(|s| s.into()))?; + let right = self.right.derive(right_path.into_iter(), seed.map(|s| s.into()))?; let seed = match (left.1, right.1) { (Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()), @@ -542,13 +543,30 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn seed_and_derive_should_work() { let seed_for_right_and_left: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed_for_right_and_left); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -599,13 +617,13 @@ mod test { assert_eq!( public, Public::unchecked_from( - array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd916dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" ), ), ); let message = b""; let signature = - array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let signature = Signature::unchecked_from(signature); assert!(pair.sign(&message[..]) == signature); @@ -664,6 +682,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index b821055e2c56..7c02afc3cd5f 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -970,6 +970,22 @@ mod tests { assert!(Pair::verify(&signature, &message[..], &public)); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn generated_pair_should_work() { let (pair, _) = Pair::generate(); diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 07c4e2d5fd1d..5fae27bc3283 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -355,6 +355,24 @@ pub trait Keystore: Send + Sync { msg: &[u8], ) -> Result, Error>; + /// Hashes the `message` using keccak256 and then signs it using ECDSA + /// algorithm. It does not affect the behavior of BLS12-377 component. It generates + /// BLS12-377 Signature according to IETF standard. + /// + /// Receives [`KeyTypeId`] and a [`ecdsa_bls377::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns an [`ecdsa_bls377::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error>; + /// Insert a new secret key. fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; @@ -661,6 +679,16 @@ impl Keystore for Arc { (**self).ecdsa_bls377_sign(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).ecdsa_bls377_sign_with_keccak256(key_type, public, msg) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { (**self).insert(key_type, suri, public) } diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 2879c458b4f6..e10660b126a3 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -22,7 +22,7 @@ use crate::{Error, Keystore, KeystorePtr}; #[cfg(feature = "bandersnatch-experimental")] use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; use sp_core::{ crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, @@ -346,6 +346,19 @@ impl Keystore for MemoryKeystore { self.sign::(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + let sig = self + .pair::(key_type, public) + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys .write() @@ -493,6 +506,38 @@ mod tests { assert!(res.is_some()); } + #[test] + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak_works() { + use sp_core::testing::ECDSA_BLS377; + + let store = MemoryKeystore::new(); + + let suri = "//Alice"; + let pair = ecdsa_bls377::Pair::from_string(suri, None).unwrap(); + + let msg = b"this should be a normal unhashed message not "; + + // insert key, sign again + store.insert(ECDSA_BLS377, suri, pair.public().as_ref()).unwrap(); + + let res = store + .ecdsa_bls377_sign_with_keccak256(ECDSA_BLS377, &pair.public(), &msg[..]) + .unwrap(); + + assert!(res.is_some()); + + // does not verify with default out-of-the-box verification + assert!(!ecdsa_bls377::Pair::verify(&res.clone().unwrap(), &msg[..], &pair.public())); + + // should verify using keccak256 as hasher + assert!(ecdsa_bls377::Pair::verify_with_hasher::( + &res.unwrap(), + msg, + &pair.public() + )); + } + #[test] #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign() {