arnaucube
arnaucube/NovaPublic Self-hosted repository
hash of public parameters in the transcript (#168)

Srinath Setty srinath@microsoft.com
Thu, 18 May 2023 10:19:44 -0700
commit

b28aaf70a854d540e48607705e880aa61505ae2a

parent

f16fa1e40103e9bc0d63766cee11e4c00e2633d2

6 files changed, 133 insertions(+), 142 deletions(-)

jump to
M src/circuit.rs → src/circuit.rs

@@ -378,6 +378,7 @@

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 @@ // Execute the base case for the primary

let zero1 = <<G2 as Group>::Base as Field>::ZERO; let mut cs1: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); let inputs1: NovaAugmentedCircuitInputs<G2> = NovaAugmentedCircuitInputs::new( - shape2.get_digest(), + scalar_as_base::<G1>(zero1), // pass zero for testing zero1, vec![zero1], None,

@@ -444,7 +445,7 @@ // Execute the base case for the secondary

let zero2 = <<G1 as Group>::Base as Field>::ZERO; let mut cs2: SatisfyingAssignment<G2> = SatisfyingAssignment::new(); let inputs2: NovaAugmentedCircuitInputs<G1> = NovaAugmentedCircuitInputs::new( - shape1.get_digest(), + scalar_as_base::<G2>(zero2), // pass zero for testing zero2, vec![zero2], None,
M src/lib.rs → src/lib.rs

@@ -36,10 +36,12 @@ use constants::{BN_LIMB_WIDTH, BN_N_LIMBS, NUM_FE_WITHOUT_IO_FOR_CRHF, NUM_HASH_BITS};

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 @@ ck_secondary: CommitmentKey<G2>,

r1cs_shape_secondary: R1CSShape<G2>, 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<C1>, _p_c2: PhantomData<C2>, }

@@ -119,7 +122,7 @@ let mut cs: ShapeCS<G2> = ShapeCS::new();

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 @@ ck_secondary,

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::<G1, PublicParams<G1, G2, C1, C2>>(&pp); + + pp } /// Returns the number of constraints in the primary and secondary circuits

@@ -205,7 +214,7 @@ None => {

// base case for the primary let mut cs_primary: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs<G2> = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_secondary.get_digest(), + scalar_as_base::<G1>(pp.digest), G1::Scalar::ZERO, z0_primary.clone(), None,

@@ -228,7 +237,7 @@

// base case for the secondary let mut cs_secondary: SatisfyingAssignment<G2> = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs<G1> = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_primary.get_digest(), + pp.digest, G2::Scalar::ZERO, z0_secondary.clone(), None,

@@ -294,6 +303,7 @@ // fold the secondary circuit's instance

let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, + &scalar_as_base::<G1>(pp.digest), &pp.r1cs_shape_secondary, &r_snark.r_U_secondary, &r_snark.r_W_secondary,

@@ -303,7 +313,7 @@ )?;

let mut cs_primary: SatisfyingAssignment<G1> = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs<G2> = NovaAugmentedCircuitInputs::new( - pp.r1cs_shape_secondary.get_digest(), + scalar_as_base::<G1>(pp.digest), G1::Scalar::from(r_snark.i as u64), z0_primary, Some(r_snark.zi_primary.clone()),

@@ -328,6 +338,7 @@ // fold the primary circuit's instance

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 @@ )?;

let mut cs_secondary: SatisfyingAssignment<G2> = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs<G1> = 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 @@ let mut hasher = <G2 as Group>::RO::new(

pp.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, ); - hasher.absorb(scalar_as_base::<G2>(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 @@ let mut hasher2 = <G1 as Group>::RO::new(

pp.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::<G1>(pp.r1cs_shape_primary.get_digest())); + hasher2.absorb(scalar_as_base::<G1>(pp.digest)); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in &z0_secondary { hasher2.absorb(*e);

@@ -531,8 +542,7 @@ F_arity_primary: usize,

F_arity_secondary: usize, ro_consts_primary: ROConstants<G1>, ro_consts_secondary: ROConstants<G2>, - 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<C1>,

@@ -602,8 +612,7 @@ F_arity_primary: pp.F_arity_primary,

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 @@ || {

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 @@ // fold the secondary circuit's instance

NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, + &scalar_as_base::<G1>(pp.digest), &pp.r1cs_shape_secondary, &recursive_snark.r_U_secondary, &recursive_snark.r_W_secondary,

@@ -709,7 +720,7 @@ let mut hasher = <G2 as Group>::RO::new(

vk.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, ); - hasher.absorb(scalar_as_base::<G2>(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 @@ let mut hasher2 = <G1 as Group>::RO::new(

vk.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::<G1>(vk.r1cs_shape_primary_digest)); + hasher2.absorb(scalar_as_base::<G1>(vk.digest)); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in z0_secondary { hasher2.absorb(e);

@@ -748,13 +759,13 @@

// 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::<G1>(vk.digest), &self.r_U_secondary, &self.l_u_secondary, )?;

@@ -780,6 +791,36 @@ type CommitmentKey<G> = <<G as traits::Group>::CE as CommitmentEngineTrait<G>>::CommitmentKey;

type Commitment<G> = <<G as Group>::CE as CommitmentEngineTrait<G>>::Commitment; type CompressedCommitment<G> = <<<G as Group>::CE as CommitmentEngineTrait<G>>::Commitment as CommitmentTrait<G>>::CompressedCommitment; type CE<G> = <G as Group>::CE; + +fn compute_digest<G: Group, T: Serialize>(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 {
M src/nifs.rs → src/nifs.rs

@@ -32,9 +32,11 @@ /// and defined with respect to the same `ck`, and outputs

/// 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<G>, ro_consts: &ROConstants<G>, + pp_digest: &G::Scalar, S: &R1CSShape<G>, U1: &RelaxedR1CSInstance<G>, W1: &RelaxedR1CSWitness<G>,

@@ -44,8 +46,8 @@ ) -> Result<(NIFS<G>, (RelaxedR1CSInstance<G>, RelaxedR1CSWitness<G>)), NovaError> {

// 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::<G>(*pp_digest)); // append U1 and U2 to transcript U1.absorb_in_ro(&mut ro);

@@ -84,15 +86,15 @@ /// if and only if `U1` and `U2` are satisfiable.

pub fn verify( &self, ro_consts: &ROConstants<G>, - S_digest: &G::Scalar, + pp_digest: &G::Scalar, U1: &RelaxedR1CSInstance<G>, U2: &R1CSInstance<G>, ) -> Result<RelaxedR1CSInstance<G>, 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::<G>(*S_digest)); + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::<G>(*pp_digest)); // append U1 and U2 to transcript U1.absorb_in_ro(&mut ro);

@@ -192,12 +194,23 @@ // Make sure that the second instance is satisfiable

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, + &<G as Group>::Scalar::ZERO, + &shape, + &U1, + &W1, + &U2, + &W2, + ); } + #[allow(clippy::too_many_arguments)] fn execute_sequence( ck: &CommitmentKey<G>, ro_consts: &<<G as Group>::RO as ROTrait<<G as Group>::Base, <G as Group>::Scalar>>::Constants, + pp_digest: &<G as Group>::Scalar, shape: &R1CSShape<G>, U1: &R1CSInstance<G>, W1: &R1CSWitness<G>,

@@ -209,12 +222,12 @@ let mut r_W = RelaxedR1CSWitness::default(shape);

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 @@ r_W = W;

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 @@ let (O, U1, W1) = rand_inst_witness_generator(&ck, &I);

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, + &<G as Group>::Scalar::ZERO, + &S, + &U1, + &W1, + &U2, + &W2, + ); } }
M src/r1cs.rs → 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 @@ Commitment, CommitmentKey, CE,

}; 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(crate) num_io: usize,

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 @@ if num_io % 2 != 0 {

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 @@

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<G: Group> { - 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::<G> { - 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 @@

// 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 @@ num_io: self.num_io,

A: self.A.clone(), B: self.B.clone(), C: self.C.clone(), - digest, }; }

@@ -414,15 +342,6 @@ let A_padded = apply_pad(&self.A);

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,20 +349,7 @@ num_io: self.num_io,

A: A_padded, B: B_padded, C: C_padded, - digest, } - } -} - -impl<G: Group> TranscriptReprTrait<G> for R1CSShape<G> { - fn to_transcript_bytes(&self) -> Vec<u8> { - self.get_digest().to_transcript_bytes() - } -} - -impl<G: Group> AbsorbInROTrait<G> for R1CSShape<G> { - fn absorb_in_ro(&self, ro: &mut G::RO) { - ro.absorb(scalar_as_base::<G>(self.get_digest())); } }
M src/spartan/mod.rs → 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 @@ #[serde(bound = "")]

pub struct ProverKey<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { pk_ee: EE::ProverKey, S: R1CSShape<G>, + vk_digest: G::Scalar, // digest of the verifier's key } /// A type that represents the verifier's key

@@ -137,6 +139,7 @@ #[serde(bound = "")]

pub struct VerifierKey<G: Group, EE: EvaluationEngineTrait<G, CE = G::CE>> { vk_ee: EE::VerifierKey, S: R1CSShape<G>, + digest: G::Scalar, } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance

@@ -169,12 +172,21 @@ let (pk_ee, vk_ee) = EE::setup(ck);

let S = S.pad(); - let vk = VerifierKey { - vk_ee, - S: S.clone(), + let vk = { + let mut vk = VerifierKey { + vk_ee, + S: S.clone(), + digest: G::Scalar::ZERO, + }; + vk.digest = compute_digest::<G, VerifierKey<G, EE>>(&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 @@ assert_eq!(pk.S.num_vars.next_power_of_two(), pk.S.num_vars);

assert_eq!(pk.S.num_io.next_power_of_two(), pk.S.num_io); assert!(pk.S.num_io < pk.S.num_vars); - // append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"S", &pk.S); + // append the digest of vk (which includes R1CS matrices) and the RelaxedR1CSInstance to the transcript + transcript.absorb(b"vk", &pk.vk_digest); transcript.absorb(b"U", U); // compute the full satisfying assignment by concatenating W.W, U.u, and U.X

@@ -437,7 +449,7 @@ fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance<G>) -> Result<(), NovaError> {

let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"S", &vk.S); + transcript.absorb(b"vk", &vk.digest); transcript.absorb(b"U", U); let (num_rounds_x, num_rounds_y) = (
M src/spartan/pp.rs → src/spartan/pp.rs

@@ -6,6 +6,7 @@ r1cs::{NovaShape, NovaWitness},

shape_cs::ShapeCS, solver::SatisfyingAssignment, }, + compute_digest, errors::NovaError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{

@@ -736,6 +737,7 @@ pk_ee: EE::ProverKey,

S: R1CSShape<G>, S_repr: R1CSShapeSparkRepr<G>, S_comm: R1CSShapeSparkCommitment<G>, + vk_digest: G::Scalar, // digest of verifier's key } /// A type that represents the verifier's key

@@ -746,6 +748,7 @@ num_cons: usize,

num_vars: usize, vk_ee: EE::VerifierKey, S_comm: R1CSShapeSparkCommitment<G>, + digest: G::Scalar, } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance

@@ -938,11 +941,16 @@

let S_repr = R1CSShapeSparkRepr::new(&S); let S_comm = S_repr.commit(ck); - let vk = VerifierKey { - num_cons: S.num_cons, - num_vars: S.num_vars, - S_comm: S_comm.clone(), - vk_ee, + let vk = { + let mut vk = VerifierKey { + num_cons: S.num_cons, + num_vars: S.num_vars, + S_comm: S_comm.clone(), + vk_ee, + digest: G::Scalar::ZERO, + }; + vk.digest = compute_digest::<G, VerifierKey<G, EE>>(&vk); + vk }; let pk = ProverKey {

@@ -950,6 +958,7 @@ pk_ee,

S, S_repr, S_comm, + vk_digest: vk.digest, }; Ok((pk, vk))

@@ -974,8 +983,8 @@ assert_eq!(pk.S.num_vars.next_power_of_two(), pk.S.num_vars);

assert_eq!(pk.S.num_io.next_power_of_two(), pk.S.num_io); assert!(pk.S.num_io < pk.S.num_vars); - // append the commitment to R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"C", &pk.S_comm); + // append the verifier key (which includes commitment to R1CS matrices) and the RelaxedR1CSInstance to the transcript + transcript.absorb(b"vk", &pk.vk_digest); transcript.absorb(b"U", U); // compute the full satisfying assignment by concatenating W.W, U.u, and U.X

@@ -1568,8 +1577,8 @@ fn verify(&self, vk: &Self::VerifierKey, U: &RelaxedR1CSInstance<G>) -> Result<(), NovaError> {

let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); let mut u_vec: Vec<PolyEvalInstance<G>> = 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::<G>::decompress(&self.comm_Az)?;