#![warn(missing_docs)]
mod commitment;
mod payload;
pub mod mmr;
pub mod witness;
#[cfg(feature = "std")]
pub mod test_utils;
pub use commitment::{Commitment, KnownSignature, SignedCommitment, VersionedFinalityProof};
pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider};
pub use crate::application_crypto::key_types::BEEFY as KEY_TYPE;
use crate::application_crypto::{AppPublic, RuntimeAppPublic};
use crate::core::H256;
#[cfg(feature = "std")]
use crate::keystore::KeystorePtr;
use crate::runtime::{
traits::{Header as HeaderT, Keccak256, NumberFor},
OpaqueValue,
};
use crate::weights::Weight;
use alloc::vec::Vec;
use codec::{Codec, Decode, DecodeWithMemTracking, Encode};
use core::fmt::{Debug, Display};
use scale_info::TypeInfo;
use KEY_TYPE as BEEFY_KEY_TYPE;
pub trait BeefyAuthorityId: RuntimeAppPublic {
#[cfg(feature = "std")]
fn get_all_public_keys_from_store(store: KeystorePtr) -> Vec<impl AsRef<[u8]>>;
#[cfg(feature = "std")]
fn try_sign_with_store(
&self,
store: KeystorePtr,
msg: &[u8],
) -> Result<Option<impl AsRef<[u8]> + Debug>, crate::keystore::Error>;
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool;
}
pub trait AuthorityIdBound:
Ord + AppPublic + Display + BeefyAuthorityId<Signature = Self::BoundedSignature>
{
type BoundedSignature: Debug + Eq + PartialEq + Clone + TypeInfo + Codec + Send + Sync;
}
pub mod ecdsa_crypto {
#[cfg(feature = "std")]
use super::Vec;
use super::{AuthorityIdBound, BeefyAuthorityId, RuntimeAppPublic, BEEFY_KEY_TYPE};
use crate::application_crypto::ecdsa;
use crate::core::crypto::Wraps;
#[cfg(feature = "std")]
use crate::core::ByteArray;
use crate::crypto_hashing::keccak_256;
#[cfg(feature = "std")]
use crate::keystore::KeystorePtr;
#[cfg(feature = "std")]
use core::fmt::Debug;
crate::app_crypto!(ecdsa, BEEFY_KEY_TYPE);
pub type AuthorityId = Public;
pub type AuthoritySignature = Signature;
impl BeefyAuthorityId for AuthorityId {
#[cfg(feature = "std")]
fn get_all_public_keys_from_store(store: KeystorePtr) -> Vec<impl AsRef<[u8]>> {
store.ecdsa_public_keys(BEEFY_KEY_TYPE)
}
#[cfg(feature = "std")]
fn try_sign_with_store(
&self,
store: crate::keystore::KeystorePtr,
msg: &[u8],
) -> Result<Option<impl AsRef<[u8]> + Debug>, crate::keystore::Error> {
let msg_hash = keccak_256(msg);
let public = ecdsa::Public::try_from(self.as_slice()).unwrap();
store.ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash)
}
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
let msg_hash = keccak_256(msg);
match crate::io::crypto::secp256k1_ecdsa_recover_compressed(
signature.as_inner_ref().as_ref(),
&msg_hash,
) {
Ok(raw_pubkey) => raw_pubkey.as_ref() == AsRef::<[u8]>::as_ref(self),
_ => false,
}
}
}
impl AuthorityIdBound for AuthorityId {
type BoundedSignature = Signature;
}
}
#[cfg(feature = "bls-experimental")]
pub mod bls_crypto {
#[cfg(feature = "std")]
use super::Vec;
use super::{AuthorityIdBound, BeefyAuthorityId, RuntimeAppPublic, BEEFY_KEY_TYPE};
use crate::application_crypto::bls381;
use crate::core::{bls381::Pair as BlsPair, crypto::Wraps, ByteArray, Pair as _};
#[cfg(feature = "std")]
use crate::keystore::KeystorePtr;
#[cfg(feature = "std")]
use core::fmt::Debug;
crate::app_crypto!(bls381, BEEFY_KEY_TYPE);
pub type AuthorityId = Public;
pub type AuthoritySignature = Signature;
impl BeefyAuthorityId for AuthorityId {
#[cfg(feature = "std")]
fn get_all_public_keys_from_store(store: KeystorePtr) -> Vec<impl AsRef<[u8]>> {
store.bls381_public_keys(BEEFY_KEY_TYPE)
}
#[cfg(feature = "std")]
fn try_sign_with_store(
&self,
store: crate::keystore::KeystorePtr,
msg: &[u8],
) -> Result<Option<impl AsRef<[u8]> + Debug>, crate::keystore::Error> {
let public = bls381::Public::try_from(self.as_slice()).unwrap();
store.bls381_sign(BEEFY_KEY_TYPE, &public, msg)
}
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
BlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())
}
}
impl AuthorityIdBound for AuthorityId {
type BoundedSignature = Signature;
}
}
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls_crypto {
#[cfg(feature = "std")]
use super::Vec;
use super::{AuthorityIdBound, BeefyAuthorityId, RuntimeAppPublic, BEEFY_KEY_TYPE};
use crate::application_crypto::ecdsa_bls381;
use crate::core::{crypto::Wraps, ecdsa_bls381::Pair as EcdsaBlsPair, ByteArray};
#[cfg(feature = "std")]
use crate::keystore::KeystorePtr;
use crate::runtime::traits::Keccak256;
#[cfg(feature = "std")]
use core::fmt::Debug;
crate::app_crypto!(ecdsa_bls381, BEEFY_KEY_TYPE);
pub type AuthorityId = Public;
pub type AuthoritySignature = Signature;
impl BeefyAuthorityId for AuthorityId {
#[cfg(feature = "std")]
fn get_all_public_keys_from_store(store: KeystorePtr) -> Vec<impl AsRef<[u8]>> {
store.ecdsa_bls381_public_keys(BEEFY_KEY_TYPE)
}
#[cfg(feature = "std")]
fn try_sign_with_store(
&self,
store: crate::keystore::KeystorePtr,
msg: &[u8],
) -> Result<Option<impl AsRef<[u8]> + Debug>, crate::keystore::Error> {
let public = ecdsa_bls381::Public::try_from(self.as_slice()).unwrap();
store.ecdsa_bls381_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &msg)
}
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
EcdsaBlsPair::verify_with_hasher::<Keccak256>(
signature.as_inner_ref(),
msg,
self.as_inner_ref(),
)
}
}
impl AuthorityIdBound for AuthorityId {
type BoundedSignature = Signature;
}
}
pub const BEEFY_ENGINE_ID: crate::runtime::ConsensusEngineId = *b"BEEF";
pub const GENESIS_AUTHORITY_SET_ID: u64 = 0;
pub type ValidatorSetId = u64;
#[derive(Decode, Encode, Debug, PartialEq, Clone, TypeInfo)]
pub struct ValidatorSet<AuthorityId> {
validators: Vec<AuthorityId>,
id: ValidatorSetId,
}
impl<AuthorityId> ValidatorSet<AuthorityId> {
pub fn new<I>(validators: I, id: ValidatorSetId) -> Option<Self>
where
I: IntoIterator<Item = AuthorityId>,
{
let validators: Vec<AuthorityId> = validators.into_iter().collect();
if validators.is_empty() {
None
} else {
Some(Self { validators, id })
}
}
pub fn validators(&self) -> &[AuthorityId] {
&self.validators
}
pub fn id(&self) -> ValidatorSetId {
self.id
}
pub fn len(&self) -> usize {
self.validators.len()
}
}
pub type AuthorityIndex = u32;
pub type MmrHashing = Keccak256;
pub type MmrRootHash = H256;
#[derive(Decode, Encode, TypeInfo)]
pub enum ConsensusLog<AuthorityId: Codec> {
#[codec(index = 1)]
AuthoritiesChange(ValidatorSet<AuthorityId>),
#[codec(index = 2)]
OnDisabled(AuthorityIndex),
#[codec(index = 3)]
MmrRoot(MmrRootHash),
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct VoteMessage<Number, Id, Signature> {
pub commitment: Commitment<Number>,
pub id: Id,
pub signature: Signature,
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct DoubleVotingProof<Number, Id, Signature> {
pub first: VoteMessage<Number, Id, Signature>,
pub second: VoteMessage<Number, Id, Signature>,
}
impl<Number, Id, Signature> DoubleVotingProof<Number, Id, Signature> {
pub fn offender_id(&self) -> &Id {
&self.first.id
}
pub fn round_number(&self) -> &Number {
&self.first.commitment.block_number
}
pub fn set_id(&self) -> ValidatorSetId {
self.first.commitment.validator_set_id
}
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct ForkVotingProof<Header: HeaderT, Id: RuntimeAppPublic, AncestryProof> {
pub vote: VoteMessage<Header::Number, Id, Id::Signature>,
pub ancestry_proof: AncestryProof,
pub header: Header,
}
impl<Header: HeaderT, Id: RuntimeAppPublic> ForkVotingProof<Header, Id, OpaqueValue> {
pub fn try_into<AncestryProof: Decode>(
self,
) -> Option<ForkVotingProof<Header, Id, AncestryProof>> {
Some(ForkVotingProof::<Header, Id, AncestryProof> {
vote: self.vote,
ancestry_proof: self.ancestry_proof.decode()?,
header: self.header,
})
}
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct FutureBlockVotingProof<Number, Id: RuntimeAppPublic> {
pub vote: VoteMessage<Number, Id, Id::Signature>,
}
pub fn check_commitment_signature<Number, Id>(
commitment: &Commitment<Number>,
authority_id: &Id,
signature: &<Id as RuntimeAppPublic>::Signature,
) -> bool
where
Id: BeefyAuthorityId,
Number: Clone + Encode + PartialEq,
{
let encoded_commitment = commitment.encode();
BeefyAuthorityId::verify(authority_id, signature, &encoded_commitment)
}
pub fn check_double_voting_proof<Number, Id>(
report: &DoubleVotingProof<Number, Id, <Id as RuntimeAppPublic>::Signature>,
) -> bool
where
Id: BeefyAuthorityId + PartialEq,
Number: Clone + Encode + PartialEq,
{
let first = &report.first;
let second = &report.second;
if first.id != second.id
|| first.commitment.block_number != second.commitment.block_number
|| first.commitment.validator_set_id != second.commitment.validator_set_id
|| first.commitment.payload == second.commitment.payload
{
return false;
}
let valid_first = check_commitment_signature(&first.commitment, &first.id, &first.signature);
let valid_second =
check_commitment_signature(&second.commitment, &second.id, &second.signature);
return valid_first && valid_second;
}
pub trait OnNewValidatorSet<AuthorityId> {
fn on_new_validator_set(
validator_set: &ValidatorSet<AuthorityId>,
next_validator_set: &ValidatorSet<AuthorityId>,
);
}
impl<AuthorityId> OnNewValidatorSet<AuthorityId> for () {
fn on_new_validator_set(_: &ValidatorSet<AuthorityId>, _: &ValidatorSet<AuthorityId>) {}
}
pub trait AncestryHelper<Header: HeaderT> {
type Proof: Clone + Debug + Decode + Encode + PartialEq + TypeInfo;
type ValidationContext;
fn is_proof_optimal(proof: &Self::Proof) -> bool;
fn extract_validation_context(header: Header) -> Option<Self::ValidationContext>;
fn is_non_canonical(
commitment: &Commitment<Header::Number>,
proof: Self::Proof,
context: Self::ValidationContext,
) -> bool;
}
pub trait AncestryHelperWeightInfo<Header: HeaderT>: AncestryHelper<Header> {
fn is_proof_optimal(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
fn extract_validation_context() -> Weight;
fn is_non_canonical(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
}
pub type OpaqueKeyOwnershipProof = OpaqueValue;
crate::api::decl_runtime_apis! {
#[api_version(6)]
pub trait BeefyApi<AuthorityId> where
AuthorityId : Codec + RuntimeAppPublic,
{
fn beefy_genesis() -> Option<NumberFor<Block>>;
fn validator_set() -> Option<ValidatorSet<AuthorityId>>;
fn submit_report_double_voting_unsigned_extrinsic(
equivocation_proof:
DoubleVotingProof<NumberFor<Block>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
fn submit_report_fork_voting_unsigned_extrinsic(
equivocation_proof:
ForkVotingProof<Block::Header, AuthorityId, OpaqueValue>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
fn submit_report_future_block_voting_unsigned_extrinsic(
equivocation_proof:
FutureBlockVotingProof<NumberFor<Block>, AuthorityId>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
fn generate_key_ownership_proof(
set_id: ValidatorSetId,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::application_crypto::ecdsa::{self, Public};
use crate::core::crypto::{Pair, Wraps};
use crate::crypto_hashing::keccak_256;
#[test]
fn validator_set() {
assert_eq!(ValidatorSet::<Public>::new(vec![], 0), None);
let alice = ecdsa::Pair::from_string("//Alice", None).unwrap();
let set_id = 0;
let validators = ValidatorSet::<Public>::new(vec![alice.public()], set_id).unwrap();
assert_eq!(validators.id(), set_id);
assert_eq!(validators.validators(), &vec![alice.public()]);
}
#[test]
fn ecdsa_beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = ecdsa_crypto::Pair::generate();
let signature: ecdsa_crypto::Signature =
pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into();
assert!(BeefyAuthorityId::verify(&pair.public(), &signature, msg));
let (other_pair, _) = ecdsa_crypto::Pair::generate();
assert!(!BeefyAuthorityId::verify(&other_pair.public(), &signature, msg,));
}
#[test]
#[cfg(feature = "bls-experimental")]
fn bls_beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = bls_crypto::Pair::generate();
let signature: bls_crypto::Signature = pair.as_inner_ref().sign(&msg).into();
assert!(BeefyAuthorityId::verify(&pair.public(), &signature, msg));
let (other_pair, _) = bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::verify(&other_pair.public(), &signature, msg,));
}
#[test]
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls_beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = ecdsa_bls_crypto::Pair::generate();
let signature: ecdsa_bls_crypto::Signature =
pair.as_inner_ref().sign_with_hasher::<Keccak256>(&msg).into();
assert!(BeefyAuthorityId::verify(&pair.public(), &signature, msg));
assert!(!ecdsa_bls_crypto::Pair::verify(&signature, msg, &pair.public()));
let (other_pair, _) = ecdsa_bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::verify(&other_pair.public(), &signature, msg,));
}
}