mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 18:07:58 +00:00
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:
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user