Skip to content

Commit 6f7e0f2

Browse files
committed
feat: add BLS verification and signing roots (PR #1)
1 parent 6c28cbd commit 6f7e0f2

3 files changed

Lines changed: 59 additions & 27 deletions

File tree

src/beacon_chain/containers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::beacon_chain::constants::MAX_PAYLOAD_ATTESTATIONS;
21
/// EIP-7732 — SSZ containers
32
/// All containers directly mirror the spec definitions.
43
/// Reference: https://eips.ethereum.org/EIPS/eip-7732#containers
4+
use crate::beacon_chain::constants::MAX_PAYLOAD_ATTESTATIONS;
55
use crate::beacon_chain::types::*;
66
use serde::{Deserialize, Serialize};
77

src/beacon_chain/process_payload_attestation.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
///
66
/// Reference: https://eips.ethereum.org/EIPS/eip-7732#beacon-chain-changes
77
use crate::beacon_chain::{
8-
constants::PTC_SIZE,
8+
constants::{DOMAIN_PTC_ATTESTER, PTC_SIZE},
99
containers::{PayloadAttestation, PayloadAttestationData},
10-
types::{Slot, ValidatorIndex},
10+
types::{BLSPubkey, Slot, ValidatorIndex},
1111
};
12+
use crate::utils::{crypto, ssz};
1213
use thiserror::Error;
1314

1415
#[derive(Debug, Error)]
@@ -24,11 +25,15 @@ pub enum PayloadAttestationError {
2425

2526
#[error("Attesting index {0} is not a PTC member for this slot")]
2627
NotPtcMember(ValidatorIndex),
28+
29+
#[error("PTC pubkey list length does not match committee size")]
30+
MissingPubkeys,
2731
}
2832

2933
pub trait BeaconStateRead {
3034
fn parent_slot(&self) -> Slot;
3135
fn get_ptc(&self, slot: Slot) -> Vec<ValidatorIndex>;
36+
fn ptc_pubkeys(&self, slot: Slot) -> Vec<BLSPubkey>;
3237
fn parent_beacon_block_root(&self) -> [u8; 32];
3338
}
3439

@@ -66,28 +71,40 @@ pub fn process_payload_attestation<S: BeaconStateRead>(
6671

6772
// Get the PTC members for the attested slot
6873
let ptc = state.get_ptc(data.slot);
74+
let ptc_pubkeys = state.ptc_pubkeys(data.slot);
75+
if ptc_pubkeys.len() != ptc.len() {
76+
return Err(PayloadAttestationError::MissingPubkeys);
77+
}
6978

70-
// Collect attesting validators
71-
let attesting: Vec<ValidatorIndex> = attestation
72-
.aggregation_bits
73-
.iter()
74-
.enumerate()
75-
.filter(|(_, &bit)| bit)
76-
.map(|(i, _)| ptc[i])
77-
.collect();
79+
// Collect attesting validators and their pubkeys
80+
let mut attesting_indices = Vec::new();
81+
let mut attesting_pubkeys = Vec::new();
82+
for (i, bit) in attestation.aggregation_bits.iter().enumerate() {
83+
if *bit {
84+
attesting_indices.push(ptc[i]);
85+
attesting_pubkeys.push(ptc_pubkeys[i]);
86+
}
87+
}
7888

79-
// Verify aggregated signature (stub)
80-
verify_aggregate_ptc_signature(&attestation.signature, data, &attesting)?;
89+
// Verify aggregated signature
90+
verify_aggregate_ptc_signature(
91+
&attestation.signature,
92+
data,
93+
&attesting_indices,
94+
&attesting_pubkeys,
95+
)?;
8196

8297
Ok(())
8398
}
8499

85100
fn verify_aggregate_ptc_signature(
86-
_signature: &[u8; 96],
87-
_data: &PayloadAttestationData,
101+
signature: &[u8; 96],
102+
data: &PayloadAttestationData,
88103
_validators: &[ValidatorIndex],
104+
pubkeys: &[BLSPubkey],
89105
) -> Result<(), PayloadAttestationError> {
90-
// TODO: aggregate public keys, compute signing_root with DOMAIN_PTC_ATTESTER,
91-
// verify with blst
92-
Ok(())
106+
let domain = ssz::compute_domain_simple(DOMAIN_PTC_ATTESTER);
107+
let signing_root = ssz::signing_root(data, domain);
108+
crypto::bls_verify_aggregate(pubkeys, &signing_root, signature)
109+
.map_err(|_| PayloadAttestationError::InvalidSignature)
93110
}

src/beacon_chain/process_payload_bid.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
///
88
/// Reference: https://eips.ethereum.org/EIPS/eip-7732#beacon-chain-changes
99
use crate::beacon_chain::{
10+
constants::DOMAIN_BEACON_BUILDER,
1011
containers::{BuilderPendingPayment, BuilderPendingWithdrawal, SignedExecutionPayloadBid},
11-
types::{BuilderIndex, Gwei, Slot},
12+
types::{BLSPubkey, BuilderIndex, Gwei, Slot},
1213
};
14+
use crate::utils::{crypto, ssz};
1315
use thiserror::Error;
1416

1517
#[derive(Debug, Error)]
@@ -28,11 +30,15 @@ pub enum PayloadBidError {
2830

2931
#[error("Parent block hash mismatch")]
3032
ParentHashMismatch,
33+
34+
#[error("Builder pubkey missing for index {0}")]
35+
MissingPubkey(BuilderIndex),
3136
}
3237

3338
/// Minimal beacon state surface needed by this function.
3439
pub trait BeaconStateMut {
3540
fn builder_balance(&self, index: BuilderIndex) -> Option<Gwei>;
41+
fn builder_pubkey(&self, index: BuilderIndex) -> Option<BLSPubkey>;
3642
fn deduct_builder_balance(&mut self, index: BuilderIndex, amount: Gwei);
3743
fn push_pending_payment(&mut self, payment: BuilderPendingPayment);
3844
fn current_slot(&self) -> Slot;
@@ -68,10 +74,7 @@ pub fn process_execution_payload_bid<S: BeaconStateMut>(
6874
return Err(PayloadBidError::ParentHashMismatch);
6975
}
7076

71-
// Step 3 — BLS signature (stub — wire to blst crate in full impl)
72-
verify_builder_signature(signed_bid)?;
73-
74-
// Step 4 + 5 — balance check and deduction
77+
// Step 3 — balance check
7578
let balance = state
7679
.builder_balance(bid.builder_index)
7780
.ok_or(PayloadBidError::BuilderNotFound(bid.builder_index))?;
@@ -83,6 +86,10 @@ pub fn process_execution_payload_bid<S: BeaconStateMut>(
8386
});
8487
}
8588

89+
// Step 4 — BLS signature
90+
verify_builder_signature(state, signed_bid)?;
91+
92+
// Step 5 — deduct balance
8693
state.deduct_builder_balance(bid.builder_index, bid.value);
8794

8895
// Step 6 — queue pending payment
@@ -100,9 +107,17 @@ pub fn process_execution_payload_bid<S: BeaconStateMut>(
100107
}
101108

102109
/// Stub — replace with blst domain-separated BLS verify in full impl.
103-
fn verify_builder_signature(
104-
_signed_bid: &SignedExecutionPayloadBid,
110+
fn verify_builder_signature<S: BeaconStateMut>(
111+
state: &S,
112+
signed_bid: &SignedExecutionPayloadBid,
105113
) -> Result<(), PayloadBidError> {
106-
// TODO: compute signing_root with DOMAIN_BEACON_BUILDER and verify
107-
Ok(())
114+
let message = &signed_bid.message;
115+
let pk = state
116+
.builder_pubkey(message.builder_index)
117+
.ok_or(PayloadBidError::MissingPubkey(message.builder_index))?;
118+
119+
let domain = ssz::compute_domain_simple(DOMAIN_BEACON_BUILDER);
120+
let signing_root = ssz::signing_root(message, domain);
121+
crypto::bls_verify(&pk, &signing_root, &signed_bid.signature)
122+
.map_err(|_| PayloadBidError::InvalidSignature)
108123
}

0 commit comments

Comments
 (0)