diff --git a/src/circuit.rs b/src/circuit.rs index 9f6b858..614e61c 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -378,6 +378,7 @@ mod tests { use crate::constants::{BN_LIMB_WIDTH, BN_N_LIMBS}; use crate::{ bellperson::r1cs::{NovaShape, NovaWitness}, + gadgets::utils::scalar_as_base, provider::poseidon::PoseidonConstantsCircuit, traits::{circuit::TrivialTestCircuit, ROConstantsTrait}, }; @@ -420,7 +421,7 @@ mod tests { let zero1 = <::Base as Field>::ZERO; let mut cs1: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs1: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - shape2.get_digest(), + scalar_as_base::(zero1), // pass zero for testing zero1, vec![zero1], None, @@ -444,7 +445,7 @@ mod tests { let zero2 = <::Base as Field>::ZERO; let mut cs2: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs2: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - shape1.get_digest(), + scalar_as_base::(zero2), // pass zero for testing zero2, vec![zero2], None, diff --git a/src/lib.rs b/src/lib.rs index 7ab0cf7..ac0a548 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,10 +36,12 @@ use constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_FE_WITHOUT_IO_FOR_CRHF, NUM_HASH_ use core::marker::PhantomData; use errors::NovaError; use ff::Field; +use flate2::{write::ZlibEncoder, Compression}; use gadgets::utils::scalar_as_base; use nifs::NIFS; use r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness}; use serde::{Deserialize, Serialize}; +use sha3::{Digest, Sha3_256}; use traits::{ circuit::StepCircuit, commitment::{CommitmentEngineTrait, CommitmentTrait}, @@ -69,6 +71,7 @@ where r1cs_shape_secondary: R1CSShape, augmented_circuit_params_primary: NovaAugmentedCircuitParams, augmented_circuit_params_secondary: NovaAugmentedCircuitParams, + digest: G1::Scalar, // digest of everything else with this field set to G1::Scalar::ZERO _p_c1: PhantomData, _p_c2: PhantomData, } @@ -119,7 +122,7 @@ where let _ = circuit_secondary.synthesize(&mut cs); let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(); - Self { + let mut pp = Self { F_arity_primary, F_arity_secondary, ro_consts_primary, @@ -132,9 +135,15 @@ where r1cs_shape_secondary, augmented_circuit_params_primary, augmented_circuit_params_secondary, + digest: G1::Scalar::ZERO, _p_c1: Default::default(), _p_c2: Default::default(), - } + }; + + // set the digest in pp + pp.digest = compute_digest::>(&pp); + + pp } /// Returns the number of constraints in the primary and secondary circuits @@ -205,7 +214,7 @@ where // base case for the primary let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_secondary.get_digest(), + scalar_as_base::(pp.digest), G1::Scalar::ZERO, z0_primary.clone(), None, @@ -228,7 +237,7 @@ where // base case for the secondary let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_primary.get_digest(), + pp.digest, G2::Scalar::ZERO, z0_secondary.clone(), None, @@ -294,6 +303,7 @@ where let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest), &pp.r1cs_shape_secondary, &r_snark.r_U_secondary, &r_snark.r_W_secondary, @@ -303,7 +313,7 @@ where let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_secondary.get_digest(), + scalar_as_base::(pp.digest), G1::Scalar::from(r_snark.i as u64), z0_primary, Some(r_snark.zi_primary.clone()), @@ -328,6 +338,7 @@ where let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove( &pp.ck_primary, &pp.ro_consts_primary, + &pp.digest, &pp.r1cs_shape_primary, &r_snark.r_U_primary, &r_snark.r_W_primary, @@ -337,7 +348,7 @@ where let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_primary.get_digest(), + pp.digest, G2::Scalar::from(r_snark.i as u64), z0_secondary, Some(r_snark.zi_secondary.clone()), @@ -414,7 +425,7 @@ where pp.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, ); - hasher.absorb(scalar_as_base::(pp.r1cs_shape_secondary.get_digest())); + hasher.absorb(pp.digest); hasher.absorb(G1::Scalar::from(num_steps as u64)); for e in &z0_primary { hasher.absorb(*e); @@ -428,7 +439,7 @@ where pp.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::(pp.r1cs_shape_primary.get_digest())); + hasher2.absorb(scalar_as_base::(pp.digest)); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in &z0_secondary { hasher2.absorb(*e); @@ -531,8 +542,7 @@ where F_arity_secondary: usize, ro_consts_primary: ROConstants, ro_consts_secondary: ROConstants, - r1cs_shape_primary_digest: G1::Scalar, - r1cs_shape_secondary_digest: G2::Scalar, + digest: G1::Scalar, vk_primary: S1::VerifierKey, vk_secondary: S2::VerifierKey, _p_c1: PhantomData, @@ -602,8 +612,7 @@ where F_arity_secondary: pp.F_arity_secondary, ro_consts_primary: pp.ro_consts_primary.clone(), ro_consts_secondary: pp.ro_consts_secondary.clone(), - r1cs_shape_primary_digest: pp.r1cs_shape_primary.get_digest(), - r1cs_shape_secondary_digest: pp.r1cs_shape_secondary.get_digest(), + digest: pp.digest, vk_primary, vk_secondary, _p_c1: Default::default(), @@ -625,6 +634,7 @@ where NIFS::prove( &pp.ck_primary, &pp.ro_consts_primary, + &pp.digest, &pp.r1cs_shape_primary, &recursive_snark.r_U_primary, &recursive_snark.r_W_primary, @@ -637,6 +647,7 @@ where NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest), &pp.r1cs_shape_secondary, &recursive_snark.r_U_secondary, &recursive_snark.r_W_secondary, @@ -709,7 +720,7 @@ where vk.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, ); - hasher.absorb(scalar_as_base::(vk.r1cs_shape_secondary_digest)); + hasher.absorb(vk.digest); hasher.absorb(G1::Scalar::from(num_steps as u64)); for e in z0_primary { hasher.absorb(e); @@ -723,7 +734,7 @@ where vk.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::(vk.r1cs_shape_primary_digest)); + hasher2.absorb(scalar_as_base::(vk.digest)); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in z0_secondary { hasher2.absorb(e); @@ -748,13 +759,13 @@ where // fold the running instance and last instance to get a folded instance let f_U_primary = self.nifs_primary.verify( &vk.ro_consts_primary, - &vk.r1cs_shape_primary_digest, + &vk.digest, &self.r_U_primary, &self.l_u_primary, )?; let f_U_secondary = self.nifs_secondary.verify( &vk.ro_consts_secondary, - &vk.r1cs_shape_secondary_digest, + &scalar_as_base::(vk.digest), &self.r_U_secondary, &self.l_u_secondary, )?; @@ -781,6 +792,36 @@ type Commitment = <::CE as CommitmentEngineTrait>::Commitment; type CompressedCommitment = <<::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment; type CE = ::CE; +fn compute_digest(o: &T) -> G::Scalar { + // obtain a vector of bytes representing public parameters + let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); + bincode::serialize_into(&mut encoder, o).unwrap(); + let pp_bytes = encoder.finish().unwrap(); + + // convert pp_bytes into a short digest + let mut hasher = Sha3_256::new(); + hasher.input(&pp_bytes); + let digest = hasher.result(); + + // truncate the digest to NUM_HASH_BITS bits + let bv = (0..NUM_HASH_BITS).map(|i| { + let (byte_pos, bit_pos) = (i / 8, i % 8); + let bit = (digest[byte_pos] >> bit_pos) & 1; + bit == 1 + }); + + // turn the bit vector into a scalar + let mut digest = G::Scalar::ZERO; + let mut coeff = G::Scalar::ONE; + for bit in bv { + if bit { + digest += coeff; + } + coeff += coeff; + } + digest +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/nifs.rs b/src/nifs.rs index 8c46f58..0993349 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -32,9 +32,11 @@ impl NIFS { /// a folded Relaxed R1CS instance-witness tuple `(U, W)` of the same shape `shape`, /// with the guarantee that the folded witness `W` satisfies the folded instance `U` /// if and only if `W1` satisfies `U1` and `W2` satisfies `U2`. + #[allow(clippy::too_many_arguments)] pub fn prove( ck: &CommitmentKey, ro_consts: &ROConstants, + pp_digest: &G::Scalar, S: &R1CSShape, U1: &RelaxedR1CSInstance, W1: &RelaxedR1CSWitness, @@ -44,8 +46,8 @@ impl NIFS { // initialize a new RO let mut ro = G::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); - // append S to the transcript - S.absorb_in_ro(&mut ro); + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::(*pp_digest)); // append U1 and U2 to transcript U1.absorb_in_ro(&mut ro); @@ -84,15 +86,15 @@ impl NIFS { pub fn verify( &self, ro_consts: &ROConstants, - S_digest: &G::Scalar, + pp_digest: &G::Scalar, U1: &RelaxedR1CSInstance, U2: &R1CSInstance, ) -> Result, NovaError> { // initialize a new RO let mut ro = G::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); - // append the digest of S to the transcript - ro.absorb(scalar_as_base::(*S_digest)); + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::(*pp_digest)); // append U1 and U2 to transcript U1.absorb_in_ro(&mut ro); @@ -192,12 +194,23 @@ mod tests { assert!(shape.is_sat(&ck, &U2, &W2).is_ok()); // execute a sequence of folds - execute_sequence(&ck, &ro_consts, &shape, &U1, &W1, &U2, &W2); + execute_sequence( + &ck, + &ro_consts, + &::Scalar::ZERO, + &shape, + &U1, + &W1, + &U2, + &W2, + ); } + #[allow(clippy::too_many_arguments)] fn execute_sequence( ck: &CommitmentKey, ro_consts: &<::RO as ROTrait<::Base, ::Scalar>>::Constants, + pp_digest: &::Scalar, shape: &R1CSShape, U1: &R1CSInstance, W1: &R1CSWitness, @@ -209,12 +222,12 @@ mod tests { let mut r_U = RelaxedR1CSInstance::default(ck, shape); // produce a step SNARK with (W1, U1) as the first incoming witness-instance pair - let res = NIFS::prove(ck, ro_consts, shape, &r_U, &r_W, U1, W1); + let res = NIFS::prove(ck, ro_consts, pp_digest, shape, &r_U, &r_W, U1, W1); assert!(res.is_ok()); let (nifs, (_U, W)) = res.unwrap(); // verify the step SNARK with U1 as the first incoming instance - let res = nifs.verify(ro_consts, &shape.get_digest(), &r_U, U1); + let res = nifs.verify(ro_consts, pp_digest, &r_U, U1); assert!(res.is_ok()); let U = res.unwrap(); @@ -225,12 +238,12 @@ mod tests { r_U = U; // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair - let res = NIFS::prove(ck, ro_consts, shape, &r_U, &r_W, U2, W2); + let res = NIFS::prove(ck, ro_consts, pp_digest, shape, &r_U, &r_W, U2, W2); assert!(res.is_ok()); let (nifs, (_U, W)) = res.unwrap(); // verify the step SNARK with U1 as the first incoming instance - let res = nifs.verify(ro_consts, &shape.get_digest(), &r_U, U2); + let res = nifs.verify(ro_consts, pp_digest, &r_U, U2); assert!(res.is_ok()); let U = res.unwrap(); @@ -349,6 +362,15 @@ mod tests { let (_O, U2, W2) = rand_inst_witness_generator(&ck, &O); // execute a sequence of folds - execute_sequence(&ck, &ro_consts, &S, &U1, &W1, &U2, &W2); + execute_sequence( + &ck, + &ro_consts, + &::Scalar::ZERO, + &S, + &U1, + &W1, + &U2, + &W2, + ); } } diff --git a/src/r1cs.rs b/src/r1cs.rs index 8b0e997..4b204e9 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -1,7 +1,7 @@ //! This module defines R1CS related types and a folding scheme for Relaxed R1CS #![allow(clippy::type_complexity)] use crate::{ - constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_HASH_BITS}, + constants::{BN_LIMB_WIDTH, BN_N_LIMBS}, errors::NovaError, gadgets::{ nonnative::{bignat::nat_to_limbs, util::f_to_nat}, @@ -14,11 +14,9 @@ use crate::{ }; use core::{cmp::max, marker::PhantomData}; use ff::Field; -use flate2::{write::ZlibEncoder, Compression}; use itertools::concat; use rayon::prelude::*; use serde::{Deserialize, Serialize}; -use sha3::{Digest, Sha3_256}; /// Public parameters for a given R1CS #[derive(Clone, Serialize, Deserialize)] @@ -36,7 +34,6 @@ pub struct R1CSShape { pub(crate) A: Vec<(usize, usize, G::Scalar)>, pub(crate) B: Vec<(usize, usize, G::Scalar)>, pub(crate) C: Vec<(usize, usize, G::Scalar)>, - digest: G::Scalar, // digest of the rest of R1CSShape } /// A type that holds a witness for a given R1CS instance @@ -126,19 +123,14 @@ impl R1CSShape { return Err(NovaError::OddInputLength); } - let digest = Self::compute_digest(num_cons, num_vars, num_io, A, B, C); - - let shape = R1CSShape { + Ok(R1CSShape { num_cons, num_vars, num_io, A: A.to_owned(), B: B.to_owned(), C: C.to_owned(), - digest, - }; - - Ok(shape) + }) } pub fn multiply_vec( @@ -303,67 +295,6 @@ impl R1CSShape { Ok((T, comm_T)) } - /// returns the digest of R1CSShape - pub fn get_digest(&self) -> G::Scalar { - self.digest - } - - fn compute_digest( - num_cons: usize, - num_vars: usize, - num_io: usize, - A: &[(usize, usize, G::Scalar)], - B: &[(usize, usize, G::Scalar)], - C: &[(usize, usize, G::Scalar)], - ) -> G::Scalar { - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] - struct R1CSShapeWithoutDigest { - num_cons: usize, - num_vars: usize, - num_io: usize, - A: Vec<(usize, usize, G::Scalar)>, - B: Vec<(usize, usize, G::Scalar)>, - C: Vec<(usize, usize, G::Scalar)>, - } - - let shape = R1CSShapeWithoutDigest:: { - num_cons, - num_vars, - num_io, - A: A.to_vec(), - B: B.to_vec(), - C: C.to_vec(), - }; - - // obtain a vector of bytes representing the R1CS shape - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - bincode::serialize_into(&mut encoder, &shape).unwrap(); - let shape_bytes = encoder.finish().unwrap(); - - // convert shape_bytes into a short digest - let mut hasher = Sha3_256::new(); - hasher.input(&shape_bytes); - let digest = hasher.result(); - - // 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 = (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; - } - res - } - /// Pads the R1CSShape so that the number of variables is a power of two /// Renumbers variables to accomodate padded variables pub fn pad(&self) -> Self { @@ -378,8 +309,6 @@ impl R1CSShape { // check if the number of variables are as expected, then // we simply set the number of constraints to the next power of two if self.num_vars == m { - let digest = Self::compute_digest(m, self.num_vars, self.num_io, &self.A, &self.B, &self.C); - return R1CSShape { num_cons: m, num_vars: m, @@ -387,7 +316,6 @@ impl R1CSShape { A: self.A.clone(), B: self.B.clone(), C: self.C.clone(), - digest, }; } @@ -414,15 +342,6 @@ impl R1CSShape { let B_padded = apply_pad(&self.B); let C_padded = apply_pad(&self.C); - let digest = Self::compute_digest( - num_cons_padded, - num_vars_padded, - self.num_io, - &A_padded, - &B_padded, - &C_padded, - ); - R1CSShape { num_cons: num_cons_padded, num_vars: num_vars_padded, @@ -430,23 +349,10 @@ impl R1CSShape { A: A_padded, B: B_padded, C: C_padded, - digest, } } } -impl TranscriptReprTrait for R1CSShape { - fn to_transcript_bytes(&self) -> Vec { - self.get_digest().to_transcript_bytes() - } -} - -impl AbsorbInROTrait for R1CSShape { - fn absorb_in_ro(&self, ro: &mut G::RO) { - ro.absorb(scalar_as_base::(self.get_digest())); - } -} - impl R1CSWitness { /// A method to create a witness object using a vector of scalars pub fn new(S: &R1CSShape, W: &[G::Scalar]) -> Result, NovaError> { diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index 87ae3eb..6e9b18d 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -6,6 +6,7 @@ pub mod pp; mod sumcheck; use crate::{ + compute_digest, errors::NovaError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, traits::{ @@ -129,6 +130,7 @@ impl PolyEvalInstance { pub struct ProverKey> { pk_ee: EE::ProverKey, S: R1CSShape, + vk_digest: G::Scalar, // digest of the verifier's key } /// A type that represents the verifier's key @@ -137,6 +139,7 @@ pub struct ProverKey> { pub struct VerifierKey> { vk_ee: EE::VerifierKey, S: R1CSShape, + digest: G::Scalar, } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance @@ -169,12 +172,21 @@ impl> RelaxedR1CSSNARKTrait>(&vk); + vk }; - let pk = ProverKey { pk_ee, S }; + let pk = ProverKey { + pk_ee, + S, + vk_digest: vk.digest, + }; Ok((pk, vk)) } @@ -195,8 +207,8 @@ impl> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> { S: R1CSShape, S_repr: R1CSShapeSparkRepr, S_comm: R1CSShapeSparkCommitment, + vk_digest: G::Scalar, // digest of verifier's key } /// A type that represents the verifier's key @@ -746,6 +748,7 @@ pub struct VerifierKey> { num_vars: usize, vk_ee: EE::VerifierKey, S_comm: R1CSShapeSparkCommitment, + digest: G::Scalar, } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance @@ -938,11 +941,16 @@ impl> RelaxedR1CSSNARKTrait>(&vk); + vk }; let pk = ProverKey { @@ -950,6 +958,7 @@ impl> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> RelaxedR1CSSNARKTrait> = Vec::new(); - // append the commitment to R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"C", &vk.S_comm); + // append the verifier key (including commitment to R1CS matrices) and the RelaxedR1CSInstance to the transcript + transcript.absorb(b"vk", &vk.digest); transcript.absorb(b"U", U); let comm_Az = Commitment::::decompress(&self.comm_Az)?;