spec v0.6.1: attestation processing/verif
This commit is contained in:
parent
f8d4e742ad
commit
0b2aa26f2d
29
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
29
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use super::get_attesting_indices;
|
||||
use itertools::{Either, Itertools};
|
||||
use types::*;
|
||||
|
||||
/// Convert `attestation` to (almost) indexed-verifiable form.
|
||||
///
|
||||
/// Spec v0.6.1
|
||||
pub fn convert_to_indexed<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestation: &Attestation,
|
||||
) -> Result<IndexedAttestation, BeaconStateError> {
|
||||
let attesting_indices =
|
||||
get_attesting_indices(state, &attestation.data, &attestation.aggregation_bitfield)?;
|
||||
|
||||
let (custody_bit_0_indices, custody_bit_1_indices) =
|
||||
attesting_indices.into_iter().enumerate().partition_map(
|
||||
|(committee_idx, validator_idx)| match attestation.custody_bitfield.get(committee_idx) {
|
||||
Ok(true) => Either::Right(validator_idx as u64),
|
||||
_ => Either::Left(validator_idx as u64),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(IndexedAttestation {
|
||||
custody_bit_0_indices,
|
||||
custody_bit_1_indices,
|
||||
data: attestation.data.clone(),
|
||||
signature: attestation.signature.clone(),
|
||||
})
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
mod convert_to_indexed;
|
||||
mod exit;
|
||||
mod get_attesting_indices;
|
||||
mod slash_validator;
|
||||
mod verify_bitfield;
|
||||
|
||||
pub use convert_to_indexed::convert_to_indexed;
|
||||
pub use exit::initiate_validator_exit;
|
||||
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_unsorted};
|
||||
pub use slash_validator::slash_validator;
|
||||
|
@ -14,7 +14,9 @@ pub use validate_attestation::{
|
||||
};
|
||||
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
||||
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
||||
pub use verify_indexed_attestation::verify_indexed_attestation;
|
||||
pub use verify_indexed_attestation::{
|
||||
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||
};
|
||||
pub use verify_transfer::{
|
||||
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
||||
};
|
||||
@ -304,7 +306,7 @@ pub fn process_attester_slashings<T: EthSpec>(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
/// Spec v0.6.1
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attestations: &[Attestation],
|
||||
@ -327,13 +329,20 @@ pub fn process_attestations<T: EthSpec>(
|
||||
})?;
|
||||
|
||||
// Update the state in series.
|
||||
let proposer_index =
|
||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)? as u64;
|
||||
for attestation in attestations {
|
||||
let pending_attestation = PendingAttestation::from_attestation(attestation, state.slot);
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||
let pending_attestation = PendingAttestation {
|
||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||
data: attestation.data.clone(),
|
||||
inclusion_delay: (state.slot - attestation_slot).as_u64(),
|
||||
proposer_index,
|
||||
};
|
||||
|
||||
if attestation_epoch == state.current_epoch() {
|
||||
if attestation.data.target_epoch == state.current_epoch() {
|
||||
state.current_epoch_attestations.push(pending_attestation)
|
||||
} else if attestation_epoch == state.previous_epoch(spec) {
|
||||
} else {
|
||||
state.previous_epoch_attestations.push(pending_attestation)
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,8 @@ pub enum AttestationInvalid {
|
||||
},
|
||||
/// Attestation slot is too far in the past to be included in a block.
|
||||
IncludedTooLate { state: Slot, attestation: Slot },
|
||||
/// Attestation target epoch does not match the current or previous epoch.
|
||||
BadTargetEpoch,
|
||||
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
||||
///
|
||||
/// `is_current` is `true` if the attestation was compared to the
|
||||
@ -170,11 +172,20 @@ pub enum AttestationInvalid {
|
||||
BadSignature,
|
||||
/// The shard block root was not set to zero. This is a phase 0 requirement.
|
||||
ShardBlockRootNotZero,
|
||||
/// The indexed attestation created from this attestation was found to be invalid.
|
||||
BadIndexedAttestation(IndexedAttestationInvalid),
|
||||
}
|
||||
|
||||
impl_from_beacon_state_error!(AttestationValidationError);
|
||||
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
||||
|
||||
impl From<IndexedAttestationValidationError> for AttestationValidationError {
|
||||
fn from(err: IndexedAttestationValidationError) -> Self {
|
||||
let IndexedAttestationValidationError::Invalid(e) = err;
|
||||
AttestationValidationError::Invalid(AttestationInvalid::BadIndexedAttestation(e))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* `AttesterSlashing` Validation
|
||||
*/
|
||||
@ -224,25 +235,23 @@ pub enum IndexedAttestationValidationError {
|
||||
/// Describes why an object is invalid.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum IndexedAttestationInvalid {
|
||||
/// The custody bit 0 validators intersect with the bit 1 validators.
|
||||
CustodyBitValidatorsIntersect,
|
||||
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
||||
CustodyBitfieldHasSetBits,
|
||||
/// No validator indices were specified.
|
||||
NoValidatorIndices,
|
||||
/// The number of indices exceeds the global maximum.
|
||||
///
|
||||
/// (max_indices, indices_given)
|
||||
MaxIndicesExceed(u64, usize),
|
||||
/// The validator indices were not in increasing order.
|
||||
///
|
||||
/// The error occured between the given `index` and `index + 1`
|
||||
BadValidatorIndicesOrdering(usize),
|
||||
/// The custody bitfield length is not the smallest possible size to represent the validators.
|
||||
///
|
||||
/// (validators_len, bitfield_len)
|
||||
BadCustodyBitfieldLength(usize, usize),
|
||||
/// The number of slashable indices exceed the global maximum.
|
||||
///
|
||||
/// (max_indices, indices_given)
|
||||
MaxIndicesExceed(usize, usize),
|
||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||
UnknownValidator(u64),
|
||||
/// The slashable attestation aggregate signature was not valid.
|
||||
/// The indexed attestation aggregate signature was not valid.
|
||||
BadSignature,
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
||||
use crate::common::verify_bitfield_length;
|
||||
use crate::common::convert_to_indexed;
|
||||
use crate::per_block_processing::{
|
||||
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||
};
|
||||
use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
|
||||
@ -8,7 +11,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
/// Spec v0.6.1
|
||||
pub fn validate_attestation<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestation: &Attestation,
|
||||
@ -31,7 +34,7 @@ pub fn validate_attestation_time_independent_only<T: EthSpec>(
|
||||
///
|
||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
/// Spec v0.6.1
|
||||
pub fn validate_attestation_without_signature<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestation: &Attestation,
|
||||
@ -44,7 +47,7 @@ pub fn validate_attestation_without_signature<T: EthSpec>(
|
||||
/// given state, optionally validating the aggregate signature.
|
||||
///
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
/// Spec v0.6.1
|
||||
fn validate_attestation_parametric<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestation: &Attestation,
|
||||
@ -52,107 +55,29 @@ fn validate_attestation_parametric<T: EthSpec>(
|
||||
verify_signature: bool,
|
||||
time_independent_only: bool,
|
||||
) -> Result<(), Error> {
|
||||
// Can't submit pre-historic attestations.
|
||||
verify!(
|
||||
attestation.data.slot >= spec.genesis_slot,
|
||||
Invalid::PreGenesis {
|
||||
genesis: spec.genesis_slot,
|
||||
attestation: attestation.data.slot
|
||||
}
|
||||
);
|
||||
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||
|
||||
// Can't submit attestations too far in history.
|
||||
verify!(
|
||||
state.slot <= attestation.data.slot + spec.slots_per_epoch,
|
||||
Invalid::IncludedTooLate {
|
||||
state: spec.genesis_slot,
|
||||
attestation: attestation.data.slot
|
||||
}
|
||||
);
|
||||
|
||||
// Can't submit attestation too quickly.
|
||||
// Check attestation slot.
|
||||
verify!(
|
||||
time_independent_only
|
||||
|| attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
|| attestation_slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
Invalid::IncludedTooEarly {
|
||||
state: state.slot,
|
||||
delay: spec.min_attestation_inclusion_delay,
|
||||
attestation: attestation.data.slot
|
||||
attestation: attestation_slot
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
state.slot <= attestation_slot + spec.slots_per_epoch,
|
||||
Invalid::IncludedTooLate {
|
||||
state: state.slot,
|
||||
attestation: attestation_slot
|
||||
}
|
||||
);
|
||||
|
||||
// Verify the justified epoch and root is correct.
|
||||
// Verify the Casper FFG vote.
|
||||
if !time_independent_only {
|
||||
verify_justified_epoch_and_root(attestation, state, spec)?;
|
||||
}
|
||||
|
||||
// Check that the crosslink data is valid.
|
||||
//
|
||||
// Verify that either:
|
||||
//
|
||||
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
|
||||
//
|
||||
// (ii) `state.latest_crosslinks[attestation.data.shard] ==
|
||||
// Crosslink(crosslink_data_root=attestation.data.crosslink_data_root,
|
||||
// epoch=slot_to_epoch(attestation.data.slot))`.
|
||||
let potential_crosslink = Crosslink {
|
||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
||||
};
|
||||
verify!(
|
||||
(attestation.data.previous_crosslink
|
||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
||||
Invalid::BadPreviousCrosslink
|
||||
);
|
||||
|
||||
// Attestation must be non-empty!
|
||||
verify!(
|
||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
||||
Invalid::AggregationBitfieldIsEmpty
|
||||
);
|
||||
// Custody bitfield must be empty (be be removed in phase 1)
|
||||
verify!(
|
||||
attestation.custody_bitfield.num_set_bits() == 0,
|
||||
Invalid::CustodyBitfieldHasSetBits
|
||||
);
|
||||
|
||||
// Get the committee for the specific shard that this attestation is for.
|
||||
let crosslink_committee = state
|
||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||
.iter()
|
||||
.find(|c| c.shard == attestation.data.shard)
|
||||
.ok_or_else(|| {
|
||||
Error::Invalid(Invalid::NoCommitteeForShard {
|
||||
shard: attestation.data.shard,
|
||||
slot: attestation.data.slot,
|
||||
})
|
||||
})?;
|
||||
let committee = &crosslink_committee.committee;
|
||||
|
||||
// Custody bitfield length is correct.
|
||||
//
|
||||
// This is not directly in the spec, but it is inferred.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
|
||||
Invalid::BadCustodyBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: attestation.custody_bitfield.len()
|
||||
}
|
||||
);
|
||||
// Aggregation bitfield length is correct.
|
||||
//
|
||||
// This is not directly in the spec, but it is inferred.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
Invalid::BadAggregationBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: attestation.custody_bitfield.len()
|
||||
}
|
||||
);
|
||||
|
||||
if verify_signature {
|
||||
verify_attestation_signature(state, committee, attestation, spec)?;
|
||||
verify_casper_ffg_vote(attestation, state, spec)?;
|
||||
}
|
||||
|
||||
// Crosslink data root is zero (to be removed in phase 1).
|
||||
@ -161,145 +86,72 @@ fn validate_attestation_parametric<T: EthSpec>(
|
||||
Invalid::ShardBlockRootNotZero
|
||||
);
|
||||
|
||||
// Check signature and bitfields
|
||||
let indexed_attestation = convert_to_indexed(state, attestation)?;
|
||||
if verify_signature {
|
||||
verify_indexed_attestation(state, &indexed_attestation, spec)?;
|
||||
} else {
|
||||
verify_indexed_attestation_without_signature(state, &indexed_attestation, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that the `source_epoch` and `source_root` of an `Attestation` correctly
|
||||
/// match the current (or previous) justified epoch and root from the state.
|
||||
/// Check target epoch, source epoch, source root, and source crosslink.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
fn verify_justified_epoch_and_root<T: EthSpec>(
|
||||
/// Spec v0.6.1
|
||||
fn verify_casper_ffg_vote<T: EthSpec>(
|
||||
attestation: &Attestation,
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
if attestation_epoch >= state_epoch {
|
||||
let data = &attestation.data;
|
||||
if data.target_epoch == state.current_epoch() {
|
||||
verify!(
|
||||
attestation.data.source_epoch == state.current_justified_epoch,
|
||||
data.source_epoch == state.current_justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch {
|
||||
state: state.current_justified_epoch,
|
||||
attestation: attestation.data.source_epoch,
|
||||
attestation: data.source_epoch,
|
||||
is_current: true,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
attestation.data.source_root == state.current_justified_root,
|
||||
data.source_root == state.current_justified_root,
|
||||
Invalid::WrongJustifiedRoot {
|
||||
state: state.current_justified_root,
|
||||
attestation: attestation.data.source_root,
|
||||
attestation: data.source_root,
|
||||
is_current: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
verify!(
|
||||
attestation.data.source_epoch == state.previous_justified_epoch,
|
||||
data.previous_crosslink_root
|
||||
== Hash256::from_slice(&state.get_current_crosslink(data.shard)?.tree_hash_root()),
|
||||
Invalid::BadPreviousCrosslink
|
||||
);
|
||||
} else if data.target_epoch == state.previous_epoch() {
|
||||
verify!(
|
||||
data.source_epoch == state.previous_justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch {
|
||||
state: state.previous_justified_epoch,
|
||||
attestation: attestation.data.source_epoch,
|
||||
attestation: data.source_epoch,
|
||||
is_current: false,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
attestation.data.source_root == state.previous_justified_root,
|
||||
data.source_root == state.previous_justified_root,
|
||||
Invalid::WrongJustifiedRoot {
|
||||
state: state.previous_justified_root,
|
||||
attestation: attestation.data.source_root,
|
||||
is_current: true,
|
||||
attestation: data.source_root,
|
||||
is_current: false,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
data.previous_crosslink_root
|
||||
== Hash256::from_slice(&state.get_previous_crosslink(data.shard)?.tree_hash_root()),
|
||||
Invalid::BadPreviousCrosslink
|
||||
);
|
||||
} else {
|
||||
invalid!(Invalid::BadTargetEpoch)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies an aggregate signature for some given `AttestationData`, returning `true` if the
|
||||
/// `aggregate_signature` is valid.
|
||||
///
|
||||
/// Returns `false` if:
|
||||
/// - `aggregate_signature` was not signed correctly.
|
||||
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
||||
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
fn verify_attestation_signature<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
committee: &[usize],
|
||||
a: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||
let mut message_exists = vec![false; 2];
|
||||
let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
for (i, v) in committee.iter().enumerate() {
|
||||
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
|
||||
Error::Invalid(Invalid::BadAggregationBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: a.aggregation_bitfield.len(),
|
||||
})
|
||||
})?;
|
||||
|
||||
if validator_signed {
|
||||
let custody_bit: bool = match a.custody_bitfield.get(i) {
|
||||
Ok(bit) => bit,
|
||||
// Invalidate signature if custody_bitfield.len() < committee
|
||||
Err(_) => {
|
||||
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: a.aggregation_bitfield.len(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
message_exists[custody_bit as usize] = true;
|
||||
|
||||
match state.validator_registry.get(*v as usize) {
|
||||
Some(validator) => {
|
||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
||||
}
|
||||
// Return error if validator index is unknown.
|
||||
None => return Err(Error::BeaconStateError(BeaconStateError::UnknownValidator)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Message when custody bitfield is `false`
|
||||
let message_0 = AttestationDataAndCustodyBit {
|
||||
data: a.data.clone(),
|
||||
custody_bit: false,
|
||||
}
|
||||
.tree_hash_root();
|
||||
|
||||
// Message when custody bitfield is `true`
|
||||
let message_1 = AttestationDataAndCustodyBit {
|
||||
data: a.data.clone(),
|
||||
custody_bit: true,
|
||||
}
|
||||
.tree_hash_root();
|
||||
|
||||
let mut messages = vec![];
|
||||
let mut keys = vec![];
|
||||
|
||||
// If any validator signed a message with a `false` custody bit.
|
||||
if message_exists[0] {
|
||||
messages.push(&message_0[..]);
|
||||
keys.push(&aggregate_pubs[0]);
|
||||
}
|
||||
// If any validator signed a message with a `true` custody bit.
|
||||
if message_exists[1] {
|
||||
messages.push(&message_1[..]);
|
||||
keys.push(&aggregate_pubs[1]);
|
||||
}
|
||||
|
||||
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
|
||||
|
||||
verify!(
|
||||
a.aggregate_signature
|
||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,76 +1,119 @@
|
||||
use super::errors::{
|
||||
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
|
||||
};
|
||||
use crate::common::verify_bitfield_length;
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
|
||||
/// Indicates if a `IndexedAttestation` is valid to be included in a block in the current epoch of the given
|
||||
/// state.
|
||||
/// Verify an `IndexedAttestation`.
|
||||
///
|
||||
/// Returns `Ok(())` if the `IndexedAttestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.5.1
|
||||
/// Spec v0.6.1
|
||||
pub fn verify_indexed_attestation<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
indexed_attestation: &IndexedAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if indexed_attestation.custody_bitfield.num_set_bits() > 0 {
|
||||
verify_indexed_attestation_parametric(state, indexed_attestation, spec, true)
|
||||
}
|
||||
|
||||
/// Verify but don't check the signature.
|
||||
///
|
||||
/// Spec v0.6.1
|
||||
pub fn verify_indexed_attestation_without_signature<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
indexed_attestation: &IndexedAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
verify_indexed_attestation_parametric(state, indexed_attestation, spec, false)
|
||||
}
|
||||
|
||||
/// Optionally check the signature.
|
||||
///
|
||||
/// Spec v0.6.1
|
||||
fn verify_indexed_attestation_parametric<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
indexed_attestation: &IndexedAttestation,
|
||||
spec: &ChainSpec,
|
||||
verify_signature: bool,
|
||||
) -> Result<(), Error> {
|
||||
let custody_bit_0_indices = &indexed_attestation.custody_bit_0_indices;
|
||||
let custody_bit_1_indices = &indexed_attestation.custody_bit_1_indices;
|
||||
|
||||
// Ensure no duplicate indices across custody bits
|
||||
let custody_bit_intersection =
|
||||
&HashSet::from_iter(custody_bit_0_indices) & &HashSet::from_iter(custody_bit_1_indices);
|
||||
verify!(
|
||||
custody_bit_intersection.is_empty(),
|
||||
Invalid::CustodyBitValidatorsIntersect
|
||||
);
|
||||
|
||||
// Check that nobody signed with custody bit 1 (to be removed in phase 1)
|
||||
if custody_bit_1_indices.len() > 0 {
|
||||
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||
}
|
||||
|
||||
if indexed_attestation.validator_indices.is_empty() {
|
||||
invalid!(Invalid::NoValidatorIndices);
|
||||
}
|
||||
let total_indices = custody_bit_0_indices.len() + custody_bit_1_indices.len();
|
||||
verify!(1 <= total_indices, Invalid::NoValidatorIndices);
|
||||
verify!(
|
||||
total_indices as u64 <= spec.max_indices_per_attestation,
|
||||
Invalid::MaxIndicesExceed(spec.max_indices_per_attestation, total_indices)
|
||||
);
|
||||
|
||||
for i in 0..(indexed_attestation.validator_indices.len() - 1) {
|
||||
if indexed_attestation.validator_indices[i] >= indexed_attestation.validator_indices[i + 1]
|
||||
{
|
||||
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
||||
}
|
||||
}
|
||||
|
||||
if !verify_bitfield_length(
|
||||
&indexed_attestation.custody_bitfield,
|
||||
indexed_attestation.validator_indices.len(),
|
||||
) {
|
||||
invalid!(Invalid::BadCustodyBitfieldLength(
|
||||
indexed_attestation.validator_indices.len(),
|
||||
indexed_attestation.custody_bitfield.len()
|
||||
));
|
||||
}
|
||||
|
||||
if indexed_attestation.validator_indices.len() > spec.max_indices_per_indexed_vote as usize {
|
||||
invalid!(Invalid::MaxIndicesExceed(
|
||||
spec.max_indices_per_indexed_vote as usize,
|
||||
indexed_attestation.validator_indices.len()
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: this signature verification could likely be replaced with:
|
||||
//
|
||||
// super::validate_attestation::validate_attestation_signature(..)
|
||||
|
||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||
let mut message_exists = vec![false; 2];
|
||||
|
||||
for (i, v) in indexed_attestation.validator_indices.iter().enumerate() {
|
||||
let custody_bit = match indexed_attestation.custody_bitfield.get(i) {
|
||||
Ok(bit) => bit,
|
||||
Err(_) => unreachable!(),
|
||||
};
|
||||
|
||||
message_exists[custody_bit as usize] = true;
|
||||
|
||||
match state.validator_registry.get(*v as usize) {
|
||||
Some(validator) => {
|
||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
||||
// Check that both vectors of indices are sorted
|
||||
let check_sorted = |list: &Vec<u64>| {
|
||||
for i in 0..list.len() - 1 {
|
||||
if list[i] >= list[i + 1] {
|
||||
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
||||
}
|
||||
None => invalid!(Invalid::UnknownValidator(*v)),
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
check_sorted(custody_bit_0_indices)?;
|
||||
check_sorted(custody_bit_1_indices)?;
|
||||
|
||||
if verify_signature {
|
||||
verify_indexed_attestation_signature(state, indexed_attestation, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create an aggregate public key for a list of validators, failing if any key can't be found.
|
||||
fn create_aggregate_pubkey<'a, T, I>(
|
||||
state: &BeaconState<T>,
|
||||
validator_indices: I,
|
||||
) -> Result<AggregatePublicKey, Error>
|
||||
where
|
||||
I: IntoIterator<Item = &'a u64>,
|
||||
T: EthSpec,
|
||||
{
|
||||
validator_indices.into_iter().try_fold(
|
||||
AggregatePublicKey::new(),
|
||||
|mut aggregate_pubkey, &validator_idx| {
|
||||
state
|
||||
.validator_registry
|
||||
.get(validator_idx as usize)
|
||||
.ok_or(Error::Invalid(Invalid::UnknownValidator(validator_idx)))
|
||||
.map(|validator| {
|
||||
aggregate_pubkey.add(&validator.pubkey);
|
||||
aggregate_pubkey
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Verify the signature of an IndexedAttestation.
|
||||
///
|
||||
/// Spec v0.6.1
|
||||
fn verify_indexed_attestation_signature<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
indexed_attestation: &IndexedAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let bit_0_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_0_indices)?;
|
||||
let bit_1_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_1_indices)?;
|
||||
|
||||
let message_0 = AttestationDataAndCustodyBit {
|
||||
data: indexed_attestation.data.clone(),
|
||||
custody_bit: false,
|
||||
@ -85,23 +128,24 @@ pub fn verify_indexed_attestation<T: EthSpec>(
|
||||
let mut messages = vec![];
|
||||
let mut keys = vec![];
|
||||
|
||||
if message_exists[0] {
|
||||
if !indexed_attestation.custody_bit_0_indices.is_empty() {
|
||||
messages.push(&message_0[..]);
|
||||
keys.push(&aggregate_pubs[0]);
|
||||
keys.push(&bit_0_pubkey);
|
||||
}
|
||||
if message_exists[1] {
|
||||
if !indexed_attestation.custody_bit_1_indices.is_empty() {
|
||||
messages.push(&message_1[..]);
|
||||
keys.push(&aggregate_pubs[1]);
|
||||
keys.push(&bit_1_pubkey);
|
||||
}
|
||||
|
||||
let domain = {
|
||||
let epoch = indexed_attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
||||
};
|
||||
let domain = spec.get_domain(
|
||||
indexed_attestation.data.target_epoch,
|
||||
Domain::Attestation,
|
||||
&state.fork,
|
||||
);
|
||||
|
||||
verify!(
|
||||
indexed_attestation
|
||||
.aggregate_signature
|
||||
.signature
|
||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Attestation, AttestationData, Bitfield};
|
||||
use crate::{AttestationData, Bitfield};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@ -28,22 +28,6 @@ pub struct PendingAttestation {
|
||||
pub proposer_index: u64,
|
||||
}
|
||||
|
||||
impl PendingAttestation {
|
||||
/// Create a `PendingAttestation` from an `Attestation`.
|
||||
pub fn from_attestation(
|
||||
attestation: &Attestation,
|
||||
inclusion_delay: u64,
|
||||
proposer_index: u64,
|
||||
) -> Self {
|
||||
PendingAttestation {
|
||||
data: attestation.data.clone(),
|
||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||
inclusion_delay,
|
||||
proposer_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
Loading…
Reference in New Issue
Block a user