From 94866b26ecd5f151c1cfabad7fa574553a0e211d Mon Sep 17 00:00:00 2001 From: Srikar Rao Date: Mon, 27 Oct 2025 22:01:49 +0530 Subject: [PATCH] fix: add input validation for BLS signature aggregation - Add EmptySignatureList variant to BlsError enum - Validate signature list is non-empty in aggregate function - Return specific error instead of generic Other error - Add F2P test for empty signature list validation - Add edge case test for single signature aggregation - All existing P2P tests continue to pass (no regressions) Fixes #XXX --- src/signatures/bls/mod.rs | 28 ++++++++++++++++++++++------ src/signatures/bls/tests.rs | 25 ++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/signatures/bls/mod.rs b/src/signatures/bls/mod.rs index 98d5ec6..4fae2cd 100644 --- a/src/signatures/bls/mod.rs +++ b/src/signatures/bls/mod.rs @@ -36,6 +36,7 @@ pub enum BlsError { Other(String), /// Invalid point encountered. InvalidPoint, + EmptySignatureList } /// BLS private key. @@ -50,6 +51,7 @@ pub struct BlsPublicKey { } /// BLS signature. +#[derive(Debug, Clone, PartialEq)] pub struct BlsSignature { sig: AffinePoint, } @@ -379,20 +381,34 @@ impl BlsPublicKey { impl BlsSignature where ::BaseField: FieldExt { - /// Aggregates multiple BLS signatures into a single signature. - /// - /// This function sums the individual signature points. All signatures must be on the same - /// message. + + /// /// Aggregates multiple BLS signatures into a single signature. +/// +/// # Arguments +/// * `signatures` - A non-empty slice of signatures to aggregate +/// +/// # Returns +/// * `Ok(BlsSignature)` - The aggregated signature +/// * `Err(BlsError::EmptySignatureList)` - If the input slice is empty +/// +/// # Examples +/// ``` +/// let sigs = vec![sig1, sig2, sig3]; +/// let aggregated = BlsSignature::aggregate(&sigs)?; +/// ``` pub fn aggregate(signatures: &[BlsSignature]) -> Result, BlsError> { + // CHANGE THIS LINE ONLY (from Other to EmptySignatureList) if signatures.is_empty() { - return Err(BlsError::Other("No signatures to aggregate".into())); + return Err(BlsError::EmptySignatureList); } + + // KEEP THE ORIGINAL CODE BELOW (DON'T CHANGE IT) let mut agg = signatures[0].sig; for sig in signatures.iter().skip(1) { agg += sig.sig; } Ok(BlsSignature { sig: agg }) - } +} } /// Verifies an aggregated BLS signature for a single common message: diff --git a/src/signatures/bls/tests.rs b/src/signatures/bls/tests.rs index 3a1e6c2..2a0ceee 100644 --- a/src/signatures/bls/tests.rs +++ b/src/signatures/bls/tests.rs @@ -1,5 +1,5 @@ use super::*; - +use crate::curve::pluto_curve::{PlutoBaseCurve, PlutoExtendedCurve}; /// Creates a deterministic private key for testing using seed fn create_test_private_key(seed: u64) -> BlsPrivateKey { BlsPrivateKey::generate_deterministic(seed) @@ -23,6 +23,29 @@ fn test_invalid_signature() { assert!(pk.verify(msg, &tampered_sig).is_err(), "Tampered signature should fail verification"); } +#[test] +fn test_aggregate_empty_signatures_returns_error() { + let empty_sigs: Vec> = vec![]; + let result = BlsSignature::::aggregate(&empty_sigs); + + assert!(result.is_err()); + match result { + Err(BlsError::EmptySignatureList) => {}, + _ => panic!("Expected EmptySignatureList error"), + } +} + +#[test] +fn test_aggregate_single_signature() { + let sk = BlsPrivateKey::::generate_deterministic(42); + let msg = b"test"; + // Specify BOTH curve types: sign + let sig = sk.sign::(msg).unwrap(); + + let result = BlsSignature::::aggregate(&[sig.clone()]); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), sig); +} #[test] fn test_aggregate_signatures() { let msg = b"Hello, BLS!";