Make BEEFY client keystore generic over BEEFY AuthorityId type (#2258)

This is the significant step to make BEEFY client able to handle both
ECDSA and (ECDSA, BLS) type signature. The idea is having BEEFY Client
generic on crypto types makes migration to new types smoother.

This makes the BEEFY Keystore generic over AuthorityId and extends its
tests to cover the case when the AuthorityId is of type (ECDSA,
BLS12-377)

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
This commit is contained in:
drskalman
2024-02-08 11:08:51 -05:00
committed by GitHub
parent bc5a758c0c
commit 0a94124d24
22 changed files with 742 additions and 231 deletions
Generated
+1
View File
@@ -18225,6 +18225,7 @@ dependencies = [
"sp-core",
"sp-crypto-hashing",
"sp-io",
"sp-keystore",
"sp-mmr-primitives",
"sp-runtime",
"sp-std 14.0.0",
@@ -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",
]
@@ -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<BN: Encode>(who: &Keyring, commitment: &Commitment<BN>) -> Signature {
pub fn sign_commitment<BN: Encode>(
who: &Keyring<AuthorityId>,
commitment: &Commitment<BN>,
) -> 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<AuthorityId> = 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::<AuthorityId>::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::<AuthorityId>::Alice.public()];
let validator_set = ValidatorSet::<AuthorityId>::new(keys.clone(), 0).unwrap();
let (gv, mut report_stream) =
GossipValidator::<Block>::new(Arc::new(Mutex::new(KnownPeers::new())));
@@ -76,7 +76,7 @@ pub(crate) fn verify_with_validator_set<Block: BlockT>(
.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<Block: BlockT>(
#[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<Block>,
validator_set: &ValidatorSet<AuthorityId>,
keys: &[Keyring],
keys: &[Keyring<AuthorityId>],
) -> BeefyVersionedFinalityProof<Block> {
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::<AuthorityId>::Dave.sign(&bad_signed_commitment.commitment.encode()));
match verify_with_validator_set::<Block>(block_num, &validator_set, &bad_proof.into()) {
Err((ConsensusError::InvalidJustification, 3)) => (),
e => assert!(false, "Got unexpected {:?}", e),
+336 -117
View File
@@ -16,41 +16,45 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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<KeystorePtr>);
pub(crate) struct BeefyKeystore<AuthorityId: AuthorityIdBound>(
Option<KeystorePtr>,
PhantomData<fn() -> AuthorityId>,
);
impl BeefyKeystore {
impl<AuthorityId: AuthorityIdBound> BeefyKeystore<AuthorityId> {
/// 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<Public> {
pub fn authority_id(&self, keys: &[AuthorityId]) -> Option<AuthorityId> {
let store = self.0.clone()?;
// we do check for multiple private keys as a key store sanity check.
let public: Vec<Public> = keys
let public: Vec<AuthorityId> = keys
.iter()
.filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)]))
.filter(|k| {
store
.has_keys(&[(<AuthorityId as RuntimeAppPublic>::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<Signature, error::Error> {
pub fn sign(
&self,
public: &AuthorityId,
message: &[u8],
) -> Result<<AuthorityId as RuntimeAppPublic>::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();
// 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<u8> = match <AuthorityId as AppCrypto>::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)
.map_err(|e| error::Error::Keystore(e.to_string()))?
.ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?;
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()
},
// 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))
#[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 = <AuthorityId as RuntimeAppPublic>::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<Vec<Public>, error::Error> {
pub fn public_keys(&self) -> Result<Vec<AuthorityId>, error::Error> {
let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?;
let pk: Vec<Public> =
store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect();
let pk = match <AuthorityId as AppCrypto>::CRYPTO_ID {
ecdsa::CRYPTO_ID => store
.ecdsa_public_keys(BEEFY_KEY_TYPE)
.drain(..)
.map(|pk| AuthorityId::try_from(pk.as_ref()))
.collect::<Result<Vec<_>, _>>()
.or_else(|_| {
Err(error::Error::Keystore(
"unable to convert public key into authority id".into(),
))
}),
Ok(pk)
#[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::<Result<Vec<_>, _>>()
.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: &<AuthorityId as RuntimeAppPublic>::Signature,
message: &[u8],
) -> bool {
BeefyAuthorityId::<BeefySignatureHasher>::verify(public, sig, message)
}
}
impl From<Option<KeystorePtr>> for BeefyKeystore {
fn from(store: Option<KeystorePtr>) -> BeefyKeystore {
BeefyKeystore(store)
impl<AuthorityId: AuthorityIdBound> From<Option<KeystorePtr>> for BeefyKeystore<AuthorityId>
where
<AuthorityId as RuntimeAppPublic>::Signature: Send + Sync,
{
fn from(store: Option<KeystorePtr>) -> BeefyKeystore<AuthorityId> {
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!");
fn pair_verify_should_work<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
let msg = b"I am Alice!";
let sig = Keyring::<AuthorityId>::Alice.sign(b"I am Alice!");
assert!(ecdsa::Pair::verify_prehashed(
&sig.clone().into(),
&msg,
&Keyring::Alice.public().into(),
assert!(<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::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!(!<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::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!(!<AuthorityId as BeefyAuthorityId<BeefySignatureHasher>>::verify(
&Keyring::Alice.public(),
&sig,
&msg.as_slice(),
));
}
/// Generate key pair in the given store using the provided seed
fn generate_in_store<AuthorityId>(
store: KeystorePtr,
key_type: sp_application_crypto::KeyTypeId,
owner: Option<Keyring<AuthorityId>>,
) -> AuthorityId
where
AuthorityId:
AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<BeefySignatureHasher>,
<AuthorityId as RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
{
let optional_seed: Option<String> = owner.map(|owner| owner.to_seed());
match <AuthorityId as AppCrypto>::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::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn pair_verify_should_work_ecdsa_n_bls() {
pair_verify_should_work::<ecdsa_bls_crypto::AuthorityId>();
}
fn pair_works<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Alice", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Alice.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Alice.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Bob", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Bob", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Bob.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Bob.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Charlie", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Charlie", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Charlie.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Charlie.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Dave", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Dave", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Dave.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Dave.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Eve", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Eve", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Eve.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Eve.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Ferdie", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Ferdie", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Ferdie.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Ferdie.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//One", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//One", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::One.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::One.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Two", None)
let want = <AuthorityId as AppCrypto>::Pair::from_string("//Two", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Two.pair().to_raw_vec();
let got = Keyring::<AuthorityId>::Two.pair().to_raw_vec();
assert_eq!(want, got);
}
#[test]
fn authority_id_works() {
fn ecdsa_pair_works() {
pair_works::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn ecdsa_n_bls_pair_works() {
pair_works::<ecdsa_bls_crypto::AuthorityId>();
}
fn authority_id_works<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
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::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
let alice = Keyring::<AuthorityId>::Alice.public();
let bob = Keyring::Bob.public();
let charlie = Keyring::Charlie.public();
let store: BeefyKeystore = Some(store).into();
let beefy_store: BeefyKeystore<AuthorityId> = 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::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn authority_id_works_for_ecdsa_n_bls() {
authority_id_works::<ecdsa_bls_crypto::AuthorityId>();
}
fn sign_works<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
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::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
let store: BeefyKeystore = Some(store).into();
let alice = Keyring::Alice.public();
let store: BeefyKeystore<AuthorityId> = 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::<AuthorityId>::Alice.sign(msg);
assert_eq!(sig1, sig2);
}
#[test]
fn sign_error() {
fn sign_works_for_ecdsa() {
sign_works::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn sign_works_for_ecdsa_n_bls() {
sign_works::<ecdsa_bls_crypto::AuthorityId>();
}
fn sign_error<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>(
expected_error_message: &str,
) where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
let store = keystore();
store
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed()))
.ok()
.unwrap();
generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob));
let store: BeefyKeystore = Some(store).into();
let store: BeefyKeystore<AuthorityId> = 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_crypto::AuthorityId>("ecdsa_sign_prehashed() failed");
}
#[cfg(feature = "bls-experimental")]
#[test]
fn sign_error_for_ecdsa_n_bls() {
sign_error::<ecdsa_bls_crypto::AuthorityId>("bls377_sign() failed");
}
#[test]
fn sign_no_keystore() {
let store: BeefyKeystore = None.into();
let store: BeefyKeystore<ecdsa_crypto::Public> = 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<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
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::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice));
let store: BeefyKeystore = Some(store).into();
let store: BeefyKeystore<AuthorityId> = 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::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn verify_works_for_ecdsa_n_bls() {
verify_works::<ecdsa_bls_crypto::AuthorityId>();
}
// Note that we use keys with and without a seed for this test.
fn public_keys_works<
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
>()
where
<AuthorityId as sp_runtime::RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<sp_runtime::traits::Keccak256>,
{
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::<AuthorityId>(store.clone(), TEST_TYPE, Some(Keyring::Alice));
let _ = generate_in_store::<AuthorityId>(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::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave));
let _ = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve));
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 _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, None);
let _ = generate_in_store::<AuthorityId>(store.clone(), TEST_TYPE, None);
let store: BeefyKeystore = Some(store).into();
let key1 = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, None);
let key2 = generate_in_store::<AuthorityId>(store.clone(), BEEFY_KEY_TYPE, None);
let store: BeefyKeystore<AuthorityId> = 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::<ecdsa_crypto::AuthorityId>();
}
#[cfg(feature = "bls-experimental")]
#[test]
fn public_keys_works_for_ecdsa_n_bls() {
public_keys_works::<ecdsa_bls_crypto::AuthorityId>();
}
}
+25 -20
View File
@@ -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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::Alice.public(),
Keyring::<AuthorityId>::Bob.public(),
Keyring::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::Alice.sign(b"I am committed")),
Some(Keyring::<AuthorityId>::Bob.sign(b"I am committed")),
Some(Keyring::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::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::<AuthorityId>::Alice.sign(b"I am committed")),
Some(Keyring::<AuthorityId>::Bob.sign(b"I am committed")),
Some(Keyring::<AuthorityId>::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::<AuthorityId>::Alice.sign(b"I am committed"),
};
let mut alice_vote2 = alice_vote1.clone();
alice_vote2.commitment = commitment2;
+11 -10
View File
@@ -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<AuthorityId> {
keys.iter().map(|&key| key.public().into()).collect()
pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring<AuthorityId>]) -> Vec<AuthorityId> {
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<AuthorityId>) -> 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<API>(
net: &mut BeefyTestNet,
peers: Vec<(usize, &BeefyKeyring, Arc<API>)>,
peers: Vec<(usize, &BeefyKeyring<AuthorityId>, Arc<API>)>,
min_block_delta: u32,
) -> impl Future<Output = ()>
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<Mutex<BeefyTestNet>>) {
pub(crate) fn get_beefy_streams(
net: &mut BeefyTestNet,
// peer index and key
peers: impl Iterator<Item = (usize, BeefyKeyring)>,
peers: impl Iterator<Item = (usize, BeefyKeyring<AuthorityId>)>,
) -> (Vec<NotificationReceiver<H256>>, Vec<NotificationReceiver<BeefyVersionedFinalityProof<Block>>>)
{
let mut best_block_streams = Vec::new();
@@ -569,7 +570,7 @@ async fn streams_empty_after_timeout<T>(
async fn finalize_block_and_wait_for_beefy(
net: &Arc<Mutex<BeefyTestNet>>,
// peer index and key
peers: impl Iterator<Item = (usize, BeefyKeyring)> + Clone,
peers: impl Iterator<Item = (usize, BeefyKeyring<AuthorityId>)> + Clone,
finalize_target: &H256,
expected_beefy: &[u64],
) {
+11 -8
View File
@@ -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<B: Block, BE, RuntimeApi> {
// utilities
pub backend: Arc<BE>,
pub runtime: Arc<RuntimeApi>,
pub key_store: BeefyKeystore,
pub key_store: BeefyKeystore<AuthorityId>,
/// BEEFY client metrics.
pub metrics: Option<VoterMetrics>,
@@ -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<AuthorityId>,
min_block_delta: u32,
genesis_validator_set: ValidatorSet<AuthorityId>,
) -> BeefyWorker<
@@ -1319,7 +1322,7 @@ pub(crate) mod tests {
TestApi,
Arc<SyncingService<Block>>,
> {
let keystore = create_beefy_keystore(*key);
let keystore = create_beefy_keystore(key);
let (to_rpc_justif_sender, from_voter_justif_stream) =
BeefyVersionedFinalityProofStream::<Block>::channel();
+15 -1
View File
@@ -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::<ecdsa_bls377::Pair>(key_type, public, msg)
}
fn ecdsa_bls377_sign_with_keccak256(
&self,
key_type: KeyTypeId,
public: &ecdsa_bls377::Public,
msg: &[u8],
) -> std::result::Result<Option<ecdsa_bls377::Signature>, TraitError> {
let sig = self.0
.read()
.key_pair_by_type::<ecdsa_bls377::Pair>(public, key_type)?
.map(|pair| pair.sign_with_hasher::<KeccakHasher>(msg));
Ok(sig)
}
}
}
+8 -8
View File
@@ -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};
@@ -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",
@@ -398,7 +398,7 @@ mod tests {
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01667603fc041cf9d7147d22bf54b15e5778893d6986b71a929747befd3b4d233fbe668bc480e8865116b94db46ca25a01e03c71955f2582604e415da68f2c3c406b9d5f4ad416230ec5453f05ac16a50d8d0923dfb0413cc956ae3fa6334465bd1f2cacec8e9cd606438390fe2a29dc052d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00df61d3b2be0963eb6caa243cc505d327aec73e1bb7ffe9a14b1354b0c406792ac6d6f47c06987c15dec9993f43eefa001d866fe0850d986702c414840f0d9ec0fdc04832ef91ae37c8d49e2f573ca50cb37f152801d489a19395cb04e5fc8f2ab6954b58a3bcc40ef9b6409d2ff7ef07"
"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba015dd1c9b2237e54baa93d232cdf83a430b58a5efbc2f86ca1bab173a315ff6f15bef161425750c028055e9a23947b73002889a8b22168628438875a8ef25d76db998a80187b50719471286f054f3b3809b77a0cd87d7fe9c1a9d5d562683e25a70610f0804e92340549a43a7159b77b0c2d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a001074884b6998c82331bd57ffa0a02cbfd02483c765b9216eab6a1fc119206236bf7971be68acaebff7400edee943240006a6096c9cfa65e9eb4e67f025c27112d14b4574fb208c439500f45cf3a8060f6cf009044f3141cce0364a7c2710a19b1bdf4abf27f86e5e3db08bddd35a7d12"
)
);
}
+35 -10
View File
@@ -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<MsgHash: Hash>: RuntimeAppPublic {
fn verify(&self, signature: &<Self as RuntimeAppPublic>::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<BeefySignatureHasher>
{
}
/// BEEFY cryptographic types for ECDSA crypto
///
/// This module basically introduces four crypto types:
@@ -74,7 +95,7 @@ pub trait BeefyAuthorityId<MsgHash: Hash>: 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: &<Self as RuntimeAppPublic>::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.
@@ -15,18 +15,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg(feature = "std")]
use crate::{ecdsa_crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage};
use codec::Encode;
#[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 std::collections::HashMap;
use sp_runtime::traits::Hash;
use codec::Encode;
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<AuthorityId> {
Alice,
Bob,
Charlie,
@@ -35,71 +41,110 @@ pub enum Keyring {
Ferdie,
One,
Two,
_Marker(PhantomData<AuthorityId>),
}
impl Keyring {
/// 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<MsgHash: Hash>: AppPair {
/// Generate and return signature for `message` using custom hashing `MsgHash`
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature;
}
impl<MsgHash> BeefySignerAuthority<MsgHash> for <ecdsa_crypto::AuthorityId as AppCrypto>::Pair
where
MsgHash: Hash,
<MsgHash as Hash>::Output: Into<[u8; 32]>,
{
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
let hashed_message = <MsgHash as Hash>::hash(message).into();
self.as_inner_ref().sign_prehashed(&hashed_message).into()
}
}
#[cfg(feature = "bls-experimental")]
impl<MsgHash> BeefySignerAuthority<MsgHash> for <ecdsa_bls_crypto::AuthorityId as AppCrypto>::Pair
where
MsgHash: Hash,
<MsgHash as Hash>::Output: Into<[u8; 32]>,
{
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
self.as_inner_ref().sign_with_hasher::<MsgHash>(&message).into()
}
}
/// Implement Keyring functionalities generically over AuthorityId
impl<AuthorityId> Keyring<AuthorityId>
where
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<BeefySignatureHasher>,
<AuthorityId as RuntimeAppPublic>::Signature:
Send + Sync + From<<<AuthorityId as AppCrypto>::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]) -> <AuthorityId as RuntimeAppPublic>::Signature {
let key_pair: <AuthorityId as AppCrypto>::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) -> <AuthorityId as AppCrypto>::Pair {
<AuthorityId as AppCrypto>::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<Keyring> {
Self::iter().find(|&k| &ecdsa_crypto::Public::from(k) == who)
pub fn from_public(who: &AuthorityId) -> Option<Keyring<AuthorityId>> {
Self::iter().find(|k| k.public() == *who)
}
}
lazy_static::lazy_static! {
static ref PRIVATE_KEYS: HashMap<Keyring, ecdsa_crypto::Pair> =
Keyring::iter().map(|i| (i, i.pair())).collect();
static ref PUBLIC_KEYS: HashMap<Keyring, ecdsa_crypto::Public> =
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect();
static ref PRIVATE_KEYS: HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Pair> =
Keyring::iter().map(|i| (i.clone(), i.pair())).collect();
static ref PUBLIC_KEYS: HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Public> =
PRIVATE_KEYS.iter().map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))).collect();
}
impl From<Keyring> for ecdsa_crypto::Pair {
fn from(k: Keyring) -> Self {
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Pair {
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
k.pair()
}
}
impl From<Keyring> for ecdsa::Pair {
fn from(k: Keyring) -> Self {
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa::Pair {
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
k.pair().into()
}
}
impl From<Keyring> for ecdsa_crypto::Public {
fn from(k: Keyring) -> Self {
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Public {
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> 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<ecdsa_crypto::AuthorityId>),
vote2: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
) -> EquivocationProof<u64, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
let signed_vote = |block_number: u64,
payload: Payload,
validator_set_id: ValidatorSetId,
keyring: &Keyring| {
keyring: &Keyring<ecdsa_crypto::AuthorityId>| {
let commitment = Commitment { validator_set_id, block_number, payload };
let signature = keyring.sign(&commitment.encode());
VoteMessage { commitment, id: keyring.public(), signature }
@@ -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);
+33 -8
View File
@@ -15,7 +15,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Simple BLS (BonehLynnShacham) Signature API.
//! BLS (BonehLynnShacham) 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<T: BlsBound> TraitPair for Pair<T> {
fn derive<Iter: Iterator<Item = DeriveJunction>>(
&self,
path: Iter,
_seed: Option<Seed>,
seed: Option<Seed>,
) -> Result<(Self, Option<Seed>), 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]
+17
View File
@@ -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]
+17
View File
@@ -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]
+25 -6
View File
@@ -460,10 +460,11 @@ where
path: Iter,
seed: Option<Self::Seed>,
) -> Result<(Self, Option<Self::Seed>), 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]
+16
View File
@@ -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();
+28
View File
@@ -355,6 +355,24 @@ pub trait Keystore: Send + Sync {
msg: &[u8],
) -> Result<Option<ecdsa_bls377::Signature>, 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<Option<ecdsa_bls377::Signature>, Error>;
/// Insert a new secret key.
fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>;
@@ -661,6 +679,16 @@ impl<T: Keystore + ?Sized> Keystore for Arc<T> {
(**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<Option<ecdsa_bls377::Signature>, 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)
}
+46 -1
View File
@@ -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::<ecdsa_bls377::Pair>(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<Option<ecdsa_bls377::Signature>, Error> {
let sig = self
.pair::<ecdsa_bls377::Pair>(key_type, public)
.map(|pair| pair.sign_with_hasher::<KeccakHasher>(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::<KeccakHasher>(
&res.unwrap(),
msg,
&pair.public()
));
}
#[test]
#[cfg(feature = "bandersnatch-experimental")]
fn bandersnatch_vrf_sign() {