diff --git a/src/circuit.rs b/src/circuit.rs index 6c1f474..d06ed8b 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -333,6 +333,7 @@ mod tests { use crate::bellperson::{shape_cs::ShapeCS, solver::SatisfyingAssignment}; type G1 = pasta_curves::pallas::Point; type G2 = pasta_curves::vesta::Point; + use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, commitments::CommitTrait, @@ -361,7 +362,7 @@ mod tests { #[test] fn test_verification_circuit() { // We experiment with 8 limbs of 32 bits each - let params = NIFSVerifierCircuitParams::new(32, 8); + let params = NIFSVerifierCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS); // The first circuit that verifies G2 let poseidon_constants1: NovaPoseidonConstants<::Base> = NovaPoseidonConstants::new(); diff --git a/src/commitments.rs b/src/commitments.rs index e95f1f4..1de5a22 100644 --- a/src/commitments.rs +++ b/src/commitments.rs @@ -1,6 +1,6 @@ use super::{ errors::NovaError, - traits::{AppendToTranscriptTrait, CompressedGroup, Group}, + traits::{AbsorbInROTrait, AppendToTranscriptTrait, CompressedGroup, Group, HashFuncTrait}, }; use core::{ fmt::Debug, @@ -8,6 +8,7 @@ use core::{ ops::{Add, AddAssign, Mul, MulAssign}, }; use digest::{ExtendableOutput, Input}; +use ff::Field; use merlin::Transcript; use sha3::Shake256; use std::io::Read; @@ -86,6 +87,19 @@ impl AppendToTranscriptTrait for Commitment { } } +impl AbsorbInROTrait for Commitment { + fn absorb_in_ro(&self, ro: &mut G::HashFunc) { + let (x, y, is_infinity) = self.comm.to_coordinates(); + ro.absorb(x); + ro.absorb(y); + ro.absorb(if is_infinity { + G::Base::one() + } else { + G::Base::zero() + }); + } +} + impl AppendToTranscriptTrait for CompressedCommitment { fn append_to_transcript(&self, label: &'static [u8], transcript: &mut Transcript) { transcript.append_message(label, self.comm.as_bytes()); diff --git a/src/constants.rs b/src/constants.rs index d8601a6..bb3009c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,2 +1,4 @@ pub(crate) const NUM_CHALLENGE_BITS: usize = 128; pub(crate) const NUM_HASH_BITS: usize = 250; +pub(crate) const BN_LIMB_WIDTH: usize = 32; +pub(crate) const BN_N_LIMBS: usize = 8; diff --git a/src/gadgets/utils.rs b/src/gadgets/utils.rs index 2fb45c4..af9908c 100644 --- a/src/gadgets/utils.rs +++ b/src/gadgets/utils.rs @@ -99,6 +99,20 @@ where }) } +/// interepret scalar as base +pub fn scalar_as_base(input: G::Scalar) -> G::Base { + let input_bits = input.to_le_bits(); + let mut mult = G::Base::one(); + let mut val = G::Base::zero(); + for bit in input_bits { + if bit { + val += mult; + } + mult = mult + mult; + } + val +} + /// Allocate bignat a constant pub fn alloc_bignat_constant>( mut cs: CS, diff --git a/src/lib.rs b/src/lib.rs index df7de20..8f21d96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,12 +16,11 @@ pub mod traits; use commitments::CompressedCommitment; use errors::NovaError; -use merlin::Transcript; use r1cs::{ R1CSGens, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, }; use std::marker::PhantomData; -use traits::{AppendToTranscriptTrait, ChallengeTrait, Group}; +use traits::{AbsorbInROTrait, Group, HashFuncConstantsTrait, HashFuncTrait}; /// A SNARK that holds the proof of a step of an incremental computation pub struct StepSNARK { @@ -30,10 +29,6 @@ pub struct StepSNARK { } impl StepSNARK { - fn protocol_name() -> &'static [u8] { - b"NovaStepSNARK" - } - /// Takes as input a Relaxed R1CS instance-witness tuple `(U1, W1)` and /// an R1CS instance-witness tuple `(U2, W2)` with the same structure `shape` /// and defined with respect to the same `gens`, and outputs @@ -47,7 +42,6 @@ impl StepSNARK { W1: &RelaxedR1CSWitness, U2: &R1CSInstance, W2: &R1CSWitness, - transcript: &mut Transcript, ) -> Result< ( StepSNARK, @@ -55,24 +49,27 @@ impl StepSNARK { ), NovaError, > { - // append the protocol name to the transcript - transcript.append_message(b"protocol-name", StepSNARK::::protocol_name()); + // initialize a new RO + let mut ro = G::HashFunc::new(<::HashFunc as HashFuncTrait< + G::Base, + G::Scalar, + >>::Constants::new()); // append S to the transcript - S.append_to_transcript(b"S", transcript); + S.absorb_in_ro(&mut ro); // append U1 and U2 to transcript - U1.append_to_transcript(b"U1", transcript); - U2.append_to_transcript(b"U2", transcript); + U1.absorb_in_ro(&mut ro); + U2.absorb_in_ro(&mut ro); // compute a commitment to the cross-term let (T, comm_T) = S.commit_T(gens, U1, W1, U2, W2)?; // append `comm_T` to the transcript and obtain a challenge - comm_T.append_to_transcript(b"comm_T", transcript); + comm_T.absorb_in_ro(&mut ro); - // compute a challenge from the transcript - let r = G::Scalar::challenge(b"r", transcript); + // compute a challenge from the RO + let r = ro.get_challenge(); // fold the instance using `r` and `comm_T` let U = U1.fold(U2, &comm_T, &r)?; @@ -83,7 +80,7 @@ impl StepSNARK { // return the folded instance and witness Ok(( StepSNARK { - comm_T, + comm_T: comm_T.compress(), _p: Default::default(), }, (U, W), @@ -100,26 +97,29 @@ impl StepSNARK { S: &R1CSShape, U1: &RelaxedR1CSInstance, U2: &R1CSInstance, - transcript: &mut Transcript, ) -> Result, NovaError> { - // append the protocol name to the transcript - transcript.append_message(b"protocol-name", StepSNARK::::protocol_name()); + // initialize a new RO + let mut ro = G::HashFunc::new(<::HashFunc as HashFuncTrait< + G::Base, + G::Scalar, + >>::Constants::new()); // append S to the transcript - S.append_to_transcript(b"S", transcript); + S.absorb_in_ro(&mut ro); // append U1 and U2 to transcript - U1.append_to_transcript(b"U1", transcript); - U2.append_to_transcript(b"U2", transcript); + U1.absorb_in_ro(&mut ro); + U2.absorb_in_ro(&mut ro); // append `comm_T` to the transcript and obtain a challenge - self.comm_T.append_to_transcript(b"comm_T", transcript); + let comm_T = self.comm_T.decompress()?; + comm_T.absorb_in_ro(&mut ro); - // compute a challenge from the transcript - let r = G::Scalar::challenge(b"r", transcript); + // compute a challenge from the RO + let r = ro.get_challenge(); // fold the instance using `r` and `comm_T` - let U = U1.fold(U2, &self.comm_T, &r)?; + let U = U1.fold(U2, &comm_T, &r)?; // return the folded instance Ok(U) @@ -239,14 +239,12 @@ mod tests { let mut r_U = RelaxedR1CSInstance::default(gens, shape); // produce a step SNARK with (W1, U1) as the first incoming witness-instance pair - let mut prover_transcript = Transcript::new(b"StepSNARKExample"); - let res = StepSNARK::prove(gens, shape, &r_U, &r_W, U1, W1, &mut prover_transcript); + let res = StepSNARK::prove(gens, shape, &r_U, &r_W, U1, W1); assert!(res.is_ok()); let (step_snark, (_U, W)) = res.unwrap(); // verify the step SNARK with U1 as the first incoming instance - let mut verifier_transcript = Transcript::new(b"StepSNARKExample"); - let res = step_snark.verify(shape, &r_U, U1, &mut verifier_transcript); + let res = step_snark.verify(shape, &r_U, U1); assert!(res.is_ok()); let U = res.unwrap(); @@ -257,12 +255,12 @@ mod tests { r_U = U; // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair - let res = StepSNARK::prove(gens, shape, &r_U, &r_W, U2, W2, &mut prover_transcript); + let res = StepSNARK::prove(gens, shape, &r_U, &r_W, U2, W2); assert!(res.is_ok()); let (step_snark, (_U, W)) = res.unwrap(); // verify the step SNARK with U1 as the first incoming instance - let res = step_snark.verify(shape, &r_U, U2, &mut verifier_transcript); + let res = step_snark.verify(shape, &r_U, U2); assert!(res.is_ok()); let U = res.unwrap(); diff --git a/src/r1cs.rs b/src/r1cs.rs index 3564127..7894337 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -1,11 +1,13 @@ //! This module defines R1CS related types and a folding scheme for Relaxed R1CS #![allow(clippy::type_complexity)] use super::{ - commitments::{CommitGens, CommitTrait, Commitment, CompressedCommitment}, - constants::NUM_HASH_BITS, + commitments::{CommitGens, CommitTrait, Commitment}, + constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_HASH_BITS}, errors::NovaError, - traits::{AppendToTranscriptTrait, Group}, + gadgets::utils::scalar_as_base, + traits::{AbsorbInROTrait, AppendToTranscriptTrait, Group, HashFuncTrait}, }; +use bellperson_nonnative::{mp::bignat::nat_to_limbs, util::convert::f_to_nat}; use ff::{Field, PrimeField}; use flate2::{write::ZlibEncoder, Compression}; use itertools::concat; @@ -255,13 +257,7 @@ impl R1CSShape { W1: &RelaxedR1CSWitness, U2: &R1CSInstance, W2: &R1CSWitness, - ) -> Result< - ( - Vec, - CompressedCommitment, - ), - NovaError, - > { + ) -> Result<(Vec, Commitment), NovaError> { let (AZ_1, BZ_1, CZ_1) = { let Z1 = concat(vec![W1.W.clone(), vec![U1.u], U1.X.clone()]); self.multiply_vec(&Z1)? @@ -291,24 +287,13 @@ impl R1CSShape { .map(|(((a, b), c), d)| *a + *b - *c - *d) .collect::>(); - let comm_T = T.commit(&gens.gens_E).compress(); + let comm_T = T.commit(&gens.gens_E); Ok((T, comm_T)) } -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -struct R1CSShapeSerialized { - num_cons: usize, - num_vars: usize, - num_io: usize, - A: Vec<(usize, usize, Vec)>, - B: Vec<(usize, usize, Vec)>, - C: Vec<(usize, usize, Vec)>, -} -impl AppendToTranscriptTrait for R1CSShape { - fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { + /// returns the digest of R1CSShape + fn get_digest(&self) -> G::Scalar { let shape_serialized = R1CSShapeSerialized { num_cons: self.num_cons, num_vars: self.num_vars, @@ -340,27 +325,47 @@ impl AppendToTranscriptTrait for R1CSShape { hasher.input(&shape_bytes); let shape_digest = hasher.result(); - let shape_digest_trun = { - // truncate the digest to 250 bits - let bv = (0..NUM_HASH_BITS).map(|i| { - let (byte_pos, bit_pos) = (i / 8, i % 8); - let bit = (shape_digest[byte_pos] >> bit_pos) & 1; - bit == 1 - }); - - // turn the bit vector into a scalar - let mut res = G::Scalar::zero(); - let mut coeff = G::Scalar::one(); - for bit in bv { - if bit { - res += coeff; - } - coeff += coeff; + // truncate the digest to 250 bits + let bv = (0..NUM_HASH_BITS).map(|i| { + let (byte_pos, bit_pos) = (i / 8, i % 8); + let bit = (shape_digest[byte_pos] >> bit_pos) & 1; + bit == 1 + }); + + // turn the bit vector into a scalar + let mut res = G::Scalar::zero(); + let mut coeff = G::Scalar::one(); + for bit in bv { + if bit { + res += coeff; } - res - }; + coeff += coeff; + } + res + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct R1CSShapeSerialized { + num_cons: usize, + num_vars: usize, + num_io: usize, + A: Vec<(usize, usize, Vec)>, + B: Vec<(usize, usize, Vec)>, + C: Vec<(usize, usize, Vec)>, +} + +impl AppendToTranscriptTrait for R1CSShape { + fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { + self + .get_digest() + .append_to_transcript(b"R1CSShape", transcript); + } +} - shape_digest_trun.append_to_transcript(b"R1CSShape", transcript); +impl AbsorbInROTrait for R1CSShape { + fn absorb_in_ro(&self, ro: &mut G::HashFunc) { + ro.absorb(scalar_as_base::(self.get_digest())); } } @@ -405,6 +410,15 @@ impl AppendToTranscriptTrait for R1CSInstance { } } +impl AbsorbInROTrait for R1CSInstance { + fn absorb_in_ro(&self, ro: &mut G::HashFunc) { + self.comm_W.absorb_in_ro(ro); + for x in &self.X { + ro.absorb(scalar_as_base::(*x)); + } + } +} + impl RelaxedR1CSWitness { /// Produces a default RelaxedR1CSWitness given an R1CSShape pub fn default(S: &R1CSShape) -> RelaxedR1CSWitness { @@ -465,10 +479,9 @@ impl RelaxedR1CSInstance { pub fn fold( &self, U2: &R1CSInstance, - comm_T: &CompressedCommitment, + comm_T: &Commitment, r: &G::Scalar, ) -> Result, NovaError> { - let comm_T_unwrapped = comm_T.decompress()?; let (X1, u1, comm_W_1, comm_E_1) = (&self.X, &self.u, &self.comm_W.clone(), &self.comm_E.clone()); let (X2, comm_W_2) = (&U2.X, &U2.comm_W); @@ -493,7 +506,7 @@ impl RelaxedR1CSInstance { .map(|(a, b)| *a + *r * *b) .collect::>(); let comm_W = comm_W_1 + comm_W_2 * r; - let comm_E = *comm_E_1 + comm_T_unwrapped * *r; + let comm_E = *comm_E_1 + *comm_T * *r; let u = *u1 + *r; Ok(RelaxedR1CSInstance { @@ -511,7 +524,23 @@ impl AppendToTranscriptTrait for RelaxedR1CSInstance { fn append_to_transcript(&self, _label: &'static [u8], transcript: &mut Transcript) { self.comm_W.append_to_transcript(b"comm_W", transcript); self.comm_E.append_to_transcript(b"comm_E", transcript); - self.X.append_to_transcript(b"X", transcript); self.u.append_to_transcript(b"u", transcript); + self.X.append_to_transcript(b"X", transcript); + } +} + +impl AbsorbInROTrait for RelaxedR1CSInstance { + fn absorb_in_ro(&self, ro: &mut G::HashFunc) { + self.comm_W.absorb_in_ro(ro); + self.comm_E.absorb_in_ro(ro); + ro.absorb(scalar_as_base::(self.u)); + + // absorb each element of self.X in bignum format + for x in &self.X { + let limbs: Vec = nat_to_limbs(&f_to_nat(x), BN_LIMB_WIDTH, BN_N_LIMBS).unwrap(); + for limb in limbs { + ro.absorb(scalar_as_base::(limb)); + } + } } } diff --git a/src/traits.rs b/src/traits.rs index 4944fbf..dfbbf47 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -74,6 +74,12 @@ pub trait AppendToTranscriptTrait { 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::HashFunc); +} + /// A helper trait to generate challenges using a transcript object pub trait ChallengeTrait { /// Returns a Scalar representing the challenge using the transcript