Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: remove verifier RNG requirement #116

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading