diff --git a/crates/blockchain/src/lib.rs b/crates/blockchain/src/lib.rs index 998159c..6c23fb7 100644 --- a/crates/blockchain/src/lib.rs +++ b/crates/blockchain/src/lib.rs @@ -315,6 +315,12 @@ impl BlockChainServer { return; } + // Pre-check signatures for external blocks before processing + if let Err(err) = store::precheck_block_signatures(&self.store, &signed_block) { + warn!(%slot, %err, "Block signature verification failed"); + return; + } + // Parent exists, proceed with processing match self.process_block(signed_block) { Ok(_) => { diff --git a/crates/blockchain/src/store.rs b/crates/blockchain/src/store.rs index 04138a9..582d12b 100644 --- a/crates/blockchain/src/store.rs +++ b/crates/blockchain/src/store.rs @@ -140,6 +140,40 @@ fn validate_attestation(store: &Store, attestation: &Attestation) -> Result<(), Ok(()) } +/// Verify all cryptographic signatures in a block before processing. +/// +/// This is a pre-check that can be called before `on_block()` to validate signatures early. +/// It verifies: +/// 1. Each attestation's aggregated signature proof +/// 2. The proposer attestation signature +/// +/// Returns `Ok(())` if all signatures are valid, otherwise returns an error. +/// +/// If the feature `skip-signature-verification` is enabled, this function returns `Ok(())` immediately. +pub fn precheck_block_signatures( + store: &Store, + signed_block: &SignedBlockWithAttestation, +) -> Result<(), StoreError> { + // Skip verification if feature flag is enabled + if cfg!(feature = "skip-signature-verification") { + return Ok(()); + } + + let block = &signed_block.message.block; + let parent_root = block.parent_root; + let slot = block.slot; + + // Retrieve parent state for validator pubkeys + let parent_state = store + .get_state(&parent_root) + .ok_or(StoreError::MissingParentState { parent_root, slot })?; + + // Verify all cryptographic signatures + verify_signatures(&parent_state, signed_block)?; + + Ok(()) +} + /// Process a tick event. pub fn on_tick(store: &mut Store, timestamp: u64, has_proposal: bool) { let time = timestamp - store.config().genesis_time; @@ -346,13 +380,6 @@ pub fn on_block( slot, })?; - // Validate cryptographic signatures - // TODO: extract signature verification to a pre-checks function - // to avoid the need for this - if cfg!(not(feature = "skip-signature-verification")) { - verify_signatures(&parent_state, &signed_block)?; - } - // Execute state transition function to compute post-block state let mut post_state = parent_state.clone(); ethlambda_state_transition::state_transition(&mut post_state, &block)?; diff --git a/crates/blockchain/tests/signature_spectests.rs b/crates/blockchain/tests/signature_spectests.rs index 40ec7aa..362d69c 100644 --- a/crates/blockchain/tests/signature_spectests.rs +++ b/crates/blockchain/tests/signature_spectests.rs @@ -52,8 +52,16 @@ fn run(path: &Path) -> datatest_stable::Result<()> { let block_time = signed_block.message.block.slot * SECONDS_PER_SLOT + genesis_time; store::on_tick(&mut st, block_time, true); - // Process the block (this includes signature verification) - let result = store::on_block(&mut st, signed_block); + // Pre-check signatures before processing the block + let precheck_result = store::precheck_block_signatures(&st, &signed_block); + + // If pre-check failed, use that as the result + let result = if precheck_result.is_err() { + precheck_result + } else { + // Otherwise, process the block + store::on_block(&mut st, signed_block) + }; // Step 3: Check that it succeeded or failed as expected match (result.is_ok(), test.expect_exception.as_ref()) {