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
@@ -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 }