Merge pull request #264 from sigp/yaml-chain-tests

YAML-defined test_harness routines
This commit is contained in:
Age Manning
2019-03-04 13:19:25 +11:00
committed by GitHub
26 changed files with 1543 additions and 75 deletions

View File

@@ -1,19 +1,14 @@
use self::verify_slashable_attestation::verify_slashable_attestation;
use crate::SlotProcessingError;
use hashing::hash;
use int_to_bytes::int_to_bytes32;
use log::{debug, trace};
use ssz::{ssz_encode, TreeHash};
use types::{
AggregatePublicKey, Attestation, BeaconBlock, BeaconState, BeaconStateError, ChainSpec,
Crosslink, Epoch, Exit, Fork, Hash256, PendingAttestation, PublicKey, RelativeEpoch, Signature,
};
use types::*;
mod verify_slashable_attestation;
// TODO: define elsehwere.
const DOMAIN_PROPOSAL: u64 = 2;
const DOMAIN_EXIT: u64 = 3;
const DOMAIN_RANDAO: u64 = 4;
const PHASE_0_CUSTODY_BIT: bool = false;
const DOMAIN_ATTESTATION: u64 = 1;
#[derive(Debug, PartialEq)]
pub enum Error {
@@ -31,10 +26,13 @@ pub enum Error {
BadRandaoSignature,
MaxProposerSlashingsExceeded,
BadProposerSlashing,
MaxAttesterSlashingsExceed,
MaxAttestationsExceeded,
BadAttesterSlashing,
InvalidAttestation(AttestationValidationError),
NoBlockRoot,
MaxDepositsExceeded,
BadDeposit,
MaxExitsExceeded,
BadExit,
BadCustodyReseeds,
@@ -89,7 +87,7 @@ impl BlockProcessable for BeaconState {
}
fn per_block_processing_signature_optional(
state: &mut BeaconState,
mut state: &mut BeaconState,
block: &BeaconBlock,
verify_block_signature: bool,
spec: &ChainSpec,
@@ -113,7 +111,7 @@ fn per_block_processing_signature_optional(
&block_proposer.pubkey,
&block.proposal_root(spec)[..],
&block.signature,
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL)
get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal)
),
Error::BadBlockSignature
);
@@ -127,7 +125,7 @@ fn per_block_processing_signature_optional(
&block_proposer.pubkey,
&int_to_bytes32(state.current_epoch(spec).as_u64()),
&block.randao_reveal,
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO)
get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao)
),
Error::BadRandaoSignature
);
@@ -188,7 +186,7 @@ fn per_block_processing_signature_optional(
.proposal_data_1
.slot
.epoch(spec.epoch_length),
DOMAIN_PROPOSAL
spec.domain_proposal
)
),
Error::BadProposerSlashing
@@ -204,7 +202,7 @@ fn per_block_processing_signature_optional(
.proposal_data_2
.slot
.epoch(spec.epoch_length),
DOMAIN_PROPOSAL
spec.domain_proposal
)
),
Error::BadProposerSlashing
@@ -212,6 +210,17 @@ fn per_block_processing_signature_optional(
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
}
/*
* Attester slashings
*/
ensure!(
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
Error::MaxAttesterSlashingsExceed
);
for attester_slashing in &block.body.attester_slashings {
verify_slashable_attestation(&mut state, &attester_slashing, spec)?;
}
/*
* Attestations
*/
@@ -242,7 +251,27 @@ fn per_block_processing_signature_optional(
Error::MaxDepositsExceeded
);
// TODO: process deposits.
// TODO: verify deposit merkle branches.
for deposit in &block.body.deposits {
debug!(
"Processing deposit for pubkey {:?}",
deposit.deposit_data.deposit_input.pubkey
);
state
.process_deposit(
deposit.deposit_data.deposit_input.pubkey.clone(),
deposit.deposit_data.amount,
deposit
.deposit_data
.deposit_input
.proof_of_possession
.clone(),
deposit.deposit_data.deposit_input.withdrawal_credentials,
None,
spec,
)
.map_err(|_| Error::BadDeposit)?;
}
/*
* Exits
@@ -276,7 +305,7 @@ fn per_block_processing_signature_optional(
&validator.pubkey,
&exit_message,
&exit.signature,
get_domain(&state.fork, exit.epoch, DOMAIN_EXIT)
get_domain(&state.fork, exit.epoch, spec.domain_exit)
),
Error::BadProposerSlashing
);
@@ -370,11 +399,7 @@ fn validate_attestation_signature_optional(
);
let mut group_public_key = AggregatePublicKey::new();
for participant in participants {
group_public_key.add(
state.validator_registry[participant as usize]
.pubkey
.as_raw(),
)
group_public_key.add(&state.validator_registry[participant as usize].pubkey)
}
ensure!(
attestation.verify_signature(
@@ -383,7 +408,7 @@ fn validate_attestation_signature_optional(
get_domain(
&state.fork,
attestation.data.slot.epoch(spec.epoch_length),
DOMAIN_ATTESTATION,
spec.domain_attestation,
)
),
AttestationValidationError::BadSignature

View File

@@ -0,0 +1,61 @@
use super::Error;
use types::*;
macro_rules! ensure {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
/// otherwise returns an `Err`.
pub fn verify_slashable_attestation(
state: &mut BeaconState,
attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<(), Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
ensure!(
slashable_attestation_1.data != slashable_attestation_2.data,
Error::BadAttesterSlashing
);
ensure!(
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
Error::BadAttesterSlashing
);
ensure!(
state.verify_slashable_attestation(&slashable_attestation_1, spec),
Error::BadAttesterSlashing
);
ensure!(
state.verify_slashable_attestation(&slashable_attestation_2, spec),
Error::BadAttesterSlashing
);
let mut slashable_indices = vec![];
for i in &slashable_attestation_1.validator_indices {
let validator = state
.validator_registry
.get(*i as usize)
.ok_or_else(|| Error::BadAttesterSlashing)?;
if slashable_attestation_1.validator_indices.contains(&i)
& !validator.is_penalized_at(state.current_epoch(spec))
{
slashable_indices.push(i);
}
}
ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing);
for i in slashable_indices {
state.penalize_validator(*i as usize, spec)?;
}
Ok(())
}

View File

@@ -4,6 +4,10 @@ use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::AttesterSlashingBuilder;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct AttesterSlashing {
pub slashable_attestation_1: SlashableAttestation,

View File

@@ -0,0 +1,96 @@
use crate::*;
use ssz::TreeHash;
/// Builds an `AttesterSlashing`.
pub struct AttesterSlashingBuilder();
impl AttesterSlashingBuilder {
/// Builds an `AttesterSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
///
/// - `validator_index: u64`
/// - `message: &[u8]`
/// - `epoch: Epoch`
/// - `domain: u64`
///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(
validator_indices: &[u64],
signer: F,
spec: &ChainSpec,
) -> AttesterSlashing
where
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
{
let double_voted_slot = Slot::new(0);
let shard = 0;
let justified_epoch = Epoch::new(0);
let epoch = Epoch::new(0);
let hash_1 = Hash256::from(&[1][..]);
let hash_2 = Hash256::from(&[2][..]);
let mut slashable_attestation_1 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_1,
epoch_boundary_root: hash_1,
shard_block_root: hash_1,
latest_crosslink: Crosslink {
epoch,
shard_block_root: hash_1,
},
justified_epoch,
justified_block_root: hash_1,
},
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let mut slashable_attestation_2 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_2,
epoch_boundary_root: hash_2,
shard_block_root: hash_2,
latest_crosslink: Crosslink {
epoch,
shard_block_root: hash_2,
},
justified_epoch,
justified_block_root: hash_2,
},
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let add_signatures = |attestation: &mut SlashableAttestation| {
for (i, validator_index) in validator_indices.iter().enumerate() {
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
};
let message = attestation_data_and_custody_bit.hash_tree_root();
let signature = signer(
*validator_index,
&message[..],
epoch,
spec.domain_attestation,
);
attestation.aggregate_signature.add(&signature);
}
};
add_signatures(&mut slashable_attestation_1);
add_signatures(&mut slashable_attestation_2);
AttesterSlashing {
slashable_attestation_1,
slashable_attestation_2,
}
}
}

View File

@@ -1,14 +1,11 @@
use self::epoch_cache::EpochCache;
use crate::test_utils::TestRandom;
use crate::{
validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData,
Bitfield, ChainSpec, Crosslink, Deposit, DepositData, DepositInput, Epoch, Eth1Data,
Eth1DataVote, Fork, Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator,
};
use crate::{validator::StatusFlags, validator_registry::get_active_validator_indices, *};
use bls::verify_proof_of_possession;
use honey_badger_split::SplitExt;
use log::{debug, error, trace};
use rand::RngCore;
use rayon::prelude::*;
use serde_derive::Serialize;
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
use std::collections::HashMap;
@@ -199,10 +196,10 @@ impl BeaconState {
let mut genesis_state =
BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?;
trace!("Processing genesis deposits...");
debug!("Processing genesis deposits...");
let deposit_data = initial_validator_deposits
.iter()
.par_iter()
.map(|deposit| &deposit.deposit_data)
.collect();
@@ -411,6 +408,8 @@ impl BeaconState {
return Err(Error::InsufficientValidators);
}
debug!("Shuffling {} validators...", active_validator_indices.len());
let committees_per_epoch =
self.get_epoch_committee_count(active_validator_indices.len(), spec);
@@ -420,8 +419,7 @@ impl BeaconState {
committees_per_epoch
);
let active_validator_indices: Vec<usize> =
active_validator_indices.iter().cloned().collect();
let active_validator_indices: Vec<usize> = active_validator_indices.to_vec();
let shuffled_active_validator_indices = shuffle_list(
active_validator_indices,
@@ -1000,6 +998,10 @@ impl BeaconState {
whistleblower_reward
);
self.validator_registry[validator_index].penalized_epoch = current_epoch;
debug!(
"Whistleblower {} penalized validator {}.",
whistleblower_index, validator_index
);
Ok(())
}
@@ -1138,6 +1140,114 @@ impl BeaconState {
)
}
/// Verify ``bitfield`` against the ``committee_size``.
///
/// Spec v0.2.0
pub fn verify_bitfield(&self, bitfield: &Bitfield, committee_size: usize) -> bool {
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
return false;
}
for i in committee_size..(bitfield.num_bytes() * 8) {
match bitfield.get(i) {
Ok(bit) => {
if bit {
return false;
}
}
Err(_) => unreachable!(),
}
}
true
}
/// Verify validity of ``slashable_attestation`` fields.
///
/// Spec v0.2.0
pub fn verify_slashable_attestation(
&self,
slashable_attestation: &SlashableAttestation,
spec: &ChainSpec,
) -> bool {
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
return false;
}
if slashable_attestation.validator_indices.is_empty() {
return false;
}
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
if slashable_attestation.validator_indices[i]
>= slashable_attestation.validator_indices[i + 1]
{
return false;
}
}
if !self.verify_bitfield(
&slashable_attestation.custody_bitfield,
slashable_attestation.validator_indices.len(),
) {
return false;
}
if slashable_attestation.validator_indices.len()
> spec.max_indices_per_slashable_vote as usize
{
return false;
}
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
Ok(bit) => bit,
Err(_) => unreachable!(),
};
message_exists[custody_bit as usize] = true;
match self.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
None => return false,
};
}
let message_0 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: false,
}
.hash_tree_root();
let message_1 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: true,
}
.hash_tree_root();
let mut messages = vec![];
let mut keys = vec![];
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
slashable_attestation.aggregate_signature.verify_multiple(
&messages[..],
spec.domain_attestation,
&keys[..],
)
}
/// Return the block root at a recent `slot`.
///
/// Spec v0.2.0

View File

@@ -6,6 +6,10 @@ use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::ProposerSlashingBuilder;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ProposerSlashing {
pub proposer_index: u64,

View File

@@ -0,0 +1,59 @@
use crate::*;
use ssz::TreeHash;
/// Builds a `ProposerSlashing`.
pub struct ProposerSlashingBuilder();
impl ProposerSlashingBuilder {
/// Builds a `ProposerSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
///
/// - `validator_index: u64`
/// - `message: &[u8]`
/// - `epoch: Epoch`
/// - `domain: u64`
///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
where
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
{
let slot = Slot::new(0);
let shard = 0;
let proposal_data_1 = ProposalSignedData {
slot,
shard,
block_root: Hash256::from(&[1][..]),
};
let proposal_data_2 = ProposalSignedData {
slot,
shard,
block_root: Hash256::from(&[2][..]),
};
let proposal_signature_1 = {
let message = proposal_data_1.hash_tree_root();
let epoch = slot.epoch(spec.epoch_length);
let domain = spec.domain_proposal;
signer(proposer_index, &message[..], epoch, domain)
};
let proposal_signature_2 = {
let message = proposal_data_2.hash_tree_root();
let epoch = slot.epoch(spec.epoch_length);
let domain = spec.domain_proposal;
signer(proposer_index, &message[..], epoch, domain)
};
ProposerSlashing {
proposer_index,
proposal_data_1,
proposal_signature_1,
proposal_data_2,
proposal_signature_2,
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield};
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec};
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
@@ -12,12 +12,107 @@ pub struct SlashableAttestation {
pub aggregate_signature: AggregateSignature,
}
impl SlashableAttestation {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.3.0
pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length)
}
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
///
/// Spec v0.3.0
pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
let source_epoch_1 = self.data.justified_epoch;
let source_epoch_2 = other.data.justified_epoch;
let target_epoch_1 = self.data.slot.epoch(spec.epoch_length);
let target_epoch_2 = other.data.slot.epoch(spec.epoch_length);
(source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain_spec::ChainSpec;
use crate::slot_epoch::{Epoch, Slot};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_is_double_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
true
)
}
#[test]
pub fn test_is_double_vote_false() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(2, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(2, 1, &spec);
let slashable_vote_second = create_slashable_attestation(1, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_true_realistic() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(4, 1, &spec);
let slashable_vote_second = create_slashable_attestation(3, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_false_source_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(2, 2, &spec);
let slashable_vote_second = create_slashable_attestation(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_false_target_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_attestation(1, 1, &spec);
let slashable_vote_second = create_slashable_attestation(2, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
@@ -40,4 +135,17 @@ mod tests {
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
fn create_slashable_attestation(
slot_factor: u64,
justified_epoch: u64,
spec: &ChainSpec,
) -> SlashableAttestation {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng);
slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length);
slashable_vote.data.justified_epoch = Epoch::new(justified_epoch);
slashable_vote
}
}

View File

@@ -54,9 +54,19 @@ pub struct Validator {
}
impl Validator {
/// This predicate indicates if the validator represented by this record is considered "active" at `slot`.
pub fn is_active_at(&self, slot: Epoch) -> bool {
self.activation_epoch <= slot && slot < self.exit_epoch
/// Returns `true` if the validator is considered active at some epoch.
pub fn is_active_at(&self, epoch: Epoch) -> bool {
self.activation_epoch <= epoch && epoch < self.exit_epoch
}
/// Returns `true` if the validator is considered exited at some epoch.
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
self.exit_epoch <= epoch
}
/// Returns `true` if the validator is considered penalized at some epoch.
pub fn is_penalized_at(&self, epoch: Epoch) -> bool {
self.penalized_epoch <= epoch
}
}

View File

@@ -0,0 +1,24 @@
use super::PublicKey;
use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey;
/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Default)]
pub struct AggregatePublicKey(RawAggregatePublicKey);
impl AggregatePublicKey {
pub fn new() -> Self {
AggregatePublicKey(RawAggregatePublicKey::new())
}
pub fn add(&mut self, public_key: &PublicKey) {
self.0.add(public_key.as_raw())
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawAggregatePublicKey {
&self.0
}
}

View File

@@ -1,5 +1,7 @@
use super::{AggregatePublicKey, Signature};
use bls_aggregates::AggregateSignature as RawAggregateSignature;
use bls_aggregates::{
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
};
use serde::ser::{Serialize, Serializer};
use ssz::{
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
@@ -33,7 +35,38 @@ impl AggregateSignature {
domain: u64,
aggregate_public_key: &AggregatePublicKey,
) -> bool {
self.0.verify(msg, domain, aggregate_public_key)
self.0.verify(msg, domain, aggregate_public_key.as_raw())
}
/// Verify this AggregateSignature against multiple AggregatePublickeys with multiple Messages.
///
/// All PublicKeys related to a Message should be aggregated into one AggregatePublicKey.
/// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message.
pub fn verify_multiple(
&self,
messages: &[&[u8]],
domain: u64,
aggregate_public_keys: &[&AggregatePublicKey],
) -> bool {
// TODO: the API for `RawAggregatePublicKey` shoudn't need to take an owned
// `AggregatePublicKey`. There is an issue to fix this, but in the meantime we need to
// clone.
//
// https://github.com/sigp/signature-schemes/issues/10
let aggregate_public_keys: Vec<RawAggregatePublicKey> = aggregate_public_keys
.iter()
.map(|pk| pk.as_raw())
.cloned()
.collect();
// Messages are concatenated into one long message.
let mut msg: Vec<u8> = vec![];
for message in messages {
msg.extend_from_slice(message);
}
self.0
.verify_multiple(&msg[..], domain, &aggregate_public_keys[..])
}
}

View File

@@ -1,20 +1,20 @@
extern crate bls_aggregates;
extern crate ssz;
mod aggregate_public_key;
mod aggregate_signature;
mod keypair;
mod public_key;
mod secret_key;
mod signature;
pub use crate::aggregate_public_key::AggregatePublicKey;
pub use crate::aggregate_signature::AggregateSignature;
pub use crate::keypair::Keypair;
pub use crate::public_key::PublicKey;
pub use crate::secret_key::SecretKey;
pub use crate::signature::Signature;
pub use self::bls_aggregates::AggregatePublicKey;
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
use ssz::ssz_encode;