Move BEEFY code to consensus (#13484)

* Move beefy primitives to consensus dir
* Move beefy gadget to client consensus folder
* Rename beefy crates
This commit is contained in:
Davide Galassi
2023-02-28 15:56:22 +01:00
committed by GitHub
parent 1eb0cd31b9
commit 1ef9c473e7
47 changed files with 260 additions and 262 deletions
@@ -0,0 +1,441 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use codec::{Decode, Encode, Error, Input};
use scale_info::TypeInfo;
use sp_std::{cmp, prelude::*};
use crate::{Payload, ValidatorSetId};
/// A commitment signed by GRANDPA validators as part of BEEFY protocol.
///
/// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at
/// height [block_number](Commitment::block_number).
/// GRANDPA validators collect signatures on commitments and a stream of such signed commitments
/// (see [SignedCommitment]) forms the BEEFY protocol.
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo)]
pub struct Commitment<TBlockNumber> {
/// A collection of payloads to be signed, see [`Payload`] for details.
///
/// One of the payloads should be some form of cumulative representation of the chain (think
/// MMR root hash). Additionally one of the payloads should also contain some details that
/// allow the light client to verify next validator set. The protocol does not enforce any
/// particular format of this data, nor how often it should be present in commitments, however
/// the light client has to be provided with full validator set whenever it performs the
/// transition (i.e. importing first block with
/// [validator_set_id](Commitment::validator_set_id) incremented).
pub payload: Payload,
/// Finalized block number this commitment is for.
///
/// GRANDPA validators agree on a block they create a commitment for and start collecting
/// signatures. This process is called a round.
/// There might be multiple rounds in progress (depending on the block choice rule), however
/// since the payload is supposed to be cumulative, it is not required to import all
/// commitments.
/// BEEFY light client is expected to import at least one commitment per epoch,
/// but is free to import as many as it requires.
pub block_number: TBlockNumber,
/// BEEFY validator set supposed to sign this commitment.
///
/// Validator set is changing once per epoch. The Light Client must be provided by details
/// about the validator set whenever it's importing first commitment with a new
/// `validator_set_id`. Validator set data MUST be verifiable, for instance using
/// [payload](Commitment::payload) information.
pub validator_set_id: ValidatorSetId,
}
impl<TBlockNumber> cmp::PartialOrd for Commitment<TBlockNumber>
where
TBlockNumber: cmp::Ord,
{
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<TBlockNumber> cmp::Ord for Commitment<TBlockNumber>
where
TBlockNumber: cmp::Ord,
{
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.validator_set_id
.cmp(&other.validator_set_id)
.then_with(|| self.block_number.cmp(&other.block_number))
.then_with(|| self.payload.cmp(&other.payload))
}
}
/// A commitment with matching GRANDPA validators' signatures.
///
/// Note that SCALE-encoding of the structure is optimized for size efficiency over the wire,
/// please take a look at custom [`Encode`] and [`Decode`] implementations and
/// `CompactSignedCommitment` struct.
#[derive(Clone, Debug, PartialEq, Eq, TypeInfo)]
pub struct SignedCommitment<TBlockNumber, TSignature> {
/// The commitment signatures are collected for.
pub commitment: Commitment<TBlockNumber>,
/// GRANDPA validators' signatures for the commitment.
///
/// The length of this `Vec` must match number of validators in the current set (see
/// [Commitment::validator_set_id]).
pub signatures: Vec<Option<TSignature>>,
}
impl<TBlockNumber, TSignature> SignedCommitment<TBlockNumber, TSignature> {
/// Return the number of collected signatures.
pub fn no_of_signatures(&self) -> usize {
self.signatures.iter().filter(|x| x.is_some()).count()
}
}
/// Type to be used to denote placement of signatures
type BitField = Vec<u8>;
/// Compress 8 bit values into a single u8 Byte
const CONTAINER_BIT_SIZE: usize = 8;
/// Compressed representation of [`SignedCommitment`], used for encoding efficiency.
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
struct CompactSignedCommitment<TBlockNumber, TSignature> {
/// The commitment, unchanged compared to regular [`SignedCommitment`].
commitment: Commitment<TBlockNumber>,
/// A bitfield representing presence of a signature coming from a validator at some index.
///
/// The bit at index `0` is set to `1` in case we have a signature coming from a validator at
/// index `0` in in the original validator set. In case the [`SignedCommitment`] does not
/// contain that signature the `bit` will be set to `0`. Bits are packed into `Vec<u8>`
signatures_from: BitField,
/// Number of validators in the Validator Set and hence number of significant bits in the
/// [`signatures_from`] collection.
///
/// Note this might be smaller than the size of `signatures_compact` in case some signatures
/// are missing.
validator_set_len: u32,
/// A `Vec` containing all `Signature`s present in the original [`SignedCommitment`].
///
/// Note that in order to associate a `Signature` from this `Vec` with a validator, one needs
/// to look at the `signatures_from` bitfield, since some validators might have not produced a
/// signature.
signatures_compact: Vec<TSignature>,
}
impl<'a, TBlockNumber: Clone, TSignature> CompactSignedCommitment<TBlockNumber, &'a TSignature> {
/// Packs a `SignedCommitment` into the compressed `CompactSignedCommitment` format for
/// efficient network transport.
fn pack(signed_commitment: &'a SignedCommitment<TBlockNumber, TSignature>) -> Self {
let SignedCommitment { commitment, signatures } = signed_commitment;
let validator_set_len = signatures.len() as u32;
let signatures_compact: Vec<&'a TSignature> =
signatures.iter().filter_map(|x| x.as_ref()).collect();
let bits = {
let mut bits: Vec<u8> =
signatures.iter().map(|x| if x.is_some() { 1 } else { 0 }).collect();
// Resize with excess bits for placement purposes
let excess_bits_len =
CONTAINER_BIT_SIZE - (validator_set_len as usize % CONTAINER_BIT_SIZE);
bits.resize(bits.len() + excess_bits_len, 0);
bits
};
let mut signatures_from: BitField = vec![];
let chunks = bits.chunks(CONTAINER_BIT_SIZE);
for chunk in chunks {
let mut iter = chunk.iter().copied();
let mut v = iter.next().unwrap() as u8;
for bit in iter {
v <<= 1;
v |= bit as u8;
}
signatures_from.push(v);
}
Self {
commitment: commitment.clone(),
signatures_from,
validator_set_len,
signatures_compact,
}
}
/// Unpacks a `CompactSignedCommitment` into the uncompressed `SignedCommitment` form.
fn unpack(
temporary_signatures: CompactSignedCommitment<TBlockNumber, TSignature>,
) -> SignedCommitment<TBlockNumber, TSignature> {
let CompactSignedCommitment {
commitment,
signatures_from,
validator_set_len,
signatures_compact,
} = temporary_signatures;
let mut bits: Vec<u8> = vec![];
for block in signatures_from {
for bit in 0..CONTAINER_BIT_SIZE {
bits.push((block >> (CONTAINER_BIT_SIZE - bit - 1)) & 1);
}
}
bits.truncate(validator_set_len as usize);
let mut next_signature = signatures_compact.into_iter();
let signatures: Vec<Option<TSignature>> = bits
.iter()
.map(|&x| if x == 1 { next_signature.next() } else { None })
.collect();
SignedCommitment { commitment, signatures }
}
}
impl<TBlockNumber, TSignature> Encode for SignedCommitment<TBlockNumber, TSignature>
where
TBlockNumber: Encode + Clone,
TSignature: Encode,
{
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
let temp = CompactSignedCommitment::pack(self);
temp.using_encoded(f)
}
}
impl<TBlockNumber, TSignature> Decode for SignedCommitment<TBlockNumber, TSignature>
where
TBlockNumber: Decode + Clone,
TSignature: Decode,
{
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
let temp = CompactSignedCommitment::decode(input)?;
Ok(CompactSignedCommitment::unpack(temp))
}
}
/// A [SignedCommitment] with a version number.
///
/// This variant will be appended to the block justifications for the block
/// for which the signed commitment has been generated.
///
/// Note that this enum is subject to change in the future with introduction
/// of additional cryptographic primitives to BEEFY.
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
pub enum VersionedFinalityProof<N, S> {
#[codec(index = 1)]
/// Current active version
V1(SignedCommitment<N, S>),
}
impl<N, S> From<SignedCommitment<N, S>> for VersionedFinalityProof<N, S> {
fn from(commitment: SignedCommitment<N, S>) -> Self {
VersionedFinalityProof::V1(commitment)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{crypto, known_payloads, KEY_TYPE};
use codec::Decode;
use sp_core::{keccak_256, Pair};
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
type TestCommitment = Commitment<u128>;
type TestSignedCommitment = SignedCommitment<u128, crypto::Signature>;
type TestVersionedFinalityProof = VersionedFinalityProof<u128, crypto::Signature>;
const LARGE_RAW_COMMITMENT: &[u8] = include_bytes!("../test-res/large-raw-commitment");
// The mock signatures are equivalent to the ones produced by the BEEFY keystore
fn mock_signatures() -> (crypto::Signature, crypto::Signature) {
let store: SyncCryptoStorePtr = KeyStore::new().into();
let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
let _ =
SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref())
.unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg)
.unwrap()
.unwrap();
let msg = keccak_256(b"This is the second message");
let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg)
.unwrap()
.unwrap();
(sig1.into(), sig2.into())
}
#[test]
fn commitment_encode_decode() {
// given
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
// when
let encoded = codec::Encode::encode(&commitment);
let decoded = TestCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(commitment));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000"
)
);
}
#[test]
fn signed_commitment_encode_decode() {
// given
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let signed = SignedCommitment {
commitment,
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
};
// when
let encoded = codec::Encode::encode(&signed);
let decoded = TestSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(signed));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"\
046d68343048656c6c6f20576f726c64210500000000000000000000000000000000000000000000000\
4300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746c\
c321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aa\
acdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6\
a0046395a71681be3d0c2a00\
"
)
);
}
#[test]
fn signed_commitment_count_signatures() {
// given
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let mut signed = SignedCommitment {
commitment,
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
};
assert_eq!(signed.no_of_signatures(), 2);
// when
signed.signatures[2] = None;
// then
assert_eq!(signed.no_of_signatures(), 1);
}
#[test]
fn commitment_ordering() {
fn commitment(
block_number: u128,
validator_set_id: crate::ValidatorSetId,
) -> TestCommitment {
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
Commitment { payload, block_number, validator_set_id }
}
// given
let a = commitment(1, 0);
let b = commitment(2, 1);
let c = commitment(10, 0);
let d = commitment(10, 1);
// then
assert!(a < b);
assert!(a < c);
assert!(c < b);
assert!(c < d);
assert!(b < d);
}
#[test]
fn versioned_commitment_encode_decode() {
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let signed = SignedCommitment {
commitment,
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
};
let versioned = TestVersionedFinalityProof::V1(signed.clone());
let encoded = codec::Encode::encode(&versioned);
assert_eq!(1, encoded[0]);
assert_eq!(encoded[1..], codec::Encode::encode(&signed));
let decoded = TestVersionedFinalityProof::decode(&mut &*encoded);
assert_eq!(decoded, Ok(versioned));
}
#[test]
fn large_signed_commitment_encode_decode() {
// given
let payload =
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let signatures: Vec<Option<_>> = (0..1024)
.into_iter()
.map(|x| if x < 340 { None } else { Some(sigs.0.clone()) })
.collect();
let signed = SignedCommitment { commitment, signatures };
// when
let encoded = codec::Encode::encode(&signed);
let decoded = TestSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(signed));
assert_eq!(encoded, LARGE_RAW_COMMITMENT);
}
}
@@ -0,0 +1,404 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
//! Primitives for BEEFY protocol.
//!
//! The crate contains shared data types used by BEEFY protocol and documentation (in a form of
//! code) for building a BEEFY light client.
//!
//! BEEFY is a gadget that runs alongside another finality gadget (for instance GRANDPA).
//! For simplicity (and the initially intended use case) the documentation says GRANDPA in places
//! where a more abstract "Finality Gadget" term could be used, but there is no reason why BEEFY
//! wouldn't run with some other finality scheme.
//! BEEFY validator set is supposed to be tracking the Finality Gadget validator set, but note that
//! it will use a different set of keys. For Polkadot use case we plan to use `secp256k1` for BEEFY,
//! while GRANDPA uses `ed25519`.
mod commitment;
pub mod mmr;
mod payload;
#[cfg(feature = "std")]
mod test_utils;
pub mod witness;
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 scale_info::TypeInfo;
use sp_application_crypto::RuntimeAppPublic;
use sp_core::H256;
use sp_runtime::traits::{Hash, NumberFor};
use sp_std::prelude::*;
/// Key type for BEEFY module.
pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"beef");
/// Trait representing BEEFY authority id, including custom signature verification.
///
/// Accepts custom hashing fn for the message and custom convertor fn for the signer.
pub trait BeefyAuthorityId<MsgHash: Hash>: RuntimeAppPublic {
/// Verify a signature.
///
/// Return `true` if signature over `msg` is valid for this id.
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool;
}
/// BEEFY cryptographic types
///
/// This module basically introduces three crypto types:
/// - `crypto::Pair`
/// - `crypto::Public`
/// - `crypto::Signature`
///
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
///
/// The current underlying crypto scheme used is ECDSA. This can be changed,
/// without affecting code restricted against the above listed crypto types.
pub mod crypto {
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic};
use sp_application_crypto::{app_crypto, ecdsa};
use sp_core::crypto::Wraps;
app_crypto!(ecdsa, crate::KEY_TYPE);
/// Identity of a BEEFY authority using ECDSA as its crypto.
pub type AuthorityId = Public;
/// Signature for a BEEFY authority using ECDSA as its crypto.
pub type AuthoritySignature = Signature;
impl<MsgHash: Hash> BeefyAuthorityId<MsgHash> for AuthorityId
where
<MsgHash as Hash>::Output: Into<[u8; 32]>,
{
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
let msg_hash = <MsgHash as Hash>::hash(msg).into();
match sp_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,
}
}
}
}
/// The `ConsensusEngineId` of BEEFY.
pub const BEEFY_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"BEEF";
/// Authority set id starts with zero at BEEFY pallet genesis.
pub const GENESIS_AUTHORITY_SET_ID: u64 = 0;
/// A typedef for validator set id.
pub type ValidatorSetId = u64;
/// A set of BEEFY authorities, a.k.a. validators.
#[derive(Decode, Encode, Debug, PartialEq, Clone, TypeInfo)]
pub struct ValidatorSet<AuthorityId> {
/// Public keys of the validator set elements
validators: Vec<AuthorityId>,
/// Identifier of the validator set
id: ValidatorSetId,
}
impl<AuthorityId> ValidatorSet<AuthorityId> {
/// Return a validator set with the given validators and set id.
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() {
// No validators; the set would be empty.
None
} else {
Some(Self { validators, id })
}
}
/// Return a reference to the vec of validators.
pub fn validators(&self) -> &[AuthorityId] {
&self.validators
}
/// Return the validator set id.
pub fn id(&self) -> ValidatorSetId {
self.id
}
/// Return the number of validators in the set.
pub fn len(&self) -> usize {
self.validators.len()
}
}
/// The index of an authority.
pub type AuthorityIndex = u32;
/// The type used to represent an MMR root hash.
pub type MmrRootHash = H256;
/// A consensus log item for BEEFY.
#[derive(Decode, Encode, TypeInfo)]
pub enum ConsensusLog<AuthorityId: Codec> {
/// The authorities have changed.
#[codec(index = 1)]
AuthoritiesChange(ValidatorSet<AuthorityId>),
/// Disable the authority with given index.
#[codec(index = 2)]
OnDisabled(AuthorityIndex),
/// MMR root hash.
#[codec(index = 3)]
MmrRoot(MmrRootHash),
}
/// BEEFY vote message.
///
/// A vote message is a direct vote created by a BEEFY node on every voting round
/// and is gossiped to its peers.
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct VoteMessage<Number, Id, Signature> {
/// Commit to information extracted from a finalized block
pub commitment: Commitment<Number>,
/// Node authority id
pub id: Id,
/// Node signature
pub signature: Signature,
}
/// Proof of voter misbehavior on a given set id. Misbehavior/equivocation in
/// BEEFY happens when a voter votes on the same round/block for different payloads.
/// Proving is achieved by collecting the signed commitments of conflicting votes.
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct EquivocationProof<Number, Id, Signature> {
/// The first vote in the equivocation.
pub first: VoteMessage<Number, Id, Signature>,
/// The second vote in the equivocation.
pub second: VoteMessage<Number, Id, Signature>,
}
impl<Number, Id, Signature> EquivocationProof<Number, Id, Signature> {
/// Returns the authority id of the equivocator.
pub fn offender_id(&self) -> &Id {
&self.first.id
}
/// Returns the round number at which the equivocation occurred.
pub fn round_number(&self) -> &Number {
&self.first.commitment.block_number
}
/// Returns the set id at which the equivocation occurred.
pub fn set_id(&self) -> ValidatorSetId {
self.first.commitment.validator_set_id
}
}
/// Check a commitment signature by encoding the commitment and
/// verifying the provided signature using the expected authority id.
pub fn check_commitment_signature<Number, Id, MsgHash>(
commitment: &Commitment<Number>,
authority_id: &Id,
signature: &<Id as RuntimeAppPublic>::Signature,
) -> bool
where
Id: BeefyAuthorityId<MsgHash>,
Number: Clone + Encode + PartialEq,
MsgHash: Hash,
{
let encoded_commitment = commitment.encode();
BeefyAuthorityId::<MsgHash>::verify(authority_id, signature, &encoded_commitment)
}
/// Verifies the equivocation proof by making sure that both votes target
/// different blocks and that its signatures are valid.
pub fn check_equivocation_proof<Number, Id, MsgHash>(
report: &EquivocationProof<Number, Id, <Id as RuntimeAppPublic>::Signature>,
) -> bool
where
Id: BeefyAuthorityId<MsgHash> + PartialEq,
Number: Clone + Encode + PartialEq,
MsgHash: Hash,
{
let first = &report.first;
let second = &report.second;
// if votes
// come from different authorities,
// are for different rounds,
// have different validator set ids,
// or both votes have the same commitment,
// --> the equivocation is invalid.
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
}
// check signatures on both votes are valid
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
}
/// New BEEFY validator set notification hook.
pub trait OnNewValidatorSet<AuthorityId> {
/// Function called by the pallet when BEEFY validator set changes.
fn on_new_validator_set(
validator_set: &ValidatorSet<AuthorityId>,
next_validator_set: &ValidatorSet<AuthorityId>,
);
}
/// No-op implementation of [OnNewValidatorSet].
impl<AuthorityId> OnNewValidatorSet<AuthorityId> for () {
fn on_new_validator_set(_: &ValidatorSet<AuthorityId>, _: &ValidatorSet<AuthorityId>) {}
}
/// An opaque type used to represent the key ownership proof at the runtime API
/// boundary. The inner value is an encoded representation of the actual key
/// ownership proof which will be parameterized when defining the runtime. At
/// the runtime API boundary this type is unknown and as such we keep this
/// opaque representation, implementors of the runtime API will have to make
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
#[derive(Decode, Encode, PartialEq)]
pub struct OpaqueKeyOwnershipProof(Vec<u8>);
impl OpaqueKeyOwnershipProof {
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
/// representation.
pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
OpaqueKeyOwnershipProof(inner)
}
/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
/// ownership proof type.
pub fn decode<T: Decode>(self) -> Option<T> {
codec::Decode::decode(&mut &self.0[..]).ok()
}
}
sp_api::decl_runtime_apis! {
/// API necessary for BEEFY voters.
pub trait BeefyApi
{
/// Return the block number where BEEFY consensus is enabled/started
fn beefy_genesis() -> Option<NumberFor<Block>>;
/// Return the current active BEEFY validator set
fn validator_set() -> Option<ValidatorSet<crypto::AuthorityId>>;
/// Submits an unsigned extrinsic to report an equivocation. The caller
/// must provide the equivocation proof and a key ownership proof
/// (should be obtained using `generate_key_ownership_proof`). The
/// extrinsic will be unsigned and should only be accepted for local
/// authorship (not to be broadcast to the network). This method returns
/// `None` when creation of the extrinsic fails, e.g. if equivocation
/// reporting is disabled for the given runtime (i.e. this method is
/// hardcoded to return `None`). Only useful in an offchain context.
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof:
EquivocationProof<NumberFor<Block>, crypto::AuthorityId, crypto::Signature>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
/// Generates a proof of key ownership for the given authority in the
/// given set. An example usage of this module is coupled with the
/// session historical module to prove that a given authority key is
/// tied to a given staking identity during a specific session. Proofs
/// of key ownership are necessary for submitting equivocation reports.
/// NOTE: even though the API takes a `set_id` as parameter the current
/// implementations ignores this parameter and instead relies on this
/// method being called at the correct block height, i.e. any point at
/// which the given set id is live on-chain. Future implementations will
/// instead use indexed data through an offchain worker, not requiring
/// older states to be available.
fn generate_key_ownership_proof(
set_id: ValidatorSetId,
authority_id: crypto::AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_application_crypto::ecdsa::{self, Public};
use sp_core::{blake2_256, crypto::Wraps, keccak_256, Pair};
use sp_runtime::traits::{BlakeTwo256, Keccak256};
#[test]
fn validator_set() {
// Empty set not allowed.
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 beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = crypto::Pair::generate();
let keccak_256_signature: crypto::Signature =
pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into();
let blake2_256_signature: crypto::Signature =
pair.as_inner_ref().sign_prehashed(&blake2_256(msg)).into();
// Verification works if same hashing function is used when signing and verifying.
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &keccak_256_signature, msg));
assert!(BeefyAuthorityId::<BlakeTwo256>::verify(
&pair.public(),
&blake2_256_signature,
msg
));
// Verification fails if distinct hashing functions are used when signing and verifying.
assert!(!BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &blake2_256_signature, msg));
assert!(!BeefyAuthorityId::<BlakeTwo256>::verify(
&pair.public(),
&keccak_256_signature,
msg
));
// Other public key doesn't work
let (other_pair, _) = crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(
&other_pair.public(),
&keccak_256_signature,
msg,
));
assert!(!BeefyAuthorityId::<BlakeTwo256>::verify(
&other_pair.public(),
&blake2_256_signature,
msg,
));
}
}
@@ -0,0 +1,245 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! BEEFY + MMR utilties.
//!
//! While BEEFY can be used completely independently as an additional consensus gadget,
//! it is designed around a main use case of bridging standalone networks together.
//! For that use case it's common to use some aggregated data structure (like MMR) to be
//! used in conjunction with BEEFY, to be able to efficiently prove any past blockchain data.
//!
//! This module contains primitives used by Polkadot implementation of the BEEFY+MMR bridge,
//! but we imagine they will be useful for other chains that either want to bridge with Polkadot
//! or are completely standalone, but heavily inspired by Polkadot.
use crate::{crypto::AuthorityId, ConsensusLog, MmrRootHash, Vec, BEEFY_ENGINE_ID};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
generic::OpaqueDigestItemId,
traits::{Block, Header},
};
/// A provider for extra data that gets added to the Mmr leaf
pub trait BeefyDataProvider<ExtraData> {
/// Return a vector of bytes, ideally should be a merkle root hash
fn extra_data() -> ExtraData;
}
/// A default implementation for runtimes.
impl BeefyDataProvider<Vec<u8>> for () {
fn extra_data() -> Vec<u8> {
Vec::new()
}
}
/// A standard leaf that gets added every block to the MMR constructed by Substrate's `pallet_mmr`.
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub struct MmrLeaf<BlockNumber, Hash, MerkleRoot, ExtraData> {
/// Version of the leaf format.
///
/// Can be used to enable future format migrations and compatibility.
/// See [`MmrLeafVersion`] documentation for details.
pub version: MmrLeafVersion,
/// Current block parent number and hash.
pub parent_number_and_hash: (BlockNumber, Hash),
/// A merkle root of the next BEEFY authority set.
pub beefy_next_authority_set: BeefyNextAuthoritySet<MerkleRoot>,
/// Arbitrary extra leaf data to be used by downstream pallets to include custom data in the
/// [`MmrLeaf`]
pub leaf_extra: ExtraData,
}
/// An MMR leaf versioning scheme.
///
/// Version is a single byte that constist of two components:
/// - `major` - 3 bits
/// - `minor` - 5 bits
///
/// Any change in encoding that adds new items to the structure is considered non-breaking, hence
/// only requires an update of `minor` version. Any backward incompatible change (i.e. decoding to a
/// previous leaf format fails) should be indicated with `major` version bump.
///
/// Given that adding new struct elements in SCALE is backward compatible (i.e. old format can be
/// still decoded, the new fields will simply be ignored). We expect the major version to be bumped
/// very rarely (hopefuly never).
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub struct MmrLeafVersion(u8);
impl MmrLeafVersion {
/// Create new version object from `major` and `minor` components.
///
/// Panics if any of the component occupies more than 4 bits.
pub fn new(major: u8, minor: u8) -> Self {
if major > 0b111 || minor > 0b11111 {
panic!("Version components are too big.");
}
let version = (major << 5) + minor;
Self(version)
}
/// Split the version into `major` and `minor` sub-components.
pub fn split(&self) -> (u8, u8) {
let major = self.0 >> 5;
let minor = self.0 & 0b11111;
(major, minor)
}
}
/// Details of a BEEFY authority set.
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct BeefyAuthoritySet<MerkleRoot> {
/// Id of the set.
///
/// Id is required to correlate BEEFY signed commitments with the validator set.
/// Light Client can easily verify that the commitment witness it is getting is
/// produced by the latest validator set.
pub id: crate::ValidatorSetId,
/// Number of validators in the set.
///
/// Some BEEFY Light Clients may use an interactive protocol to verify only a subset
/// of signatures. We put set length here, so that these clients can verify the minimal
/// number of required signatures.
pub len: u32,
/// Merkle Root Hash built from BEEFY AuthorityIds.
///
/// This is used by Light Clients to confirm that the commitments are signed by the correct
/// validator set. Light Clients using interactive protocol, might verify only subset of
/// signatures, hence don't require the full list here (will receive inclusion proofs).
pub root: MerkleRoot,
}
/// Details of the next BEEFY authority set.
pub type BeefyNextAuthoritySet<MerkleRoot> = BeefyAuthoritySet<MerkleRoot>;
/// Extract the MMR root hash from a digest in the given header, if it exists.
pub fn find_mmr_root_digest<B: Block>(header: &B::Header) -> Option<MmrRootHash> {
let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID);
let filter = |log: ConsensusLog<AuthorityId>| match log {
ConsensusLog::MmrRoot(root) => Some(root),
_ => None,
};
header.digest().convert_first(|l| l.try_to(id).and_then(filter))
}
#[cfg(feature = "std")]
pub use mmr_root_provider::MmrRootProvider;
#[cfg(feature = "std")]
mod mmr_root_provider {
use super::*;
use crate::{known_payloads, payload::PayloadProvider, Payload};
use sp_api::{NumberFor, ProvideRuntimeApi};
use sp_mmr_primitives::MmrApi;
use sp_std::{marker::PhantomData, sync::Arc};
/// A [`crate::Payload`] provider where payload is Merkle Mountain Range root hash.
///
/// Encoded payload contains a [`crate::MmrRootHash`] type (i.e. 32-bytes hash).
pub struct MmrRootProvider<B, R> {
runtime: Arc<R>,
_phantom: PhantomData<B>,
}
impl<B, R> MmrRootProvider<B, R>
where
B: Block,
R: ProvideRuntimeApi<B>,
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
{
/// Create new BEEFY Payload provider with MMR Root as payload.
pub fn new(runtime: Arc<R>) -> Self {
Self { runtime, _phantom: PhantomData }
}
/// Simple wrapper that gets MMR root from header digests or from client state.
fn mmr_root_from_digest_or_runtime(&self, header: &B::Header) -> Option<MmrRootHash> {
find_mmr_root_digest::<B>(header).or_else(|| {
self.runtime.runtime_api().mmr_root(header.hash()).ok().and_then(|r| r.ok())
})
}
}
impl<B: Block, R> PayloadProvider<B> for MmrRootProvider<B, R>
where
B: Block,
R: ProvideRuntimeApi<B>,
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
{
fn payload(&self, header: &B::Header) -> Option<Payload> {
self.mmr_root_from_digest_or_runtime(header).map(|mmr_root| {
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode())
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::H256;
use sp_runtime::{traits::BlakeTwo256, Digest, DigestItem, OpaqueExtrinsic};
#[test]
fn should_construct_version_correctly() {
let tests = vec![(0, 0, 0b00000000), (7, 2, 0b11100010), (7, 31, 0b11111111)];
for (major, minor, version) in tests {
let v = MmrLeafVersion::new(major, minor);
assert_eq!(v.encode(), vec![version], "Encoding does not match.");
assert_eq!(v.split(), (major, minor));
}
}
#[test]
#[should_panic]
fn should_panic_if_major_too_large() {
MmrLeafVersion::new(8, 0);
}
#[test]
#[should_panic]
fn should_panic_if_minor_too_large() {
MmrLeafVersion::new(0, 32);
}
#[test]
fn extract_mmr_root_digest() {
type Header = sp_runtime::generic::Header<u64, BlakeTwo256>;
type Block = sp_runtime::generic::Block<Header, OpaqueExtrinsic>;
let mut header = Header::new(
1u64,
Default::default(),
Default::default(),
Default::default(),
Digest::default(),
);
// verify empty digest shows nothing
assert!(find_mmr_root_digest::<Block>(&header).is_none());
let mmr_root_hash = H256::random();
header.digest_mut().push(DigestItem::Consensus(
BEEFY_ENGINE_ID,
ConsensusLog::<AuthorityId>::MmrRoot(mmr_root_hash).encode(),
));
// verify validator set is correctly extracted from digest
let extracted = find_mmr_root_digest::<Block>(&header);
assert_eq!(extracted, Some(mmr_root_hash));
}
}
@@ -0,0 +1,105 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::traits::Block;
use sp_std::prelude::*;
/// Id of different payloads in the [`crate::Commitment`] data.
pub type BeefyPayloadId = [u8; 2];
/// Registry of all known [`BeefyPayloadId`].
pub mod known_payloads {
use crate::BeefyPayloadId;
/// A [`Payload`](super::Payload) identifier for Merkle Mountain Range root hash.
///
/// Encoded value should contain a [`crate::MmrRootHash`] type (i.e. 32-bytes hash).
pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh";
}
/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads.
///
/// The idea is to store a vector of SCALE-encoded values with an extra identifier.
/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected
/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only
/// support a subset of possible values.
#[derive(Decode, Encode, Debug, PartialEq, Eq, Clone, Ord, PartialOrd, Hash, TypeInfo)]
pub struct Payload(Vec<(BeefyPayloadId, Vec<u8>)>);
impl Payload {
/// Construct a new payload given an initial vallue
pub fn from_single_entry(id: BeefyPayloadId, value: Vec<u8>) -> Self {
Self(vec![(id, value)])
}
/// Returns a raw payload under given `id`.
///
/// If the [`BeefyPayloadId`] is not found in the payload `None` is returned.
pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec<u8>> {
let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?;
Some(&self.0[index].1)
}
/// Returns a decoded payload value under given `id`.
///
/// In case the value is not there or it cannot be decoded does not match `None` is returned.
pub fn get_decoded<T: Decode>(&self, id: &BeefyPayloadId) -> Option<T> {
self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok())
}
/// Push a `Vec<u8>` with a given id into the payload vec.
/// This method will internally sort the payload vec after every push.
///
/// Returns self to allow for daisy chaining.
pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec<u8>) -> Self {
self.0.push((id, value));
self.0.sort_by_key(|(id, _)| *id);
self
}
}
/// Trait for custom BEEFY payload providers.
pub trait PayloadProvider<B: Block> {
/// Provide BEEFY payload if available for `header`.
fn payload(&self, header: &B::Header) -> Option<Payload>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payload_methods_work_as_expected() {
let id1: BeefyPayloadId = *b"hw";
let msg1: String = "1. Hello World!".to_string();
let id2: BeefyPayloadId = *b"yb";
let msg2: String = "2. Yellow Board!".to_string();
let id3: BeefyPayloadId = *b"cs";
let msg3: String = "3. Cello Cord!".to_string();
let payload = Payload::from_single_entry(id1, msg1.encode())
.push_raw(id2, msg2.encode())
.push_raw(id3, msg3.encode());
assert_eq!(payload.get_decoded(&id1), Some(msg1));
assert_eq!(payload.get_decoded(&id2), Some(msg2));
assert_eq!(payload.get_raw(&id3), Some(&msg3.encode()));
assert_eq!(payload.get_raw(&known_payloads::MMR_ROOT_ID), None);
}
}
@@ -0,0 +1,110 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg(feature = "std")]
use crate::{crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage};
use codec::Encode;
use sp_core::{ecdsa, keccak_256, Pair};
use std::collections::HashMap;
use strum::IntoEnumIterator;
/// Set of test accounts using [`crate::crypto`] types.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)]
pub enum Keyring {
Alice,
Bob,
Charlie,
Dave,
Eve,
Ferdie,
One,
Two,
}
impl Keyring {
/// Sign `msg`.
pub fn sign(self, msg: &[u8]) -> crypto::Signature {
// todo: use custom signature hashing type
let msg = keccak_256(msg);
ecdsa::Pair::from(self).sign_prehashed(&msg).into()
}
/// Return key pair.
pub fn pair(self) -> crypto::Pair {
ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into()
}
/// Return public key.
pub fn public(self) -> crypto::Public {
self.pair().public()
}
/// Return seed string.
pub fn to_seed(self) -> String {
format!("//{}", self)
}
/// Get Keyring from public key.
pub fn from_public(who: &crypto::Public) -> Option<Keyring> {
Self::iter().find(|&k| &crypto::Public::from(k) == who)
}
}
lazy_static::lazy_static! {
static ref PRIVATE_KEYS: HashMap<Keyring, crypto::Pair> =
Keyring::iter().map(|i| (i, i.pair())).collect();
static ref PUBLIC_KEYS: HashMap<Keyring, crypto::Public> =
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect();
}
impl From<Keyring> for crypto::Pair {
fn from(k: Keyring) -> Self {
k.pair()
}
}
impl From<Keyring> for ecdsa::Pair {
fn from(k: Keyring) -> Self {
k.pair().into()
}
}
impl From<Keyring> for 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),
) -> EquivocationProof<u64, crypto::Public, crypto::Signature> {
let signed_vote = |block_number: u64,
payload: Payload,
validator_set_id: ValidatorSetId,
keyring: &Keyring| {
let commitment = Commitment { validator_set_id, block_number, payload };
let signature = keyring.sign(&commitment.encode());
VoteMessage { commitment, id: keyring.public(), signature }
};
let first = signed_vote(vote1.0, vote1.1, vote1.2, vote1.3);
let second = signed_vote(vote2.0, vote2.1, vote2.2, vote2.3);
EquivocationProof { first, second }
}
@@ -0,0 +1,164 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Primitives for light, 2-phase interactive verification protocol.
//!
//! Instead of submitting full list of signatures, it's possible to submit first a witness
//! form of [SignedCommitment].
//! This can later be verified by the client requesting only some (out of all) signatures for
//! verification. This allows lowering the data and computation cost of verifying the
//! signed commitment.
use sp_std::prelude::*;
use crate::commitment::{Commitment, SignedCommitment};
/// A light form of [SignedCommitment].
///
/// This is a light ("witness") form of the signed commitment. Instead of containing full list of
/// signatures, which might be heavy and expensive to verify, it only contains a bit vector of
/// validators which signed the original [SignedCommitment] and a merkle root of all signatures.
///
/// This can be used by light clients for 2-phase interactive verification (for instance for
/// Ethereum Mainnet), in a commit-reveal like scheme, where first we submit only the signed
/// commitment witness and later on, the client picks only some signatures to verify at random.
#[derive(Debug, PartialEq, Eq, codec::Encode, codec::Decode)]
pub struct SignedCommitmentWitness<TBlockNumber, TMerkleRoot> {
/// The full content of the commitment.
pub commitment: Commitment<TBlockNumber>,
/// The bit vector of validators who signed the commitment.
pub signed_by: Vec<bool>, // TODO [ToDr] Consider replacing with bitvec crate
/// A merkle root of signatures in the original signed commitment.
pub signatures_merkle_root: TMerkleRoot,
}
impl<TBlockNumber, TMerkleRoot> SignedCommitmentWitness<TBlockNumber, TMerkleRoot> {
/// Convert [SignedCommitment] into [SignedCommitmentWitness].
///
/// This takes a [SignedCommitment], which contains full signatures
/// and converts it into a witness form, which does not contain full signatures,
/// only a bit vector indicating which validators have signed the original [SignedCommitment]
/// and a merkle root of all signatures.
///
/// Returns the full list of signatures along with the witness.
pub fn from_signed<TMerkelize, TSignature>(
signed: SignedCommitment<TBlockNumber, TSignature>,
merkelize: TMerkelize,
) -> (Self, Vec<Option<TSignature>>)
where
TMerkelize: FnOnce(&[Option<TSignature>]) -> TMerkleRoot,
{
let SignedCommitment { commitment, signatures } = signed;
let signed_by = signatures.iter().map(|s| s.is_some()).collect();
let signatures_merkle_root = merkelize(&signatures);
(Self { commitment, signed_by, signatures_merkle_root }, signatures)
}
}
#[cfg(test)]
mod tests {
use sp_core::{keccak_256, Pair};
use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr};
use super::*;
use codec::Decode;
use crate::{crypto, known_payloads, Payload, KEY_TYPE};
type TestCommitment = Commitment<u128>;
type TestSignedCommitment = SignedCommitment<u128, crypto::Signature>;
type TestSignedCommitmentWitness =
SignedCommitmentWitness<u128, Vec<Option<crypto::Signature>>>;
// The mock signatures are equivalent to the ones produced by the BEEFY keystore
fn mock_signatures() -> (crypto::Signature, crypto::Signature) {
let store: SyncCryptoStorePtr = KeyStore::new().into();
let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
let _ =
SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref())
.unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg)
.unwrap()
.unwrap();
let msg = keccak_256(b"This is the second message");
let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg)
.unwrap()
.unwrap();
(sig1.into(), sig2.into())
}
fn signed_commitment() -> TestSignedCommitment {
let payload = Payload::from_single_entry(
known_payloads::MMR_ROOT_ID,
"Hello World!".as_bytes().to_vec(),
);
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
SignedCommitment { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)] }
}
#[test]
fn should_convert_signed_commitment_to_witness() {
// given
let signed = signed_commitment();
// when
let (witness, signatures) =
TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
// then
assert_eq!(witness.signatures_merkle_root, signatures);
}
#[test]
fn should_encode_and_decode_witness() {
// given
let signed = signed_commitment();
let (witness, _) = TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
// when
let encoded = codec::Encode::encode(&witness);
let decoded = TestSignedCommitmentWitness::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(witness));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"\
046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010\
0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c\
746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a8\
6cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487b\
ca2324b6a0046395a71681be3d0c2a00\
"
)
);
}
}