@@ -1271,40 +1271,58 @@ fn verify_signatures(
12711271 let validators = & state. validators ;
12721272 let num_validators = validators. len ( ) as u64 ;
12731273
1274- // Verify each attestation's signature proof
1274+ // Verify each attestation's signature proof in parallel
12751275 let aggregated_start = std:: time:: Instant :: now ( ) ;
1276- for ( attestation, aggregated_proof) in attestations. iter ( ) . zip ( attestation_signatures) {
1277- if attestation. aggregation_bits != aggregated_proof. participants {
1278- return Err ( StoreError :: ParticipantsMismatch ) ;
1279- }
12801276
1281- let slot: u32 = attestation. data . slot . try_into ( ) . expect ( "slot exceeds u32" ) ;
1282- let message = attestation. data . hash_tree_root ( ) ;
1277+ // Prepare verification inputs sequentially (cheap: bit checks + pubkey lookups)
1278+ let verification_inputs: Vec < _ > = attestations
1279+ . iter ( )
1280+ . zip ( attestation_signatures)
1281+ . map ( |( attestation, aggregated_proof) | {
1282+ if attestation. aggregation_bits != aggregated_proof. participants {
1283+ return Err ( StoreError :: ParticipantsMismatch ) ;
1284+ }
12831285
1284- // Collect public keys with bounds check in a single pass
1285- let public_keys: Vec < _ > = validator_indices ( & attestation. aggregation_bits )
1286- . map ( |vid| {
1287- if vid >= num_validators {
1288- return Err ( StoreError :: InvalidValidatorIndex ) ;
1289- }
1290- validators[ vid as usize ]
1291- . get_pubkey ( )
1292- . map_err ( |_| StoreError :: PubkeyDecodingFailed ( vid) )
1293- } )
1294- . collect :: < Result < _ , _ > > ( ) ?;
1286+ let slot: u32 = attestation. data . slot . try_into ( ) . expect ( "slot exceeds u32" ) ;
1287+ let message = attestation. data . hash_tree_root ( ) ;
1288+
1289+ let public_keys: Vec < _ > = validator_indices ( & attestation. aggregation_bits )
1290+ . map ( |vid| {
1291+ if vid >= num_validators {
1292+ return Err ( StoreError :: InvalidValidatorIndex ) ;
1293+ }
1294+ validators[ vid as usize ]
1295+ . get_pubkey ( )
1296+ . map_err ( |_| StoreError :: PubkeyDecodingFailed ( vid) )
1297+ } )
1298+ . collect :: < Result < _ , _ > > ( ) ?;
12951299
1296- let verification_result = {
1297- let _timing = metrics:: time_pq_sig_aggregated_signatures_verification ( ) ;
1298- verify_aggregated_signature ( & aggregated_proof. proof_data , public_keys, & message, slot)
1299- } ;
1300- match verification_result {
1301- Ok ( ( ) ) => metrics:: inc_pq_sig_aggregated_signatures_valid ( ) ,
1302- Err ( e) => {
1303- metrics:: inc_pq_sig_aggregated_signatures_invalid ( ) ;
1304- return Err ( StoreError :: AggregateVerificationFailed ( e) ) ;
1300+ Ok ( ( & aggregated_proof. proof_data , public_keys, message, slot) )
1301+ } )
1302+ . collect :: < Result < _ , StoreError > > ( ) ?;
1303+
1304+ // Run expensive signature verification in parallel.
1305+ // into_par_iter() moves each tuple, avoiding a clone of public_keys.
1306+ use rayon:: prelude:: * ;
1307+ verification_inputs. into_par_iter ( ) . try_for_each (
1308+ |( proof_data, public_keys, message, slot) | {
1309+ let result = {
1310+ let _timing = metrics:: time_pq_sig_aggregated_signatures_verification ( ) ;
1311+ verify_aggregated_signature ( proof_data, public_keys, & message, slot)
1312+ } ;
1313+ match result {
1314+ Ok ( ( ) ) => {
1315+ metrics:: inc_pq_sig_aggregated_signatures_valid ( ) ;
1316+ Ok ( ( ) )
1317+ }
1318+ Err ( e) => {
1319+ metrics:: inc_pq_sig_aggregated_signatures_invalid ( ) ;
1320+ Err ( StoreError :: AggregateVerificationFailed ( e) )
1321+ }
13051322 }
1306- }
1307- }
1323+ } ,
1324+ ) ?;
1325+
13081326 let aggregated_elapsed = aggregated_start. elapsed ( ) ;
13091327
13101328 let proposer_start = std:: time:: Instant :: now ( ) ;
0 commit comments