Support for multiple signature scheme for BEEFY primitves (#14373)

* Merged BEEFY primitives with generic signature and keyset commitment support from old pull to current code

* - Add bls-experimental feature to application-crypto and beefy primitives
- Fix remaining crypto -> ecdsa_crypto
- code build but not tests

* Make beefy primitive tests compile

* move bls related beefy primitives code and test behind bls-experimental flag

* Make BEEFY clients complies with BEEFY API depending on AuthorityId

* - Rename `BeefyAuthoritySet.root` → `BeefyAuthoritySet.keyset_commitment`.
- Remove apk proof keyset_commitment from `BeefyAuthoritySet`.
- Fix failing signed commitment and signature to witness test.
- Make client compatible with BeefyAPI generic on AuthorityId.
- `crypto` → `ecdsa_crypto` in BEEFY client and frame.

* Commit Cargo lock remove ark-serialize from BEEFY primitives

* Use Codec instead of Encode + Decode in primitives/consensus/beefy/src/lib.rs

Co-authored-by: Davide Galassi <davxy@datawok.net>

* - Make `BeefyApi` generic over Signature type.
- Make new `BeeyApi` functinos also generic over AuthorityId and Signature

* Unmake BeefyAPI generic over Signature. Recover Signature type from AuthId.

* - dont use hex or hex-literal use array-bytes instead in beefy primitives and bls crypto.
- CamelCase ECDSA and BLS everywhere.

* Move the definition of BEEFY key type from `primitives/beefy` to `crypto.rs` according to new convention.

* - Add bls377_generate_new to `sp-io` and `application_crypto::bls`.
- Add `bls-experimental` to `sp-io`

Does not compile because PassByCodec can not derive PassBy using customly implemented PassByIner.

* Implement PassBy for `bls::Public` manually

* fix Beefy `KEY_TYPE` in `frame/beefy` tests to come from `sp-core::key_types` enum

* specify both generic for `hex2array_unchecked` in `sp-core/bls.rs`

* Rename `crypto`→`ecdsa_crypto` in `primitives/consensus/beefy/src/test_utils.rs` docs

* remove commented-out code in `primitives/consensus/beefy/src/commitment.rs`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Fix inconsistency in panic message in  `primitives/io/src/lib.rs`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Remove redundant feature activation in `primitives/io/Cargo.toml`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* - make `w3f-bls` a dev-dependancy only for beefy primitives.

- clean up comments.

Co-authored-by: Davide Galassi <davxy@datawok.net>

* export BEEFY KEY_TYPE from primitives/consensus/beefy
make `frame/consensus/beefy` in dependent of sp_crypto_app
use consistent naming in the beefy primitive tests.

* - implement `BeefyAuthorityId` for `bls_crypto::AuthorityId`.
- implement `bls_verify_works` test for BEEFY `bls_crypto`.

* Remove BEEFY `ecdsa_n_bls_crypto` for now for later re-introduction

* Make commitment and witness BEEFY tests not use Keystore.

* put `bls_beefy_verify_works` test under `bls-experimental` flag.

* bump up Runtime `BeefyAPI` to version 3 due to introducing generic AuthorityId.

* reuse code and encapsulate w3f-bls backend in sp-core as most as possible

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Make comments in primities BEEFY `commitment.rs` and `witness.rs``tests convention conforming

* Use master dep versions

* Trivial change. Mostly to trigger CI

* Apply suggestions from code review

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>

* Fix Cargo.toml

* Trigger CI with cumulus companion

* Trigger CI after polkadot companion change

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
drskalman
2023-08-02 15:42:04 +02:00
committed by GitHub
parent 8dc3bd729b
commit 3fef703e30
29 changed files with 502 additions and 192 deletions
@@ -249,30 +249,60 @@ impl<N, S> From<SignedCommitment<N, S>> for VersionedFinalityProof<N, S> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{crypto, known_payloads, KEY_TYPE};
use crate::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads};
use codec::Decode;
use sp_core::{keccak_256, Pair};
use sp_keystore::{testing::MemoryKeystore, KeystorePtr};
#[cfg(feature = "bls-experimental")]
use crate::bls_crypto::Signature as BlsSignature;
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: KeystorePtr = MemoryKeystore::new().into();
// 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 = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
store.insert(KEY_TYPE, "//Alice", alice.public().as_ref()).unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap();
let sig1 = alice.sign_prehashed(&msg);
let msg = keccak_256(b"This is the second message");
let sig2 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap();
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 = sp_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())
}
@@ -300,26 +330,26 @@ mod tests {
}
#[test]
fn signed_commitment_encode_decode() {
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 sigs = mock_signatures();
let ecdsa_sigs = mock_ecdsa_signatures();
let signed = SignedCommitment {
commitment,
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
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(&signed);
let decoded = TestSignedCommitment::decode(&mut &*encoded);
let encoded = codec::Encode::encode(&ecdsa_signed);
let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(signed));
assert_eq!(decoded, Ok(ecdsa_signed));
assert_eq!(
encoded,
array_bytes::hex2bytes_unchecked(
@@ -334,6 +364,44 @@ mod tests {
);
}
#[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(
"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01667603fc041cf9d7147d22bf54b15e5778893d6986b71a929747befd3b4d233fbe668bc480e8865116b94db46ca25a01e03c71955f2582604e415da68f2c3c406b9d5f4ad416230ec5453f05ac16a50d8d0923dfb0413cc956ae3fa6334465bd1f2cacec8e9cd606438390fe2a29dc052d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00df61d3b2be0963eb6caa243cc505d327aec73e1bb7ffe9a14b1354b0c406792ac6d6f47c06987c15dec9993f43eefa001d866fe0850d986702c414840f0d9ec0fdc04832ef91ae37c8d49e2f573ca50cb37f152801d489a19395cb04e5fc8f2ab6954b58a3bcc40ef9b6409d2ff7ef07"
)
);
}
#[test]
fn signed_commitment_count_signatures() {
// given
@@ -342,7 +410,7 @@ mod tests {
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let sigs = mock_ecdsa_signatures();
let mut signed = SignedCommitment {
commitment,
@@ -389,7 +457,7 @@ mod tests {
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let sigs = mock_ecdsa_signatures();
let signed = SignedCommitment {
commitment,
@@ -416,7 +484,7 @@ mod tests {
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
let sigs = mock_ecdsa_signatures();
let signatures: Vec<Option<_>> = (0..1024)
.into_iter()
@@ -426,7 +494,7 @@ mod tests {
// when
let encoded = codec::Encode::encode(&signed);
let decoded = TestSignedCommitment::decode(&mut &*encoded);
let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
// then
assert_eq!(decoded, Ok(signed));
+76 -22
View File
@@ -51,7 +51,7 @@ use sp_runtime::traits::{Hash, Keccak256, NumberFor};
use sp_std::prelude::*;
/// Key type for BEEFY module.
pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"beef");
pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BEEFY;
/// Trait representing BEEFY authority id, including custom signature verification.
///
@@ -63,23 +63,21 @@ pub trait BeefyAuthorityId<MsgHash: Hash>: RuntimeAppPublic {
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool;
}
/// BEEFY cryptographic types
/// BEEFY cryptographic types for ECDSA crypto
///
/// This module basically introduces three crypto types:
/// - `crypto::Pair`
/// - `crypto::Public`
/// - `crypto::Signature`
/// 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.
///
/// 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};
pub mod ecdsa_crypto {
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE as BEEFY_KEY_TYPE};
use sp_application_crypto::{app_crypto, ecdsa};
use sp_core::crypto::Wraps;
app_crypto!(ecdsa, crate::KEY_TYPE);
app_crypto!(ecdsa, BEEFY_KEY_TYPE);
/// Identity of a BEEFY authority using ECDSA as its crypto.
pub type AuthorityId = Public;
@@ -104,6 +102,44 @@ pub mod crypto {
}
}
/// 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::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE as BEEFY_KEY_TYPE};
use sp_application_crypto::{app_crypto, bls377};
use sp_core::{bls377::Pair as BlsPair, crypto::Wraps, Pair as _};
app_crypto!(bls377, BEEFY_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 exposes
// 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())
}
}
}
/// The `ConsensusEngineId` of BEEFY.
pub const BEEFY_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"BEEF";
@@ -304,14 +340,15 @@ impl OpaqueKeyOwnershipProof {
sp_api::decl_runtime_apis! {
/// API necessary for BEEFY voters.
#[api_version(2)]
pub trait BeefyApi
#[api_version(3)]
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<crypto::AuthorityId>>;
fn validator_set() -> Option<ValidatorSet<AuthorityId>>;
/// Submits an unsigned extrinsic to report an equivocation. The caller
/// must provide the equivocation proof and a key ownership proof
@@ -323,7 +360,7 @@ sp_api::decl_runtime_apis! {
/// 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>,
EquivocationProof<NumberFor<Block>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
@@ -340,9 +377,10 @@ sp_api::decl_runtime_apis! {
/// older states to be available.
fn generate_key_ownership_proof(
set_id: ValidatorSetId,
authority_id: crypto::AuthorityId,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
}
}
#[cfg(test)]
@@ -366,14 +404,14 @@ mod tests {
}
#[test]
fn beefy_verify_works() {
fn ecdsa_beefy_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = crypto::Pair::generate();
let (pair, _) = ecdsa_crypto::Pair::generate();
let keccak_256_signature: crypto::Signature =
let keccak_256_signature: ecdsa_crypto::Signature =
pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into();
let blake2_256_signature: crypto::Signature =
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.
@@ -392,7 +430,7 @@ mod tests {
));
// Other public key doesn't work
let (other_pair, _) = crypto::Pair::generate();
let (other_pair, _) = ecdsa_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(
&other_pair.public(),
&keccak_256_signature,
@@ -404,4 +442,20 @@ mod tests {
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,));
}
}
@@ -26,7 +26,7 @@
//! 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 crate::{ecdsa_crypto::AuthorityId, ConsensusLog, MmrRootHash, Vec, BEEFY_ENGINE_ID};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_runtime::{
@@ -102,7 +102,7 @@ impl MmrLeafVersion {
/// 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<MerkleRoot> {
pub struct BeefyAuthoritySet<AuthoritySetCommitment> {
/// Id of the set.
///
/// Id is required to correlate BEEFY signed commitments with the validator set.
@@ -115,12 +115,19 @@ pub struct BeefyAuthoritySet<MerkleRoot> {
/// 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.
/// 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).
pub root: MerkleRoot,
///
/// 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.
@@ -17,13 +17,13 @@
#![cfg(feature = "std")]
use crate::{crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage};
use crate::{ecdsa_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.
/// 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 {
@@ -39,19 +39,19 @@ pub enum Keyring {
impl Keyring {
/// Sign `msg`.
pub fn sign(self, msg: &[u8]) -> crypto::Signature {
pub fn sign(self, msg: &[u8]) -> ecdsa_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 {
pub fn pair(self) -> ecdsa_crypto::Pair {
ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into()
}
/// Return public key.
pub fn public(self) -> crypto::Public {
pub fn public(self) -> ecdsa_crypto::Public {
self.pair().public()
}
@@ -61,19 +61,19 @@ impl Keyring {
}
/// Get Keyring from public key.
pub fn from_public(who: &crypto::Public) -> Option<Keyring> {
Self::iter().find(|&k| &crypto::Public::from(k) == who)
pub fn from_public(who: &ecdsa_crypto::Public) -> Option<Keyring> {
Self::iter().find(|&k| &ecdsa_crypto::Public::from(k) == who)
}
}
lazy_static::lazy_static! {
static ref PRIVATE_KEYS: HashMap<Keyring, crypto::Pair> =
static ref PRIVATE_KEYS: HashMap<Keyring, ecdsa_crypto::Pair> =
Keyring::iter().map(|i| (i, i.pair())).collect();
static ref PUBLIC_KEYS: HashMap<Keyring, crypto::Public> =
static ref PUBLIC_KEYS: HashMap<Keyring, ecdsa_crypto::Public> =
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect();
}
impl From<Keyring> for crypto::Pair {
impl From<Keyring> for ecdsa_crypto::Pair {
fn from(k: Keyring) -> Self {
k.pair()
}
@@ -85,7 +85,7 @@ impl From<Keyring> for ecdsa::Pair {
}
}
impl From<Keyring> for crypto::Public {
impl From<Keyring> for ecdsa_crypto::Public {
fn from(k: Keyring) -> Self {
(*PUBLIC_KEYS).get(&k).cloned().unwrap()
}
@@ -95,7 +95,7 @@ impl From<Keyring> for crypto::Public {
pub fn generate_equivocation_proof(
vote1: (u64, Payload, ValidatorSetId, &Keyring),
vote2: (u64, Payload, ValidatorSetId, &Keyring),
) -> EquivocationProof<u64, crypto::Public, crypto::Signature> {
) -> EquivocationProof<u64, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
let signed_vote = |block_number: u64,
payload: Payload,
validator_set_id: ValidatorSetId,
@@ -37,18 +37,21 @@ use crate::commitment::{Commitment, SignedCommitment};
/// 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> {
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
/// A merkle root of signatures in the original signed commitment.
pub signatures_merkle_root: TMerkleRoot,
/// 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, TMerkleRoot> SignedCommitmentWitness<TBlockNumber, TMerkleRoot> {
impl<TBlockNumber, TSignatureAccumulator>
SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator>
{
/// Convert [SignedCommitment] into [SignedCommitmentWitness].
///
/// This takes a [SignedCommitment], which contains full signatures
@@ -57,53 +60,85 @@ impl<TBlockNumber, TMerkleRoot> SignedCommitmentWitness<TBlockNumber, TMerkleRoo
/// and a merkle root of all signatures.
///
/// Returns the full list of signatures along with the witness.
pub fn from_signed<TMerkelize, TSignature>(
pub fn from_signed<TSignatureAggregator, TSignature>(
signed: SignedCommitment<TBlockNumber, TSignature>,
merkelize: TMerkelize,
aggregator: TSignatureAggregator,
) -> (Self, Vec<Option<TSignature>>)
where
TMerkelize: FnOnce(&[Option<TSignature>]) -> TMerkleRoot,
TSignatureAggregator: FnOnce(&[Option<TSignature>]) -> TSignatureAccumulator,
{
let SignedCommitment { commitment, signatures } = signed;
let signed_by = signatures.iter().map(|s| s.is_some()).collect();
let signatures_merkle_root = merkelize(&signatures);
let signature_accumulator = aggregator(&signatures);
(Self { commitment, signed_by, signatures_merkle_root }, signatures)
(Self { commitment, signed_by, signature_accumulator }, signatures)
}
}
#[cfg(test)]
mod tests {
use sp_core::{keccak_256, Pair};
use sp_keystore::{testing::MemoryKeystore, KeystorePtr};
use super::*;
use codec::Decode;
use crate::{crypto, known_payloads, Payload, KEY_TYPE};
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, TinyBLS377,
};
type TestCommitment = Commitment<u128>;
type TestSignedCommitment = SignedCommitment<u128, crypto::Signature>;
type TestSignedCommitmentWitness =
SignedCommitmentWitness<u128, Vec<Option<crypto::Signature>>>;
// 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_signatures() -> (crypto::Signature, crypto::Signature) {
let store: KeystorePtr = MemoryKeystore::new().into();
fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
store.insert(KEY_TYPE, "//Alice", alice.public().as_ref()).unwrap();
let msg = keccak_256(b"This is the first message");
let sig1 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap();
let sig1 = alice.sign_prehashed(&msg);
let msg = keccak_256(b"This is the second message");
let sig2 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap();
let sig2 = alice.sign_prehashed(&msg);
(sig1.into(), sig2.into())
}
fn signed_commitment() -> TestSignedCommitment {
// Generates mock aggregatable bls signature for generating test commitment
// BLS signatures
#[cfg(feature = "bls-experimental")]
fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
let alice = sp_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(),
@@ -111,35 +146,97 @@ mod tests {
let commitment: TestCommitment =
Commitment { payload, block_number: 5, validator_set_id: 0 };
let sigs = mock_signatures();
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 = signed_commitment();
let signed = ecdsa_signed_commitment();
// when
let (witness, signatures) =
TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
TestEcdsaSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
// then
assert_eq!(witness.signatures_merkle_root, signatures);
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<TinyBLS377> =
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::<TinyBLS377>::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::<TinyBLS377>::from_bytes(witness.signature_accumulator.as_slice())
.unwrap();
}
#[test]
fn should_encode_and_decode_witness() {
// given
let signed = signed_commitment();
let (witness, _) = TestSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
// Given
let signed = ecdsa_signed_commitment();
let (witness, _) = TestEcdsaSignedCommitmentWitness::from_signed::<_, _>(
signed,
|sigs: &[std::option::Option<EcdsaSignature>]| sigs.to_vec(),
);
// when
// When
let encoded = codec::Encode::encode(&witness);
let decoded = TestSignedCommitmentWitness::decode(&mut &*encoded);
let decoded = TestEcdsaSignedCommitmentWitness::decode(&mut &*encoded);
// then
// Then
assert_eq!(decoded, Ok(witness));
assert_eq!(
encoded,