From 045ed63c1c2ca64368653afe1aec78c7238b26c8 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:49:34 -0600 Subject: [PATCH] Remove verifier RNG requirement --- benches/range_proof.rs | 17 ++-- src/protocols/transcript_protocol.rs | 7 ++ src/range_proof.rs | 123 +++++++++++++-------------- src/transcripts.rs | 17 ++++ src/utils/mod.rs | 1 + src/utils/nullrng.rs | 42 +++++++++ tests/ristretto.rs | 18 ++-- 7 files changed, 137 insertions(+), 88 deletions(-) create mode 100644 src/utils/nullrng.rs diff --git a/benches/range_proof.rs b/benches/range_proof.rs index 06b3635..49d1475 100644 --- a/benches/range_proof.rs +++ b/benches/range_proof.rs @@ -172,14 +172,9 @@ 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_batch_with_rng( - &transcript_labels, - &statements, - &proofs, - VerifyAction::VerifyOnly, - &mut rng, - ) - .unwrap(); + let _masks = + RangeProof::verify_batch(&transcript_labels, &statements, &proofs, VerifyAction::VerifyOnly) + .unwrap(); }); }); } @@ -259,22 +254,20 @@ fn verify_batched_rangeproofs_helper(bit_length: usize, extension_degree: Extens // Verify the entire batch of proofs match extract_masks { VerifyAction::VerifyOnly => { - let _masks = RangeProof::verify_batch_with_rng( + let _masks = RangeProof::verify_batch( &transcript_labels, &statements, &proofs, VerifyAction::VerifyOnly, - &mut rng, ) .unwrap(); }, VerifyAction::RecoverOnly => { - let _masks = RangeProof::verify_batch_with_rng( + let _masks = RangeProof::verify_batch( &transcript_labels, &statements, &proofs, VerifyAction::RecoverOnly, - &mut rng, ) .unwrap(); }, diff --git a/src/protocols/transcript_protocol.rs b/src/protocols/transcript_protocol.rs index 767d5cb..4aeae68 100644 --- a/src/protocols/transcript_protocol.rs +++ b/src/protocols/transcript_protocol.rs @@ -29,6 +29,9 @@ pub trait TranscriptProtocol { point: &P, ) -> Result<(), ProofError>; + /// Append a `scalar` with a given `label`. + fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar); + /// Compute a `label`ed challenge variable. fn challenge_scalar(&mut self, label: &'static [u8]) -> Result; } @@ -57,6 +60,10 @@ impl TranscriptProtocol for Transcript { } } + fn append_scalar(&mut self, label: &'static [u8], scalar: &Scalar) { + self.append_message(label, scalar.as_bytes()); + } + fn challenge_scalar(&mut self, label: &'static [u8]) -> Result { let mut buf = [0u8; 64]; self.challenge_bytes(label, &mut buf); diff --git a/src/range_proof.rs b/src/range_proof.rs index a9b17c4..6899592 100644 --- a/src/range_proof.rs +++ b/src/range_proof.rs @@ -19,6 +19,7 @@ use curve25519_dalek::{ traits::{Identity, IsIdentity, MultiscalarMul, VartimePrecomputedMultiscalarMul}, }; use itertools::{izip, Itertools}; +use merlin::Transcript; #[cfg(feature = "rand")] use rand::rngs::OsRng; use rand_core::CryptoRngCore; @@ -34,7 +35,10 @@ use crate::{ range_witness::RangeWitness, traits::{Compressable, Decompressable, FixedBytesRepr, Precomputable}, transcripts::RangeProofTranscript, - utils::generic::{nonce, split_at_checked}, + utils::{ + generic::{nonce, split_at_checked}, + nullrng::NullRng, + }, }; /// Optionally extract masks when verifying the proofs @@ -676,23 +680,11 @@ where } /// Wrapper function for batch verification in different modes: mask recovery, verification, or both - #[cfg(feature = "rand")] pub fn verify_batch( transcript_labels: &[&'static str], statements: &[RangeStatement

], proofs: &[RangeProof

], action: VerifyAction, - ) -> Result>, ProofError> { - Self::verify_batch_with_rng(transcript_labels, statements, proofs, action, &mut OsRng) - } - - /// Wrapper function for batch verification in different modes: mask recovery, verification, or both - pub fn verify_batch_with_rng( - transcript_labels: &[&'static str], - statements: &[RangeStatement

], - proofs: &[RangeProof

], - action: VerifyAction, - rng: &mut R, ) -> Result>, ProofError> { // By definition, an empty batch fails if statements.is_empty() || proofs.is_empty() || transcript_labels.is_empty() { @@ -722,7 +714,7 @@ where // 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_labels, batch_statements, batch_proofs, action, rng)?; + let mut result = RangeProof::verify(transcript_labels, batch_statements, batch_proofs, action)?; masks.append(&mut result); } @@ -732,12 +724,11 @@ where // Verify a batch of single and/or aggregated range proofs as a public entity, or recover the masks for single // range proofs by a party that can supply the optional seed nonces - fn verify( + fn verify( transcript_labels: &[&'static str], statements: &[RangeStatement

], range_proofs: &[RangeProof

], extract_masks: VerifyAction, - rng: &mut R, ) -> Result>, ProofError> { // Verify generators consistency & select largest aggregation factor let (max_mn, max_index) = RangeProof::verify_statements_and_generators_consistency(statements, range_proofs)?; @@ -787,8 +778,50 @@ where // Recovered masks let mut masks = Vec::with_capacity(range_proofs.len()); - // Process each proof and add it to the batch + // Set up the weight transcript + let mut weight_transcript = Transcript::new(b"Bulletproofs+ verifier weights"); + + // Generate challenges from all proofs in the batch, using the final transcript RNG of each to obtain a new + // weight + let mut batch_challenges = Vec::with_capacity(range_proofs.len()); for (proof, statement, transcript_label) in izip!(range_proofs, statements, transcript_labels) { + let mut null_rng = NullRng; + + // Start the transcript, using `NullRng` since we don't need or want actual randomness there + let mut transcript = RangeProofTranscript::new( + transcript_label, + &h_base_compressed, + g_bases_compressed, + bit_length, + extension_degree, + statement.commitments.len(), + statement, + None, + &mut null_rng, + )?; + + // Get the challenges and include them in the batch vectors + let (y, z) = transcript.challenges_y_z(&proof.a)?; + let round_e = proof + .li + .iter() + .zip(proof.ri.iter()) + .map(|(l, r)| transcript.challenge_round_e(l, r)) + .collect::, ProofError>>()?; + let e = transcript.challenge_final_e(&proof.a1, &proof.b)?; + + batch_challenges.push((y, z, round_e, e)); + + // Use the transcript RNG to bind this proof to the weight transcript + let mut transcript_rng = transcript.to_verifier_rng(&proof.r1, &proof.s1, &proof.d1); + weight_transcript.append_u64(b"proof", transcript_rng.as_rngcore().next_u64()); + } + + // Finalize the weight transcript so it can be used for pseudorandom weights + let mut weight_transcript_rng = weight_transcript.build_rng().finalize(&mut NullRng); + + // Process each proof and add it to the batch + for (proof, statement, batch_challenge) in izip!(range_proofs, statements, batch_challenges) { let commitments = statement.commitments.clone(); let minimum_value_promises = statement.minimum_value_promises.clone(); let a = proof.a_decompressed()?; @@ -822,31 +855,11 @@ where return Err(ProofError::InvalidLength("Vector L/R length not adequate".to_string())); } - // Start the transcript - let mut transcript = RangeProofTranscript::new( - transcript_label, - &h_base_compressed, - g_bases_compressed, - bit_length, - extension_degree, - aggregation_factor, - statement, - None, - rng, - )?; - - // Reconstruct challenges - let (y, z) = transcript.challenges_y_z(&proof.a)?; - let challenges = proof - .li - .iter() - .zip(proof.ri.iter()) - .map(|(l, r)| transcript.challenge_round_e(l, r)) - .collect::, ProofError>>()?; - let e = transcript.challenge_final_e(&proof.a1, &proof.b)?; + // Parse out the challenges + let (y, z, challenges, e) = batch_challenge; - // Batch weight (may not be equal to a zero valued scalar) - this may not be zero ever - let weight = Scalar::random_not_zero(transcript.as_mut_rng()); + // Nonzero batch weight + let weight = Scalar::random_not_zero(&mut weight_transcript_rng); // Compute challenge inverses in a batch let mut challenges_inv = challenges.clone(); @@ -1728,38 +1741,21 @@ mod tests { let mut proof = RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).unwrap(); // Empty statement and proof vectors - assert!( - RangeProof::verify_batch_with_rng(&[], &[], &[proof.clone()], VerifyAction::VerifyOnly, &mut rng).is_err() - ); - assert!(RangeProof::verify_batch_with_rng( - &["test"], - &[statement.clone()], - &[], - VerifyAction::VerifyOnly, - &mut rng - ) - .is_err()); + assert!(RangeProof::verify_batch(&[], &[], &[proof.clone()], VerifyAction::VerifyOnly).is_err()); + assert!(RangeProof::verify_batch(&["test"], &[statement.clone()], &[], VerifyAction::VerifyOnly,).is_err()); // Proof vector mismatches proof.li.pop(); - assert!(RangeProof::verify_batch_with_rng( + assert!(RangeProof::verify_batch( &["test"], &[statement.clone()], &[proof.clone()], VerifyAction::VerifyOnly, - &mut rng, ) .is_err()); proof.ri.pop(); - assert!(RangeProof::verify_batch_with_rng( - &["test"], - &[statement], - &[proof], - VerifyAction::VerifyOnly, - &mut rng - ) - .is_err()); + assert!(RangeProof::verify_batch(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly,).is_err()); } #[test] @@ -1789,8 +1785,7 @@ mod tests { let proof = RangeProof::prove_with_rng("test", &statement, &witness, &mut rng).unwrap(); // The proof should verify - RangeProof::verify_batch_with_rng(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly, &mut rng) - .unwrap(); + RangeProof::verify_batch(&["test"], &[statement], &[proof], VerifyAction::VerifyOnly).unwrap(); } #[test] diff --git a/src/transcripts.rs b/src/transcripts.rs index 159bf79..bdecf7b 100644 --- a/src/transcripts.rs +++ b/src/transcripts.rs @@ -161,6 +161,23 @@ where self.transcript.challenge_scalar(b"e") } + /// Update the RNG with the prover's reponses and return it, consuming the transcript + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_verifier_rng(mut self, r1: &Scalar, s1: &Scalar, d1: &[Scalar]) -> TranscriptRng { + // Update the transcript + self.transcript.append_scalar(b"r1", r1); + self.transcript.append_scalar(b"s1", s1); + for item in d1 { + self.transcript.append_scalar(b"d1", item); + } + + // Update the RNG + self.transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), self.external_rng); + + // Return the transcript RNG + self.transcript_rng + } + /// Construct a random number generator from the current transcript state /// /// Internally, this builds the RNG using a clone of the transcript state, the secret bytes (if provided), and the diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 02f1a6e..43be62d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,3 +4,4 @@ //! Bulletproofs+ utilities pub mod generic; +pub(crate) mod nullrng; diff --git a/src/utils/nullrng.rs b/src/utils/nullrng.rs new file mode 100644 index 0000000..724349e --- /dev/null +++ b/src/utils/nullrng.rs @@ -0,0 +1,42 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +//! A null random number generator useful for batch verification. + +use rand_core::{ + impls::{next_u32_via_fill, next_u64_via_fill}, + CryptoRng, + RngCore, +}; +use zeroize::Zeroize; + +/// This is a null random number generator that exists only for deterministic transcript-based weight generation. +/// It only produces zero. +/// This is DANGEROUS in general; don't use this for any other purpose! +pub(crate) struct NullRng; + +impl RngCore for NullRng { + #[allow(unused_variables)] + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.zeroize(); + } + + #[allow(unused_variables)] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + + Ok(()) + } + + fn next_u32(&mut self) -> u32 { + next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + next_u64_via_fill(self) + } +} + +// This is not actually cryptographically secure! +// We do this so we can use `NullRng` with `TranscriptRng`. +impl CryptoRng for NullRng {} diff --git a/tests/ristretto.rs b/tests/ristretto.rs index 156ccde..cf2647f 100644 --- a/tests/ristretto.rs +++ b/tests/ristretto.rs @@ -247,43 +247,39 @@ 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::verify_batch_with_rng( + let recovered_private_masks = RangeProof::verify_batch( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::RecoverOnly, - &mut rng, ) .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Recover the masks and verify the proofs - let recovered_private_masks = RangeProof::verify_batch_with_rng( + let recovered_private_masks = RangeProof::verify_batch( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::RecoverAndVerify, - &mut rng, ) .unwrap(); assert_eq!(private_masks, recovered_private_masks); // --- Verify the proofs but do not recover the masks - let recovered_private_masks = RangeProof::verify_batch_with_rng( + let recovered_private_masks = RangeProof::verify_batch( &transcript_labels, &statements_private.clone(), &proofs.clone(), VerifyAction::VerifyOnly, - &mut rng, ) .unwrap(); assert_eq!(public_masks, recovered_private_masks); // 6. Verify the entire batch as public entity - let recovered_public_masks = RangeProof::verify_batch_with_rng( + let recovered_public_masks = RangeProof::verify_batch( &transcript_labels, &statements_public, &proofs, VerifyAction::VerifyOnly, - &mut rng, ) .unwrap(); assert_eq!(public_masks, recovered_public_masks); @@ -307,12 +303,11 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce.map(|seed_nonce| seed_nonce + Scalar::ONE), }); } - let recovered_private_masks_changed = RistrettoRangeProof::verify_batch_with_rng( + let recovered_private_masks_changed = RistrettoRangeProof::verify_batch( &transcript_labels, &statements_private_changed, &proofs.clone(), VerifyAction::RecoverAndVerify, - &mut rng, ) .unwrap(); assert_ne!(private_masks, recovered_private_masks_changed); @@ -339,12 +334,11 @@ fn prove_and_verify( seed_nonce: statement.seed_nonce, }); } - match RangeProof::verify_batch_with_rng( + match RangeProof::verify_batch( &transcript_labels, &statements_public_changed, &proofs, VerifyAction::VerifyOnly, - &mut rng, ) { Ok(_) => { panic!("Range proof should not verify")