feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,74 @@
[package]
name = "pezsp-consensus-beefy"
version = "13.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Primitives for BEEFY protocol."
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
serde = { optional = true, features = ["alloc", "derive"], workspace = true }
pezsp-api = { workspace = true }
pezsp-application-crypto = { workspace = true }
pezsp-core = { workspace = true }
pezsp-crypto-hashing = { workspace = true }
pezsp-io = { workspace = true }
pezsp-keystore = { workspace = true }
pezsp-mmr-primitives = { workspace = true }
pezsp-runtime = { workspace = true }
pezsp-weights = { workspace = true }
strum = { features = ["derive"], workspace = true }
[dev-dependencies]
array-bytes = { workspace = true, default-features = true }
w3f-bls = { features = ["std"], workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"serde/std",
"pezsp-api/std",
"pezsp-application-crypto/std",
"pezsp-core/std",
"pezsp-crypto-hashing/std",
"pezsp-io/std",
"pezsp-keystore/std",
"pezsp-mmr-primitives/std",
"pezsp-runtime/std",
"pezsp-weights/std",
"strum/std",
]
# Serde support without relying on std features.
serde = [
"dep:serde",
"scale-info/serde",
"pezsp-application-crypto/serde",
"pezsp-core/serde",
"pezsp-runtime/serde",
]
# 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 = [
"pezsp-application-crypto/bls-experimental",
"pezsp-core/bls-experimental",
]
runtime-benchmarks = [
"pezsp-api/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-mmr-primitives/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
@@ -0,0 +1,588 @@
// This file is part of Bizinikiwi.
// 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 alloc::{vec, vec::Vec};
use codec::{Decode, DecodeWithMemTracking, Encode, Error, Input};
use core::cmp;
use scale_info::TypeInfo;
use pezsp_application_crypto::RuntimeAppPublic;
use pezsp_runtime::traits::Hash;
use crate::{BeefyAuthorityId, Payload, ValidatorSet, ValidatorSetId};
/// A commitment signature, accompanied by the id of the validator that it belongs to.
#[derive(Debug)]
pub struct KnownSignature<TAuthorityId, TSignature> {
/// The signing validator.
pub validator_id: TAuthorityId,
/// The signature.
pub signature: TSignature,
}
impl<TAuthorityId: Clone, TSignature: Clone> KnownSignature<&TAuthorityId, &TSignature> {
/// Creates a `KnownSignature<TAuthorityId, TSignature>` from an
/// `KnownSignature<&TAuthorityId, &TSignature>`.
pub fn to_owned(&self) -> KnownSignature<TAuthorityId, TSignature> {
KnownSignature {
validator_id: self.validator_id.clone(),
signature: self.signature.clone(),
}
}
}
/// 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, DecodeWithMemTracking, 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: core::fmt::Debug, TSignature> core::fmt::Display
for SignedCommitment<TBlockNumber, TSignature>
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let signatures_count = self.signatures.iter().filter(|s| s.is_some()).count();
write!(
f,
"SignedCommitment(commitment: {:?}, signatures_count: {})",
self.commitment, signatures_count
)
}
}
impl<TBlockNumber, TSignature> SignedCommitment<TBlockNumber, TSignature> {
/// Return the number of collected signatures.
pub fn signature_count(&self) -> usize {
self.signatures.iter().filter(|x| x.is_some()).count()
}
/// Verify all the commitment signatures against the validator set that was active
/// at the block where the commitment was generated.
///
/// Returns the valid validator-signature pairs if the commitment can be verified.
pub fn verify_signatures<'a, TAuthorityId, MsgHash>(
&'a self,
target_number: TBlockNumber,
validator_set: &'a ValidatorSet<TAuthorityId>,
) -> Result<Vec<KnownSignature<&'a TAuthorityId, &'a TSignature>>, u32>
where
TBlockNumber: Clone + Encode + PartialEq,
TAuthorityId: RuntimeAppPublic<Signature = TSignature> + BeefyAuthorityId<MsgHash>,
MsgHash: Hash,
{
if self.signatures.len() != validator_set.len() ||
self.commitment.validator_set_id != validator_set.id() ||
self.commitment.block_number != target_number
{
return Err(0);
}
// Arrangement of signatures in the commitment should be in the same order
// as validators for that set.
let encoded_commitment = self.commitment.encode();
let signatories: Vec<_> = validator_set
.validators()
.into_iter()
.zip(self.signatures.iter())
.filter_map(|(id, maybe_signature)| {
let signature = maybe_signature.as_ref()?;
match BeefyAuthorityId::verify(id, signature, &encoded_commitment) {
true => Some(KnownSignature { validator_id: id, signature }),
false => None,
}
})
.collect();
Ok(signatories)
}
}
/// 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: core::fmt::Debug, S> core::fmt::Display for VersionedFinalityProof<N, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VersionedFinalityProof::V1(sc) => write!(f, "VersionedFinalityProof::V1({})", sc),
}
}
}
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::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads};
use codec::Decode;
use pezsp_core::Pair;
use pezsp_crypto_hashing::keccak_256;
#[cfg(feature = "bls-experimental")]
use crate::bls_crypto::Signature as BlsSignature;
type TestCommitment = Commitment<u128>;
const LARGE_RAW_COMMITMENT: &[u8] = include_bytes!("../test-res/large-raw-commitment");
// Types for bls-less commitment
type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
type TestVersionedFinalityProof = VersionedFinalityProof<u128, EcdsaSignature>;
// Types for commitment supporting aggregatable bls signature
#[cfg(feature = "bls-experimental")]
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
struct BlsAggregatableSignature(BlsSignature);
#[cfg(feature = "bls-experimental")]
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
#[cfg(feature = "bls-experimental")]
type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
// Generates mock aggregatable ecdsa signature for generating test commitment
// BLS signatures
fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
let alice = pezsp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = alice.sign_prehashed(&msg);
let msg = keccak_256(b"This is the second message");
let sig2 = alice.sign_prehashed(&msg);
(sig1.into(), sig2.into())
}
// Generates mock aggregatable bls signature for generating test commitment
// BLS signatures
#[cfg(feature = "bls-experimental")]
fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
let alice = pezsp_core::bls::Pair::from_string("//Alice", None).unwrap();
let msg = b"This is the first message";
let sig1 = alice.sign(msg);
let msg = b"This is the second message";
let sig2 = alice.sign(msg);
(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_ecdsa() {
// 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 ecdsa_sigs = mock_ecdsa_signatures();
let ecdsa_signed = SignedCommitment {
commitment: commitment.clone(),
signatures: vec![None, None, Some(ecdsa_sigs.0.clone()), Some(ecdsa_sigs.1.clone())],
};
// when
let encoded = codec::Encode::encode(&ecdsa_signed);
let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(ecdsa_signed));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"\
046d68343048656c6c6f20576f726c64210500000000000000000000000000000000000000000000000\
4300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746c\
c321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aa\
acdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6\
a0046395a71681be3d0c2a00\
"
)
);
}
#[test]
#[cfg(feature = "bls-experimental")]
fn signed_commitment_encode_decode_ecdsa_n_bls() {
// 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 ecdsa_sigs = mock_ecdsa_signatures();
//including bls signature
let bls_signed_msgs = mock_bls_signatures();
let ecdsa_and_bls_signed = SignedCommitment {
commitment,
signatures: vec![
None,
None,
Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_signed_msgs.0)),
Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_signed_msgs.1)),
],
};
//when
let encoded = codec::Encode::encode(&ecdsa_and_bls_signed);
let decoded = TestBlsSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(ecdsa_and_bls_signed));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba0182022df4689ef25499205f7154a1a62eb2d6d5c4a3657efed321e2c277998130d1b01a264c928afb79534cb0fa9dcf79f67ed4e6bf2de576bb936146f2fa60fa56b8651677cc764ea4fe317c62294c2a0c5966e439653eed0572fded5e2461c888518e0769718dcce9f3ff612fb89d262d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00a90973bea76fac3a4e2d76a25ec3926d6a5a20aacee15ec0756cd268088ed5612b67b4a49349cee70bc1185078d17c7f7df9d944e8be30022d9680d0437c4ba4600d74050692e8ee9b96e37df2a39d1cb4b4af4b6a058342dd9e8c7481a3a0b8975ad8614c953e950253aa327698d842"
)
);
}
#[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_ecdsa_signatures();
let mut signed = SignedCommitment {
commitment,
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
};
assert_eq!(signed.signature_count(), 2);
// when
signed.signatures[2] = None;
// then
assert_eq!(signed.signature_count(), 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_ecdsa_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_ecdsa_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 = TestEcdsaSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(signed));
assert_eq!(encoded, LARGE_RAW_COMMITMENT);
}
}
@@ -0,0 +1,649 @@
// This file is part of Bizinikiwi.
// 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 Pezkuwi use case we plan to use `secp256k1` for BEEFY,
//! while GRANDPA uses `ed25519`.
extern crate alloc;
mod commitment;
mod payload;
pub mod mmr;
pub mod witness;
/// Test utilities
#[cfg(feature = "std")]
pub mod test_utils;
pub use commitment::{Commitment, KnownSignature, SignedCommitment, VersionedFinalityProof};
pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider};
use alloc::vec::Vec;
use codec::{Codec, Decode, DecodeWithMemTracking, Encode};
use core::fmt::{Debug, Display};
use scale_info::TypeInfo;
use pezsp_application_crypto::{AppPublic, RuntimeAppPublic};
use pezsp_core::H256;
use pezsp_runtime::{
traits::{Hash, Header as HeaderT, Keccak256, NumberFor},
OpaqueValue,
};
use pezsp_weights::Weight;
/// Key type for BEEFY module.
pub const KEY_TYPE: pezsp_core::crypto::KeyTypeId = pezsp_application_crypto::key_types::BEEFY;
/// 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;
}
/// Hasher used for BEEFY signatures.
pub type BeefySignatureHasher = pezsp_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:
Ord
+ AppPublic
+ Display
+ BeefyAuthorityId<BeefySignatureHasher, Signature = Self::BoundedSignature>
{
/// Necessary bounds on the Signature associated with the AuthorityId
type BoundedSignature: Debug + Eq + PartialEq + Clone + TypeInfo + Codec + Send + Sync;
}
/// BEEFY cryptographic types for ECDSA crypto
///
/// This module basically introduces four crypto types:
/// - `ecdsa_crypto::Pair`
/// - `ecdsa_crypto::Public`
/// - `ecdsa_crypto::Signature`
/// - `ecdsa_crypto::AuthorityId`
///
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
pub mod ecdsa_crypto {
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use pezsp_application_crypto::{app_crypto, ecdsa};
use pezsp_core::crypto::Wraps;
app_crypto!(ecdsa, 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 pezsp_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;
}
}
/// BEEFY cryptographic types for BLS crypto
///
/// This module basically introduces four crypto types:
/// - `bls_crypto::Pair`
/// - `bls_crypto::Public`
/// - `bls_crypto::Signature`
/// - `bls_crypto::AuthorityId`
///
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
#[cfg(feature = "bls-experimental")]
pub mod bls_crypto {
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use pezsp_application_crypto::{app_crypto, bls381};
use pezsp_core::{bls381::Pair as BlsPair, crypto::Wraps, Pair as _};
app_crypto!(bls381, KEY_TYPE);
/// Identity of a BEEFY authority using BLS as its crypto.
pub type AuthorityId = Public;
/// Signature for a BEEFY authority using BLS 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 {
// `w3f-bls` library uses IETF hashing standard and as such does not expose
// 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 {
type BoundedSignature = Signature;
}
}
/// BEEFY cryptographic types for (ECDSA,BLS) crypto pair
///
/// This module basically introduces four crypto types:
/// - `ecdsa_bls_crypto::Pair`
/// - `ecdsa_bls_crypto::Public`
/// - `ecdsa_bls_crypto::Signature`
/// - `ecdsa_bls_crypto::AuthorityId`
///
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls_crypto {
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use pezsp_application_crypto::{app_crypto, ecdsa_bls381};
use pezsp_core::{crypto::Wraps, ecdsa_bls381::Pair as EcdsaBlsPair};
app_crypto!(ecdsa_bls381, KEY_TYPE);
/// Identity of a BEEFY authority using (ECDSA,BLS) as its crypto.
pub type AuthorityId = Public;
/// Signature for a BEEFY authority using (ECDSA,BLS) as its crypto.
pub type AuthoritySignature = Signature;
impl<H> BeefyAuthorityId<H> for AuthorityId
where
H: Hash,
H::Output: Into<[u8; 32]>,
{
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
// We can not simply call
// `EcdsaBlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())`
// because that invokes ECDSA default verification which performs Blake2b hash
// which we don't want. This is because ECDSA signatures are meant to be verified
// on Ethereum network where Keccak hasher is significantly cheaper than Blake2b.
// See Figure 3 of [OnSc21](https://www.scitepress.org/Papers/2021/106066/106066.pdf)
// for comparison.
EcdsaBlsPair::verify_with_hasher::<H>(
signature.as_inner_ref(),
msg,
self.as_inner_ref(),
)
}
}
impl AuthorityIdBound for AuthorityId {
type BoundedSignature = Signature;
}
}
/// The `ConsensusEngineId` of BEEFY.
pub const BEEFY_ENGINE_ID: pezsp_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 Hashing used within MMR.
pub type MmrHashing = Keccak256;
/// 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.
// TODO: Remove `Signature` generic type, instead get it from `Id::Signature`.
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, 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 showing that an authority voted twice in the same round.
///
/// One type of misbehavior in BEEFY happens when an authority votes in the same round/block
/// for different payloads.
/// Proving is achieved by collecting the signed commitments of conflicting votes.
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct DoubleVotingProof<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> DoubleVotingProof<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
}
}
/// Proof showing that an authority voted for a non-canonical chain.
///
/// Proving is achieved by providing a proof that contains relevant info about the canonical chain
/// at `commitment.block_number`. The `commitment` can be checked against this info.
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct ForkVotingProof<Header: HeaderT, Id: RuntimeAppPublic, AncestryProof> {
/// The equivocated vote.
pub vote: VoteMessage<Header::Number, Id, Id::Signature>,
/// Proof containing info about the canonical chain at `commitment.block_number`.
pub ancestry_proof: AncestryProof,
/// The header of the block where the ancestry proof was generated
pub header: Header,
}
impl<Header: HeaderT, Id: RuntimeAppPublic> ForkVotingProof<Header, Id, OpaqueValue> {
/// Try to decode the `AncestryProof`.
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,
})
}
}
/// Proof showing that an authority voted for a future block.
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
pub struct FutureBlockVotingProof<Number, Id: RuntimeAppPublic> {
/// The equivocated vote.
pub vote: VoteMessage<Number, Id, Id::Signature>,
}
/// 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_double_voting_proof<Number, Id, MsgHash>(
report: &DoubleVotingProof<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>) {}
}
/// Hook containing helper methods for proving/checking commitment canonicity.
pub trait AncestryHelper<Header: HeaderT> {
/// Type containing proved info about the canonical chain at a certain height.
type Proof: Clone + Debug + Decode + Encode + PartialEq + TypeInfo;
/// The data needed for validating the proof.
type ValidationContext;
/// Check if the proof is optimal.
fn is_proof_optimal(proof: &Self::Proof) -> bool;
/// Extract the validation context from the provided header.
fn extract_validation_context(header: Header) -> Option<Self::ValidationContext>;
/// Check if a commitment is pointing to a header on a non-canonical chain
/// against a canonicity proof generated at the same header height.
fn is_non_canonical(
commitment: &Commitment<Header::Number>,
proof: Self::Proof,
context: Self::ValidationContext,
) -> bool;
}
/// Weight information for the logic in `AncestryHelper`.
pub trait AncestryHelperWeightInfo<Header: HeaderT>: AncestryHelper<Header> {
/// Weight info for the `AncestryHelper::is_proof_optimal()` method.
fn is_proof_optimal(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
/// Weight info for the `AncestryHelper::extract_validation_context()` method.
fn extract_validation_context() -> Weight;
/// Weight info for the `AncestryHelper::is_non_canonical()` method.
fn is_non_canonical(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
}
/// 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.
pub type OpaqueKeyOwnershipProof = OpaqueValue;
pezsp_api::decl_runtime_apis! {
/// API necessary for BEEFY voters.
#[api_version(6)]
pub trait BeefyApi<AuthorityId> where
AuthorityId : Codec + RuntimeAppPublic,
{
/// 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<AuthorityId>>;
/// Submits an unsigned extrinsic to report a double voting equivocation. The caller
/// must provide the double voting 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_double_voting_unsigned_extrinsic(
equivocation_proof:
DoubleVotingProof<NumberFor<Block>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
/// Submits an unsigned extrinsic to report a fork voting equivocation. The caller
/// must provide the fork voting proof (the ancestry proof should be obtained using
/// `generate_ancestry_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_fork_voting_unsigned_extrinsic(
equivocation_proof:
ForkVotingProof<Block::Header, AuthorityId, OpaqueValue>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
/// Submits an unsigned extrinsic to report a future block voting equivocation. The caller
/// must provide the future block voting 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_future_block_voting_unsigned_extrinsic(
equivocation_proof:
FutureBlockVotingProof<NumberFor<Block>, AuthorityId>,
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: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsp_application_crypto::ecdsa::{self, Public};
use pezsp_core::crypto::{Pair, Wraps};
use pezsp_crypto_hashing::{blake2_256, keccak_256};
use pezsp_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 ecdsa_beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = ecdsa_crypto::Pair::generate();
let keccak_256_signature: ecdsa_crypto::Signature =
pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into();
let blake2_256_signature: ecdsa_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, _) = ecdsa_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,
));
}
#[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();
// Verification works if same hashing function is used when signing and verifying.
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));
// Other public key doesn't work
let (other_pair, _) = bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::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();
// Verification works if same hashing function is used when signing and verifying.
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));
// Verification doesn't work if we verify function provided by pair_crypto implementation
assert!(!ecdsa_bls_crypto::Pair::verify(&signature, msg, &pair.public()));
// Other public key doesn't work
let (other_pair, _) = ecdsa_bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
}
}
@@ -0,0 +1,261 @@
// This file is part of Bizinikiwi.
// 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 utilities.
//!
//! 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 Pezkuwi implementation of the BEEFY+MMR bridge,
//! but we imagine they will be useful for other chains that either want to bridge with Pezkuwi
//! or are completely standalone, but heavily inspired by Pezkuwi.
use crate::{ecdsa_crypto::AuthorityId, ConsensusLog, MmrRootHash, BEEFY_ENGINE_ID};
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use pezsp_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 Bizinikiwi's `pezpallet_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 consists 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 (hopefully 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 = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BeefyAuthoritySet<AuthoritySetCommitment> {
/// 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,
/// Commitment(s) to 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).
///
/// This could be Merkle Root Hash built from BEEFY ECDSA public keys and/or
/// polynomial commitment to the polynomial interpolating BLS public keys
/// which is used by APK proof based light clients to verify the validity
/// of aggregated BLS keys using APK proofs.
/// Multiple commitments can be tupled together.
pub keyset_commitment: AuthoritySetCommitment,
}
/// 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 alloc::sync::Arc;
use core::marker::PhantomData;
use pezsp_api::ProvideRuntimeApi;
use pezsp_mmr_primitives::MmrApi;
use pezsp_runtime::traits::NumberFor;
/// 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> Clone for MmrRootProvider<B, R> {
fn clone(&self) -> Self {
Self { runtime: self.runtime.clone(), _phantom: PhantomData }
}
}
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 pezsp_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 = pezsp_runtime::generic::Header<u64, BlakeTwo256>;
type Block = pezsp_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,135 @@
// This file is part of Bizinikiwi.
// 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 alloc::{vec, vec::Vec};
use codec::{Decode, DecodeWithMemTracking, Encode};
use scale_info::TypeInfo;
use pezsp_runtime::traits::Block;
/// 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,
DecodeWithMemTracking,
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 value
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 all the raw payloads under given `id`.
pub fn get_all_raw<'a>(
&'a self,
id: &'a BeefyPayloadId,
) -> impl Iterator<Item = &'a Vec<u8>> + 'a {
self.0
.iter()
.filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) })
}
/// Returns a decoded payload value under given `id`.
///
/// In case the value is not there, or it cannot be decoded `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())
}
/// Returns all decoded payload values under given `id`.
pub fn get_all_decoded<'a, T: Decode>(
&'a self,
id: &'a BeefyPayloadId,
) -> impl Iterator<Item = Option<T>> + 'a {
self.get_all_raw(id).map(|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,180 @@
// This file is part of Bizinikiwi.
// 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 = "bls-experimental")]
use crate::ecdsa_bls_crypto;
use crate::{
ecdsa_crypto, AuthorityIdBound, BeefySignatureHasher, Commitment, DoubleVotingProof,
ForkVotingProof, FutureBlockVotingProof, Payload, ValidatorSetId, VoteMessage,
};
use pezsp_application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps};
use pezsp_core::{ecdsa, Pair};
use pezsp_runtime::traits::{BlockNumber, Hash, Header as HeaderT};
use codec::Encode;
use std::{collections::HashMap, marker::PhantomData, sync::LazyLock};
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<AuthorityId> {
Alice,
Bob,
Charlie,
Dave,
Eve,
Ferdie,
One,
Two,
_Marker(PhantomData<AuthorityId>),
}
/// 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]) -> <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) -> <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) -> AuthorityId {
self.pair().public().into()
}
/// Return seed string.
pub fn to_seed(&self) -> String {
format!("//{}", self)
}
/// Get Keyring from public key.
pub fn from_public(who: &AuthorityId) -> Option<Keyring<AuthorityId>> {
Self::iter().find(|k| k.public() == *who)
}
}
static PRIVATE_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Pair>> =
LazyLock::new(|| Keyring::iter().map(|i| (i.clone(), i.pair())).collect());
static PUBLIC_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Public>> =
LazyLock::new(|| {
PRIVATE_KEYS
.iter()
.map(|(name, pair)| (name.clone(), pezsp_application_crypto::Pair::public(pair)))
.collect()
});
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Pair {
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
k.pair()
}
}
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa::Pair {
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
k.pair().into()
}
}
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 `VoteMessage` from commitment primitives and keyring
pub fn signed_vote<Number: BlockNumber>(
block_number: Number,
payload: Payload,
validator_set_id: ValidatorSetId,
keyring: &Keyring<ecdsa_crypto::AuthorityId>,
) -> VoteMessage<Number, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
let commitment = Commitment { validator_set_id, block_number, payload };
let signature = keyring.sign(&commitment.encode());
VoteMessage { commitment, id: keyring.public(), signature }
}
/// Create a new `DoubleVotingProof` based on given arguments.
pub fn generate_double_voting_proof(
vote1: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
vote2: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
) -> DoubleVotingProof<u64, ecdsa_crypto::Public, ecdsa_crypto::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);
DoubleVotingProof { first, second }
}
/// Create a new `ForkVotingProof` based on vote & canonical header.
pub fn generate_fork_voting_proof<Header: HeaderT<Number = u64>, AncestryProof>(
vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
ancestry_proof: AncestryProof,
header: Header,
) -> ForkVotingProof<Header, ecdsa_crypto::Public, AncestryProof> {
let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
ForkVotingProof { vote: signed_vote, ancestry_proof, header }
}
/// Create a new `ForkVotingProof` based on vote & canonical header.
pub fn generate_future_block_voting_proof(
vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
) -> FutureBlockVotingProof<u64, ecdsa_crypto::Public> {
let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
FutureBlockVotingProof { vote: signed_vote }
}
@@ -0,0 +1,254 @@
// This file is part of Bizinikiwi.
// 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 crate::commitment::{Commitment, SignedCommitment};
use alloc::vec::Vec;
/// 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, TSignatureAccumulator> {
/// 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
/// Either a merkle root of signatures in the original signed commitment or a single aggregated
/// BLS signature aggregating all original signatures.
pub signature_accumulator: TSignatureAccumulator,
}
impl<TBlockNumber, TSignatureAccumulator>
SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator>
{
/// 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<TSignatureAggregator, TSignature>(
signed: SignedCommitment<TBlockNumber, TSignature>,
aggregator: TSignatureAggregator,
) -> (Self, Vec<Option<TSignature>>)
where
TSignatureAggregator: FnOnce(&[Option<TSignature>]) -> TSignatureAccumulator,
{
let SignedCommitment { commitment, signatures } = signed;
let signed_by = signatures.iter().map(|s| s.is_some()).collect();
let signature_accumulator = aggregator(&signatures);
(Self { commitment, signed_by, signature_accumulator }, signatures)
}
}
#[cfg(test)]
mod tests {
use pezsp_core::Pair;
use pezsp_crypto_hashing::keccak_256;
use super::*;
use codec::Decode;
use crate::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads, Payload};
#[cfg(feature = "bls-experimental")]
use crate::bls_crypto::Signature as BlsSignature;
#[cfg(feature = "bls-experimental")]
use w3f_bls::{
single_pop_aggregator::SignatureAggregatorAssumingPoP, Message, SerializableToBytes,
Signed, TinyBLS381,
};
type TestCommitment = Commitment<u128>;
// Types for ecdsa signed commitment.
type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
type TestEcdsaSignedCommitmentWitness =
SignedCommitmentWitness<u128, Vec<Option<EcdsaSignature>>>;
#[cfg(feature = "bls-experimental")]
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
// types for commitment containing bls signature along side ecdsa signature
#[cfg(feature = "bls-experimental")]
type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
#[cfg(feature = "bls-experimental")]
type TestBlsSignedCommitmentWitness = SignedCommitmentWitness<u128, Vec<u8>>;
// The mock signatures are equivalent to the ones produced by the BEEFY keystore
fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
let alice = pezsp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = alice.sign_prehashed(&msg);
let msg = keccak_256(b"This is the second message");
let sig2 = alice.sign_prehashed(&msg);
(sig1.into(), sig2.into())
}
// Generates mock aggregatable bls signature for generating test commitment
// BLS signatures
#[cfg(feature = "bls-experimental")]
fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
let alice = pezsp_core::bls::Pair::from_string("//Alice", None).unwrap();
let msg = b"This is the first message";
let sig1 = alice.sign(msg);
let msg = b"This is the second message";
let sig2 = alice.sign(msg);
(sig1.into(), sig2.into())
}
fn ecdsa_signed_commitment() -> TestEcdsaSignedCommitment {
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_ecdsa_signatures();
SignedCommitment { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)] }
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_and_bls_signed_commitment() -> TestBlsSignedCommitment {
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 ecdsa_sigs = mock_ecdsa_signatures();
let bls_sigs = mock_bls_signatures();
SignedCommitment {
commitment,
signatures: vec![
None,
None,
Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_sigs.0)),
Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_sigs.1)),
],
}
}
#[test]
fn should_convert_signed_commitment_to_witness() {
// given
let signed = ecdsa_signed_commitment();
// when
let (witness, signatures) =
TestEcdsaSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
// then
assert_eq!(witness.signature_accumulator, signatures);
}
#[test]
#[cfg(feature = "bls-experimental")]
fn should_convert_dually_signed_commitment_to_witness() {
// given
let signed = ecdsa_and_bls_signed_commitment();
// when
let (witness, _signatures) =
// from signed take a function as the aggregator
TestBlsSignedCommitmentWitness::from_signed::<_, _>(signed, |sigs| {
// we are going to aggregate the signatures here
let mut aggregatedsigs: SignatureAggregatorAssumingPoP<TinyBLS381> =
SignatureAggregatorAssumingPoP::new(Message::new(b"", b"mock payload"));
for sig in sigs {
match sig {
Some(sig) => {
let serialized_sig : Vec<u8> = (*sig.1).to_vec();
aggregatedsigs.add_signature(
&w3f_bls::Signature::<TinyBLS381>::from_bytes(
serialized_sig.as_slice()
).unwrap()
);
},
None => (),
}
}
(&aggregatedsigs).signature().to_bytes()
});
// We can't use BlsSignature::try_from because it expected 112Bytes (CP (64) + BLS 48)
// single signature while we are having a BLS aggregated signature corresponding to no CP.
w3f_bls::Signature::<TinyBLS381>::from_bytes(witness.signature_accumulator.as_slice())
.unwrap();
}
#[test]
fn should_encode_and_decode_witness() {
// Given
let signed = ecdsa_signed_commitment();
let (witness, _) = TestEcdsaSignedCommitmentWitness::from_signed::<_, _>(
signed,
|sigs: &[std::option::Option<EcdsaSignature>]| sigs.to_vec(),
);
// When
let encoded = codec::Encode::encode(&witness);
let decoded = TestEcdsaSignedCommitmentWitness::decode(&mut &*encoded);
// Then
assert_eq!(decoded, Ok(witness));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
"\
046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010\
0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c\
746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a8\
6cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487b\
ca2324b6a0046395a71681be3d0c2a00\
"
)
);
}
}