spec v0.6.1: attestation processing/verif

This commit is contained in:
Michael Sproul 2019-05-21 16:38:16 +10:00
parent f8d4e742ad
commit 0b2aa26f2d
No known key found for this signature in database
GPG Key ID: 77B1309D2E54E914
7 changed files with 227 additions and 298 deletions

View 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(),
})
}

View File

@ -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;

View File

@ -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)
}
}

View File

@ -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,
}

View File

@ -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(())
}

View File

@ -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
);

View File

@ -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::*;