Skip to content

Commit

Permalink
feat: move RNGs into RangeProofTranscript (#111)
Browse files Browse the repository at this point in the history
Recent work in #109 uses Merlin's `TranscriptRng` functionality to
improve the handling of random number generation, and also refactors
transcript operations for safer use through a `RangeProofTranscript`
wrapper.

A
[suggestion](#109 (comment))
by @sdbondi recommends moving the `TranscriptRng` into
`RangeProofTranscript` so the prover and verifier aren't responsible for
it. This PR adds such a change.

It also updates `RangeProofTranscript` to hold a mutable reference to
the external random number generator. This ensures the prover and
verifier can't accidentally use it instead of the `TranscriptRng`.
  • Loading branch information
AaronFeickert authored Jan 15, 2024
1 parent 6f1aab6 commit 854dd88
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 65 deletions.
32 changes: 15 additions & 17 deletions src/range_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ where
}

// Start a new transcript and generate the transcript RNG
let (mut transcript, mut transcript_rng) = RangeProofTranscript::<P>::new(
let mut transcript = RangeProofTranscript::<P, R>::new(
transcript_label,
&statement.generators.h_base().compress(),
statement.generators.g_bases_compressed(),
Expand Down Expand Up @@ -322,7 +322,7 @@ where
nonce(&seed_nonce, "alpha", None, Some(k))?
} else {
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
Scalar::random_not_zero(&mut transcript_rng)
Scalar::random_not_zero(transcript.as_mut_rng())
});
}
let a = statement.generators.precomp().vartime_mixed_multiscalar_mul(
Expand All @@ -332,7 +332,7 @@ where
);

// Update transcript, get challenges, and update RNG
let (y, z) = transcript.challenges_y_z(&mut transcript_rng, rng, &a.compress())?;
let (y, z) = transcript.challenges_y_z(&a.compress())?;

let z_square = z * z;

Expand Down Expand Up @@ -418,7 +418,7 @@ where
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
Zeroizing::new(
(0..extension_degree)
.map(|_| Scalar::random_not_zero(&mut transcript_rng))
.map(|_| Scalar::random_not_zero(transcript.as_mut_rng()))
.collect(),
)
};
Expand All @@ -432,7 +432,7 @@ where
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
Zeroizing::new(
(0..extension_degree)
.map(|_| Scalar::random_not_zero(&mut transcript_rng))
.map(|_| Scalar::random_not_zero(transcript.as_mut_rng()))
.collect(),
)
};
Expand Down Expand Up @@ -466,8 +466,6 @@ where

// Update transcript, get challenge, and update RNG
let e = transcript.challenge_round_e(
&mut transcript_rng,
rng,
&li.last()
.ok_or(ProofError::InvalidLength("Bad inner product vector length".to_string()))?
.compress(),
Expand Down Expand Up @@ -511,8 +509,8 @@ where

// Random masks
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
let r = Zeroizing::new(Scalar::random_not_zero(&mut transcript_rng));
let s = Zeroizing::new(Scalar::random_not_zero(&mut transcript_rng));
let r = Zeroizing::new(Scalar::random_not_zero(transcript.as_mut_rng()));
let s = Zeroizing::new(Scalar::random_not_zero(transcript.as_mut_rng()));
let d = if let Some(seed_nonce) = statement.seed_nonce {
Zeroizing::new(
(0..extension_degree)
Expand All @@ -523,7 +521,7 @@ where
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
Zeroizing::new(
(0..extension_degree)
.map(|_| Scalar::random_not_zero(&mut transcript_rng))
.map(|_| Scalar::random_not_zero(transcript.as_mut_rng()))
.collect(),
)
};
Expand All @@ -537,7 +535,7 @@ where
// Zero is allowed by the protocol, but excluded by the implementation to be unambiguous
Zeroizing::new(
(0..extension_degree)
.map(|_| Scalar::random_not_zero(&mut transcript_rng))
.map(|_| Scalar::random_not_zero(transcript.as_mut_rng()))
.collect(),
)
};
Expand All @@ -553,7 +551,7 @@ where
}

// Update transcript, get challenge, and update RNG
let e = transcript.challenge_final_e(&mut transcript_rng, rng, &a1.compress(), &b.compress())?;
let e = transcript.challenge_final_e(&a1.compress(), &b.compress())?;
let e_square = e * e;

let r1 = *r + a_li[0] * e;
Expand Down Expand Up @@ -825,7 +823,7 @@ where
}

// Start the transcript
let (mut transcript, mut transcript_rng) = RangeProofTranscript::new(
let mut transcript = RangeProofTranscript::new(
transcript_label,
&h_base_compressed,
g_bases_compressed,
Expand All @@ -838,17 +836,17 @@ where
)?;

// Reconstruct challenges
let (y, z) = transcript.challenges_y_z(&mut transcript_rng, rng, &proof.a)?;
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(&mut transcript_rng, rng, l, r))
.map(|(l, r)| transcript.challenge_round_e(l, r))
.collect::<Result<Vec<Scalar>, ProofError>>()?;
let e = transcript.challenge_final_e(&mut transcript_rng, rng, &proof.a1, &proof.b)?;
let e = transcript.challenge_final_e(&proof.a1, &proof.b)?;

// Batch weight (may not be equal to a zero valued scalar) - this may not be zero ever
let weight = Scalar::random_not_zero(&mut transcript_rng);
let weight = Scalar::random_not_zero(transcript.as_mut_rng());

// Compute challenge inverses in a batch
let mut challenges_inv = challenges.clone();
Expand Down
81 changes: 33 additions & 48 deletions src/transcripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,35 @@ use crate::{
///
/// When the prover initializes the wrapper, it includes the witness as the secret data.
/// The verifier doesn't have any secret data to include, so it passes `None` instead.
/// In either case, you get a `RangeProofTranscript` and a `TranscriptRng`.
/// In either case, you get a `RangeProofTranscript` that is updated when you use the challenge functions.
///
/// When the transcript is updated using the challenge functions, you must provide the `TranscriptRng`, which is also
/// updated.
///
/// When randomness is needed, just use the `TranscriptRng`.
/// When you need randomness, use `as_mut_rng()` to get an `&mut TranscriptRng` for this purpose.
/// The prover uses this whenever it needs a random nonce.
/// The batch verifier uses this to generate weights.
pub(crate) struct RangeProofTranscript<P>
pub(crate) struct RangeProofTranscript<'a, P, R>
where
P: Compressable + Precomputable,
P::Compressed: FixedBytesRepr + IsIdentity,
R: CryptoRngCore,
{
transcript: Transcript,
bytes: Option<Zeroizing<Vec<u8>>>,
transcript_rng: TranscriptRng,
external_rng: &'a mut R,
_phantom: PhantomData<P>,
}

impl<P> RangeProofTranscript<P>
impl<'a, P, R> RangeProofTranscript<'a, P, R>
where
P: Compressable + Precomputable,
P::Compressed: FixedBytesRepr + IsIdentity,
R: CryptoRngCore,
{
/// Initialize a transcript.
///
/// The prover should include its `witness` here; the verifier should pass `None`.
#[allow(clippy::too_many_arguments)]
pub(crate) fn new<R: CryptoRngCore>(
pub(crate) fn new(
label: &'static str,
h_base_compressed: &P::Compressed,
g_base_compressed: &[P::Compressed],
Expand All @@ -64,8 +65,8 @@ where
aggregation_factor: usize,
statement: &RangeStatement<P>,
witness: Option<&RangeWitness>,
external_rng: &mut R,
) -> Result<(Self, TranscriptRng), ProofError> {
external_rng: &'a mut R,
) -> Result<Self, ProofError> {
// Initialize the transcript with parameters and statement
let mut transcript = Transcript::new(label.as_bytes());
transcript.domain_separator(b"Bulletproofs+", b"Range Proof");
Expand Down Expand Up @@ -108,30 +109,24 @@ where
};

// Set up the RNG
let transcript_rng = Self::build_rng(&transcript, bytes.as_ref(), external_rng);

Ok((
Self {
transcript,
bytes,
_phantom: PhantomData,
},
transcript_rng,
))
let rng = Self::build_rng(&transcript, bytes.as_ref(), external_rng);

Ok(Self {
transcript,
bytes,
transcript_rng: rng,
external_rng,
_phantom: PhantomData,
})
}

// Construct the `y` and `z` challenges and update the RNG
pub(crate) fn challenges_y_z<R: CryptoRngCore>(
&mut self,
transcript_rng: &mut TranscriptRng,
external_rng: &mut R,
a: &P::Compressed,
) -> Result<(Scalar, Scalar), ProofError> {
pub(crate) fn challenges_y_z(&mut self, a: &P::Compressed) -> Result<(Scalar, Scalar), ProofError> {
// Update the transcript
self.transcript.validate_and_append_point(b"A", a)?;

// Update the RNG
*transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), external_rng);
self.transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), self.external_rng);

// Return the challenges
Ok((
Expand All @@ -141,38 +136,26 @@ where
}

/// Construct an inner-product round `e` challenge and update the RNG
pub(crate) fn challenge_round_e<R: CryptoRngCore>(
&mut self,
transcript_rng: &mut TranscriptRng,
external_rng: &mut R,
l: &P::Compressed,
r: &P::Compressed,
) -> Result<Scalar, ProofError> {
pub(crate) fn challenge_round_e(&mut self, l: &P::Compressed, r: &P::Compressed) -> Result<Scalar, ProofError> {
// Update the transcript
self.transcript.validate_and_append_point(b"L", l)?;
self.transcript.validate_and_append_point(b"R", r)?;

// Update the RNG
*transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), external_rng);
self.transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), self.external_rng);

// Return the challenge
self.transcript.challenge_scalar(b"e")
}

/// Construct the final `e` challenge and update the RNG
pub(crate) fn challenge_final_e<R: CryptoRngCore>(
&mut self,
transcript_rng: &mut TranscriptRng,
external_rng: &mut R,
a1: &P::Compressed,
b: &P::Compressed,
) -> Result<Scalar, ProofError> {
pub(crate) fn challenge_final_e(&mut self, a1: &P::Compressed, b: &P::Compressed) -> Result<Scalar, ProofError> {
// Update the transcript
self.transcript.validate_and_append_point(b"A1", a1)?;
self.transcript.validate_and_append_point(b"B", b)?;

// Update the RNG
*transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), external_rng);
self.transcript_rng = Self::build_rng(&self.transcript, self.bytes.as_ref(), self.external_rng);

// Return the challenge
self.transcript.challenge_scalar(b"e")
Expand All @@ -182,11 +165,7 @@ where
///
/// Internally, this builds the RNG using a clone of the transcript state, the secret bytes (if provided), and the
/// external RNG.
fn build_rng<R: CryptoRngCore>(
transcript: &Transcript,
bytes: Option<&Zeroizing<Vec<u8>>>,
external_rng: &mut R,
) -> TranscriptRng {
fn build_rng(transcript: &Transcript, bytes: Option<&Zeroizing<Vec<u8>>>, external_rng: &mut R) -> TranscriptRng {
if let Some(bytes) = bytes {
transcript
.build_rng()
Expand All @@ -196,4 +175,10 @@ where
transcript.build_rng().finalize(external_rng)
}
}

/// Get a mutable reference to the transcript RNG.
/// This is suitable for passing into functions that use it to generate random data.
pub(crate) fn as_mut_rng(&mut self) -> &mut TranscriptRng {
&mut self.transcript_rng
}
}

0 comments on commit 854dd88

Please sign in to comment.