Skip to content

Commit

Permalink
Remove verifier RNG requirement
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronFeickert committed Feb 13, 2024
1 parent da71f78 commit 045ed63
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 88 deletions.
17 changes: 5 additions & 12 deletions benches/range_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
}
Expand Down Expand Up @@ -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();
},
Expand Down
7 changes: 7 additions & 0 deletions src/protocols/transcript_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Scalar, ProofError>;
}
Expand Down Expand Up @@ -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<Scalar, ProofError> {
let mut buf = [0u8; 64];
self.challenge_bytes(label, &mut buf);
Expand Down
123 changes: 59 additions & 64 deletions src/range_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<P>],
proofs: &[RangeProof<P>],
action: VerifyAction,
) -> Result<Vec<Option<ExtendedMask>>, 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<R: CryptoRngCore>(
transcript_labels: &[&'static str],
statements: &[RangeStatement<P>],
proofs: &[RangeProof<P>],
action: VerifyAction,
rng: &mut R,
) -> Result<Vec<Option<ExtendedMask>>, ProofError> {
// By definition, an empty batch fails
if statements.is_empty() || proofs.is_empty() || transcript_labels.is_empty() {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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<R: CryptoRngCore>(
fn verify(
transcript_labels: &[&'static str],
statements: &[RangeStatement<P>],
range_proofs: &[RangeProof<P>],
extract_masks: VerifyAction,
rng: &mut R,
) -> Result<Vec<Option<ExtendedMask>>, ProofError> {
// Verify generators consistency & select largest aggregation factor
let (max_mn, max_index) = RangeProof::verify_statements_and_generators_consistency(statements, range_proofs)?;
Expand Down Expand Up @@ -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::<Result<Vec<Scalar>, 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()?;
Expand Down Expand Up @@ -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::<Result<Vec<Scalar>, 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();
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
17 changes: 17 additions & 0 deletions src/transcripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
//! Bulletproofs+ utilities
pub mod generic;
pub(crate) mod nullrng;
42 changes: 42 additions & 0 deletions src/utils/nullrng.rs
Original file line number Diff line number Diff line change
@@ -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 {}
Loading

0 comments on commit 045ed63

Please sign in to comment.