Skip to content

Commit 3f995d0

Browse files
feat: use aggregate_pubkey field when processing consensus update
- Use aggregate_pubkey to save on CPU-heavy key aggregation - Use is_torsion_free method to verify G2 point more efficiently
1 parent 89ac422 commit 3f995d0

File tree

3 files changed

+75
-55
lines changed

3 files changed

+75
-55
lines changed

ethereum/consensus-core/src/consensus_core.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ use crate::proof::{
1616
is_current_committee_proof_valid, is_execution_payload_proof_valid, is_finality_proof_valid,
1717
is_next_committee_proof_valid,
1818
};
19-
use crate::types::bls::{PublicKey, Signature};
19+
use crate::types::bls::Signature;
2020
use crate::types::{
2121
BeaconBlockHeader, Bootstrap, ExecutionPayloadHeader, FinalityUpdate, Forks, GenericUpdate,
2222
LightClientHeader, LightClientStore, OptimisticUpdate, Update,
2323
};
2424
use crate::utils::{
2525
calculate_fork_version, compute_committee_sign_root, compute_fork_data_root,
26-
get_participating_keys,
26+
get_participating_aggregate_pubkey,
2727
};
2828

2929
pub fn verify_bootstrap<S: ConsensusSpec>(
@@ -343,12 +343,15 @@ pub fn verify_generic_update<S: ConsensusSpec>(
343343
store.next_sync_committee.as_ref().unwrap()
344344
};
345345

346-
let pks = get_participating_keys(sync_committee, &update.sync_aggregate.sync_committee_bits)?;
346+
let agg_pk = get_participating_aggregate_pubkey(
347+
sync_committee,
348+
&update.sync_aggregate.sync_committee_bits,
349+
)?;
347350

348351
let fork_version = calculate_fork_version::<S>(forks, update.signature_slot.saturating_sub(1));
349352
let fork_data_root = compute_fork_data_root(fork_version, genesis_root);
350353
let is_valid_sig = verify_sync_committee_signature(
351-
&pks,
354+
&agg_pk,
352355
update.attested_header.beacon(),
353356
&update.sync_aggregate.sync_committee_signature,
354357
fork_data_root,
@@ -484,14 +487,14 @@ fn has_finality_update<S: ConsensusSpec>(update: &GenericUpdate<S>) -> bool {
484487
}
485488

486489
fn verify_sync_committee_signature(
487-
pks: &[PublicKey],
490+
aggregate_public_key: &bls12_381::G1Affine,
488491
attested_header: &BeaconBlockHeader,
489492
signature: &Signature,
490493
fork_data_root: B256,
491494
) -> bool {
492495
let header_root = attested_header.tree_hash_root();
493496
let signing_root = compute_committee_sign_root(header_root, fork_data_root);
494-
signature.verify(signing_root.as_slice(), pks)
497+
signature.verify_with_aggregate_pubkey(signing_root.as_slice(), aggregate_public_key)
495498
}
496499

497500
fn safety_threshold<S: ConsensusSpec>(store: &LightClientStore<S>) -> u64 {

ethereum/consensus-core/src/types/bls.rs

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use bls12_381::{
22
hash_to_curve::{ExpandMsgXmd, HashToCurve},
3-
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, Scalar,
3+
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt,
44
};
55
use eyre::{eyre, Result};
66
use serde::{Deserialize, Serialize};
@@ -24,7 +24,7 @@ pub struct Signature {
2424
}
2525

2626
impl PublicKey {
27-
fn point(&self) -> Result<G1Affine> {
27+
pub(crate) fn point(&self) -> Result<G1Affine> {
2828
let bytes = self.inner.inner.to_vec();
2929
let bytes = bytes.as_slice().try_into()?;
3030
let point_opt = G1Affine::from_compressed(bytes);
@@ -60,20 +60,26 @@ impl Signature {
6060
} else {
6161
return false;
6262
};
63+
verify_with_aggregate_pk(&sig_point, msg, &aggregate_public_key)
64+
}
6365

64-
// Ensure AggregatePublicKey is not infinity
65-
if aggregate_public_key.is_identity().into() {
66+
pub fn verify_with_aggregate_pubkey(
67+
&self,
68+
msg: &[u8],
69+
aggregate_public_key: &G1Affine,
70+
) -> bool {
71+
let sig_point = if let Ok(point) = self.point() {
72+
point
73+
} else {
6674
return false;
67-
}
68-
69-
// Points must be affine for pairing
70-
let key_point = aggregate_public_key;
71-
let msg_hash = G2Affine::from(hash_to_curve(msg));
75+
};
7276

73-
let generator_g1_negative = G1Affine::from(-G1Projective::generator());
77+
// Subgroup check for signature
78+
if !subgroup_check_g2(&sig_point) {
79+
return false;
80+
}
7481

75-
// Faster ate2 evaluation checks e(S, -G1) * e(H, PK) == 1
76-
ate2_evaluation(&sig_point, &generator_g1_negative, &msg_hash, &key_point)
82+
verify_with_aggregate_pk(&sig_point, msg, aggregate_public_key)
7783
}
7884

7985
fn point(&self) -> Result<G2Affine> {
@@ -102,12 +108,29 @@ fn aggregate(pks: &[PublicKey]) -> Result<G1Affine> {
102108
Ok(G1Affine::from(agg_key))
103109
}
104110

111+
fn verify_with_aggregate_pk(
112+
sig_point: &G2Affine,
113+
msg: &[u8],
114+
aggregate_public_key: &G1Affine,
115+
) -> bool {
116+
// Ensure AggregatePublicKey is not infinity
117+
if aggregate_public_key.is_identity().into() {
118+
return false;
119+
}
120+
121+
// Points must be affine for pairing
122+
let key_point = *aggregate_public_key;
123+
let msg_hash = G2Affine::from(hash_to_curve(msg));
124+
125+
let generator_g1_negative = G1Affine::from(-G1Projective::generator());
126+
127+
// Faster ate2 evaluation checks e(S, -G1) * e(H, PK) == 1
128+
ate2_evaluation(sig_point, &generator_g1_negative, &msg_hash, &key_point)
129+
}
130+
105131
/// Verifies a G2 point is in subgroup `r`.
106132
fn subgroup_check_g2(point: &G2Affine) -> bool {
107-
const CURVE_ORDER: &str = "73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001";
108-
let r = hex_to_scalar(CURVE_ORDER).unwrap();
109-
let check = point * r;
110-
check.is_identity().into()
133+
point.is_torsion_free().into()
111134
}
112135

113136
/// Evaluation of e(S, -G1) * e(H, PK) == 1
@@ -131,25 +154,3 @@ fn hash_to_curve(msg: &[u8]) -> G2Projective {
131154
const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
132155
<G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, DST)
133156
}
134-
135-
/// Converts hex string to scalar
136-
fn hex_to_scalar(hex: &str) -> Option<Scalar> {
137-
if hex.len() != 64 {
138-
return None;
139-
}
140-
141-
let mut raw = [0u64; 4];
142-
for (i, chunk) in hex.as_bytes().chunks(16).enumerate().take(4) {
143-
if let Ok(hex_chunk) = core::str::from_utf8(chunk) {
144-
if let Ok(value) = u64::from_str_radix(hex_chunk, 16) {
145-
raw[3 - i] = value.to_le();
146-
} else {
147-
return None;
148-
}
149-
} else {
150-
return None;
151-
}
152-
}
153-
154-
Some(Scalar::from_raw(raw))
155-
}

ethereum/consensus-core/src/utils.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use tree_hash_derive::TreeHash;
66

77
use crate::{
88
consensus_spec::ConsensusSpec,
9-
types::{bls::PublicKey, Forks, SyncCommittee},
9+
types::{Forks, SyncCommittee},
1010
};
11+
use bls12_381::{G1Affine, G1Projective};
1112

1213
pub fn compute_committee_sign_root(header: B256, fork_data_root: B256) -> B256 {
1314
let domain_type = [7, 00, 00, 00];
@@ -52,20 +53,35 @@ pub fn compute_fork_data_root(
5253
fork_data.tree_hash_root()
5354
}
5455

55-
pub fn get_participating_keys<S: ConsensusSpec>(
56+
/// Computes the aggregate public key for the participating members of the sync committee.
57+
/// with given participation bitfield and committee aggregate public key.
58+
pub fn get_participating_aggregate_pubkey<S: ConsensusSpec>(
5659
committee: &SyncCommittee<S>,
5760
bitfield: &BitVector<S::SyncCommitteeSize>,
58-
) -> Result<Vec<PublicKey>> {
59-
let mut pks: Vec<PublicKey> = Vec::new();
61+
) -> Result<G1Affine> {
62+
let total = bitfield.len();
63+
let participating = bitfield.iter().filter(|b| *b).count();
64+
if participating == 0 {
65+
return Err(eyre::eyre!("no participating keys"));
66+
}
6067

61-
bitfield.iter().enumerate().for_each(|(i, bit)| {
62-
if bit {
63-
let pk = committee.pubkeys[i].clone();
64-
pks.push(pk);
68+
if participating > total / 2 {
69+
let mut agg = G1Projective::from(committee.aggregate_pubkey.point()?);
70+
for (i, bit) in bitfield.iter().enumerate() {
71+
if !bit {
72+
agg -= G1Projective::from(committee.pubkeys[i].point()?);
73+
}
6574
}
66-
});
67-
68-
Ok(pks)
75+
Ok(G1Affine::from(agg))
76+
} else {
77+
let mut agg = G1Projective::identity();
78+
for (i, bit) in bitfield.iter().enumerate() {
79+
if bit {
80+
agg += G1Projective::from(committee.pubkeys[i].point()?);
81+
}
82+
}
83+
Ok(G1Affine::from(agg))
84+
}
6985
}
7086

7187
fn compute_signing_root(object_root: B256, domain: B256) -> B256 {

0 commit comments

Comments
 (0)