forked from tari-project/triptych
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor transcripting (tari-project#64)
This PR refactors Fiat-Shamir transcript functionality. It creates a new `ProofTranscript` wrapper around an existing Merlin transcript. This allows us to more cleanly unify the prover and verifier's operations. The design also better handles challenge power generation and the transcript random number generator used for both prover nonces and verifier weights. Because it also adds a version identifier to input set and parameter hashes, existing proofs will not verify. BREAKING CHANGE: Updates how internal hashing is performed, so existing proofs will not verify.
- Loading branch information
1 parent
c7d7924
commit c455a66
Showing
6 changed files
with
177 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// Copyright (c) 2024, The Tari Project | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
use alloc::vec::Vec; | ||
|
||
use curve25519_dalek::{RistrettoPoint, Scalar}; | ||
use merlin::{Transcript, TranscriptRng}; | ||
use rand_core::CryptoRngCore; | ||
|
||
use crate::{proof::ProofError, Parameters, Statement, Witness}; | ||
|
||
// Version identifier | ||
const VERSION: u64 = 0; | ||
|
||
// Domain separator | ||
const DOMAIN: &str = "Triptych proof"; | ||
|
||
/// A Triptych proof transcript. | ||
pub(crate) struct ProofTranscript<'a, R: CryptoRngCore> { | ||
transcript: &'a mut Transcript, | ||
witness: Option<&'a Witness>, | ||
transcript_rng: TranscriptRng, | ||
external_rng: &'a mut R, | ||
} | ||
|
||
impl<'a, R: CryptoRngCore> ProofTranscript<'a, R> { | ||
/// Initialize a transcript. | ||
pub(crate) fn new( | ||
transcript: &'a mut Transcript, | ||
statement: &Statement, | ||
external_rng: &'a mut R, | ||
witness: Option<&'a Witness>, | ||
) -> Self { | ||
// Update the transcript | ||
transcript.append_message(b"dom-sep", DOMAIN.as_bytes()); | ||
transcript.append_u64(b"version", VERSION); | ||
transcript.append_message(b"params", statement.get_params().get_hash()); | ||
transcript.append_message(b"M", statement.get_input_set().get_hash()); | ||
transcript.append_message(b"J", statement.get_J().compress().as_bytes()); | ||
|
||
// Set up the transcript generator | ||
let transcript_rng = Self::build_transcript_rng(transcript, witness, external_rng); | ||
|
||
Self { | ||
transcript, | ||
witness, | ||
transcript_rng, | ||
external_rng, | ||
} | ||
} | ||
|
||
/// Run the Fiat-Shamir commitment phase and produce challenge powers | ||
#[allow(non_snake_case, clippy::too_many_arguments)] | ||
pub(crate) fn commit( | ||
&mut self, | ||
params: &Parameters, | ||
A: &RistrettoPoint, | ||
B: &RistrettoPoint, | ||
C: &RistrettoPoint, | ||
D: &RistrettoPoint, | ||
X: &Vec<RistrettoPoint>, | ||
Y: &Vec<RistrettoPoint>, | ||
) -> Result<Vec<Scalar>, ProofError> { | ||
let m = params.get_m() as usize; | ||
|
||
// Update the transcript | ||
self.transcript.append_message(b"A", A.compress().as_bytes()); | ||
self.transcript.append_message(b"B", B.compress().as_bytes()); | ||
self.transcript.append_message(b"C", C.compress().as_bytes()); | ||
self.transcript.append_message(b"D", D.compress().as_bytes()); | ||
for X_item in X { | ||
self.transcript.append_message(b"X", X_item.compress().as_bytes()); | ||
} | ||
for Y_item in Y { | ||
self.transcript.append_message(b"Y", Y_item.compress().as_bytes()); | ||
} | ||
|
||
// Update the transcript generator | ||
self.transcript_rng = Self::build_transcript_rng(self.transcript, self.witness, self.external_rng); | ||
|
||
// Get the initial challenge using wide reduction | ||
let mut xi_bytes = [0u8; 64]; | ||
self.transcript.challenge_bytes("xi".as_bytes(), &mut xi_bytes); | ||
let xi = Scalar::from_bytes_mod_order_wide(&xi_bytes); | ||
|
||
// Get powers of the challenge and confirm they are nonzero | ||
let mut xi_powers = Vec::with_capacity(m.checked_add(1).ok_or(ProofError::InvalidParameter)?); | ||
let mut xi_power = Scalar::ONE; | ||
for _ in 0..=m { | ||
if xi_power == Scalar::ZERO { | ||
return Err(ProofError::InvalidChallenge); | ||
} | ||
|
||
xi_powers.push(xi_power); | ||
xi_power *= xi; | ||
} | ||
|
||
Ok(xi_powers) | ||
} | ||
|
||
/// Run the Fiat-Shamir response phase | ||
#[allow(non_snake_case)] | ||
pub(crate) fn response(mut self, f: &Vec<Vec<Scalar>>, z_A: &Scalar, z_C: &Scalar, z: &Scalar) -> TranscriptRng { | ||
// Update the transcript | ||
for f_row in f { | ||
for f in f_row { | ||
self.transcript.append_message(b"f", f.as_bytes()); | ||
} | ||
} | ||
self.transcript.append_message(b"z_A", z_A.as_bytes()); | ||
self.transcript.append_message(b"z_C", z_C.as_bytes()); | ||
self.transcript.append_message(b"z", z.as_bytes()); | ||
|
||
// Update the transcript generator | ||
self.transcript_rng = Self::build_transcript_rng(self.transcript, self.witness, self.external_rng); | ||
|
||
self.transcript_rng | ||
} | ||
|
||
/// Get a mutable reference to the transcript generator | ||
pub(crate) fn as_mut_rng(&mut self) -> &mut TranscriptRng { | ||
&mut self.transcript_rng | ||
} | ||
|
||
/// Build a random number generator from a transcript, optionally binding in witness data. | ||
fn build_transcript_rng(transcript: &Transcript, witness: Option<&Witness>, external_rng: &mut R) -> TranscriptRng { | ||
if let Some(witness) = witness { | ||
transcript | ||
.build_rng() | ||
.rekey_with_witness_bytes(b"l", &witness.get_l().to_le_bytes()) | ||
.rekey_with_witness_bytes(b"r", witness.get_r().as_bytes()) | ||
.finalize(external_rng) | ||
} else { | ||
transcript.build_rng().finalize(external_rng) | ||
} | ||
} | ||
} |
Oops, something went wrong.