Skip to content

Commit 2d0d15a

Browse files
committed
Use bulk verification for sync_aggregate signature
Updated to handle block batches correctly.
1 parent a3a7f39 commit 2d0d15a

File tree

8 files changed

+186
-79
lines changed

8 files changed

+186
-79
lines changed

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ use store::{Error as DBError, HotColdDB, HotStateSummary, KeyValueStore, StoreOp
7272
use tree_hash::TreeHash;
7373
use types::{
7474
BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256,
75-
InconsistentFork, PublicKey, RelativeEpoch, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
75+
InconsistentFork, PublicKey, PublicKeyBytes, RelativeEpoch, SignedBeaconBlock,
76+
SignedBeaconBlockHeader, Slot,
7677
};
7778

7879
/// Maximum block slot number. Block with slots bigger than this constant will NOT be processed.
@@ -1382,22 +1383,31 @@ fn get_signature_verifier<'a, T: BeaconChainTypes>(
13821383
state: &'a BeaconState<T::EthSpec>,
13831384
validator_pubkey_cache: &'a ValidatorPubkeyCache<T>,
13841385
spec: &'a ChainSpec,
1385-
) -> BlockSignatureVerifier<'a, T::EthSpec, impl Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone> {
1386-
BlockSignatureVerifier::new(
1387-
state,
1388-
move |validator_index| {
1389-
// Disallow access to any validator pubkeys that are not in the current beacon
1390-
// state.
1391-
if validator_index < state.validators().len() {
1392-
validator_pubkey_cache
1393-
.get(validator_index)
1394-
.map(|pk| Cow::Borrowed(pk))
1395-
} else {
1396-
None
1397-
}
1398-
},
1399-
spec,
1400-
)
1386+
) -> BlockSignatureVerifier<
1387+
'a,
1388+
T::EthSpec,
1389+
impl Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
1390+
impl Fn(&'a PublicKeyBytes) -> Option<Cow<'a, PublicKey>>,
1391+
> {
1392+
let get_pubkey = move |validator_index| {
1393+
// Disallow access to any validator pubkeys that are not in the current beacon state.
1394+
if validator_index < state.validators().len() {
1395+
validator_pubkey_cache
1396+
.get(validator_index)
1397+
.map(Cow::Borrowed)
1398+
} else {
1399+
None
1400+
}
1401+
};
1402+
1403+
let decompressor = move |pk_bytes| {
1404+
// Map compressed pubkey to validator index.
1405+
let validator_index = validator_pubkey_cache.get_index(pk_bytes)?;
1406+
// Map validator index to pubkey (respecting guard on unknown validators).
1407+
get_pubkey(validator_index)
1408+
};
1409+
1410+
BlockSignatureVerifier::new(state, get_pubkey, decompressor, spec)
14011411
}
14021412

14031413
/// Verify that `header` was signed with a valid signature from its proposer.

consensus/state_processing/src/per_block_processing.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
22
use rayon::prelude::*;
33
use safe_arith::{ArithError, SafeArith};
44
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
5+
use std::borrow::Cow;
56
use tree_hash::TreeHash;
67
use types::*;
78

@@ -102,6 +103,7 @@ pub fn per_block_processing<T: EthSpec>(
102103
BlockSignatureVerifier::verify_entire_block(
103104
state,
104105
|i| get_pubkey_from_state(state, i),
106+
|pk_bytes| pk_bytes.decompress().ok().map(Cow::Owned),
105107
signed_block,
106108
block_root,
107109
spec
@@ -130,7 +132,13 @@ pub fn per_block_processing<T: EthSpec>(
130132
process_operations(state, block.body(), verify_signatures, spec)?;
131133

132134
if let BeaconBlockRef::Altair(inner) = block {
133-
process_sync_aggregate(state, &inner.body.sync_aggregate, proposer_index, spec)?;
135+
process_sync_aggregate(
136+
state,
137+
&inner.body.sync_aggregate,
138+
proposer_index,
139+
verify_signatures,
140+
spec,
141+
)?;
134142
}
135143

136144
Ok(())

consensus/state_processing/src/per_block_processing/altair/sync_committee.rs

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,44 @@
11
use crate::common::{altair::get_base_reward_per_increment, decrease_balance, increase_balance};
22
use crate::per_block_processing::errors::{BlockProcessingError, SyncAggregateInvalid};
3+
use crate::{signature_sets::sync_aggregate_signature_set, VerifySignatures};
34
use safe_arith::SafeArith;
4-
use tree_hash::TreeHash;
5+
use std::borrow::Cow;
56
use types::consts::altair::{PROPOSER_WEIGHT, SYNC_REWARD_WEIGHT, WEIGHT_DENOMINATOR};
6-
use types::{BeaconState, ChainSpec, Domain, EthSpec, SigningData, SyncAggregate, Unsigned};
7+
use types::{BeaconState, ChainSpec, EthSpec, PublicKeyBytes, SyncAggregate, Unsigned};
78

89
pub fn process_sync_aggregate<T: EthSpec>(
910
state: &mut BeaconState<T>,
1011
aggregate: &SyncAggregate<T>,
1112
proposer_index: u64,
13+
verify_signatures: VerifySignatures,
1214
spec: &ChainSpec,
1315
) -> Result<(), BlockProcessingError> {
14-
// Verify sync committee aggregate signature signing over the previous slot block root
15-
let previous_slot = state.slot().saturating_sub(1u64);
16-
1716
let current_sync_committee = state.current_sync_committee()?.clone();
18-
let committee_pubkeys = &current_sync_committee.pubkeys;
1917

20-
let participant_pubkeys = committee_pubkeys
21-
.iter()
22-
.zip(aggregate.sync_committee_bits.iter())
23-
.flat_map(|(pubkey, bit)| {
24-
if bit {
25-
// FIXME(altair): accelerate pubkey decompression with a cache
26-
Some(pubkey.decompress())
27-
} else {
28-
None
29-
}
30-
})
31-
.collect::<Result<Vec<_>, _>>()
32-
.map_err(|_| SyncAggregateInvalid::PubkeyInvalid)?;
18+
// Verify sync committee aggregate signature signing over the previous slot block root
19+
if verify_signatures.is_true() {
20+
// This decompression could be avoided with a cache, but we're not likely
21+
// to encounter this case in practice due to the use of pre-emptive signature
22+
// verification (which uses the `ValidatorPubkeyCache`).
23+
let decompressor = |pk_bytes: &PublicKeyBytes| pk_bytes.decompress().ok().map(Cow::Owned);
3324

34-
let domain = spec.get_domain(
35-
previous_slot.epoch(T::slots_per_epoch()),
36-
Domain::SyncCommittee,
37-
&state.fork(),
38-
state.genesis_validators_root(),
39-
);
25+
// Check that the signature is over the previous block root.
26+
let previous_slot = state.slot().saturating_sub(1u64);
27+
let previous_block_root = *state.get_block_root(previous_slot)?;
4028

41-
let signing_root = SigningData {
42-
object_root: *state.get_block_root(previous_slot)?,
43-
domain,
44-
}
45-
.tree_hash_root();
29+
let signature_set = sync_aggregate_signature_set(
30+
decompressor,
31+
aggregate,
32+
state.slot(),
33+
previous_block_root,
34+
state,
35+
spec,
36+
)?;
4637

47-
let pubkey_refs = participant_pubkeys.iter().collect::<Vec<_>>();
48-
if !aggregate
49-
.sync_committee_signature
50-
.eth2_fast_aggregate_verify(signing_root, &pubkey_refs)
51-
{
52-
return Err(SyncAggregateInvalid::SignatureInvalid.into());
38+
// If signature set is `None` then the signature is valid (infinity).
39+
if signature_set.map_or(false, |signature| !signature.verify()) {
40+
return Err(SyncAggregateInvalid::SignatureInvalid.into());
41+
}
5342
}
5443

5544
// Compute participant and proposer rewards

consensus/state_processing/src/per_block_processing/block_signature_verifier.rs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use super::signature_sets::{Error as SignatureSetError, *};
44
use crate::common::get_indexed_attestation;
55
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
6-
use bls::{verify_signature_sets, PublicKey, SignatureSet};
6+
use bls::{verify_signature_sets, PublicKey, PublicKeyBytes, SignatureSet};
77
use rayon::prelude::*;
88
use std::borrow::Cow;
99
use types::{
@@ -63,27 +63,36 @@ impl From<BlockOperationError<AttestationInvalid>> for Error {
6363
///
6464
/// This allows for optimizations related to batch BLS operations (see the
6565
/// `Self::verify_entire_block(..)` function).
66-
pub struct BlockSignatureVerifier<'a, T, F>
66+
pub struct BlockSignatureVerifier<'a, T, F, D>
6767
where
6868
T: EthSpec,
6969
F: Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
70+
D: Fn(&'a PublicKeyBytes) -> Option<Cow<'a, PublicKey>>,
7071
{
7172
get_pubkey: F,
73+
decompressor: D,
7274
state: &'a BeaconState<T>,
7375
spec: &'a ChainSpec,
7476
sets: Vec<SignatureSet<'a>>,
7577
}
7678

77-
impl<'a, T, F> BlockSignatureVerifier<'a, T, F>
79+
impl<'a, T, F, D> BlockSignatureVerifier<'a, T, F, D>
7880
where
7981
T: EthSpec,
8082
F: Fn(usize) -> Option<Cow<'a, PublicKey>> + Clone,
83+
D: Fn(&'a PublicKeyBytes) -> Option<Cow<'a, PublicKey>>,
8184
{
8285
/// Create a new verifier without any included signatures. See the `include...` functions to
8386
/// add signatures, and the `verify`
84-
pub fn new(state: &'a BeaconState<T>, get_pubkey: F, spec: &'a ChainSpec) -> Self {
87+
pub fn new(
88+
state: &'a BeaconState<T>,
89+
get_pubkey: F,
90+
decompressor: D,
91+
spec: &'a ChainSpec,
92+
) -> Self {
8593
Self {
8694
get_pubkey,
95+
decompressor,
8796
state,
8897
spec,
8998
sets: vec![],
@@ -100,11 +109,12 @@ where
100109
pub fn verify_entire_block(
101110
state: &'a BeaconState<T>,
102111
get_pubkey: F,
112+
decompressor: D,
103113
block: &'a SignedBeaconBlock<T>,
104114
block_root: Option<Hash256>,
105115
spec: &'a ChainSpec,
106116
) -> Result<()> {
107-
let mut verifier = Self::new(state, get_pubkey, spec);
117+
let mut verifier = Self::new(state, get_pubkey, decompressor, spec);
108118
verifier.include_all_signatures(block, block_root)?;
109119
verifier.verify()
110120
}
@@ -146,12 +156,7 @@ where
146156
block_root: Option<Hash256>,
147157
) -> Result<()> {
148158
self.include_block_proposal(block, block_root)?;
149-
self.include_randao_reveal(block)?;
150-
self.include_proposer_slashings(block)?;
151-
self.include_attester_slashings(block)?;
152-
self.include_attestations(block)?;
153-
// Deposits are not included because they can legally have invalid signatures.
154-
self.include_exits(block)?;
159+
self.include_all_signatures_except_proposal(block)?;
155160

156161
Ok(())
157162
}
@@ -168,6 +173,7 @@ where
168173
self.include_attestations(block)?;
169174
// Deposits are not included because they can legally have invalid signatures.
170175
self.include_exits(block)?;
176+
self.include_sync_aggregate(block)?;
171177

172178
Ok(())
173179
}
@@ -308,4 +314,21 @@ where
308314
Ok(())
309315
})
310316
}
317+
318+
/// Include the signature of the block's sync aggregate (if it exists) for verification.
319+
pub fn include_sync_aggregate(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
320+
if let Some(sync_aggregate) = block.message().body().sync_aggregate() {
321+
if let Some(signature_set) = sync_aggregate_signature_set(
322+
&self.decompressor,
323+
sync_aggregate,
324+
block.slot(),
325+
block.parent_root(),
326+
&self.state,
327+
&self.spec,
328+
)? {
329+
self.sets.push(signature_set);
330+
}
331+
}
332+
Ok(())
333+
}
311334
}

consensus/state_processing/src/per_block_processing/signature_sets.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use types::{
1111
DepositData, Domain, Epoch, EthSpec, Fork, Hash256, InconsistentFork, IndexedAttestation,
1212
ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof,
1313
SignedBeaconBlock, SignedBeaconBlockHeader, SignedContributionAndProof, SignedRoot,
14-
SignedVoluntaryExit, SigningData, SyncAggregatorSelectionData, Unsigned,
14+
SignedVoluntaryExit, SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned,
1515
};
1616

1717
pub type Result<T> = std::result::Result<T, Error>;
@@ -36,6 +36,8 @@ pub enum Error {
3636
/// The public keys supplied do not match the number of objects requiring keys. Block validity
3737
/// was not determined.
3838
MismatchedPublicKeyLen { pubkey_len: usize, other_len: usize },
39+
/// Pubkey decompression failed. The block is invalid.
40+
PublicKeyDecompressionFailed,
3941
/// The public key bytes stored in the `BeaconState` were not valid. This is a serious internal
4042
/// error.
4143
BadBlsBytes { validator_index: u64 },
@@ -517,3 +519,73 @@ where
517519

518520
Ok(SignatureSet::single_pubkey(signature, pubkey, message))
519521
}
522+
523+
/// Signature set verifier for a block's `sync_aggregate` (Altair and later).
524+
///
525+
/// The `slot` should be the slot of the block that the sync aggregate is included in, which may be
526+
/// different from `state.slot()`. The `block_root` should be the block root that the sync aggregate
527+
/// signs over. It's passed in rather than extracted from the `state` because when verifying a batch
528+
/// of blocks the `state` will not yet have had the blocks applied.
529+
///
530+
/// Returns `Ok(None)` in the case where `sync_aggregate` has 0 signatures. The spec
531+
/// uses a separate function `eth2_fast_aggregate_verify` for this, but we can equivalently
532+
/// check the exceptional case eagerly and do a `fast_aggregate_verify` in the case where the
533+
/// check fails (by returning `Some(signature_set)`).
534+
pub fn sync_aggregate_signature_set<'a, T, D>(
535+
decompressor: D,
536+
sync_aggregate: &'a SyncAggregate<T>,
537+
slot: Slot,
538+
block_root: Hash256,
539+
state: &'a BeaconState<T>,
540+
spec: &ChainSpec,
541+
) -> Result<Option<SignatureSet<'a>>>
542+
where
543+
T: EthSpec,
544+
D: Fn(&'a PublicKeyBytes) -> Option<Cow<'a, PublicKey>>,
545+
{
546+
// Allow the point at infinity to count as a signature for 0 validators as per
547+
// `eth2_fast_aggregate_verify` from the spec.
548+
if sync_aggregate.sync_committee_bits.is_zero()
549+
&& sync_aggregate.sync_committee_signature.is_infinity()
550+
{
551+
return Ok(None);
552+
}
553+
554+
let committee_pubkeys = &state
555+
.get_built_sync_committee(slot.epoch(T::slots_per_epoch()), spec)?
556+
.pubkeys;
557+
558+
let participant_pubkeys = committee_pubkeys
559+
.iter()
560+
.zip(sync_aggregate.sync_committee_bits.iter())
561+
.filter_map(|(pubkey, bit)| {
562+
if bit {
563+
Some(decompressor(pubkey))
564+
} else {
565+
None
566+
}
567+
})
568+
.collect::<Option<Vec<_>>>()
569+
.ok_or(Error::PublicKeyDecompressionFailed)?;
570+
571+
let previous_slot = slot.saturating_sub(1u64);
572+
573+
let domain = spec.get_domain(
574+
previous_slot.epoch(T::slots_per_epoch()),
575+
Domain::SyncCommittee,
576+
&state.fork(),
577+
state.genesis_validators_root(),
578+
);
579+
580+
let message = SigningData {
581+
object_root: block_root,
582+
domain,
583+
}
584+
.tree_hash_root();
585+
586+
Ok(Some(SignatureSet::multiple_pubkeys(
587+
&sync_aggregate.sync_committee_signature,
588+
participant_pubkeys,
589+
message,
590+
)))
591+
}

crypto/bls/src/generic_aggregate_signature.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ where
110110
self.point.is_none()
111111
}
112112

113+
/// Returns `true` if `self` is equal to the point at infinity.
114+
pub fn is_infinity(&self) -> bool {
115+
self.is_infinity
116+
}
117+
113118
/// Returns a reference to the underlying BLS point.
114119
pub(crate) fn point(&self) -> Option<&AggSig> {
115120
self.point.as_ref()
@@ -189,18 +194,6 @@ where
189194
}
190195
}
191196

192-
/// Wrapper to `fast_aggregate_verify` accepting the infinity signature when `pubkeys` is empty.
193-
pub fn eth2_fast_aggregate_verify(
194-
&self,
195-
msg: Hash256,
196-
pubkeys: &[&GenericPublicKey<Pub>],
197-
) -> bool {
198-
if pubkeys.is_empty() && self.is_infinity {
199-
return true;
200-
}
201-
self.fast_aggregate_verify(msg, pubkeys)
202-
}
203-
204197
/// Verify that `self` represents an aggregate signature where all `pubkeys` have signed their
205198
/// corresponding message in `msgs`.
206199
///

0 commit comments

Comments
 (0)