From bca71c5d5837b0f1c1cb5203f490e579bdb81ad4 Mon Sep 17 00:00:00 2001 From: SamAg19 Date: Sun, 16 Aug 2026 21:39:21 +0530 Subject: [PATCH 1/2] Skip XMSS verify for mocked aggregate proofs --- lean_client/fork_choice/src/handlers.rs | 14 +++++++---- lean_client/http_api/src/test_driver.rs | 32 ++++++++++++++++++------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lean_client/fork_choice/src/handlers.rs b/lean_client/fork_choice/src/handlers.rs index 778c38d4..1f90c117 100644 --- a/lean_client/fork_choice/src/handlers.rs +++ b/lean_client/fork_choice/src/handlers.rs @@ -409,10 +409,14 @@ pub fn on_attestation( /// Verifies the aggregated XMSS proof against participant public keys and stores /// it in `latest_new_aggregated_payloads`. At interval 3, these are merged with /// `latest_known_aggregated_payloads` (from blocks) to compute safe target. +/// +/// `verify_proof` gates only the XMSS SNARK check (production: `true`; mocked +/// spec-test proofs: `false`). #[inline] pub fn on_aggregated_attestation( store: &mut Store, signed_aggregated_attestation: SignedAggregatedAttestation, + verify_proof: bool, ) -> Result<()> { // Structure: { data: AttestationData, proof: AggregatedSignatureProof } let attestation_data = signed_aggregated_attestation.data.clone(); @@ -472,9 +476,11 @@ pub fn on_aggregated_attestation( }) .collect::>>()?; - proof - .verify(public_keys, data_root, attestation_data.slot.0 as u32) - .context("aggregated attestation proof verification failed")?; + if verify_proof { + proof + .verify(public_keys, data_root, attestation_data.slot.0 as u32) + .context("aggregated attestation proof verification failed")?; + } let attestation_slot = attestation_data.slot; for vid in &validator_ids { @@ -796,7 +802,7 @@ pub fn apply_verified_block( .remove(&block_root) .unwrap_or_default(); for signed_agg in pending_agg { - if let Err(err) = on_aggregated_attestation(store, signed_agg) { + if let Err(err) = on_aggregated_attestation(store, signed_agg, true) { warn!(%err, "Pending aggregated attestation retry failed after block arrival"); } } diff --git a/lean_client/http_api/src/test_driver.rs b/lean_client/http_api/src/test_driver.rs index e2f17a87..cf894cd0 100644 --- a/lean_client/http_api/src/test_driver.rs +++ b/lean_client/http_api/src/test_driver.rs @@ -412,8 +412,8 @@ fn apply_step( // `Checks` steps still see a snapshot. return Ok(()); }; - let signed = build_signed_aggregated_attestation(step)?; - on_aggregated_attestation(store, signed).map_err(|err| err.to_string()) + let (signed, verify_proof) = build_signed_aggregated_attestation(step)?; + on_aggregated_attestation(store, signed, verify_proof).map_err(|err| err.to_string()) } ForkChoiceStep::Checks { .. } => { // Pure-assertion step. The simulator validates against the @@ -461,6 +461,10 @@ fn hex_root(root: &ssz::H256) -> String { format!("0x{}", hex::encode(root.as_bytes())) } +/// Sentinel emitted by the leanSpec fixture generator for `proofSetting: 0` +/// (mocked) aggregation proofs. Real proofs never contain it. +const MOCK_AGGREGATION_PROOF_MARKER: &[u8] = b"MOCKED-AGGREGATION-PROOF"; + /// Build a `SignedAggregatedAttestation` from the fixture-supplied /// `gossipAggregatedAttestation` step payload. /// @@ -468,20 +472,30 @@ fn hex_root(root: &ssz::H256) -> String { /// string (the harness cannot re-aggregate without the signers' private /// keys), so we decode it directly into an [`AggregatedSignature`] and /// wrap with the participants bitfield from the same payload. +/// +/// Returns the attestation and whether its proof should be XMSS-verified — +/// `false` when the proof is the mocked sentinel, since the simulator (in the +/// hive repo) does not forward the fixture's `proofSetting`. fn build_signed_aggregated_attestation( step: GossipAggregatedAttestationStep, -) -> Result { +) -> Result<(SignedAggregatedAttestation, bool), String> { let proof_hex = step.proof.proof.data.trim_start_matches("0x"); let proof_bytes = hex::decode(proof_hex) .map_err(|err| format!("invalid hex in aggregate proof_data: {err}"))?; + let verify_proof = !proof_bytes + .windows(MOCK_AGGREGATION_PROOF_MARKER.len()) + .any(|window| window == MOCK_AGGREGATION_PROOF_MARKER); let proof_data = AggregatedSignature::new(&proof_bytes) .map_err(|err| format!("failed to construct aggregated signature: {err}"))?; - Ok(SignedAggregatedAttestation { - data: step.data.into(), - proof: AggregatedSignatureProof { - participants: step.proof.participants.into(), - proof_data, + Ok(( + SignedAggregatedAttestation { + data: step.data.into(), + proof: AggregatedSignatureProof { + participants: step.proof.participants.into(), + proof_data, + }, }, - }) + verify_proof, + )) } From 1c62cad96360ba4998815ee20fbdf0fc361d244e Mon Sep 17 00:00:00 2001 From: SamAg19 Date: Sun, 16 Aug 2026 21:51:19 +0530 Subject: [PATCH 2/2] minor comment fixes --- lean_client/fork_choice/src/handlers.rs | 3 +-- lean_client/http_api/src/test_driver.rs | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lean_client/fork_choice/src/handlers.rs b/lean_client/fork_choice/src/handlers.rs index 1f90c117..59c0bdab 100644 --- a/lean_client/fork_choice/src/handlers.rs +++ b/lean_client/fork_choice/src/handlers.rs @@ -410,8 +410,7 @@ pub fn on_attestation( /// it in `latest_new_aggregated_payloads`. At interval 3, these are merged with /// `latest_known_aggregated_payloads` (from blocks) to compute safe target. /// -/// `verify_proof` gates only the XMSS SNARK check (production: `true`; mocked -/// spec-test proofs: `false`). +/// `verify_proof` gates only the XMSS SNARK check. #[inline] pub fn on_aggregated_attestation( store: &mut Store, diff --git a/lean_client/http_api/src/test_driver.rs b/lean_client/http_api/src/test_driver.rs index cf894cd0..ed79e1ed 100644 --- a/lean_client/http_api/src/test_driver.rs +++ b/lean_client/http_api/src/test_driver.rs @@ -34,6 +34,8 @@ use spec_test_fixtures::{ use ssz::SszHash; use xmss::{AggregatedSignature, Signature}; +const MOCK_AGGREGATION_PROOF_MARKER: &[u8] = b"MOCKED-AGGREGATION-PROOF"; + /// Shared state for test-driver routes. Carries a writable handle to the /// fork-choice store plus the `BlockCache` that `on_block` requires. /// @@ -461,10 +463,6 @@ fn hex_root(root: &ssz::H256) -> String { format!("0x{}", hex::encode(root.as_bytes())) } -/// Sentinel emitted by the leanSpec fixture generator for `proofSetting: 0` -/// (mocked) aggregation proofs. Real proofs never contain it. -const MOCK_AGGREGATION_PROOF_MARKER: &[u8] = b"MOCKED-AGGREGATION-PROOF"; - /// Build a `SignedAggregatedAttestation` from the fixture-supplied /// `gossipAggregatedAttestation` step payload. /// @@ -474,8 +472,7 @@ const MOCK_AGGREGATION_PROOF_MARKER: &[u8] = b"MOCKED-AGGREGATION-PROOF"; /// wrap with the participants bitfield from the same payload. /// /// Returns the attestation and whether its proof should be XMSS-verified — -/// `false` when the proof is the mocked sentinel, since the simulator (in the -/// hive repo) does not forward the fixture's `proofSetting`. +/// `false` when the proof is the mocked sentinel. fn build_signed_aggregated_attestation( step: GossipAggregatedAttestationStep, ) -> Result<(SignedAggregatedAttestation, bool), String> {