Skip to content

Commit 4f16808

Browse files
committed
Use bulk verification for sync_aggregate signature
1 parent 95e3069 commit 4f16808

File tree

9 files changed

+178
-83
lines changed

9 files changed

+178
-83
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: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,32 @@
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;
19-
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)?;
33-
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-
);
4017

41-
let signing_root = SigningData {
42-
object_root: *state.get_block_root(previous_slot)?,
43-
domain,
44-
}
45-
.tree_hash_root();
46-
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());
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);
24+
let signature_set = sync_aggregate_signature_set(decompressor, aggregate, state, spec)?;
25+
26+
// If signature set is `None` then the signature is valid (infinity).
27+
if signature_set.map_or(false, |signature| !signature.verify()) {
28+
return Err(SyncAggregateInvalid::SignatureInvalid.into());
29+
}
5330
}
5431

5532
// Compute participant and proposer rewards

consensus/state_processing/src/per_block_processing/block_signature_verifier.rs

Lines changed: 32 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,19 @@ 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+
&self.state,
325+
&self.spec,
326+
)? {
327+
self.sets.push(signature_set);
328+
}
329+
}
330+
Ok(())
331+
}
311332
}

consensus/state_processing/src/per_block_processing/signature_sets.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use tree_hash::TreeHash;
99
use types::{
1010
AggregateSignature, AttesterSlashing, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec,
1111
DepositData, Domain, EthSpec, Fork, Hash256, InconsistentFork, IndexedAttestation,
12-
ProposerSlashing, PublicKey, Signature, SignedAggregateAndProof, SignedBeaconBlock,
13-
SignedBeaconBlockHeader, SignedRoot, SignedVoluntaryExit, SigningData,
12+
ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof,
13+
SignedBeaconBlock, SignedBeaconBlockHeader, SignedRoot, SignedVoluntaryExit, SigningData,
14+
SyncAggregate,
1415
};
1516

1617
pub type Result<T> = std::result::Result<T, Error>;
@@ -32,6 +33,8 @@ pub enum Error {
3233
/// The public keys supplied do not match the number of objects requiring keys. Block validity
3334
/// was not determined.
3435
MismatchedPublicKeyLen { pubkey_len: usize, other_len: usize },
36+
/// Pubkey decompression failed. The block is invalid.
37+
PublicKeyDecompressionFailed,
3538
/// The public key bytes stored in the `BeaconState` were not valid. This is a serious internal
3639
/// error.
3740
BadBlsBytes { validator_index: u64 },
@@ -396,3 +399,64 @@ where
396399
message,
397400
))
398401
}
402+
403+
/// Signature set verifier for a block's `sync_aggregate` (Altair and later).
404+
///
405+
/// Returns `Ok(None)` in the case where `sync_aggregate` has 0 signatures. The spec
406+
/// uses a separate function `eth2_fast_aggregate_verify` for this, but we can equivalently
407+
/// check the exceptional case eagerly and do a `fast_aggregate_verify` in the case where the
408+
/// check fails (by returning `Some(signature_set)`).
409+
pub fn sync_aggregate_signature_set<'a, T, D>(
410+
decompressor: D,
411+
sync_aggregate: &'a SyncAggregate<T>,
412+
state: &'a BeaconState<T>,
413+
spec: &ChainSpec,
414+
) -> Result<Option<SignatureSet<'a>>>
415+
where
416+
T: EthSpec,
417+
D: Fn(&'a PublicKeyBytes) -> Option<Cow<'a, PublicKey>>,
418+
{
419+
// Allow the point at infinity to count as a signature for 0 validators as per
420+
// `eth2_fast_aggregate_verify` from the spec.
421+
if sync_aggregate.sync_committee_bits.is_zero()
422+
&& sync_aggregate.sync_committee_signature.is_infinity()
423+
{
424+
return Ok(None);
425+
}
426+
427+
let committee_pubkeys = &state.current_sync_committee()?.pubkeys;
428+
429+
let participant_pubkeys = committee_pubkeys
430+
.iter()
431+
.zip(sync_aggregate.sync_committee_bits.iter())
432+
.filter_map(|(pubkey, bit)| {
433+
if bit {
434+
Some(decompressor(pubkey))
435+
} else {
436+
None
437+
}
438+
})
439+
.collect::<Option<Vec<_>>>()
440+
.ok_or(Error::PublicKeyDecompressionFailed)?;
441+
442+
let previous_slot = state.slot().saturating_sub(1u64);
443+
444+
let domain = spec.get_domain(
445+
previous_slot.epoch(T::slots_per_epoch()),
446+
Domain::SyncCommittee,
447+
&state.fork(),
448+
state.genesis_validators_root(),
449+
);
450+
451+
let message = SigningData {
452+
object_root: *state.get_block_root(previous_slot)?,
453+
domain,
454+
}
455+
.tree_hash_root();
456+
457+
Ok(Some(SignatureSet::multiple_pubkeys(
458+
&sync_aggregate.sync_committee_signature,
459+
participant_pubkeys,
460+
message,
461+
)))
462+
}

consensus/types/src/beacon_block_body.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ pub struct BeaconBlockBody<T: EthSpec> {
4545
pub sync_aggregate: SyncAggregate<T>,
4646
}
4747

48+
impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> {
49+
/// Access the sync aggregate from the block's body, if one exists.
50+
pub fn sync_aggregate(self) -> Option<&'a SyncAggregate<T>> {
51+
match self {
52+
BeaconBlockBodyRef::Base(_) => None,
53+
BeaconBlockBodyRef::Altair(inner) => Some(&inner.sync_aggregate),
54+
}
55+
}
56+
}
57+
4858
#[cfg(test)]
4959
mod tests {
5060
mod base {

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)