Make code generic over a TranscriptEngine (#139)
@@ -13,7 +13,6 @@
[dependencies] bellperson = { version = "0.24", default-features = false } ff = { version = "0.12.0", features = ["derive"] } -merlin = {version = "3.0.0", default-features = false } digest = "0.8.1" sha3 = "0.8.2" rayon = "1.3.0"@@ -40,6 +39,7 @@
[dev-dependencies] criterion = "0.3.1" rand = "0.8.4" +hex = "0.4.3" [[bench]] name = "recursive-snark"
@@ -41,4 +41,7 @@ InvalidInitialInputLength,
/// returned when the step execution produces an output whose length differs from a previously declared arity #[error("InvalidStepOutputLength")] InvalidStepOutputLength, + /// returned when the transcript engine encounters an overflow of the round number + #[error("InternalTranscriptError")] + InternalTranscriptError, }
@@ -7,13 +7,12 @@ spartan::polynomial::EqPolynomial,
traits::{ commitment::{CommitmentEngineTrait, CommitmentGensTrait, CommitmentTrait}, evaluation::EvaluationEngineTrait, - AppendToTranscriptTrait, ChallengeTrait, Group, + AppendToTranscriptTrait, ChallengeTrait, Group, TranscriptEngineTrait, }, Commitment, CommitmentGens, CompressedCommitment, CE, }; use core::{cmp::max, iter}; use ff::Field; -use merlin::Transcript; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use std::marker::PhantomData;@@ -58,7 +57,7 @@ }
fn prove_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comms: &[Commitment<G>], polys: &[Vec<G::Scalar>], points: &[Vec<G::Scalar>],@@ -89,7 +88,7 @@ &evals[i],
), &InnerProductWitness::new(&polys[i]), transcript, - ); + )?; nifs.push(n); r_U = u; r_W = w;@@ -103,7 +102,7 @@
/// A method to verify purported evaluations of a batch of polynomials fn verify_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comms: &[Commitment<G>], points: &[Vec<G::Scalar>], evals: &[G::Scalar],@@ -129,7 +128,7 @@ &EqPolynomial::new(points[i].clone()).evals(),
&evals[i], ), transcript, - ); + )?; r_U = u; num_vars = max(num_vars, points[i].len()); }@@ -219,9 +218,9 @@ U1: &InnerProductInstance<G>,
W1: &InnerProductWitness<G>, U2: &InnerProductInstance<G>, W2: &InnerProductWitness<G>, - transcript: &mut Transcript, - ) -> (Self, InnerProductInstance<G>, InnerProductWitness<G>) { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript: &mut G::TE, + ) -> Result<(Self, InnerProductInstance<G>, InnerProductWitness<G>), NovaError> { + transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); // pad the instances and witness so they are of the same length let U1 = U1.pad(max(U1.b_vec.len(), U2.b_vec.len()));@@ -230,21 +229,25 @@ let W1 = W1.pad(max(U1.b_vec.len(), U2.b_vec.len()));
let W2 = W2.pad(max(U1.b_vec.len(), U2.b_vec.len())); // add the two commitments and two public vectors to the transcript + // we do not need to add public vectors as their compressed versions were + // read from the transcript U1.comm_a_vec .append_to_transcript(b"U1_comm_a_vec", transcript); - U1.b_vec.append_to_transcript(b"U1_b_vec", transcript); U2.comm_a_vec .append_to_transcript(b"U2_comm_a_vec", transcript); - U2.b_vec.append_to_transcript(b"U2_b_vec", transcript); // compute the cross-term let cross_term = inner_product(&W1.a_vec, &U2.b_vec) + inner_product(&W2.a_vec, &U1.b_vec); // add the cross-term to the transcript - cross_term.append_to_transcript(b"cross_term", transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &cross_term, + b"cross_term", + transcript, + ); // obtain a random challenge - let r = G::Scalar::challenge(b"r", transcript); + let r = G::Scalar::challenge(b"r", transcript)?; // fold the vectors and their inner product let a_vec = W1@@ -270,36 +273,38 @@ b_vec,
c, }; - (NIFSForInnerProduct { cross_term }, U, W) + Ok((NIFSForInnerProduct { cross_term }, U, W)) } fn verify( &self, U1: &InnerProductInstance<G>, U2: &InnerProductInstance<G>, - transcript: &mut Transcript, - ) -> InnerProductInstance<G> { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript: &mut G::TE, + ) -> Result<InnerProductInstance<G>, NovaError> { + transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); // pad the instances so they are of the same length let U1 = U1.pad(max(U1.b_vec.len(), U2.b_vec.len())); let U2 = U2.pad(max(U1.b_vec.len(), U2.b_vec.len())); // add the two commitments and two public vectors to the transcript + // we do not need to add public vectors as their compressed representation + // were derived from the transcript U1.comm_a_vec .append_to_transcript(b"U1_comm_a_vec", transcript); - U1.b_vec.append_to_transcript(b"U1_b_vec", transcript); U2.comm_a_vec .append_to_transcript(b"U2_comm_a_vec", transcript); - U2.b_vec.append_to_transcript(b"U2_b_vec", transcript); // add the cross-term to the transcript - self - .cross_term - .append_to_transcript(b"cross_term", transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.cross_term, + b"cross_term", + transcript, + ); // obtain a random challenge - let r = G::Scalar::challenge(b"r", transcript); + let r = G::Scalar::challenge(b"r", transcript)?; // fold the vectors and their inner product let b_vec = U1@@ -311,11 +316,11 @@ .collect::<Vec<G::Scalar>>();
let c = U1.c + r * r * U2.c + r * self.cross_term; let comm_a_vec = U1.comm_a_vec + U2.comm_a_vec * r; - InnerProductInstance { + Ok(InnerProductInstance { comm_a_vec, b_vec, c, - } + }) } }@@ -343,27 +348,26 @@ gens: &CommitmentGens<G>,
gens_c: &CommitmentGens<G>, U: &InnerProductInstance<G>, W: &InnerProductWitness<G>, - transcript: &mut Transcript, + transcript: &mut G::TE, ) -> Result<Self, NovaError> { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); if U.b_vec.len() != W.a_vec.len() { return Err(NovaError::InvalidInputLength); } U.comm_a_vec.append_to_transcript(b"comm_a_vec", transcript); - U.b_vec.append_to_transcript(b"b_vec", transcript); - U.c.append_to_transcript(b"c", transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript(&U.c, b"c", transcript); // sample a random base for commiting to the inner product - let r = G::Scalar::challenge(b"r", transcript); + let r = G::Scalar::challenge(b"r", transcript)?; let gens_c = gens_c.scale(&r); // a closure that executes a step of the recursive inner product argument let prove_inner = |a_vec: &[G::Scalar], b_vec: &[G::Scalar], gens: &CommitmentGens<G>, - transcript: &mut Transcript| + transcript: &mut G::TE| -> Result< ( CompressedCommitment<G>,@@ -402,7 +406,7 @@
L.append_to_transcript(b"L", transcript); R.append_to_transcript(b"R", transcript); - let r = G::Scalar::challenge(b"challenge_r", transcript); + let r = G::Scalar::challenge(b"challenge_r", transcript)?; let r_inverse = r.invert().unwrap(); // fold the left half and the right half@@ -456,9 +460,9 @@ gens: &CommitmentGens<G>,
gens_c: &CommitmentGens<G>, n: usize, U: &InnerProductInstance<G>, - transcript: &mut Transcript, + transcript: &mut G::TE, ) -> Result<(), NovaError> { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript.absorb_bytes(b"protocol-name", Self::protocol_name()); if U.b_vec.len() != n || n != (1 << self.L_vec.len()) || self.L_vec.len() != self.R_vec.len()@@ -468,11 +472,10 @@ return Err(NovaError::InvalidInputLength);
} U.comm_a_vec.append_to_transcript(b"comm_a_vec", transcript); - U.b_vec.append_to_transcript(b"b_vec", transcript); - U.c.append_to_transcript(b"c", transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript(&U.c, b"c", transcript); // sample a random base for commiting to the inner product - let r = G::Scalar::challenge(b"r", transcript); + let r = G::Scalar::challenge(b"r", transcript)?; let gens_c = gens_c.scale(&r); let P = U.comm_a_vec + CE::<G>::commit(&gens_c, &[U.c]);@@ -511,7 +514,7 @@ self.L_vec[i].append_to_transcript(b"L", transcript);
self.R_vec[i].append_to_transcript(b"R", transcript); G::Scalar::challenge(b"challenge_r", transcript) }) - .collect::<Vec<G::Scalar>>(); + .collect::<Result<Vec<G::Scalar>, NovaError>>()?; // precompute scalars necessary for verification let r_square: Vec<G::Scalar> = (0..self.L_vec.len())
@@ -0,0 +1,160 @@
+//! This module provides an implementation of TranscriptEngineTrait using keccak256 +use crate::traits::PrimeFieldExt; +use crate::{ + errors::NovaError, + traits::{Group, TranscriptEngineTrait}, +}; +use core::marker::PhantomData; +use sha3::{Digest, Keccak256}; + +const PERSONA_TAG: &[u8] = b"NovaTranscript"; +const DOM_SEP_TAG: &[u8] = b"NovaRound"; +const KECCAK256_STATE_SIZE: usize = 64; +const KECCAK256_PREFIX_CHALLENGE_LO: u8 = 0; +const KECCAK256_PREFIX_CHALLENGE_HI: u8 = 1; + +/// Provides an implementation of TranscriptEngine +#[derive(Debug, Clone)] +pub struct Keccak256Transcript<G: Group> { + round: u16, + state: [u8; KECCAK256_STATE_SIZE], + transcript: Vec<u8>, + _p: PhantomData<G>, +} + +fn compute_updated_state(input: &[u8]) -> [u8; KECCAK256_STATE_SIZE] { + let input_lo = [input, &[KECCAK256_PREFIX_CHALLENGE_LO]].concat(); + let input_hi = [input, &[KECCAK256_PREFIX_CHALLENGE_HI]].concat(); + + let mut hasher_lo = Keccak256::new(); + let mut hasher_hi = Keccak256::new(); + + hasher_lo.input(&input_lo); + hasher_hi.input(&input_hi); + + let output_lo = hasher_lo.result(); + let output_hi = hasher_hi.result(); + + [output_lo, output_hi] + .concat() + .as_slice() + .try_into() + .unwrap() +} + +impl<G: Group> TranscriptEngineTrait<G> for Keccak256Transcript<G> { + fn new(label: &'static [u8]) -> Self { + let input = [PERSONA_TAG, label].concat(); + let output = compute_updated_state(&input); + + Self { + round: 0u16, + state: output, + transcript: vec![], + _p: Default::default(), + } + } + + fn squeeze_scalar(&mut self, label: &'static [u8]) -> Result<G::Scalar, NovaError> { + let input = [ + DOM_SEP_TAG, + self.round.to_le_bytes().as_ref(), + self.state.as_ref(), + self.transcript.as_ref(), + label, + ] + .concat(); + let output = compute_updated_state(&input); + + // update state + self.round = { + if let Some(v) = self.round.checked_add(1) { + v + } else { + return Err(NovaError::InternalTranscriptError); + } + }; + self.state.copy_from_slice(&output); + self.transcript = vec![]; + + // squeeze out a challenge + Ok(G::Scalar::from_uniform(&output)) + } + + fn absorb_bytes(&mut self, label: &'static [u8], bytes: &[u8]) { + self.transcript.extend_from_slice(label); + self.transcript.extend_from_slice(bytes); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + provider::keccak::Keccak256Transcript, + traits::{AppendToTranscriptTrait, ChallengeTrait, Group, TranscriptEngineTrait}, + }; + use ff::PrimeField; + use sha3::{Digest, Keccak256}; + + type G = pasta_curves::pallas::Point; + + #[test] + fn test_keccak_transcript() { + let mut transcript = Keccak256Transcript::new(b"test"); + + // two scalars + let s1 = <G as Group>::Scalar::from(2u64); + let s2 = <G as Group>::Scalar::from(5u64); + + // add the scalars to the transcript + <<G as Group>::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &s1, + b"s1", + &mut transcript, + ); + <<G as Group>::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &s2, + b"s2", + &mut transcript, + ); + + // make a challenge + let c1 = + <<G as Group>::Scalar as ChallengeTrait<G>>::challenge(b"challenge_c1", &mut transcript) + .unwrap(); + assert_eq!( + hex::encode(c1.to_repr().as_ref()), + "51648083af5387a04a7aa2aec789ee78fdabe45dc1391d270a38fcb576447c01" + ); + + // a scalar + let s3 = <G as Group>::Scalar::from(128u64); + + // add the scalar to the transcript + <<G as Group>::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &s3, + b"s3", + &mut transcript, + ); + + // make a challenge + let c2 = + <<G as Group>::Scalar as ChallengeTrait<G>>::challenge(b"challenge_c2", &mut transcript) + .unwrap(); + assert_eq!( + hex::encode(c2.to_repr().as_ref()), + "9773f3349f7308153f6012e72b97fc304e48372bbd28bd122b37a8e46855d50f" + ); + } + + #[test] + fn test_keccak_example() { + let mut hasher = Keccak256::new(); + hasher.input(0xffffffff_u32.to_le_bytes()); + let output: [u8; 32] = hasher.result().try_into().unwrap(); + assert_eq!( + hex::encode(output), + "29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238" + ); + } +}
@@ -5,6 +5,7 @@ //! `RO` traits with Poseidon
//! `EvaluationEngine` with an IPA-based polynomial evaluation argument pub mod ipa_pc; +pub mod keccak; pub mod pasta; pub mod pedersen; pub mod poseidon;
@@ -1,23 +1,21 @@
//! This module implements the Nova traits for pallas::Point, pallas::Scalar, vesta::Point, vesta::Scalar. use crate::{ provider::{ + keccak::Keccak256Transcript, pedersen::CommitmentEngine, poseidon::{PoseidonRO, PoseidonROCircuit}, }, - traits::{ChallengeTrait, CompressedGroup, Group}, + traits::{CompressedGroup, Group, PrimeFieldExt}, }; use digest::{ExtendableOutput, Input}; -use ff::Field; -use merlin::Transcript; use num_bigint::BigInt; use num_traits::Num; use pasta_curves::{ self, - arithmetic::{CurveAffine, CurveExt, Group as OtherGroup}, + arithmetic::{CurveAffine, CurveExt, FieldExt, Group as OtherGroup}, group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}, pallas, vesta, Ep, EpAffine, Eq, EqAffine, }; -use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use sha3::Shake256;@@ -64,6 +62,7 @@ type CompressedGroupElement = $name_compressed;
type PreprocessedGroupElement = $name::Affine; type RO = PoseidonRO<Self::Base, Self::Scalar>; type ROCircuit = PoseidonROCircuit<Self::Base>; + type TE = Keccak256Transcript<Self>; type CE = CommitmentEngine<Self>; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]@@ -171,12 +170,10 @@ $name::Point::generator()
} } - impl ChallengeTrait for $name::Scalar { - fn challenge(label: &'static [u8], transcript: &mut Transcript) -> Self { - let mut key: <ChaCha20Rng as SeedableRng>::Seed = Default::default(); - transcript.challenge_bytes(label, &mut key); - let mut rng = ChaCha20Rng::from_seed(key); - $name::Scalar::random(&mut rng) + impl PrimeFieldExt for $name::Scalar { + fn from_uniform(bytes: &[u8]) -> Self { + let bytes_arr: [u8; 64] = bytes.try_into().unwrap(); + $name::Scalar::from_bytes_wide(&bytes_arr) } }
@@ -6,6 +6,7 @@ commitment::{
CommitmentEngineTrait, CommitmentGensTrait, CommitmentTrait, CompressedCommitmentTrait, }, AbsorbInROTrait, AppendToTranscriptTrait, CompressedGroup, Group, ROTrait, + TranscriptEngineTrait, }, }; use core::{@@ -13,8 +14,7 @@ fmt::Debug,
marker::PhantomData, ops::{Add, AddAssign, Mul, MulAssign}, }; -use ff::Field; -use merlin::Transcript; +use ff::{Field, PrimeField}; use rayon::prelude::*; use serde::{Deserialize, Serialize};@@ -96,9 +96,17 @@ })
} } -impl<G: Group> AppendToTranscriptTrait for Commitment<G> { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, self.comm.compress().as_bytes()); +impl<G: Group> AppendToTranscriptTrait<G> for Commitment<G> { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + let (x, y, is_infinity) = self.comm.to_coordinates(); + let is_infinity_byte = if is_infinity { 0u8 } else { 1u8 }; + let bytes = [ + x.to_repr().as_ref(), + y.to_repr().as_ref(), + &[is_infinity_byte], + ] + .concat(); + transcript.absorb_bytes(label, &bytes); } }@@ -115,9 +123,16 @@ });
} } -impl<C: CompressedGroup> AppendToTranscriptTrait for CompressedCommitment<C> { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, self.comm.as_bytes()); +impl<C: CompressedGroup> AppendToTranscriptTrait<C::GroupElement> for CompressedCommitment<C> { + fn append_to_transcript( + &self, + label: &'static [u8], + transcript: &mut <C::GroupElement as Group>::TE, + ) { + let comm = self.decompress().unwrap(); + <Commitment<C::GroupElement> as AppendToTranscriptTrait<C::GroupElement>>::append_to_transcript( + &comm, label, transcript, + ); } }
@@ -17,7 +17,6 @@ use core::cmp::max;
use ff::Field; use flate2::{write::ZlibEncoder, Compression}; use itertools::concat; -use merlin::Transcript; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use sha3::{Digest, Sha3_256};@@ -440,11 +439,13 @@ }
} } -impl<G: Group> AppendToTranscriptTrait for R1CSShape<G> { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { - self - .get_digest() - .append_to_transcript(b"R1CSShape", transcript); +impl<G: Group> AppendToTranscriptTrait<G> for R1CSShape<G> { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + <<G as Group>::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.get_digest(), + label, + transcript, + ); } }@@ -488,10 +489,10 @@ }
} } -impl<G: Group> AppendToTranscriptTrait for R1CSInstance<G> { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { +impl<G: Group> AppendToTranscriptTrait<G> for R1CSInstance<G> { + fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut G::TE) { self.comm_W.append_to_transcript(b"comm_W", transcript); - self.X.append_to_transcript(b"X", transcript); + <[G::Scalar] as AppendToTranscriptTrait<G>>::append_to_transcript(&self.X, b"X", transcript); } }@@ -629,12 +630,12 @@ })
} } -impl<G: Group> AppendToTranscriptTrait for RelaxedR1CSInstance<G> { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { +impl<G: Group> AppendToTranscriptTrait<G> for RelaxedR1CSInstance<G> { + fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut G::TE) { self.comm_W.append_to_transcript(b"comm_W", transcript); self.comm_E.append_to_transcript(b"comm_E", transcript); - self.u.append_to_transcript(b"u", transcript); - self.X.append_to_transcript(b"X", transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript(&self.u, b"u", transcript); + <[G::Scalar] as AppendToTranscriptTrait<G>>::append_to_transcript(&self.X, b"X", transcript); } }
@@ -9,12 +9,11 @@ r1cs::{R1CSGens, R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness},
traits::{ evaluation::EvaluationEngineTrait, snark::{ProverKeyTrait, RelaxedR1CSSNARKTrait, VerifierKeyTrait}, - AppendToTranscriptTrait, ChallengeTrait, Group, + AppendToTranscriptTrait, ChallengeTrait, Group, TranscriptEngineTrait, }, }; use ff::Field; use itertools::concat; -use merlin::Transcript; use polynomial::{EqPolynomial, MultilinearPolynomial, SparsePolynomial}; use rayon::prelude::*; use serde::{Deserialize, Serialize};@@ -82,7 +81,7 @@ pk: &Self::ProverKey,
U: &RelaxedR1CSInstance<G>, W: &RelaxedR1CSWitness<G>, ) -> Result<Self, NovaError> { - let mut transcript = Transcript::new(b"RelaxedR1CSSNARK"); + let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // sanity check that R1CSShape has certain size characteristics assert_eq!(pk.S.num_cons.next_power_of_two(), pk.S.num_cons);@@ -105,7 +104,7 @@
// outer sum-check let tau = (0..num_rounds_x) .map(|_i| G::Scalar::challenge(b"challenge_tau", &mut transcript)) - .collect(); + .collect::<Result<Vec<G::Scalar>, NovaError>>()?; let mut poly_tau = MultilinearPolynomial::new(EqPolynomial::new(tau).evals()); let (mut poly_Az, mut poly_Bz, poly_Cz, mut poly_uCz_E) = {@@ -136,22 +135,39 @@ &mut poly_Bz,
&mut poly_uCz_E, comb_func_outer, &mut transcript, - ); + )?; // claims from the end of sum-check let (claim_Az, claim_Bz): (G::Scalar, G::Scalar) = (claims_outer[1], claims_outer[2]); + let claim_Cz = poly_Cz.evaluate(&r_x); - claim_Az.append_to_transcript(b"claim_Az", &mut transcript); - claim_Bz.append_to_transcript(b"claim_Bz", &mut transcript); - let claim_Cz = poly_Cz.evaluate(&r_x); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &claim_Az, + b"claim_Az", + &mut transcript, + ); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &claim_Bz, + b"claim_Bz", + &mut transcript, + ); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &claim_Cz, + b"claim_Cz", + &mut transcript, + ); + let eval_E = MultilinearPolynomial::new(W.E.clone()).evaluate(&r_x); - claim_Cz.append_to_transcript(b"claim_Cz", &mut transcript); - eval_E.append_to_transcript(b"eval_E", &mut transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &eval_E, + b"eval_E", + &mut transcript, + ); // inner sum-check - let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript); - let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript); - let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript); + let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript)?; + let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript)?; + let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript)?; let claim_inner_joint = r_A * claim_Az + r_B * claim_Bz + r_C * claim_Cz; let poly_ABC = {@@ -219,10 +235,14 @@ &mut MultilinearPolynomial::new(poly_ABC),
&mut MultilinearPolynomial::new(poly_z), comb_func, &mut transcript, - ); + )?; let eval_W = MultilinearPolynomial::new(W.W.clone()).evaluate(&r_y[1..]); - eval_W.append_to_transcript(b"eval_W", &mut transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &eval_W, + b"eval_W", + &mut transcript, + ); let eval_arg = EE::prove_batch( &pk.gens,@@ -245,7 +265,7 @@ }
/// verifies a proof of satisfiability of a RelaxedR1CS instance fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance<G>) -> Result<(), NovaError> { - let mut transcript = Transcript::new(b"RelaxedR1CSSNARK"); + let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // append the R1CSShape and RelaxedR1CSInstance to the transcript vk.S.append_to_transcript(b"S", &mut transcript);@@ -259,7 +279,7 @@
// outer sum-check let tau = (0..num_rounds_x) .map(|_i| G::Scalar::challenge(b"challenge_tau", &mut transcript)) - .collect::<Vec<G::Scalar>>(); + .collect::<Result<Vec<G::Scalar>, NovaError>>()?; let (claim_outer_final, r_x) = self@@ -275,24 +295,31 @@ if claim_outer_final != claim_outer_final_expected {
return Err(NovaError::InvalidSumcheckProof); } - self - .claims_outer - .0 - .append_to_transcript(b"claim_Az", &mut transcript); - self - .claims_outer - .1 - .append_to_transcript(b"claim_Bz", &mut transcript); - self - .claims_outer - .2 - .append_to_transcript(b"claim_Cz", &mut transcript); - self.eval_E.append_to_transcript(b"eval_E", &mut transcript); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.claims_outer.0, + b"claim_Az", + &mut transcript, + ); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.claims_outer.1, + b"claim_Bz", + &mut transcript, + ); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.claims_outer.2, + b"claim_Cz", + &mut transcript, + ); + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.eval_E, + b"eval_E", + &mut transcript, + ); // inner sum-check - let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript); - let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript); - let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript); + let r_A = G::Scalar::challenge(b"challenge_rA", &mut transcript)?; + let r_B = G::Scalar::challenge(b"challenge_rB", &mut transcript)?; + let r_C = G::Scalar::challenge(b"challenge_rC", &mut transcript)?; let claim_inner_joint = r_A * self.claims_outer.0 + r_B * self.claims_outer.1 + r_C * self.claims_outer.2;@@ -346,7 +373,11 @@ return Err(NovaError::InvalidSumcheckProof);
} // verify eval_W and eval_E - self.eval_W.append_to_transcript(b"eval_W", &mut transcript); //eval_E is already in the transcript + <G::Scalar as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.eval_W, + b"eval_W", + &mut transcript, + ); //eval_E is already in the transcript EE::verify_batch( &vk.gens,
@@ -5,7 +5,6 @@ use crate::errors::NovaError;
use crate::traits::{AppendToTranscriptTrait, ChallengeTrait, Group}; use core::marker::PhantomData; use ff::Field; -use merlin::Transcript; use rayon::prelude::*; use serde::{Deserialize, Serialize};@@ -21,7 +20,7 @@ &self,
claim: G::Scalar, num_rounds: usize, degree_bound: usize, - transcript: &mut Transcript, + transcript: &mut G::TE, ) -> Result<(G::Scalar, Vec<G::Scalar>), NovaError> { let mut e = claim; let mut r: Vec<G::Scalar> = Vec::new();@@ -48,7 +47,7 @@ // append the prover's message to the transcript
poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript); + let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; r.push(r_i);@@ -65,8 +64,8 @@ num_rounds: usize,
poly_A: &mut MultilinearPolynomial<G::Scalar>, poly_B: &mut MultilinearPolynomial<G::Scalar>, comb_func: F, - transcript: &mut Transcript, - ) -> (Self, Vec<G::Scalar>, Vec<G::Scalar>) + transcript: &mut G::TE, + ) -> Result<(Self, Vec<G::Scalar>, Vec<G::Scalar>), NovaError> where F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar + Sync, {@@ -103,7 +102,7 @@ // append the prover's message to the transcript
poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript); + let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; r.push(r_i); polys.push(poly.compress());@@ -115,13 +114,13 @@ poly_A.bound_poly_var_top(&r_i);
poly_B.bound_poly_var_top(&r_i); } - ( + Ok(( SumcheckProof { compressed_polys: polys, }, r, vec![poly_A[0], poly_B[0]], - ) + )) } pub fn prove_cubic_with_additive_term<F>(@@ -132,8 +131,8 @@ poly_B: &mut MultilinearPolynomial<G::Scalar>,
poly_C: &mut MultilinearPolynomial<G::Scalar>, poly_D: &mut MultilinearPolynomial<G::Scalar>, comb_func: F, - transcript: &mut Transcript, - ) -> (Self, Vec<G::Scalar>, Vec<G::Scalar>) + transcript: &mut G::TE, + ) -> Result<(Self, Vec<G::Scalar>, Vec<G::Scalar>), NovaError> where F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync, {@@ -195,7 +194,7 @@ // append the prover's message to the transcript
poly.append_to_transcript(b"poly", transcript); //derive the verifier's challenge for the next round - let r_i = G::Scalar::challenge(b"challenge_nextround", transcript); + let r_i = G::Scalar::challenge(b"challenge_nextround", transcript)?; r.push(r_i); polys.push(poly.compress());@@ -209,13 +208,13 @@ poly_C.bound_poly_var_top(&r_i);
poly_D.bound_poly_var_top(&r_i); } - ( + Ok(( SumcheckProof { compressed_polys: polys, }, r, vec![poly_A[0], poly_B[0], poly_C[0], poly_D[0]], - ) + )) } }@@ -322,12 +321,12 @@ UniPoly { coeffs }
} } -impl<G: Group> AppendToTranscriptTrait for UniPoly<G> { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, b"UniPoly_begin"); - for i in 0..self.coeffs.len() { - self.coeffs[i].append_to_transcript(b"coeff", transcript); - } - transcript.append_message(label, b"UniPoly_end"); +impl<G: Group> AppendToTranscriptTrait<G> for UniPoly<G> { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + <[G::Scalar] as AppendToTranscriptTrait<G>>::append_to_transcript( + &self.coeffs, + label, + transcript, + ); } }
@@ -71,7 +71,7 @@ + Sync
+ Serialize + for<'de> Deserialize<'de> + AbsorbInROTrait<G> - + AppendToTranscriptTrait + + AppendToTranscriptTrait<G> + CommitmentOps + CommitmentOpsOwned + ScalarMul<G::Scalar>@@ -96,7 +96,7 @@ + Send
+ Sync + Serialize + for<'de> Deserialize<'de> - + AppendToTranscriptTrait + + AppendToTranscriptTrait<C::GroupElement> { /// Holds the type of the commitment that can be decompressed into type Commitment;
@@ -5,7 +5,6 @@ use crate::{
errors::NovaError, traits::{commitment::CommitmentEngineTrait, Group}, }; -use merlin::Transcript; use serde::{Deserialize, Serialize}; /// A trait that ties different pieces of the commitment evaluation together@@ -27,7 +26,7 @@
/// A method to prove evaluations of a batch of polynomials fn prove_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comm: &[<Self::CE as CommitmentEngineTrait<G>>::Commitment], polys: &[Vec<G::Scalar>], points: &[Vec<G::Scalar>],@@ -37,7 +36,7 @@
/// A method to verify purported evaluations of a batch of polynomials fn verify_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comm: &[<Self::CE as CommitmentEngineTrait<G>>::Commitment], points: &[Vec<G::Scalar>], evals: &[G::Scalar],
@@ -1,4 +1,5 @@
//! This module defines various traits required by the users of the library to implement. +use crate::errors::NovaError; use bellperson::{ gadgets::{boolean::AllocatedBit, num::AllocatedNum}, ConstraintSystem, SynthesisError,@@ -8,7 +9,6 @@ fmt::Debug,
ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, }; use ff::{PrimeField, PrimeFieldBits}; -use merlin::Transcript; use num_bigint::BigInt; use serde::{Deserialize, Serialize};@@ -39,7 +39,8 @@
/// A type representing an element of the scalar field of the group type Scalar: PrimeField + PrimeFieldBits - + ChallengeTrait + + PrimeFieldExt + + ChallengeTrait<Self> + Send + Sync + Serialize@@ -53,12 +54,15 @@
/// A type representing preprocessed group element type PreprocessedGroupElement: Clone + Debug + Send + Sync + Serialize + for<'de> Deserialize<'de>; - /// A type that represents a hash function that consumes elements + /// A type that represents a circuit-friendly sponge that consumes elements /// from the base field and squeezes out elements of the scalar field type RO: ROTrait<Self::Base, Self::Scalar> + Serialize + for<'de> Deserialize<'de>; /// An alternate implementation of Self::RO in the circuit model type ROCircuit: ROCircuitTrait<Self::Base> + Serialize + for<'de> Deserialize<'de>; + + /// A type that provides a generic Fiat-Shamir transcript to be used when externalizing proofs + type TE: TranscriptEngineTrait<Self>; /// A type that defines a commitment engine over scalars in the group type CE: CommitmentEngineTrait<Self> + Serialize + for<'de> Deserialize<'de>;@@ -105,24 +109,12 @@ /// Returns a byte array representing the compressed group element
fn as_bytes(&self) -> &[u8]; } -/// A helper trait to append different types to the transcript -pub trait AppendToTranscriptTrait { - /// appends the value to the transcript under the provided label - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript); -} - /// A helper trait to absorb different objects in RO pub trait AbsorbInROTrait<G: Group> { /// Absorbs the value in the provided RO fn absorb_in_ro(&self, ro: &mut G::RO); } -/// A helper trait to generate challenges using a transcript object -pub trait ChallengeTrait { - /// Returns a Scalar representing the challenge using the transcript - fn challenge(label: &'static [u8], transcript: &mut Transcript) -> Self; -} - /// A helper trait that defines the behavior of a hash function that we use as an RO pub trait ROTrait<Base, Scalar> { /// A type representing constants/parameters associated with the hash function@@ -204,17 +196,63 @@ /// A helper trait for references implementing group scalar multiplication.
pub trait ScalarMulOwned<Rhs, Output = Self>: for<'r> ScalarMul<&'r Rhs, Output> {} impl<T, Rhs, Output> ScalarMulOwned<Rhs, Output> for T where T: for<'r> ScalarMul<&'r Rhs, Output> {} -impl<F: PrimeField> AppendToTranscriptTrait for F { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, self.to_repr().as_ref()); +/// This trait defines the behavior of a transcript engine compatible with Spartan +pub trait TranscriptEngineTrait<G: Group>: Send + Sync { + /// initializes the transcript + fn new(label: &'static [u8]) -> Self; + + /// returns a scalar element of the group as a challenge + fn squeeze_scalar(&mut self, label: &'static [u8]) -> Result<G::Scalar, NovaError>; + + /// absorbs a label and a sequence of bytes + fn absorb_bytes(&mut self, label: &'static [u8], bytes: &[u8]); +} + +/// A helper trait to append different types to the transcript +pub trait AppendToTranscriptTrait<G: Group> { + /// appends the value to the transcript under the provided label + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE); +} + +/// A helper trait to generate challenges using a transcript object +pub trait ChallengeTrait<G: Group> { + /// Returns a challenge from the transcript + fn challenge(label: &'static [u8], transcript: &mut G::TE) -> Result<Self, NovaError> + where + Self: Sized; +} + +/// Defines additional methods on PrimeField objects +pub trait PrimeFieldExt: PrimeField { + /// Returns a Scalar representing the bytes + fn from_uniform(bytes: &[u8]) -> Self; + + /// Returns a byte representation + fn to_bytes(v: &[Self]) -> Vec<u8> { + (0..v.len()) + .map(|i| v[i].to_repr().as_ref().to_vec()) + .collect::<Vec<Vec<u8>>>() + .into_iter() + .flatten() + .collect::<Vec<u8>>() } } -impl<F: PrimeField> AppendToTranscriptTrait for [F] { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - for s in self { - s.append_to_transcript(label, transcript); - } +impl<G: Group<Scalar = F>, F: PrimeField> ChallengeTrait<G> for F { + fn challenge(label: &'static [u8], transcript: &mut G::TE) -> Result<F, NovaError> { + transcript.squeeze_scalar(label) + } +} + +impl<G: Group<Scalar = F>, F: PrimeField> AppendToTranscriptTrait<G> for F { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + transcript.absorb_bytes(label, self.to_repr().as_ref()); + } +} + +impl<G: Group<Scalar = F>, F: PrimeField + PrimeFieldExt> AppendToTranscriptTrait<G> for [F] { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + transcript.absorb_bytes(label, &<F as PrimeFieldExt>::to_bytes(self)); } }