From 531bae6cce6cae4cb78f8543d309ee71a7f14915 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:19:59 -0600 Subject: [PATCH] feat!: split batches for verification (#22) * Split batches for verification * Fail on an empty batch * Remove unnecessary call * Add a comment about the batch limit --- benches/range_proof.rs | 20 +++++++++--- src/range_proof.rs | 73 +++++++++++++++++++++++++----------------- tests/ristretto.rs | 44 ++++++++++++++++++------- 3 files changed, 92 insertions(+), 45 deletions(-) diff --git a/benches/range_proof.rs b/benches/range_proof.rs index 1652d8f..1f2565d 100644 --- a/benches/range_proof.rs +++ b/benches/range_proof.rs @@ -169,7 +169,8 @@ fn verify_aggregated_rangeproof_helper(bit_length: usize, extension_degree: Exte // Benchmark this code b.iter(|| { // 5. Verify the aggregated proof - let _masks = RangeProof::verify_do_not_recover_masks(transcript_label, &statements, &proofs).unwrap(); + let _masks = + RangeProof::verify_batch(transcript_label, &statements, &proofs, VerifyAction::VerifyOnly).unwrap(); }); }); } @@ -251,11 +252,22 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens // 5. Verify the entire batch of single proofs match extract_masks { VerifyAction::VerifyOnly => { - let _masks = - RangeProof::verify_do_not_recover_masks(transcript_label, statements, proofs).unwrap(); + let _masks = RangeProof::verify_batch( + transcript_label, + statements, + proofs, + VerifyAction::VerifyOnly, + ) + .unwrap(); }, VerifyAction::RecoverOnly => { - let _masks = RangeProof::recover_masks_ony(transcript_label, statements, proofs).unwrap(); + let _masks = RangeProof::verify_batch( + transcript_label, + statements, + proofs, + VerifyAction::RecoverOnly, + ) + .unwrap(); }, _ => {}, } diff --git a/src/range_proof.rs b/src/range_proof.rs index 7960891..853fcbf 100644 --- a/src/range_proof.rs +++ b/src/range_proof.rs @@ -64,6 +64,11 @@ pub struct RangeProof { /// The maximum bit length for which proofs can be generated pub const MAX_RANGE_PROOF_BIT_LENGTH: usize = 64; +/// Maximum number of proofs in a batch +/// This is only for performance reasons, where a very large batch can see diminishing returns +/// There is no theoretical limit imposed by the algorithms! +const MAX_RANGE_PROOF_BATCH_SIZE: usize = 256; + /// # Example /// ``` /// use curve25519_dalek::scalar::Scalar; @@ -160,13 +165,18 @@ pub const MAX_RANGE_PROOF_BIT_LENGTH: usize = 64; /// } /// /// // 5. Verify the entire batch as the commitment owner, i.e. the prover self -/// let recovered_private_masks = -/// RangeProof::verify_and_recover_masks(transcript_label, &statements_private, &proofs).unwrap(); +/// let recovered_private_masks = RangeProof::verify_batch( +/// transcript_label, +/// &statements_private, +/// &proofs, +/// VerifyAction::RecoverAndVerify, +/// ) +/// .unwrap(); /// assert_eq!(private_masks, recovered_private_masks); /// /// // 6. Verify the entire batch as public entity /// let recovered_public_masks = -/// RangeProof::verify_do_not_recover_masks(transcript_label, &statements_public, &proofs).unwrap(); +/// RangeProof::verify_batch(transcript_label, &statements_public, &proofs, VerifyAction::VerifyOnly).unwrap(); /// assert_eq!(public_masks, recovered_public_masks); /// /// # } @@ -433,37 +443,42 @@ where Ok((max_mn, max_index)) } - /// Verify a batch of single and/or aggregated range proofs as the commitment owner and recover the masks for single - /// range proofs by supplying the optional seed nonces - pub fn verify_and_recover_masks( + /// Wrapper function for batch verification in different modes: mask recovery, verification, or both + pub fn verify_batch( transcript_label: &'static str, statements: &[RangeStatement

], - range_proofs: &[RangeProof

], + proofs: &[RangeProof

], + action: VerifyAction, ) -> Result>, ProofError> { - RangeProof::verify( - transcript_label, - statements, - range_proofs, - VerifyAction::RecoverAndVerify, - ) - } + // By definition, an empty batch fails + if statements.is_empty() || proofs.is_empty() { + return Err(ProofError::InvalidArgument( + "Range statements or proofs length empty".to_string(), + )); + } + // We need to check for size consistency here, even though it's also done later + if statements.len() != proofs.len() { + return Err(ProofError::InvalidArgument( + "Range statements and proofs length mismatch".to_string(), + )); + } - /// Verify a batch of single and/or aggregated range proofs as a public entity - pub fn verify_do_not_recover_masks( - transcript_label: &'static str, - statements: &[RangeStatement

], - range_proofs: &[RangeProof

], - ) -> Result>, ProofError> { - RangeProof::verify(transcript_label, statements, range_proofs, VerifyAction::VerifyOnly) - } + // Store masks from all results + let mut masks = Vec::>::with_capacity(proofs.len()); - /// Recover the masks for single range proofs by supplying the optional seed nonces - pub fn recover_masks_ony( - transcript_label: &'static str, - statements: &[RangeStatement

], - range_proofs: &[RangeProof

], - ) -> Result>, ProofError> { - RangeProof::verify(transcript_label, statements, range_proofs, VerifyAction::RecoverOnly) + // Get chunks of both the statements and proofs + let mut chunks = statements + .chunks(MAX_RANGE_PROOF_BATCH_SIZE) + .zip(proofs.chunks(MAX_RANGE_PROOF_BATCH_SIZE)); + + // If the batch fails, propagate the error; otherwise, store the masks and keep going + if let Some((batch_statements, batch_proofs)) = chunks.next() { + let mut result = RangeProof::verify(transcript_label, batch_statements, batch_proofs, action)?; + + masks.append(&mut result); + } + + Ok(masks) } // Verify a batch of single and/or aggregated range proofs as a public entity, or recover the masks for single diff --git a/tests/ristretto.rs b/tests/ristretto.rs index 374f001..24b1551 100644 --- a/tests/ristretto.rs +++ b/tests/ristretto.rs @@ -12,7 +12,7 @@ use tari_bulletproofs_plus::{ generators::pedersen_gens::ExtensionDegree, protocols::scalar_protocol::ScalarProtocol, range_parameters::RangeParameters, - range_proof::RangeProof, + range_proof::{RangeProof, VerifyAction}, range_statement::RangeStatement, range_witness::RangeWitness, ristretto, @@ -244,23 +244,37 @@ fn prove_and_verify( if !proofs.is_empty() { // 5. Verify the entire batch as the commitment owner, i.e. the prover self // --- Only recover the masks - let recovered_private_masks = - RangeProof::recover_masks_ony(transcript_label, &statements_private.clone(), &proofs.clone()).unwrap(); + let recovered_private_masks = RangeProof::verify_batch( + transcript_label, + &statements_private.clone(), + &proofs.clone(), + VerifyAction::RecoverOnly, + ) + .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Recover the masks and verify the proofs - let recovered_private_masks = - RangeProof::verify_and_recover_masks(transcript_label, &statements_private.clone(), &proofs.clone()) - .unwrap(); + let recovered_private_masks = RangeProof::verify_batch( + transcript_label, + &statements_private.clone(), + &proofs.clone(), + VerifyAction::RecoverAndVerify, + ) + .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Verify the proofs but do not recover the masks - let recovered_private_masks = - RangeProof::verify_do_not_recover_masks(transcript_label, &statements_private.clone(), &proofs.clone()) - .unwrap(); + let recovered_private_masks = RangeProof::verify_batch( + transcript_label, + &statements_private.clone(), + &proofs.clone(), + VerifyAction::VerifyOnly, + ) + .unwrap(); assert_eq!(public_masks, recovered_private_masks); // 6. Verify the entire batch as public entity let recovered_public_masks = - RangeProof::verify_do_not_recover_masks(transcript_label, &statements_public, &proofs).unwrap(); + RangeProof::verify_batch(transcript_label, &statements_public, &proofs, VerifyAction::VerifyOnly) + .unwrap(); assert_eq!(public_masks, recovered_public_masks); // 7. Try to recover the masks with incorrect seed_nonce values @@ -282,10 +296,11 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce.map(|seed_nonce| seed_nonce + Scalar::one()), }); } - let recovered_private_masks_changed = RistrettoRangeProof::verify_and_recover_masks( + let recovered_private_masks_changed = RistrettoRangeProof::verify_batch( transcript_label, &statements_private_changed, &proofs.clone(), + VerifyAction::RecoverAndVerify, ) .unwrap(); assert_ne!(private_masks, recovered_private_masks_changed); @@ -312,7 +327,12 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce, }); } - match RangeProof::verify_do_not_recover_masks(transcript_label, &statements_public_changed, &proofs) { + match RangeProof::verify_batch( + transcript_label, + &statements_public_changed, + &proofs, + VerifyAction::VerifyOnly, + ) { Ok(_) => { panic!("Range proof should not verify") },