From 8faffd38eaebd5277b998a9d5ba653a007ea5711 Mon Sep 17 00:00:00 2001 From: Srinath Setty Date: Thu, 9 Feb 2023 18:33:48 -0800 Subject: [PATCH] Make code generic over a TranscriptEngine (#139) --- Cargo.toml | 2 +- src/errors.rs | 3 + src/provider/ipa_pc.rs | 79 +++++++++---------- src/provider/keccak.rs | 160 +++++++++++++++++++++++++++++++++++++++ src/provider/mod.rs | 1 + src/provider/pasta.rs | 19 ++--- src/provider/pedersen.rs | 31 ++++++-- src/r1cs.rs | 27 +++---- src/spartan/mod.rs | 99 +++++++++++++++--------- src/spartan/sumcheck.rs | 39 +++++----- src/traits/commitment.rs | 4 +- src/traits/evaluation.rs | 5 +- src/traits/mod.rs | 84 ++++++++++++++------ 13 files changed, 400 insertions(+), 153 deletions(-) create mode 100644 src/provider/keccak.rs diff --git a/Cargo.toml b/Cargo.toml index 7402559..db31ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["zkSNARKs", "cryptography", "proofs"] [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 @@ pasta-msm = { version = "0.1.0", package = "lurk-pasta-msm" } [dev-dependencies] criterion = "0.3.1" rand = "0.8.4" +hex = "0.4.3" [[bench]] name = "recursive-snark" diff --git a/src/errors.rs b/src/errors.rs index 2887d3d..8188e46 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -41,4 +41,7 @@ pub enum NovaError { /// 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, } diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 8884475..d160a91 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -7,13 +7,12 @@ use crate::{ 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 @@ where fn prove_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comms: &[Commitment], polys: &[Vec], points: &[Vec], @@ -89,7 +88,7 @@ where ), &InnerProductWitness::new(&polys[i]), transcript, - ); + )?; nifs.push(n); r_U = u; r_W = w; @@ -103,7 +102,7 @@ where /// 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], points: &[Vec], evals: &[G::Scalar], @@ -129,7 +128,7 @@ where &evals[i], ), transcript, - ); + )?; r_U = u; num_vars = max(num_vars, points[i].len()); } @@ -219,9 +218,9 @@ impl NIFSForInnerProduct { W1: &InnerProductWitness, U2: &InnerProductInstance, W2: &InnerProductWitness, - transcript: &mut Transcript, - ) -> (Self, InnerProductInstance, InnerProductWitness) { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript: &mut G::TE, + ) -> Result<(Self, InnerProductInstance, InnerProductWitness), 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 @@ impl NIFSForInnerProduct { 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); + >::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 @@ impl NIFSForInnerProduct { c, }; - (NIFSForInnerProduct { cross_term }, U, W) + Ok((NIFSForInnerProduct { cross_term }, U, W)) } fn verify( &self, U1: &InnerProductInstance, U2: &InnerProductInstance, - transcript: &mut Transcript, - ) -> InnerProductInstance { - transcript.append_message(b"protocol-name", Self::protocol_name()); + transcript: &mut G::TE, + ) -> Result, 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); + >::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 @@ impl NIFSForInnerProduct { 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 @@ where gens_c: &CommitmentGens, U: &InnerProductInstance, W: &InnerProductWitness, - transcript: &mut Transcript, + transcript: &mut G::TE, ) -> Result { - 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); + >::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, - transcript: &mut Transcript| + transcript: &mut G::TE| -> Result< ( CompressedCommitment, @@ -402,7 +406,7 @@ where 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 @@ where gens_c: &CommitmentGens, n: usize, U: &InnerProductInstance, - 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 @@ where } 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); + >::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::::commit(&gens_c, &[U.c]); @@ -511,7 +514,7 @@ where self.R_vec[i].append_to_transcript(b"R", transcript); G::Scalar::challenge(b"challenge_r", transcript) }) - .collect::>(); + .collect::, NovaError>>()?; // precompute scalars necessary for verification let r_square: Vec = (0..self.L_vec.len()) diff --git a/src/provider/keccak.rs b/src/provider/keccak.rs new file mode 100644 index 0000000..c73cbca --- /dev/null +++ b/src/provider/keccak.rs @@ -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 { + round: u16, + state: [u8; KECCAK256_STATE_SIZE], + transcript: Vec, + _p: PhantomData, +} + +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 TranscriptEngineTrait for Keccak256Transcript { + 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 { + 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 = ::Scalar::from(2u64); + let s2 = ::Scalar::from(5u64); + + // add the scalars to the transcript + <::Scalar as AppendToTranscriptTrait>::append_to_transcript( + &s1, + b"s1", + &mut transcript, + ); + <::Scalar as AppendToTranscriptTrait>::append_to_transcript( + &s2, + b"s2", + &mut transcript, + ); + + // make a challenge + let c1 = + <::Scalar as ChallengeTrait>::challenge(b"challenge_c1", &mut transcript) + .unwrap(); + assert_eq!( + hex::encode(c1.to_repr().as_ref()), + "51648083af5387a04a7aa2aec789ee78fdabe45dc1391d270a38fcb576447c01" + ); + + // a scalar + let s3 = ::Scalar::from(128u64); + + // add the scalar to the transcript + <::Scalar as AppendToTranscriptTrait>::append_to_transcript( + &s3, + b"s3", + &mut transcript, + ); + + // make a challenge + let c2 = + <::Scalar as ChallengeTrait>::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" + ); + } +} diff --git a/src/provider/mod.rs b/src/provider/mod.rs index c6d7a0d..ab55001 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -5,6 +5,7 @@ //! `EvaluationEngine` with an IPA-based polynomial evaluation argument pub mod ipa_pc; +pub mod keccak; pub mod pasta; pub mod pedersen; pub mod poseidon; diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 4a47b9c..71ab3a4 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -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 @@ macro_rules! impl_traits { type PreprocessedGroupElement = $name::Affine; type RO = PoseidonRO; type ROCircuit = PoseidonROCircuit; + type TE = Keccak256Transcript; type CE = CommitmentEngine; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] @@ -171,12 +170,10 @@ macro_rules! impl_traits { } } - impl ChallengeTrait for $name::Scalar { - fn challenge(label: &'static [u8], transcript: &mut Transcript) -> Self { - let mut key: ::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) } } diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 14d79e2..2d0c5c8 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -6,6 +6,7 @@ use crate::{ CommitmentEngineTrait, CommitmentGensTrait, CommitmentTrait, CompressedCommitmentTrait, }, AbsorbInROTrait, AppendToTranscriptTrait, CompressedGroup, Group, ROTrait, + TranscriptEngineTrait, }, }; use core::{ @@ -13,8 +14,7 @@ use core::{ 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 CompressedCommitmentTrait for CompressedCommitment AppendToTranscriptTrait for Commitment { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, self.comm.compress().as_bytes()); +impl AppendToTranscriptTrait for Commitment { + 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 AbsorbInROTrait for Commitment { } } -impl AppendToTranscriptTrait for CompressedCommitment { - fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { - transcript.append_message(label, self.comm.as_bytes()); +impl AppendToTranscriptTrait for CompressedCommitment { + fn append_to_transcript( + &self, + label: &'static [u8], + transcript: &mut ::TE, + ) { + let comm = self.decompress().unwrap(); + as AppendToTranscriptTrait>::append_to_transcript( + &comm, label, transcript, + ); } } diff --git a/src/r1cs.rs b/src/r1cs.rs index a5a1b78..7dfad58 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -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 R1CSShape { } } -impl AppendToTranscriptTrait for R1CSShape { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { - self - .get_digest() - .append_to_transcript(b"R1CSShape", transcript); +impl AppendToTranscriptTrait for R1CSShape { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + <::Scalar as AppendToTranscriptTrait>::append_to_transcript( + &self.get_digest(), + label, + transcript, + ); } } @@ -488,10 +489,10 @@ impl R1CSInstance { } } -impl AppendToTranscriptTrait for R1CSInstance { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { +impl AppendToTranscriptTrait for R1CSInstance { + 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>::append_to_transcript(&self.X, b"X", transcript); } } @@ -629,12 +630,12 @@ impl RelaxedR1CSInstance { } } -impl AppendToTranscriptTrait for RelaxedR1CSInstance { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { +impl AppendToTranscriptTrait for RelaxedR1CSInstance { + 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); + >::append_to_transcript(&self.u, b"u", transcript); + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript(&self.X, b"X", transcript); } } diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index 195e251..b1c8dbb 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -9,12 +9,11 @@ use crate::{ 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 @@ impl> RelaxedR1CSSNARKTrait, W: &RelaxedR1CSWitness, ) -> Result { - 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 @@ impl> RelaxedR1CSSNARKTrait, 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 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( + &claim_Az, + b"claim_Az", + &mut transcript, + ); + >::append_to_transcript( + &claim_Bz, + b"claim_Bz", + &mut transcript, + ); + >::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); + >::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 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( + &eval_W, + b"eval_W", + &mut transcript, + ); let eval_arg = EE::prove_batch( &pk.gens, @@ -245,7 +265,7 @@ impl> RelaxedR1CSSNARKTrait) -> 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 @@ impl> RelaxedR1CSSNARKTrait>(); + .collect::, NovaError>>()?; let (claim_outer_final, r_x) = self @@ -275,24 +295,31 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( + &self.claims_outer.0, + b"claim_Az", + &mut transcript, + ); + >::append_to_transcript( + &self.claims_outer.1, + b"claim_Bz", + &mut transcript, + ); + >::append_to_transcript( + &self.claims_outer.2, + b"claim_Cz", + &mut transcript, + ); + >::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 @@ impl> RelaxedR1CSSNARKTrait>::append_to_transcript( + &self.eval_W, + b"eval_W", + &mut transcript, + ); //eval_E is already in the transcript EE::verify_batch( &vk.gens, diff --git a/src/spartan/sumcheck.rs b/src/spartan/sumcheck.rs index deb29f0..d32d07b 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -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 @@ impl SumcheckProof { claim: G::Scalar, num_rounds: usize, degree_bound: usize, - transcript: &mut Transcript, + transcript: &mut G::TE, ) -> Result<(G::Scalar, Vec), NovaError> { let mut e = claim; let mut r: Vec = Vec::new(); @@ -48,7 +47,7 @@ impl SumcheckProof { 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 @@ impl SumcheckProof { poly_A: &mut MultilinearPolynomial, poly_B: &mut MultilinearPolynomial, comb_func: F, - transcript: &mut Transcript, - ) -> (Self, Vec, Vec) + transcript: &mut G::TE, + ) -> Result<(Self, Vec, Vec), NovaError> where F: Fn(&G::Scalar, &G::Scalar) -> G::Scalar + Sync, { @@ -103,7 +102,7 @@ impl SumcheckProof { 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 @@ impl SumcheckProof { 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( @@ -132,8 +131,8 @@ impl SumcheckProof { poly_C: &mut MultilinearPolynomial, poly_D: &mut MultilinearPolynomial, comb_func: F, - transcript: &mut Transcript, - ) -> (Self, Vec, Vec) + transcript: &mut G::TE, + ) -> Result<(Self, Vec, Vec), NovaError> where F: Fn(&G::Scalar, &G::Scalar, &G::Scalar, &G::Scalar) -> G::Scalar + Sync, { @@ -195,7 +194,7 @@ impl SumcheckProof { 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 @@ impl SumcheckProof { 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 @@ impl CompressedUniPoly { } } -impl AppendToTranscriptTrait for UniPoly { - 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 AppendToTranscriptTrait for UniPoly { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + <[G::Scalar] as AppendToTranscriptTrait>::append_to_transcript( + &self.coeffs, + label, + transcript, + ); } } diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index 0a10f4f..e4c4dc9 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -71,7 +71,7 @@ pub trait CommitmentTrait: + Serialize + for<'de> Deserialize<'de> + AbsorbInROTrait - + AppendToTranscriptTrait + + AppendToTranscriptTrait + CommitmentOps + CommitmentOpsOwned + ScalarMul @@ -96,7 +96,7 @@ pub trait CompressedCommitmentTrait: + Sync + Serialize + for<'de> Deserialize<'de> - + AppendToTranscriptTrait + + AppendToTranscriptTrait { /// Holds the type of the commitment that can be decompressed into type Commitment; diff --git a/src/traits/evaluation.rs b/src/traits/evaluation.rs index 431ec96..6853faf 100644 --- a/src/traits/evaluation.rs +++ b/src/traits/evaluation.rs @@ -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 @@ pub trait EvaluationEngineTrait: /// A method to prove evaluations of a batch of polynomials fn prove_batch( gens: &Self::EvaluationGens, - transcript: &mut Transcript, + transcript: &mut G::TE, comm: &[>::Commitment], polys: &[Vec], points: &[Vec], @@ -37,7 +36,7 @@ pub trait EvaluationEngineTrait: /// 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: &[>::Commitment], points: &[Vec], evals: &[G::Scalar], diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 98251b8..9ba9136 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -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 @@ use core::{ 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 @@ pub trait Group: /// A type representing an element of the scalar field of the group type Scalar: PrimeField + PrimeFieldBits - + ChallengeTrait + + PrimeFieldExt + + ChallengeTrait + Send + Sync + Serialize @@ -53,13 +54,16 @@ pub trait Group: /// 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 + Serialize + for<'de> Deserialize<'de>; /// An alternate implementation of Self::RO in the circuit model type ROCircuit: ROCircuitTrait + Serialize + for<'de> Deserialize<'de>; + /// A type that provides a generic Fiat-Shamir transcript to be used when externalizing proofs + type TE: TranscriptEngineTrait; + /// A type that defines a commitment engine over scalars in the group type CE: CommitmentEngineTrait + Serialize + for<'de> Deserialize<'de>; @@ -105,24 +109,12 @@ pub trait CompressedGroup: 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 { /// 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 { /// A type representing constants/parameters associated with the hash function @@ -204,17 +196,63 @@ impl ScalarMul for T where T: Mul: for<'r> ScalarMul<&'r Rhs, Output> {} impl ScalarMulOwned for T where T: for<'r> ScalarMul<&'r Rhs, Output> {} -impl 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: 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; + + /// 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 { + /// 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 { + /// Returns a challenge from the transcript + fn challenge(label: &'static [u8], transcript: &mut G::TE) -> Result + 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 { + (0..v.len()) + .map(|i| v[i].to_repr().as_ref().to_vec()) + .collect::>>() + .into_iter() + .flatten() + .collect::>() + } +} + +impl, F: PrimeField> ChallengeTrait for F { + fn challenge(label: &'static [u8], transcript: &mut G::TE) -> Result { + transcript.squeeze_scalar(label) + } +} + +impl, F: PrimeField> AppendToTranscriptTrait for F { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + transcript.absorb_bytes(label, self.to_repr().as_ref()); } } -impl 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, F: PrimeField + PrimeFieldExt> AppendToTranscriptTrait for [F] { + fn append_to_transcript(&self, label: &'static [u8], transcript: &mut G::TE) { + transcript.absorb_bytes(label, &::to_bytes(self)); } }