From 8c5bfd0f35d50ad6f15da53b159e6a39a833b1c5 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 5 Sep 2024 17:45:48 -0400 Subject: [PATCH 01/21] randomly sample instance --- src/r1cs/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 21011857..5294d344 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -15,7 +15,7 @@ use crate::{ use core::{cmp::max, marker::PhantomData}; use ff::Field; use once_cell::sync::OnceCell; - +use rand_core::OsRng; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -337,6 +337,53 @@ impl R1CSShape { digest: OnceCell::new(), } } + + /// Samples a new random `RelaxedR1CSInstance`/`RelaxedR1CSWitness` pair + pub fn sample_random_instance_witness( + &self, + ck: &CommitmentKey, + ) -> Result<(RelaxedR1CSInstance, RelaxedR1CSWitness), NovaError> { + // sample + let mut W = Vec::new(); + for _i in 0..self.num_vars { + // probably don't use this in production + W.push(E::Scalar::random(&mut OsRng)); + } + + let mut X = Vec::new(); + for _i in 0..self.num_io { + X.push(E::Scalar::random(&mut OsRng)); + } + + let u = E::Scalar::random(&mut OsRng); + + // Z + let Z = [W.clone(), vec![u], X.clone()].concat(); + + // compute E <- AZ o BZ - u * CZ + let (AZ, BZ, CZ) = self.multiply_vec(&Z)?; + + let E = AZ + .par_iter() + .zip(BZ.par_iter()) + .zip(CZ.par_iter()) + .map(|((az, bz), cz)| *az * *bz - u * *cz) + .collect::>(); + + // compute commitments to W,E + let comm_W = CE::::commit(ck, &W); + let comm_E = CE::::commit(ck, &E); + + Ok(( + RelaxedR1CSInstance { + comm_W, + comm_E, + u, + X, + }, + RelaxedR1CSWitness { W, E }, + )) + } } impl R1CSWitness { @@ -548,7 +595,7 @@ mod tests { use crate::{ provider::{Bn256EngineKZG, PallasEngine, Secp256k1Engine}, r1cs::sparse::SparseMatrix, - traits::Engine, + traits::{snark::default_ck_hint, Engine}, }; fn tiny_r1cs(num_vars: usize) -> R1CSShape { @@ -632,4 +679,18 @@ mod tests { test_pad_tiny_r1cs_with::(); test_pad_tiny_r1cs_with::(); } + + fn test_random_sample_with() { + let r1cs = tiny_r1cs::(4); + let ck = R1CS::::commitment_key(&r1cs, &*default_ck_hint()); + let (inst, wit) = r1cs.sample_random_instance_witness(&ck).unwrap(); + assert!(r1cs.is_sat_relaxed(&ck, &inst, &wit).is_ok()); + } + + #[test] + fn test_random_sample() { + test_random_sample_with::(); + test_random_sample_with::(); + test_random_sample_with::(); + } } From 9f4d39335ff41e11f7d6c24d2d7da5946080e2d2 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Fri, 6 Sep 2024 18:03:50 -0400 Subject: [PATCH 02/21] draft of randomized proving --- src/lib.rs | 209 +++++++++++++++++++++++++++++++++++++++++++ src/nifs.rs | 69 ++++++++++++++ src/r1cs/.mod.rs.swp | Bin 0 -> 16384 bytes src/r1cs/mod.rs | 100 ++++++++++++++++++++- 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 src/r1cs/.mod.rs.swp diff --git a/src/lib.rs b/src/lib.rs index 995d6413..679e42ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -256,6 +256,37 @@ where _p: PhantomData<(C1, C2)>, } +/// Final randomized fold +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RandomRecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + z0_primary: Vec, + z0_secondary: Vec, + r_U_primary: RelaxedR1CSInstance, + r_U_secondary: RelaxedR1CSInstance, + l_u_primary: R1CSInstance, + l_u_secondary: R1CSInstance, + // no r_i ?? + l_Ur_primary: RelaxedR1CSInstance, + l_Ur_secondary: RelaxedR1CSInstance, + nifs_Uf_primary: Commitment, + nifs_Uf_secondary: Commitment, + nifs_Ui_prime_primary: Commitment, + nifs_Ui_prime_secondary: Commitment, + r_Wi_prime_primary: RelaxedR1CSWitness, + r_Wi_prime_secondary: RelaxedR1CSWitness, + i: usize, + zi_primary: Vec, + zi_secondary: Vec, + _p: PhantomData<(C1, C2)>, +} + impl RecursiveSNARK where E1: Engine::Scalar>, @@ -470,6 +501,184 @@ where Ok(()) } + /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) + /// by executing a step of the incremental computation + randomizing step + pub fn prove_step_randomizing( + &mut self, + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + ) -> Result { + + let r_Ui_primary = self.r_U_primary; + let r_Ui_secondary = self.r_U_secondary; + + let r_Wi_primary = self.r_W_primary; + let r_Wi_secondary = self.r_W_secondary; + + let l_ui_secondary = self.l_u_secondary; + let l_wi_secondary = self.l_w_secondary; + + let zim1_primary = self.zi_primary; + let zim1_secondary = self.zi_secondary; + + // 1. regular fold (z_i from this fold remains the same below) + + // first step was already done in the constructor + if self.i == 0 { + self.i = 1; + return Ok(()); + } + + // fold the secondary circuit's instance + let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Ui_secondary, + &r_Wi_secondary, + &l_ui_secondary, + &l_wi_secondary, + )?; + + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::from(self.i as u64), + self.z0_primary.to_vec(), + Some(self.zi_primary.clone()), + Some(r_Ui_secondary.clone()), + Some(l_ui_secondary.clone()), + Some(nifs_Uf_secondary.comm_T), + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + + let (l_ui_primary, l_wi_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // fold the primary circuit's instance + let (nifs_Uf_primary, (r_Uf_primary, r_Wf_primary)) = NIFS::prove( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &r_Ui_primary, + &r_Wi_primary, + &l_ui_primary, + &l_wi_primary, + )?; + + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::from(self.i as u64), + self.z0_secondary.to_vec(), + Some(self.zi_secondary.clone()), + Some(r_Ui_primary.clone()), + Some(l_ui_primary), + Some(nifs_Uf_primary.comm_T), + ); + + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + + let (l_uip1_secondary, l_wip1_secondary) = cs_secondary + .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) + .map_err(|_e| NovaError::UnSat)?; + + // update the running instances and witnesses + self.zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + self.zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + // this ip1 stuff gets thrown out ....? TODO: think about this, what's in here? need V()? + self.l_u_secondary = l_uip1_secondary; + self.l_w_secondary = l_wip1_secondary; + + self.r_U_primary = r_Uf_primary; + self.r_W_primary = r_Wf_primary; + + self.i += 1; + + self.r_U_secondary = r_Uf_secondary; + self.r_W_secondary = r_Wf_secondary; + + + // 2. Randomly sample Relaxed R1CS + let (l_Ur_primary,l_Wr_primary) = pp.r1cs_shape_primary.sample_random_instance_witness(&pp.ck_primary); + let (l_Ur_secondary, l_Wr_secondary) = pp.r1cs_shape_secondary.sample_random_instance_witness(&pp.ck_secondary); + + // 3. random fold + + // fold the secondary circuit's instance + let (nifs_Ui_prime_secondary, (r_Ui_prime_secondary, r_Wi_prime_secondary)) = NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_Ur_secondary, + &l_Wr_secondary, + )?; + + // fold the primary circuit's instance + let (nifs_primary, (r_Ui_primary, r_Wi_prime_primary)) = NIFS::prove_relaxed( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &r_Uf_primary, + &r_Wf_primary, + &l_Ur_primary, + &l_Ur_secondary, + )?; + + // 4. output randomized IVC Proof + Ok(RandomRecursiveSNARK +{ + z0_primary: self.z0_primary, + z0_secondary: self.z0_secondary, + r_U_primary: self.r_U_primary, + r_U_secondary: self.r_U_secondary, + l_u_primary, + l_u_secondary, + // no r_i ?? + l_Ur_primary, + l_Ur_secondary, + // commitments to cross terms + nifs_Uf_primary, + nifs_Uf_secondary, + nifs_Ui_prime_primary, + nifs_Ui_prime_secondary, + r_Wi_prime_primary, + r_Wi_prime_secondary, + self.i, + zi_primary: self.zi_primary, + zi_secondary: self.zi_secondary, +}) + + + } + /// Verify the correctness of the `RecursiveSNARK` pub fn verify( &self, diff --git a/src/nifs.rs b/src/nifs.rs index 6ba63549..e3508982 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -73,6 +73,45 @@ impl NIFS { Ok((Self { comm_T }, (U, W))) } + /// Same as `prove`, but takes two Relaxed R1CS Instance/Witness pairs + pub fn prove_relaxed( + ck: &CommitmentKey, + ro_consts: &ROConstants, + pp_digest: &E::Scalar, + S: &R1CSShape, + U1: &RelaxedR1CSInstance, + W1: &RelaxedR1CSWitness, + U2: &RelaxedR1CSInstance, + W2: &RelaxedR1CSWitness, + ) -> Result<(NIFS, (RelaxedR1CSInstance, RelaxedR1CSWitness)), NovaError> { + // initialize a new RO + let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::(*pp_digest)); + + // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) + U2.absorb_in_ro(&mut ro); + + // compute a commitment to the cross-term + let (T, comm_T) = S.commit_T_relaxed(ck, U1, W1, U2, W2)?; + + // append `comm_T` to the transcript and obtain a challenge + comm_T.absorb_in_ro(&mut ro); + + // compute a challenge from the RO + let r = ro.squeeze(NUM_CHALLENGE_BITS); + + // fold the instance using `r` and `comm_T` + let U = U1.fold_relaxed(U2, &comm_T, &r); + + // fold the witness using `r` and `T` + let W = W1.fold_relaxed(W2, &T, &r)?; + + // return the folded instance and witness + Ok((Self { comm_T }, (U, W))) + } + /// Takes as input a relaxed R1CS instance `U1` and R1CS instance `U2` /// with the same shape and defined with respect to the same parameters, /// and outputs a folded instance `U` with the same shape, @@ -106,6 +145,36 @@ impl NIFS { // return the folded instance Ok(U) } + + /// Same as `prove`, but takes two Relaxed R1CS Instance/Witness pairs + pub fn verify_relaxed( + &self, + ro_consts: &ROConstants, + pp_digest: &E::Scalar, + U1: &RelaxedR1CSInstance, + U2: &RelaxedR1CSInstance, + ) -> Result, NovaError> { + // initialize a new RO + let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::(*pp_digest)); + + // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) + U2.absorb_in_ro(&mut ro); + + // append `comm_T` to the transcript and obtain a challenge + self.comm_T.absorb_in_ro(&mut ro); + + // compute a challenge from the RO + let r = ro.squeeze(NUM_CHALLENGE_BITS); + + // fold the instance using `r` and `comm_T` + let U = U1.fold_relaxed(U2, &self.comm_T, &r); + + // return the folded instance + Ok(U) + } } #[cfg(test)] diff --git a/src/r1cs/.mod.rs.swp b/src/r1cs/.mod.rs.swp new file mode 100644 index 0000000000000000000000000000000000000000..e26fe4979a3f5c2cbde4f246d1c59cdf38aae2e8 GIT binary patch literal 16384 zcmeI3Ta4UR8OL3cw%mH}LxG2$Y|zRRoSB_XQ;3tSG`rchOPUKiO9Cxr)?=TUb;e^m zzU+nVQY2KU5DzHr6H<9Vfd`OU)F3D~2_Y2~P+LF(9*~fb8VT`|wn&vwQsDm`AJ5pc zv$OkBRVp6!mmQz)_+0+yJICKSo;D}W%$;C|$`cAcw<^kaR{q-ZwD0bUu2+v8CJTURSvePYwK1NmO_C-fzb-wq#T&6imASLY!AER_D_$_URVnS3Iz%U3Iz%U z3Iz%U3Iz%U3I*O(3Pk>m%46u!dt|SC`S-ekzyFn=HJN^X!2Ta;k7W8k2kdW2``2Xp z=Vb+HJ=diDOVa+20sGZ~`j=#b)BAVH`*ivD?*n$3T>L2%C=@6ZC=@6ZC=@6ZC=@6Z zC=@6ZC=@6Z_`g)ZuoUH^==n9;NaOu~y8nOn7Df3sSOuR12Z0J+zgbcK4E_LK0Bz6& zH82J40K38En-t|0@H}`DTmWAI23Q1#z^&lw2NmUS;6?Cj@B;V-_!Ovs+rYm*peTO< z&w$6l=fDKG0qh35z{~Gfl$XGhU;#{kkAM$>yTI%3QI0r1S8~h5trT97c9=Hfr zz@6Y1yA|cf;0*XUxC{In2N^#FKLS4lm%vltD5!uBgX_T?IN$gqcn(|uHaG_E1~ise z(}R%_Ud@Jl+_>AYJ)Om&{ROTmLvk?6vTOulE|bF}W`{_3Y>ORbk5VSnbF-OBZ2uIS z=V9zbN9LOQLACyXetSC7o zG`)?zwvByAFNgMONH6@j$yzRR`HG6BRw@-X6L9?0g&8jMn&-F~F|Wm zml+W9DE8InnAsWAP|T`Iq;1!F16III?zNXf7HlFQ`XppEBhM8X8<9zJ_v=Y7{d?sf= zqHq!@k7`JN#v+gTfwyd196l&iPW!?juVXQbx1glOY*$#Lz_23>4|j}JZixn+tj!-^ zpyb7}EOsATOiNkRS&g+~*NkkBN-(0dzK|JAw4b%SK$H!QnmiyMXmghbM&t!y8Mn9b zJE~q8{VM7xdjW0{(qs^xgNvLy$o9xv_Oe>gj!DokNs;NJtDlQl03Qi3PzGDf>DlY= z!VlnoETL|~ZaMwdrCK;@9o zVDpCC*!epD_4n2yq(MLMt!jA20^kgv*(9q0F z^Xoc-1Db}p^cQ4qnjpOl;qnX8 z+b}^XaN7C_nT%k=*V*YM15d2(F(Wd>YCF)}!KGveOXtW%^7IyuFu8s}AZ!*gPm>V6 z5MeDGDihuwR`APbX~8onrfjsR>-u_TVa_br8;3=vz*zHKUH1d-#FTqqn(SJLy#u72 z3g_K+lIp>m8z$Tt;o_QW!Y182F}GT}ejmk3$z^i;y4m%09l<0;l|hFGx}H%Ot-yaQ z^Munf-x0&%a+aQW8%fYkPrD=*MAX{*o&>of^wLX0)yy4+pyX^t%K8U1*P|PF4RT2aA4HL3W7fRF#;P5+`uv<%A_NDQKn)%fV?9d(+b~(Y;RlFXEb-*V z^IAk=Ej+tzx6MLxiFf7tmxR6I9)If>D{6UvWl)>w$rOdImnwZIot-B~iY9JL!4?;*2I)$QA zOXb*I2@F5&UUGbn`{_ghy3J1MdViB64!p;yXf$J=JSS+#3f$4a4!vX4zz)4(#K7kAj~>_tKfgysev4Z|5shA( z;MIz}gfhYlXaI7C;VO!}smEkWCBF!GJH~sZhB#fwjY=lt=3)<&%CIMnkPZiCSt0&fNWWqM9nN_N>pGfwX`V{K^0-56&;jejF~T>Vf{7GPGn;V>mlc*9^v{H|HA2 zQQIO13PVYo|LvEZxpS~uba+vHXcBiHEU|s8%J#FlDrA{h6n;Z*z{Cq0Isn0|k^>BH Vh5}o>x?yTfo7Oh>6IJwU`5%ZW87Tk& literal 0 HcmV?d00001 diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 5294d344..0ae8d254 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -277,6 +277,45 @@ impl R1CSShape { Ok((T, comm_T)) } + /// A method to compute a commitment to the cross-term `T` given two + /// Relaxed R1CS instance-witness pairs + pub fn commit_T_relaxed( + &self, + ck: &CommitmentKey, + U1: &RelaxedR1CSInstance, + W1: &RelaxedR1CSWitness, + U2: &RelaxedR1CSInstance, + W2: &RelaxedR1CSWitness, + ) -> Result<(Vec, Commitment), NovaError> { + let Z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat(); + let Z2 = [W2.W.clone(), vec![U2.u], U2.X.clone()].concat(); + + // TODO JESS - NOT DONE CHANGING + + // The following code uses the optimization suggested in + // Section 5.2 of [Mova](https://eprint.iacr.org/2024/1220.pdf) + let Z = Z1 + .into_par_iter() + .zip(Z2.into_par_iter()) + .map(|(z1, z2)| z1 + z2) + .collect::>(); + let u = U1.u + E::Scalar::ONE; // U2.u = 1 + + let (AZ, BZ, CZ) = self.multiply_vec(&Z)?; + + let T = AZ + .par_iter() + .zip(BZ.par_iter()) + .zip(CZ.par_iter()) + .zip(W1.E.par_iter()) + .map(|(((az, bz), cz), e)| *az * *bz - u * *cz - *e) + .collect::>(); + + let comm_T = CE::::commit(ck, &T); + + Ok((T, comm_T)) + } + /// Pads the `R1CSShape` so that the shape passes `is_regular_shape` /// Renumbers variables to accommodate padded variables pub fn pad(&self) -> Self { @@ -478,6 +517,36 @@ impl RelaxedR1CSWitness { Ok(RelaxedR1CSWitness { W, E }) } + /// Folds an incoming `RelaxedR1CSWitness` into the current one + /// E2 is not necessarily zero vec + pub fn fold_relaxed( + &self, + W2: &RelaxedR1CSWitness, + T: &[E::Scalar], + r: &E::Scalar, + ) -> Result, NovaError> { + let (W1, E1) = (&self.W, &self.E); + let (W2, E2) = (&W2.W, &W2.E); + + if W1.len() != W2.len() { + return Err(NovaError::InvalidWitnessLength); + } + + let W = W1 + .par_iter() + .zip(W2) + .map(|(a, b)| *a + *r * *b) + .collect::>(); + let E = E1 + .par_iter() + .zip(T) + .zip(E2.par_iter()) + .map(|((a, b), c)| *a + *r * *b + *r * *r * *c) + .collect::>(); + + Ok(RelaxedR1CSWitness { W, E }) + } + /// Pads the provided witness to the correct length pub fn pad(&self, S: &R1CSShape) -> RelaxedR1CSWitness { let mut W = self.W.clone(); @@ -529,7 +598,7 @@ impl RelaxedR1CSInstance { } } - /// Folds an incoming `RelaxedR1CSInstance` into the current one + /// Folds an incoming `R1CSInstance` into the current one pub fn fold( &self, U2: &R1CSInstance, @@ -557,6 +626,35 @@ impl RelaxedR1CSInstance { u, } } + + /// Folds an incoming `RelaxedR1CSInstance` into the current one + pub fn fold_relaxed( + &self, + U2: &RelaxedR1CSInstance, + comm_T: &Commitment, + r: &E::Scalar, + ) -> RelaxedR1CSInstance { + let (X1, u1, comm_W_1, comm_E_1) = + (&self.X, &self.u, &self.comm_W.clone(), &self.comm_E.clone()); + let (X2, u2, comm_W_2, comm_E_2) = (&U2.X, &U2.u, &U2.comm_W, &U2.comm_E); + + // weighted sum of X, comm_W, comm_E, and u + let X = X1 + .par_iter() + .zip(X2) + .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 * *r + *comm_E_2 * *r * *r; + let u = *u1 + *r * *u2; + + RelaxedR1CSInstance { + comm_W, + comm_E, + X, + u, + } + } } impl TranscriptReprTrait for RelaxedR1CSInstance { From 15f07b80c45c61759ac47a00b72b29d9031606c9 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Fri, 6 Sep 2024 18:33:25 -0400 Subject: [PATCH 03/21] lib compiles --- src/lib.rs | 163 +++++++++++++++++++++---------------------- src/r1cs/.mod.rs.swp | Bin 16384 -> 0 bytes src/r1cs/mod.rs | 7 +- 3 files changed, 82 insertions(+), 88 deletions(-) delete mode 100644 src/r1cs/.mod.rs.swp diff --git a/src/lib.rs b/src/lib.rs index 679e42ae..33439b05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,18 +268,18 @@ where { z0_primary: Vec, z0_secondary: Vec, - r_U_primary: RelaxedR1CSInstance, - r_U_secondary: RelaxedR1CSInstance, - l_u_primary: R1CSInstance, - l_u_secondary: R1CSInstance, + r_Ui_primary: RelaxedR1CSInstance, + r_Ui_secondary: RelaxedR1CSInstance, + l_ui_primary: R1CSInstance, + l_ui_secondary: R1CSInstance, // no r_i ?? l_Ur_primary: RelaxedR1CSInstance, l_Ur_secondary: RelaxedR1CSInstance, - nifs_Uf_primary: Commitment, - nifs_Uf_secondary: Commitment, - nifs_Ui_prime_primary: Commitment, - nifs_Ui_prime_secondary: Commitment, - r_Wi_prime_primary: RelaxedR1CSWitness, + nifs_Uf_primary: NIFS, + nifs_Uf_secondary: NIFS, + nifs_Ui_prime_primary: NIFS, + nifs_Ui_prime_secondary: NIFS, + r_Wi_prime_primary: RelaxedR1CSWitness, r_Wi_prime_secondary: RelaxedR1CSWitness, i: usize, zi_primary: Vec, @@ -507,27 +507,18 @@ where &mut self, pp: &PublicParams, c_primary: &C1, - c_secondary: &C2, - ) -> Result { - - let r_Ui_primary = self.r_U_primary; - let r_Ui_secondary = self.r_U_secondary; - - let r_Wi_primary = self.r_W_primary; - let r_Wi_secondary = self.r_W_secondary; - - let l_ui_secondary = self.l_u_secondary; - let l_wi_secondary = self.l_w_secondary; - - let zim1_primary = self.zi_primary; - let zim1_secondary = self.zi_secondary; + //c_secondary: &C2, + ) -> Result, NovaError> { + //let zim1_primary = &self.zi_primary; + //let zim1_secondary = &self.zi_secondary; // 1. regular fold (z_i from this fold remains the same below) - + // first step was already done in the constructor if self.i == 0 { self.i = 1; - return Ok(()); + // don't call this until we have something to randomize + return Err(NovaError::InvalidNumSteps); } // fold the secondary circuit's instance @@ -536,10 +527,10 @@ where &pp.ro_consts_secondary, &scalar_as_base::(pp.digest()), &pp.r1cs_shape_secondary, - &r_Ui_secondary, - &r_Wi_secondary, - &l_ui_secondary, - &l_wi_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, )?; let mut cs_primary = SatisfyingAssignment::::new(); @@ -548,8 +539,8 @@ where E1::Scalar::from(self.i as u64), self.z0_primary.to_vec(), Some(self.zi_primary.clone()), - Some(r_Ui_secondary.clone()), - Some(l_ui_secondary.clone()), + Some(self.r_U_secondary.clone()), + Some(self.l_u_secondary.clone()), Some(nifs_Uf_secondary.comm_T), ); @@ -559,7 +550,7 @@ where c_primary, pp.ro_consts_circuit_primary.clone(), ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + let _zi_primary = circuit_primary.synthesize(&mut cs_primary)?; let (l_ui_primary, l_wi_primary) = cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; @@ -570,37 +561,38 @@ where &pp.ro_consts_primary, &pp.digest(), &pp.r1cs_shape_primary, - &r_Ui_primary, - &r_Wi_primary, + &self.r_U_primary, + &self.r_W_primary, &l_ui_primary, &l_wi_primary, )?; - let mut cs_secondary = SatisfyingAssignment::::new(); + /*let mut cs_secondary = SatisfyingAssignment::::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( pp.digest(), E2::Scalar::from(self.i as u64), self.z0_secondary.to_vec(), Some(self.zi_secondary.clone()), - Some(r_Ui_primary.clone()), + Some(self.r_U_primary.clone()), Some(l_ui_primary), Some(nifs_Uf_primary.comm_T), - ); + );*/ - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + /*let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( &pp.augmented_circuit_params_secondary, Some(inputs_secondary), c_secondary, pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + );*/ + //let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - let (l_uip1_secondary, l_wip1_secondary) = cs_secondary + /*let (l_uip1_secondary, l_wip1_secondary) = cs_secondary .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) .map_err(|_e| NovaError::UnSat)?; + */ // update the running instances and witnesses - self.zi_primary = zi_primary + /*self.zi_primary = zi_primary .iter() .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) .collect::::Scalar>, _>>()?; @@ -608,9 +600,9 @@ where .iter() .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) .collect::::Scalar>, _>>()?; - - // this ip1 stuff gets thrown out ....? TODO: think about this, what's in here? need V()? - self.l_u_secondary = l_uip1_secondary; + */ + // this ip1 stuff gets thrown out ....? TODO: think about this, what's in here? need V()? + /*self.l_u_secondary = l_uip1_secondary; self.l_w_secondary = l_wip1_secondary; self.r_U_primary = r_Uf_primary; @@ -620,28 +612,33 @@ where self.r_U_secondary = r_Uf_secondary; self.r_W_secondary = r_Wf_secondary; - + */ // 2. Randomly sample Relaxed R1CS - let (l_Ur_primary,l_Wr_primary) = pp.r1cs_shape_primary.sample_random_instance_witness(&pp.ck_primary); - let (l_Ur_secondary, l_Wr_secondary) = pp.r1cs_shape_secondary.sample_random_instance_witness(&pp.ck_secondary); + let (l_Ur_primary, l_Wr_primary) = pp + .r1cs_shape_primary + .sample_random_instance_witness(&pp.ck_primary)?; + let (l_Ur_secondary, l_Wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; // 3. random fold // fold the secondary circuit's instance - let (nifs_Ui_prime_secondary, (r_Ui_prime_secondary, r_Wi_prime_secondary)) = NIFS::prove_relaxed( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &r_Uf_secondary, - &r_Wf_secondary, - &l_Ur_secondary, - &l_Wr_secondary, - )?; + let (nifs_Ui_prime_secondary, (_r_Ui_prime_secondary, r_Wi_prime_secondary)) = + NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_Ur_secondary, + &l_Wr_secondary, + )?; // fold the primary circuit's instance - let (nifs_primary, (r_Ui_primary, r_Wi_prime_primary)) = NIFS::prove_relaxed( + let (nifs_Ui_prime_primary, (_r_Ui_prime_primary, r_Wi_prime_primary)) = NIFS::prove_relaxed( &pp.ck_primary, &pp.ro_consts_primary, &pp.digest(), @@ -649,34 +646,32 @@ where &r_Uf_primary, &r_Wf_primary, &l_Ur_primary, - &l_Ur_secondary, + &l_Wr_primary, )?; // 4. output randomized IVC Proof - Ok(RandomRecursiveSNARK -{ - z0_primary: self.z0_primary, - z0_secondary: self.z0_secondary, - r_U_primary: self.r_U_primary, - r_U_secondary: self.r_U_secondary, - l_u_primary, - l_u_secondary, - // no r_i ?? - l_Ur_primary, - l_Ur_secondary, - // commitments to cross terms - nifs_Uf_primary, - nifs_Uf_secondary, - nifs_Ui_prime_primary, - nifs_Ui_prime_secondary, - r_Wi_prime_primary, - r_Wi_prime_secondary, - self.i, - zi_primary: self.zi_primary, - zi_secondary: self.zi_secondary, -}) - - + Ok(RandomRecursiveSNARK { + z0_primary: self.z0_primary.clone(), + z0_secondary: self.z0_secondary.clone(), + r_Ui_primary: self.r_U_primary.clone(), + r_Ui_secondary: self.r_U_secondary.clone(), + l_ui_primary, + l_ui_secondary: self.l_u_secondary.clone(), + // no r_i ?? + l_Ur_primary, + l_Ur_secondary, + // commitments to cross terms + nifs_Uf_primary, + nifs_Uf_secondary, + nifs_Ui_prime_primary, + nifs_Ui_prime_secondary, + r_Wi_prime_primary, + r_Wi_prime_secondary, + i: self.i, + zi_primary: self.zi_primary.clone(), + zi_secondary: self.zi_secondary.clone(), + _p: Default::default(), + }) } /// Verify the correctness of the `RecursiveSNARK` diff --git a/src/r1cs/.mod.rs.swp b/src/r1cs/.mod.rs.swp deleted file mode 100644 index e26fe4979a3f5c2cbde4f246d1c59cdf38aae2e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3Ta4UR8OL3cw%mH}LxG2$Y|zRRoSB_XQ;3tSG`rchOPUKiO9Cxr)?=TUb;e^m zzU+nVQY2KU5DzHr6H<9Vfd`OU)F3D~2_Y2~P+LF(9*~fb8VT`|wn&vwQsDm`AJ5pc zv$OkBRVp6!mmQz)_+0+yJICKSo;D}W%$;C|$`cAcw<^kaR{q-ZwD0bUu2+v8CJTURSvePYwK1NmO_C-fzb-wq#T&6imASLY!AER_D_$_URVnS3Iz%U3Iz%U z3Iz%U3Iz%U3I*O(3Pk>m%46u!dt|SC`S-ekzyFn=HJN^X!2Ta;k7W8k2kdW2``2Xp z=Vb+HJ=diDOVa+20sGZ~`j=#b)BAVH`*ivD?*n$3T>L2%C=@6ZC=@6ZC=@6ZC=@6Z zC=@6ZC=@6Z_`g)ZuoUH^==n9;NaOu~y8nOn7Df3sSOuR12Z0J+zgbcK4E_LK0Bz6& zH82J40K38En-t|0@H}`DTmWAI23Q1#z^&lw2NmUS;6?Cj@B;V-_!Ovs+rYm*peTO< z&w$6l=fDKG0qh35z{~Gfl$XGhU;#{kkAM$>yTI%3QI0r1S8~h5trT97c9=Hfr zz@6Y1yA|cf;0*XUxC{In2N^#FKLS4lm%vltD5!uBgX_T?IN$gqcn(|uHaG_E1~ise z(}R%_Ud@Jl+_>AYJ)Om&{ROTmLvk?6vTOulE|bF}W`{_3Y>ORbk5VSnbF-OBZ2uIS z=V9zbN9LOQLACyXetSC7o zG`)?zwvByAFNgMONH6@j$yzRR`HG6BRw@-X6L9?0g&8jMn&-F~F|Wm zml+W9DE8InnAsWAP|T`Iq;1!F16III?zNXf7HlFQ`XppEBhM8X8<9zJ_v=Y7{d?sf= zqHq!@k7`JN#v+gTfwyd196l&iPW!?juVXQbx1glOY*$#Lz_23>4|j}JZixn+tj!-^ zpyb7}EOsATOiNkRS&g+~*NkkBN-(0dzK|JAw4b%SK$H!QnmiyMXmghbM&t!y8Mn9b zJE~q8{VM7xdjW0{(qs^xgNvLy$o9xv_Oe>gj!DokNs;NJtDlQl03Qi3PzGDf>DlY= z!VlnoETL|~ZaMwdrCK;@9o zVDpCC*!epD_4n2yq(MLMt!jA20^kgv*(9q0F z^Xoc-1Db}p^cQ4qnjpOl;qnX8 z+b}^XaN7C_nT%k=*V*YM15d2(F(Wd>YCF)}!KGveOXtW%^7IyuFu8s}AZ!*gPm>V6 z5MeDGDihuwR`APbX~8onrfjsR>-u_TVa_br8;3=vz*zHKUH1d-#FTqqn(SJLy#u72 z3g_K+lIp>m8z$Tt;o_QW!Y182F}GT}ejmk3$z^i;y4m%09l<0;l|hFGx}H%Ot-yaQ z^Munf-x0&%a+aQW8%fYkPrD=*MAX{*o&>of^wLX0)yy4+pyX^t%K8U1*P|PF4RT2aA4HL3W7fRF#;P5+`uv<%A_NDQKn)%fV?9d(+b~(Y;RlFXEb-*V z^IAk=Ej+tzx6MLxiFf7tmxR6I9)If>D{6UvWl)>w$rOdImnwZIot-B~iY9JL!4?;*2I)$QA zOXb*I2@F5&UUGbn`{_ghy3J1MdViB64!p;yXf$J=JSS+#3f$4a4!vX4zz)4(#K7kAj~>_tKfgysev4Z|5shA( z;MIz}gfhYlXaI7C;VO!}smEkWCBF!GJH~sZhB#fwjY=lt=3)<&%CIMnkPZiCSt0&fNWWqM9nN_N>pGfwX`V{K^0-56&;jejF~T>Vf{7GPGn;V>mlc*9^v{H|HA2 zQQIO13PVYo|LvEZxpS~uba+vHXcBiHEU|s8%J#FlDrA{h6n;Z*z{Cq0Isn0|k^>BH Vh5}o>x?yTfo7Oh>6IJwU`5%ZW87Tk& diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 0ae8d254..5b339d84 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -290,8 +290,6 @@ impl R1CSShape { let Z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat(); let Z2 = [W2.W.clone(), vec![U2.u], U2.X.clone()].concat(); - // TODO JESS - NOT DONE CHANGING - // The following code uses the optimization suggested in // Section 5.2 of [Mova](https://eprint.iacr.org/2024/1220.pdf) let Z = Z1 @@ -299,7 +297,7 @@ impl R1CSShape { .zip(Z2.into_par_iter()) .map(|(z1, z2)| z1 + z2) .collect::>(); - let u = U1.u + E::Scalar::ONE; // U2.u = 1 + let u = U1.u + U2.u; let (AZ, BZ, CZ) = self.multiply_vec(&Z)?; @@ -308,7 +306,8 @@ impl R1CSShape { .zip(BZ.par_iter()) .zip(CZ.par_iter()) .zip(W1.E.par_iter()) - .map(|(((az, bz), cz), e)| *az * *bz - u * *cz - *e) + .zip(W2.E.par_iter()) + .map(|((((az, bz), cz), e1), e2)| *az * *bz - u * *cz - *e1 - *e2) .collect::>(); let comm_T = CE::::commit(ck, &T); From a95548d9847f2b8921ea229c39cd8536f947f673 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Mon, 9 Sep 2024 13:06:40 -0400 Subject: [PATCH 04/21] nifs tests pass --- src/constants.rs | 1 + src/nifs.rs | 195 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 89571ccf..65a132dd 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,3 +4,4 @@ pub(crate) const BN_LIMB_WIDTH: usize = 64; pub(crate) const BN_N_LIMBS: usize = 4; pub(crate) const NUM_FE_WITHOUT_IO_FOR_CRHF: usize = 17; pub(crate) const NUM_FE_FOR_RO: usize = 9; +pub(crate) const NUM_FE_FOR_RO_RELAXED: usize = 19; diff --git a/src/nifs.rs b/src/nifs.rs index e3508982..e7dc0872 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] use crate::{ - constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO}, + constants::{NUM_CHALLENGE_BITS, NUM_FE_FOR_RO, NUM_FE_FOR_RO_RELAXED}, errors::NovaError, r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness}, scalar_as_base, @@ -85,7 +85,7 @@ impl NIFS { W2: &RelaxedR1CSWitness, ) -> Result<(NIFS, (RelaxedR1CSInstance, RelaxedR1CSWitness)), NovaError> { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO_RELAXED); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); @@ -155,7 +155,7 @@ impl NIFS { U2: &RelaxedR1CSInstance, ) -> Result, NovaError> { // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO_RELAXED); // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); @@ -321,6 +321,195 @@ mod tests { assert!(shape.is_sat_relaxed(ck, &r_U, &r_W).is_ok()); } + fn execute_sequence_relaxed( + ck: &CommitmentKey, + ro_consts: &<::RO as ROTrait<::Base, ::Scalar>>::Constants, + pp_digest: &::Scalar, + shape: &R1CSShape, + U1: &RelaxedR1CSInstance, + W1: &RelaxedR1CSWitness, + U2: &RelaxedR1CSInstance, + W2: &RelaxedR1CSWitness, + ) { + // produce a default running instance + 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_relaxed(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_relaxed(ro_consts, pp_digest, &r_U, U1); + assert!(res.is_ok()); + let U = res.unwrap(); + + assert_eq!(U, _U); + + // update the running witness and instance + r_W = W; + r_U = U; + + // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair + let res = NIFS::prove_relaxed(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_relaxed(ro_consts, pp_digest, &r_U, U2); + assert!(res.is_ok()); + let U = res.unwrap(); + + assert_eq!(U, _U); + + // update the running witness and instance + r_W = W; + r_U = U; + + // check if the running instance is satisfiable + assert!(shape.is_sat_relaxed(ck, &r_U, &r_W).is_ok()); + } + + fn test_tiny_r1cs_relaxed_with() { + let one = ::ONE; + let (num_cons, num_vars, num_io, A, B, C) = { + let num_cons = 4; + let num_vars = 3; + let num_io = 2; + + // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. + // The R1CS for this problem consists of the following constraints: + // `I0 * I0 - Z0 = 0` + // `Z0 * I0 - Z1 = 0` + // `(Z1 + I0) * 1 - Z2 = 0` + // `(Z2 + 5) * 1 - I1 = 0` + + // Relaxed R1CS is a set of three sparse matrices (A B C), where there is a row for every + // constraint and a column for every entry in z = (vars, u, inputs) + // An R1CS instance is satisfiable iff: + // Az \circ Bz = u \cdot Cz + E, where z = (vars, 1, inputs) + let mut A: Vec<(usize, usize, E::Scalar)> = Vec::new(); + let mut B: Vec<(usize, usize, E::Scalar)> = Vec::new(); + let mut C: Vec<(usize, usize, E::Scalar)> = Vec::new(); + + // constraint 0 entries in (A,B,C) + // `I0 * I0 - Z0 = 0` + A.push((0, num_vars + 1, one)); + B.push((0, num_vars + 1, one)); + C.push((0, 0, one)); + + // constraint 1 entries in (A,B,C) + // `Z0 * I0 - Z1 = 0` + A.push((1, 0, one)); + B.push((1, num_vars + 1, one)); + C.push((1, 1, one)); + + // constraint 2 entries in (A,B,C) + // `(Z1 + I0) * 1 - Z2 = 0` + A.push((2, 1, one)); + A.push((2, num_vars + 1, one)); + B.push((2, num_vars, one)); + C.push((2, 2, one)); + + // constraint 3 entries in (A,B,C) + // `(Z2 + 5) * 1 - I1 = 0` + A.push((3, 2, one)); + A.push((3, num_vars, one + one + one + one + one)); + B.push((3, num_vars, one)); + C.push((3, num_vars + 2, one)); + + (num_cons, num_vars, num_io, A, B, C) + }; + + // create a shape object + let rows = num_cons; + let num_inputs = num_io + 1; + let cols = num_vars + num_inputs; + let S = { + let res = R1CSShape::new( + num_cons, + num_vars, + num_inputs - 1, + SparseMatrix::new(&A, rows, cols), + SparseMatrix::new(&B, rows, cols), + SparseMatrix::new(&C, rows, cols), + ); + assert!(res.is_ok()); + res.unwrap() + }; + + // generate generators and ro constants + let ck = R1CS::::commitment_key(&S, &*default_ck_hint()); + let ro_consts = + <::RO as ROTrait<::Base, ::Scalar>>::Constants::default(); + + let rand_inst_witness_generator = + |ck: &CommitmentKey, I: &E::Scalar| -> (E::Scalar, R1CSInstance, R1CSWitness) { + let i0 = *I; + + // compute a satisfying (vars, X) tuple + let (O, vars, X) = { + let z0 = i0 * i0; // constraint 0 + let z1 = i0 * z0; // constraint 1 + let z2 = z1 + i0; // constraint 2 + let i1 = z2 + one + one + one + one + one; // constraint 3 + + // store the witness and IO for the instance + let W = vec![z0, z1, z2]; + let X = vec![i0, i1]; + (i1, W, X) + }; + + let W = { + let res = R1CSWitness::new(&S, &vars); + assert!(res.is_ok()); + res.unwrap() + }; + let U = { + let comm_W = W.commit(ck); + let res = R1CSInstance::new(&S, &comm_W, &X); + assert!(res.is_ok()); + res.unwrap() + }; + + // check that generated instance is satisfiable + assert!(S.is_sat(ck, &U, &W).is_ok()); + + (O, U, W) + }; + + let mut csprng: OsRng = OsRng; + let I = E::Scalar::random(&mut csprng); // the first input is picked randomly for the first instance + let (_O, U1, W1) = rand_inst_witness_generator(&ck, &I); + let (U2, W2) = S.sample_random_instance_witness(&ck).unwrap(); // random fold + + //let (_O, U2, W2) = rand_inst_witness_generator(&ck, &O); + + println!("INSTANCE {:#?}", U1.clone()); + + // execute a sequence of folds + execute_sequence_relaxed( + &ck, + &ro_consts, + &::Scalar::ZERO, + &S, + &RelaxedR1CSInstance::from_r1cs_instance(&ck, &S, &U1), + &RelaxedR1CSWitness::from_r1cs_witness(&S, &W1), + &U2, + &W2, + //&RelaxedR1CSInstance::from_r1cs_instance(&ck, &S, &U2), + //&RelaxedR1CSWitness::from_r1cs_witness(&S, &W2), + ); + } + + #[test] + fn test_tiny_r1cs_relaxed() { + test_tiny_r1cs_relaxed_with::(); + test_tiny_r1cs_relaxed_with::(); + test_tiny_r1cs_relaxed_with::(); + } + fn test_tiny_r1cs_with() { let one = ::ONE; let (num_cons, num_vars, num_io, A, B, C) = { From 9c50a86aa94d9da5be8f913831538afab642c81a Mon Sep 17 00:00:00 2001 From: jkwoods Date: Mon, 9 Sep 2024 20:30:23 -0400 Subject: [PATCH 05/21] recursive verify sketch --- src/lib.rs | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 33439b05..5c5b5129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,7 +272,6 @@ where r_Ui_secondary: RelaxedR1CSInstance, l_ui_primary: R1CSInstance, l_ui_secondary: R1CSInstance, - // no r_i ?? l_Ur_primary: RelaxedR1CSInstance, l_Ur_secondary: RelaxedR1CSInstance, nifs_Uf_primary: NIFS, @@ -791,6 +790,155 @@ where } } +impl RandomRecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Verify the correctness of the `RecursiveSNARK` randomizing layer + pub fn verify( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // TODO - WHY? check if the (relaxed) R1CS instances have two public outputs + //let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + let is_instance_has_two_outpus = self.r_Ui_primary.X.len() != 2 + || self.r_Ui_secondary.X.len() != 2 + || self.l_Ur_primary.X.len() != 2 + || self.l_Ur_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outpus + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + pp.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, + ); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_Ui_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + pp.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_Ui_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + // TODO - what is this? + if hash_primary != self.l_ui_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_ui_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // Check that (ui.E, ui.u) = (u⊥.E, 1). not necesssary for regular R1CSInstance + + // Fold the instance Ui with ui using T to get a folded instance Uf . + let r_Uf_primary = self.nifs_Uf_primary.verify( + &pp.ro_consts_primary, + &pp.digest(), + &self.r_Ui_primary, + &self.l_ui_primary, + )?; + let r_Uf_secondary = self.nifs_Uf_secondary.verify( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &self.r_Ui_secondary, + &self.l_ui_secondary, + )?; + + // Fold the instance Uf with Ur using T′ to get a folded instance Ui′ + let r_Ui_prime_primary = self.nifs_Ui_prime_primary.verify_relaxed( + &pp.ro_consts_primary, + &pp.digest(), + &r_Uf_primary, + &self.l_Ur_primary, + )?; + let r_Ui_prime_secondary = self.nifs_Ui_prime_secondary.verify_relaxed( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &r_Uf_secondary, + &self.l_Ur_secondary, + )?; + + // Check that Wi′ is a satisfying witness to Ui′ + let (res_r_secondary, res_r_primary) = rayon::join( + || { + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &r_Ui_prime_secondary, + &self.r_Wi_prime_secondary, + ) + }, + || { + pp.r1cs_shape_primary.is_sat_relaxed( + &pp.ck_primary, + &r_Ui_prime_primary, + &self.r_Wi_prime_primary, + ) + }, + ); + + // check the returned res objects + res_r_primary?; + res_r_secondary?; + //res_l_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } + + /// Get the outputs after the last step of computation. + pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { + (&self.zi_primary, &self.zi_secondary) + } + + /// The number of steps which have been executed thus far. + pub fn num_steps(&self) -> usize { + self.i + } +} + /// A type that holds the prover key for `CompressedSNARK` #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] From 6d8ebcc741cdbb1f2d9b092f1b976112683e3170 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Wed, 11 Sep 2024 16:31:19 -0400 Subject: [PATCH 06/21] correct verifying recursivesnark --- src/circuit.rs | 52 ++ src/lib.rs | 309 +++------ temp.rs | 1788 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1924 insertions(+), 225 deletions(-) create mode 100644 temp.rs diff --git a/src/circuit.rs b/src/circuit.rs index 1402e934..e9d2afb6 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -252,6 +252,58 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { Ok((U_fold, check_pass)) } + + /* /// Synthesizes non base case and returns the new relaxed `R1CSInstance` + /// And a boolean indicating if all checks pass + fn synthesize_last_random_case::Base>>( + &self, + mut cs: CS, + params: &AllocatedNum, + i: &AllocatedNum, + z_0: &[AllocatedNum], + z_i: &[AllocatedNum], + U: &AllocatedRelaxedR1CSInstance, + u: &AllocatedR1CSInstance, + T: &AllocatedPoint, + arity: usize, + ) -> Result<(AllocatedRelaxedR1CSInstance, AllocatedBit), SynthesisError> { + // Check that u.x[0] = Hash(params, U, i, z0, zi) + let mut ro = E::ROCircuit::new( + self.ro_consts.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * arity, + ); + ro.absorb(params); + ro.absorb(i); + for e in z_0 { + ro.absorb(e); + } + for e in z_i { + ro.absorb(e); + } + U.absorb_in_ro(cs.namespace(|| "absorb U"), &mut ro)?; + + let hash_bits = ro.squeeze(cs.namespace(|| "Input hash"), NUM_HASH_BITS)?; + let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), &hash_bits)?; + let check_pass = alloc_num_equals( + cs.namespace(|| "check consistency of u.X[0] with H(params, U, i, z0, zi)"), + &u.X0, + &hash, + )?; + + // Run NIFS Verifier + let U_fold = U.fold_with_r1cs( + cs.namespace(|| "compute fold of U and u"), + params, + u, + T, + self.ro_consts.clone(), + self.params.limb_width, + self.params.n_limbs, + )?; + + Ok((U_fold, check_pass)) + } + */ } impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { diff --git a/src/lib.rs b/src/lib.rs index 5c5b5129..9c828f46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,37 +253,25 @@ where i: usize, zi_primary: Vec, zi_secondary: Vec, + random_layer: Option>, _p: PhantomData<(C1, C2)>, } /// Final randomized fold #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -pub struct RandomRecursiveSNARK +pub struct RandomLayer where E1: Engine::Scalar>, E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, { - z0_primary: Vec, - z0_secondary: Vec, - r_Ui_primary: RelaxedR1CSInstance, - r_Ui_secondary: RelaxedR1CSInstance, - l_ui_primary: R1CSInstance, - l_ui_secondary: R1CSInstance, - l_Ur_primary: RelaxedR1CSInstance, - l_Ur_secondary: RelaxedR1CSInstance, - nifs_Uf_primary: NIFS, + l_ur_primary: RelaxedR1CSInstance, + l_ur_secondary: RelaxedR1CSInstance, nifs_Uf_secondary: NIFS, - nifs_Ui_prime_primary: NIFS, - nifs_Ui_prime_secondary: NIFS, - r_Wi_prime_primary: RelaxedR1CSWitness, - r_Wi_prime_secondary: RelaxedR1CSWitness, - i: usize, - zi_primary: Vec, - zi_secondary: Vec, - _p: PhantomData<(C1, C2)>, + nifs_Un_primary: NIFS, + nifs_Un_secondary: NIFS, + r_Wn_primary: RelaxedR1CSWitness, + r_Wn_secondary: RelaxedR1CSWitness, } impl RecursiveSNARK @@ -389,6 +377,7 @@ where i: 0, zi_primary, zi_secondary, + random_layer: None, _p: Default::default(), }) } @@ -500,27 +489,14 @@ where Ok(()) } - /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) - /// by executing a step of the incremental computation + randomizing step - pub fn prove_step_randomizing( - &mut self, - pp: &PublicParams, - c_primary: &C1, - //c_secondary: &C2, - ) -> Result, NovaError> { - //let zim1_primary = &self.zi_primary; - //let zim1_secondary = &self.zi_secondary; - - // 1. regular fold (z_i from this fold remains the same below) - - // first step was already done in the constructor + /// randomize the last step computed + pub fn randomizing_fold(&mut self, pp: &PublicParams) -> Result<(), NovaError> { if self.i == 0 { - self.i = 1; // don't call this until we have something to randomize return Err(NovaError::InvalidNumSteps); } - // fold the secondary circuit's instance + // fold secondary U/W with secondary u/w to get Uf/Wf let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, @@ -532,145 +508,51 @@ where &self.l_w_secondary, )?; - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::from(self.i as u64), - self.z0_primary.to_vec(), - Some(self.zi_primary.clone()), - Some(self.r_U_secondary.clone()), - Some(self.l_u_secondary.clone()), - Some(nifs_Uf_secondary.comm_T), - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let _zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - - let (l_ui_primary, l_wi_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + // fold Uf/Wf with random inst/wit to get U1/W1 + let (l_ur_secondary, l_wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; - // fold the primary circuit's instance - let (nifs_Uf_primary, (r_Uf_primary, r_Wf_primary)) = NIFS::prove( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &self.r_U_primary, - &self.r_W_primary, - &l_ui_primary, - &l_wi_primary, + let (nifs_Un_secondary, (_r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_ur_secondary, + &l_wr_secondary, )?; - /*let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::from(self.i as u64), - self.z0_secondary.to_vec(), - Some(self.zi_secondary.clone()), - Some(self.r_U_primary.clone()), - Some(l_ui_primary), - Some(nifs_Uf_primary.comm_T), - );*/ - - /*let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - );*/ - //let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - - /*let (l_uip1_secondary, l_wip1_secondary) = cs_secondary - .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) - .map_err(|_e| NovaError::UnSat)?; - */ - - // update the running instances and witnesses - /*self.zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - self.zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - */ - // this ip1 stuff gets thrown out ....? TODO: think about this, what's in here? need V()? - /*self.l_u_secondary = l_uip1_secondary; - self.l_w_secondary = l_wip1_secondary; - - self.r_U_primary = r_Uf_primary; - self.r_W_primary = r_Wf_primary; - - self.i += 1; - - self.r_U_secondary = r_Uf_secondary; - self.r_W_secondary = r_Wf_secondary; - */ - - // 2. Randomly sample Relaxed R1CS - let (l_Ur_primary, l_Wr_primary) = pp + // fold primary U/W with random inst/wit to get U2/W2 + let (l_ur_primary, l_wr_primary) = pp .r1cs_shape_primary .sample_random_instance_witness(&pp.ck_primary)?; - let (l_Ur_secondary, l_Wr_secondary) = pp - .r1cs_shape_secondary - .sample_random_instance_witness(&pp.ck_secondary)?; - - // 3. random fold - - // fold the secondary circuit's instance - let (nifs_Ui_prime_secondary, (_r_Ui_prime_secondary, r_Wi_prime_secondary)) = - NIFS::prove_relaxed( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &r_Uf_secondary, - &r_Wf_secondary, - &l_Ur_secondary, - &l_Wr_secondary, - )?; - // fold the primary circuit's instance - let (nifs_Ui_prime_primary, (_r_Ui_prime_primary, r_Wi_prime_primary)) = NIFS::prove_relaxed( + let (nifs_Un_primary, (_r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( &pp.ck_primary, &pp.ro_consts_primary, &pp.digest(), &pp.r1cs_shape_primary, - &r_Uf_primary, - &r_Wf_primary, - &l_Ur_primary, - &l_Wr_primary, + &self.r_U_primary, + &self.r_W_primary, + &l_ur_primary, + &l_wr_primary, )?; - // 4. output randomized IVC Proof - Ok(RandomRecursiveSNARK { - z0_primary: self.z0_primary.clone(), - z0_secondary: self.z0_secondary.clone(), - r_Ui_primary: self.r_U_primary.clone(), - r_Ui_secondary: self.r_U_secondary.clone(), - l_ui_primary, - l_ui_secondary: self.l_u_secondary.clone(), - // no r_i ?? - l_Ur_primary, - l_Ur_secondary, + // output randomized IVC Proof + self.random_layer = Some(RandomLayer { + l_ur_primary, + l_ur_secondary, // commitments to cross terms - nifs_Uf_primary, nifs_Uf_secondary, - nifs_Ui_prime_primary, - nifs_Ui_prime_secondary, - r_Wi_prime_primary, - r_Wi_prime_secondary, - i: self.i, - zi_primary: self.zi_primary.clone(), - zi_secondary: self.zi_secondary.clone(), - _p: Default::default(), - }) + nifs_Un_primary, + nifs_Un_secondary, + r_Wn_primary, + r_Wn_secondary, + }); + + Ok(()) } /// Verify the correctness of the `RecursiveSNARK` @@ -788,23 +670,20 @@ where pub fn num_steps(&self) -> usize { self.i } -} -impl RandomRecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ /// Verify the correctness of the `RecursiveSNARK` randomizing layer - pub fn verify( + pub fn verify_randomizing( &self, pp: &PublicParams, num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + return Err(NovaError::ProofVerifyError); + } + let random_layer = self.random_layer.as_ref().unwrap(); + // number of steps cannot be zero let is_num_steps_zero = num_steps == 0; @@ -814,12 +693,10 @@ where // check if the initial inputs match let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - // TODO - WHY? check if the (relaxed) R1CS instances have two public outputs - //let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - let is_instance_has_two_outpus = self.r_Ui_primary.X.len() != 2 - || self.r_Ui_secondary.X.len() != 2 - || self.l_Ur_primary.X.len() != 2 - || self.l_Ur_secondary.X.len() != 2; + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; if is_num_steps_zero || is_num_steps_not_match @@ -843,7 +720,7 @@ where for e in &self.zi_primary { hasher.absorb(*e); } - self.r_Ui_secondary.absorb_in_ro(&mut hasher); + self.r_U_secondary.absorb_in_ro(&mut hasher); let mut hasher2 = ::RO::new( pp.ro_consts_primary.clone(), @@ -857,7 +734,7 @@ where for e in &self.zi_secondary { hasher2.absorb(*e); } - self.r_Ui_primary.absorb_in_ro(&mut hasher2); + self.r_U_primary.absorb_in_ro(&mut hasher2); ( hasher.squeeze(NUM_HASH_BITS), @@ -865,78 +742,60 @@ where ) }; - // TODO - what is this? - if hash_primary != self.l_ui_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_ui_secondary.X[1]) + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) { return Err(NovaError::ProofVerifyError); } - // Check that (ui.E, ui.u) = (u⊥.E, 1). not necesssary for regular R1CSInstance - - // Fold the instance Ui with ui using T to get a folded instance Uf . - let r_Uf_primary = self.nifs_Uf_primary.verify( - &pp.ro_consts_primary, - &pp.digest(), - &self.r_Ui_primary, - &self.l_ui_primary, - )?; - let r_Uf_secondary = self.nifs_Uf_secondary.verify( + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( &pp.ro_consts_secondary, &scalar_as_base::(pp.digest()), - &self.r_Ui_secondary, - &self.l_ui_secondary, + &self.r_U_secondary, + &self.l_u_secondary, )?; - // Fold the instance Uf with Ur using T′ to get a folded instance Ui′ - let r_Ui_prime_primary = self.nifs_Ui_prime_primary.verify_relaxed( - &pp.ro_consts_primary, - &pp.digest(), - &r_Uf_primary, - &self.l_Ur_primary, - )?; - let r_Ui_prime_secondary = self.nifs_Ui_prime_secondary.verify_relaxed( + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( &pp.ro_consts_secondary, &scalar_as_base::(pp.digest()), &r_Uf_secondary, - &self.l_Ur_secondary, + &random_layer.l_ur_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + &pp.ro_consts_primary, + &pp.digest(), + &self.r_U_primary, + &random_layer.l_ur_primary, )?; - // Check that Wi′ is a satisfying witness to Ui′ - let (res_r_secondary, res_r_primary) = rayon::join( + // check the satisfiability of U1, U2 + let (res_primary, res_secondary) = rayon::join( || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &r_Ui_prime_secondary, - &self.r_Wi_prime_secondary, + pp.r1cs_shape_primary.is_sat_relaxed( + &pp.ck_primary, + &r_Un_primary, + &random_layer.r_Wn_primary, ) }, || { - pp.r1cs_shape_primary.is_sat_relaxed( - &pp.ck_primary, - &r_Ui_prime_primary, - &self.r_Wi_prime_primary, + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &r_Un_secondary, + &random_layer.r_Wn_secondary, ) }, ); // check the returned res objects - res_r_primary?; - res_r_secondary?; - //res_l_secondary?; + res_primary?; + res_secondary?; Ok((self.zi_primary.clone(), self.zi_secondary.clone())) } - - /// Get the outputs after the last step of computation. - pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { - (&self.zi_primary, &self.zi_secondary) - } - - /// The number of steps which have been executed thus far. - pub fn num_steps(&self) -> usize { - self.i - } } /// A type that holds the prover key for `CompressedSNARK` diff --git a/temp.rs b/temp.rs new file mode 100644 index 00000000..d543b3f0 --- /dev/null +++ b/temp.rs @@ -0,0 +1,1788 @@ +//! This library implements Nova, a high-speed recursive SNARK. +#![deny( + warnings, + unused, + future_incompatible, + nonstandard_style, + rust_2018_idioms, + missing_docs +)] +#![allow(non_snake_case)] +#![forbid(unsafe_code)] + +// private modules +mod bellpepper; +mod circuit; +mod constants; +mod digest; +mod nifs; +mod r1cs; + +// public modules +pub mod errors; +pub mod gadgets; +pub mod provider; +pub mod spartan; +pub mod traits; + +use once_cell::sync::OnceCell; + +use crate::bellpepper::{ + r1cs::{NovaShape, NovaWitness}, + shape_cs::ShapeCS, + solver::SatisfyingAssignment, +}; +use crate::digest::{DigestComputer, SimpleDigestible}; +use bellpepper_core::{ConstraintSystem, SynthesisError}; +use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; +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 gadgets::utils::scalar_as_base; +use nifs::NIFS; +use r1cs::{ + CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, +}; +use serde::{Deserialize, Serialize}; +use traits::{ + circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, + AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, +}; + +/// A type that holds public parameters of Nova +#[derive(Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_circuit_primary: ROConstantsCircuit, + ck_primary: CommitmentKey, + r1cs_shape_primary: R1CSShape, + ro_consts_secondary: ROConstants, + ro_consts_circuit_secondary: ROConstantsCircuit, + ck_secondary: CommitmentKey, + r1cs_shape_secondary: R1CSShape, + augmented_circuit_params_primary: NovaAugmentedCircuitParams, + augmented_circuit_params_secondary: NovaAugmentedCircuitParams, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, + _p: PhantomData<(C1, C2)>, +} + +impl SimpleDigestible for PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ +} + +impl PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. + /// + /// # Note + /// + /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. + /// + /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require + /// larger sizes for these parameters. These SNARKs provide a hint for these values by + /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. + /// + /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. + /// + /// # Arguments + /// + /// * `c_primary`: The primary circuit of type `C1`. + /// * `c_secondary`: The secondary circuit of type `C2`. + /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint + /// for the number of generators required in the commitment scheme for the primary circuit. + /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. + /// + /// # Example + /// + /// ```rust + /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; + /// # use nova_snark::provider::ipa_pc::EvaluationEngine; + /// # use nova_snark::provider::{PallasEngine, VestaEngine}; + /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; + /// use nova_snark::PublicParams; + /// + /// type E1 = PallasEngine; + /// type E2 = VestaEngine; + /// type EE = EvaluationEngine; + /// type SPrime = RelaxedR1CSSNARK>; + /// + /// let circuit1 = TrivialCircuit::<::Scalar>::default(); + /// let circuit2 = TrivialCircuit::<::Scalar>::default(); + /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) + /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. + /// let ck_hint1 = &*SPrime::::ck_floor(); + /// let ck_hint2 = &*SPrime::::ck_floor(); + /// + /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); + /// ``` + pub fn setup( + c_primary: &C1, + c_secondary: &C2, + ck_hint1: &CommitmentKeyHint, + ck_hint2: &CommitmentKeyHint, + ) -> Result { + let augmented_circuit_params_primary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); + let augmented_circuit_params_secondary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); + + let ro_consts_primary: ROConstants = ROConstants::::default(); + let ro_consts_secondary: ROConstants = ROConstants::::default(); + + let F_arity_primary = c_primary.arity(); + let F_arity_secondary = c_secondary.arity(); + + // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar + let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); + let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); + + // Initialize ck for the primary + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &augmented_circuit_params_primary, + None, + c_primary, + ro_consts_circuit_primary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_primary.synthesize(&mut cs); + let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); + + // Initialize ck for the secondary + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &augmented_circuit_params_secondary, + None, + c_secondary, + ro_consts_circuit_secondary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_secondary.synthesize(&mut cs); + let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); + + if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { + return Err(NovaError::InvalidStepCircuitIO); + } + + let pp = PublicParams { + F_arity_primary, + F_arity_secondary, + ro_consts_primary, + ro_consts_circuit_primary, + ck_primary, + r1cs_shape_primary, + ro_consts_secondary, + ro_consts_circuit_secondary, + ck_secondary, + r1cs_shape_secondary, + augmented_circuit_params_primary, + augmented_circuit_params_secondary, + digest: OnceCell::new(), + _p: Default::default(), + }; + + // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods + let _ = pp.digest(); + + Ok(pp) + } + + /// Retrieve the digest of the public parameters. + pub fn digest(&self) -> E1::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .expect("Failure in retrieving digest") + } + + /// Returns the number of constraints in the primary and secondary circuits + pub const fn num_constraints(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_cons, + self.r1cs_shape_secondary.num_cons, + ) + } + + /// Returns the number of variables in the primary and secondary circuits + pub const fn num_variables(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_vars, + self.r1cs_shape_secondary.num_vars, + ) + } +} + +/// A SNARK that proves the correct execution of an incremental computation +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + z0_primary: Vec, + z0_secondary: Vec, + r_W_primary: RelaxedR1CSWitness, + r_U_primary: RelaxedR1CSInstance, + r_W_secondary: RelaxedR1CSWitness, + r_U_secondary: RelaxedR1CSInstance, + l_w_secondary: R1CSWitness, + l_u_secondary: R1CSInstance, + i: usize, + zi_primary: Vec, + zi_secondary: Vec, + random_layer: Option>, + _p: PhantomData<(C1, C2)>, +} + +/// Final randomized fold +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RandomLayer +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, +{ + l_Ur_primary: RelaxedR1CSInstance, + l_Ur_secondary: RelaxedR1CSInstance, + nifs_Uf_primary: NIFS, + nifs_Uf_secondary: NIFS, + nifs_Ui_prime_primary: NIFS, + nifs_Ui_prime_secondary: NIFS, + r_Wi_prime_primary: RelaxedR1CSWitness, + r_Wi_prime_secondary: RelaxedR1CSWitness, +} + +impl RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Create new instance of recursive SNARK + pub fn new( + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result { + if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { + return Err(NovaError::InvalidInitialInputLength); + } + + // base case for the primary + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::ZERO, + z0_primary.to_vec(), + None, + None, + None, + None, + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + let (u_primary, w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // base case for the secondary + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::ZERO, + z0_secondary.to_vec(), + None, + None, + Some(u_primary.clone()), + None, + ); + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + let (u_secondary, w_secondary) = + cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; + + // IVC proof for the primary circuit + let l_w_primary = w_primary; + let l_u_primary = u_primary; + let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); + let r_U_primary = + RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); + + // IVC proof for the secondary circuit + let l_w_secondary = w_secondary; + let l_u_secondary = u_secondary; + let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); + let r_U_secondary = + RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); + + assert!( + !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), + "Invalid step length" + ); + + let zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + let zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + Ok(Self { + z0_primary: z0_primary.to_vec(), + z0_secondary: z0_secondary.to_vec(), + r_W_primary, + r_U_primary, + r_W_secondary, + r_U_secondary, + l_w_secondary, + l_u_secondary, + i: 0, + zi_primary, + zi_secondary, + random_layer: None, + _p: Default::default(), + }) + } + + /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) + /// by executing a step of the incremental computation + pub fn prove_step( + &mut self, + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + ) -> Result<(), NovaError> { + // first step was already done in the constructor + if self.i == 0 { + self.i = 1; + return Ok(()); + } + + // 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::(pp.digest()), + &pp.r1cs_shape_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + )?; + + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::from(self.i as u64), + self.z0_primary.to_vec(), + Some(self.zi_primary.clone()), + Some(self.r_U_secondary.clone()), + Some(self.l_u_secondary.clone()), + Some(nifs_secondary.comm_T), + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + + let (l_u_primary, l_w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // 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, + &self.r_U_primary, + &self.r_W_primary, + &l_u_primary, + &l_w_primary, + )?; + + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::from(self.i as u64), + self.z0_secondary.to_vec(), + Some(self.zi_secondary.clone()), + Some(self.r_U_primary.clone()), + Some(l_u_primary), + Some(nifs_primary.comm_T), + ); + + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + + let (l_u_secondary, l_w_secondary) = cs_secondary + .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) + .map_err(|_e| NovaError::UnSat)?; + + // update the running instances and witnesses + self.zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + self.zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + self.l_u_secondary = l_u_secondary; + self.l_w_secondary = l_w_secondary; + + self.r_U_primary = r_U_primary; + self.r_W_primary = r_W_primary; + + self.i += 1; + + self.r_U_secondary = r_U_secondary; + self.r_W_secondary = r_W_secondary; + + Ok(()) + } + + /// randomize the last step computed + pub fn randomizing_fold( + &mut self, + pp: &PublicParams, + ) -> Result, NovaError> { + if self.i == 0 { + // don't call this until we have something to randomize + return Err(NovaError::InvalidNumSteps); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let (nifs_Uf_secondary, (Uf_secondary, Wf_secondary)) = NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let (ur_secondary, wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; + + let (nifs_Un_secondary, (Un_secondary, Wn_secondary)) = NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &Uf_secondary, + &Wf_secondary, + &ur_secondary, + &wr_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let (ur_primary, wr_primary) = pp + .r1cs_shape_primary + .sample_random_instance_witness(&pp.ck_primary)?; + + let (nifs_Un_primary, (Un_primary, Wn_primary)) = NIFS::prove_relaxed( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &self.r_U_primary, + &self.r_W_primary, + &ur_primary, + &wr_primary, + )?; + + // 4. output randomized IVC Proof + self.random_layer = Some(Random { + l_Ur_primary, + l_Ur_secondary, + // commitments to cross terms + nifs_Uf_primary, + nifs_Uf_secondary, + nifs_Ui_prime_primary, + nifs_Ui_prime_secondary, + r_Wi_prime_primary, + r_Wi_prime_secondary, + _p: Default::default(), + }) + + Ok(()) + } + + /// Verify the correctness of the `RecursiveSNARK` + pub fn verify( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outpus + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + pp.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, + ); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + pp.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // check the satisfiability of the provided instances + let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( + || { + pp.r1cs_shape_primary + .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) + }, + || { + rayon::join( + || { + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + ) + }, + || { + pp.r1cs_shape_secondary.is_sat( + &pp.ck_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + ) + }, + ) + }, + ); + + // check the returned res objects + res_r_primary?; + res_r_secondary?; + res_l_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } + + /// Get the outputs after the last step of computation. + pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { + (&self.zi_primary, &self.zi_secondary) + } + + /// The number of steps which have been executed thus far. + pub fn num_steps(&self) -> usize { + self.i + } + + /// Verify the correctness of the `RecursiveSNARK` randomizing layer + pub fn verify_randomizing( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + return Err(NovaError::ProofVerifyError); + } + let random_layer = self.random_layer.unwrap(); + + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outpus + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + pp.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, + ); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + pp.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify_relaxed( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &r_Uf_secondary, + &random_layer.l_ur_secondary, + )?; + + + // fold primary U/W with random inst/wit to get U2/W2 +let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + &pp.ro_consts_primary, + &pp.digest(), + &self.r_U_primary, + &random_layer.l_ur_primary, + )?; + + // check the satisfiability of U1, U2 + let (res_primary, res_secondary) = rayon::join( + || { + pp.r1cs_shape_primary.is_sat_relaxed( + &pp.ck_primary, + &r_Un_primary, + &random_layer.r_Wn_secondary, + ) + }, + || { + pp.r1cs_shape_secondary.is_sat( + &pp.ck_secondary, + &r_Un_secondary, + &random_layer.r_Wn_secondary, + ) + }, + ); + + // check the returned res objects + res_primary?; + res_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } +} + +/// A type that holds the prover key for `CompressedSNARK` +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct ProverKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + pk_primary: S1::ProverKey, + pk_secondary: S2::ProverKey, + _p: PhantomData<(C1, C2)>, +} + +/// A type that holds the verifier key for `CompressedSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct VerifierKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_secondary: ROConstants, + pp_digest: E1::Scalar, + vk_primary: S1::VerifierKey, + vk_secondary: S2::VerifierKey, + _p: PhantomData<(C1, C2)>, +} + +/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + r_U_primary: RelaxedR1CSInstance, + r_W_snark_primary: S1, + + r_U_secondary: RelaxedR1CSInstance, + l_u_secondary: R1CSInstance, + nifs_secondary: NIFS, + f_W_snark_secondary: S2, + + zn_primary: Vec, + zn_secondary: Vec, + + _p: PhantomData<(C1, C2)>, +} + +impl CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + /// Creates prover and verifier keys for `CompressedSNARK` + pub fn setup( + pp: &PublicParams, + ) -> Result< + ( + ProverKey, + VerifierKey, + ), + NovaError, + > { + let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; + let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; + + let pk = ProverKey { + pk_primary, + pk_secondary, + _p: Default::default(), + }; + + let vk = VerifierKey { + 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(), + pp_digest: pp.digest(), + vk_primary, + vk_secondary, + _p: Default::default(), + }; + + Ok((pk, vk)) + } + + /// Create a new `CompressedSNARK` + pub fn prove( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + ) -> Result { + // fold the secondary circuit's instance with its running instance + let (nifs_secondary, (f_U_secondary, f_W_secondary)) = 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, + &recursive_snark.l_u_secondary, + &recursive_snark.l_w_secondary, + )?; + + // create SNARKs proving the knowledge of f_W_primary and f_W_secondary + let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( + || { + S1::prove( + &pp.ck_primary, + &pk.pk_primary, + &pp.r1cs_shape_primary, + &recursive_snark.r_U_primary, + &recursive_snark.r_W_primary, + ) + }, + || { + S2::prove( + &pp.ck_secondary, + &pk.pk_secondary, + &pp.r1cs_shape_secondary, + &f_U_secondary, + &f_W_secondary, + ) + }, + ); + + Ok(Self { + r_U_primary: recursive_snark.r_U_primary.clone(), + r_W_snark_primary: r_W_snark_primary?, + + r_U_secondary: recursive_snark.r_U_secondary.clone(), + l_u_secondary: recursive_snark.l_u_secondary.clone(), + nifs_secondary, + f_W_snark_secondary: f_W_snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + + _p: Default::default(), + }) + } + + /// Verify the correctness of the `CompressedSNARK` + pub fn verify( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // the number of steps cannot be zero + if num_steps == 0 { + return Err(NovaError::ProofVerifyError); + } + + // check if the (relaxed) R1CS instances have two public outputs + if self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2 + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + vk.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, + ); + hasher.absorb(vk.pp_digest); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zn_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + vk.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zn_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold the secondary's running instance with the last instance to get a folded instance + let f_U_secondary = self.nifs_secondary.verify( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // check the satisfiability of the folded instances using + // SNARKs proving the knowledge of their satisfying witnesses + let (res_primary, res_secondary) = rayon::join( + || { + self + .r_W_snark_primary + .verify(&vk.vk_primary, &self.r_U_primary) + }, + || { + self + .f_W_snark_secondary + .verify(&vk.vk_secondary, &f_U_secondary) + }, + ); + + res_primary?; + res_secondary?; + + Ok((self.zn_primary.clone(), self.zn_secondary.clone())) + } +} + +type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; +type Commitment = <::CE as CommitmentEngineTrait>::Commitment; +type CE = ::CE; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + provider::{ + pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, + GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, + }, + traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, + }; + use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; + use core::{fmt::Write, marker::PhantomData}; + use expect_test::{expect, Expect}; + use ff::PrimeField; + + type EE = provider::ipa_pc::EvaluationEngine; + type EEPrime = provider::hyperkzg::EvaluationEngine; + type S = spartan::snark::RelaxedR1CSSNARK; + type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; + + #[derive(Clone, Debug, Default)] + struct CubicCircuit { + _p: PhantomData, + } + + impl StepCircuit for CubicCircuit { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. + let x = &z[0]; + let x_sq = x.square(cs.namespace(|| "x_sq"))?; + let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) + })?; + + cs.enforce( + || "y = x^3 + x + 5", + |lc| { + lc + x_cu.get_variable() + + x.get_variable() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + }, + |lc| lc + CS::one(), + |lc| lc + y.get_variable(), + ); + + Ok(vec![y]) + } + } + + impl CubicCircuit { + fn output(&self, z: &[F]) -> Vec { + vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] + } + } + + fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + E1::GE: DlogGroup, + E2::GE: DlogGroup, + T1: StepCircuit, + T2: StepCircuit, + // required to use the IPA in the initialization of the commitment key hints below + >::CommitmentKey: CommitmentKeyExtTrait, + >::CommitmentKey: CommitmentKeyExtTrait, + { + // this tests public parameters with a size specifically intended for a spark-compressed SNARK + let ck_hint1 = &*SPrime::>::ck_floor(); + let ck_hint2 = &*SPrime::>::ck_floor(); + let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); + + let digest_str = pp + .digest() + .to_repr() + .as_ref() + .iter() + .fold(String::new(), |mut output, b| { + let _ = write!(output, "{b:02x}"); + output + }); + expected.assert_eq(&digest_str); + } + + #[test] + fn test_pp_digest() { + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["a69d6cf6d014c3a5cc99b77afc86691f7460faa737207dd21b30e8241fae8002"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["b22ab3456df4bd391804a39fae582b37ed4a8d90ace377337940ac956d87f701"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["c8aec89a3ea90317a0ecdc9150f4fc3648ca33f6660924a192cafd82e2939b02"], + ); + } + + fn test_ivc_trivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = TrivialCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ) + .unwrap(); + + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_trivial() { + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + } + + fn test_ivc_nontrivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + // verify the recursive snark at each step of recursion + let res = recursive_snark.verify( + &pp, + i + 1, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + } + + #[test] + fn test_ivc_nontrivial() { + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + } + + fn test_ivc_nontrivial_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_compression() { + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + ); + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + provider::hyperkzg::EvaluationEngine<_>, + EE<_>, + >(); + } + + fn test_ivc_nontrivial_with_spark_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters, which we'll use with a spark-compressed SNARK + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*SPrime::::ck_floor(), + &*SPrime::::ck_floor(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // run the compressed snark with Spark compiler + // produce the prover and verifier keys for compressed snark + let (pk, vk) = + CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( + &pp, + &pk, + &recursive_snark, + ); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_spark_compression() { + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + EEPrime<_>, + EE<_>, + >(); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + ); + } + + fn test_ivc_nondet_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + // y is a non-deterministic advice representing the fifth root of the input at a step. + #[derive(Clone, Debug)] + struct FifthRootCheckingCircuit { + y: F, + } + + impl FifthRootCheckingCircuit { + fn new(num_steps: usize) -> (Vec, Vec) { + let mut powers = Vec::new(); + let rng = &mut rand::rngs::OsRng; + let mut seed = F::random(rng); + for _i in 0..num_steps + 1 { + seed *= seed.clone().square().square(); + + powers.push(Self { y: seed }); + } + + // reverse the powers to get roots + let roots = powers.into_iter().rev().collect::>(); + (vec![roots[0].y], roots[1..].to_vec()) + } + } + + impl StepCircuit for FifthRootCheckingCircuit + where + F: PrimeField, + { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + + // we allocate a variable and set it to the provided non-deterministic advice. + let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); + + // We now check if y = x^{1/5} by checking if y^5 = x + let y_sq = y.square(cs.namespace(|| "y_sq"))?; + let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; + let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; + + cs.enforce( + || "y^5 = x", + |lc| lc + y_pow_5.get_variable(), + |lc| lc + CS::one(), + |lc| lc + x.get_variable(), + ); + + Ok(vec![y]) + } + } + + let circuit_primary = FifthRootCheckingCircuit { + y: ::Scalar::ZERO, + }; + + let circuit_secondary = TrivialCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce non-deterministic advice + let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); + let z0_secondary = vec![::Scalar::ZERO]; + + // produce a recursive SNARK + let mut recursive_snark: RecursiveSNARK< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + > = RecursiveSNARK::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::new( + &pp, + &roots[0], + &circuit_secondary, + &z0_primary, + &z0_secondary, + ) + .unwrap(); + + for circuit_primary in roots.iter().take(num_steps) { + let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nondet_with_compression() { + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + } + + fn test_ivc_base_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = CubicCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + // produce a recursive SNARK + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + assert_eq!(zn_primary, vec![::Scalar::ONE]); + assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); + } + + #[test] + fn test_ivc_base() { + test_ivc_base_with::(); + test_ivc_base_with::(); + test_ivc_base_with::(); + } + + fn test_setup_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + #[derive(Clone, Debug, Default)] + struct CircuitWithInputize { + _p: PhantomData, + } + + impl StepCircuit for CircuitWithInputize { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + let y = x.square(cs.namespace(|| "x_sq"))?; + y.inputize(cs.namespace(|| "y"))?; // inputize y + Ok(vec![y]) + } + } + + // produce public parameters with trivial secondary + let circuit = CircuitWithInputize::<::Scalar>::default(); + let pp = + PublicParams::, TrivialCircuit>::setup( + &circuit, + &TrivialCircuit::default(), + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + + // produce public parameters with the trivial primary + let circuit = CircuitWithInputize::::default(); + let pp = + PublicParams::, CircuitWithInputize>::setup( + &TrivialCircuit::default(), + &circuit, + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + } + + #[test] + fn test_setup() { + test_setup_with::(); + } +} From 75a67d02efd9d87b1ea71b126c9afee0c6bd505d Mon Sep 17 00:00:00 2001 From: jkwoods Date: Wed, 11 Sep 2024 16:31:34 -0400 Subject: [PATCH 07/21] clean up --- temp.rs | 1788 ------------------------------------------------------- 1 file changed, 1788 deletions(-) delete mode 100644 temp.rs diff --git a/temp.rs b/temp.rs deleted file mode 100644 index d543b3f0..00000000 --- a/temp.rs +++ /dev/null @@ -1,1788 +0,0 @@ -//! This library implements Nova, a high-speed recursive SNARK. -#![deny( - warnings, - unused, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - missing_docs -)] -#![allow(non_snake_case)] -#![forbid(unsafe_code)] - -// private modules -mod bellpepper; -mod circuit; -mod constants; -mod digest; -mod nifs; -mod r1cs; - -// public modules -pub mod errors; -pub mod gadgets; -pub mod provider; -pub mod spartan; -pub mod traits; - -use once_cell::sync::OnceCell; - -use crate::bellpepper::{ - r1cs::{NovaShape, NovaWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, -}; -use crate::digest::{DigestComputer, SimpleDigestible}; -use bellpepper_core::{ConstraintSystem, SynthesisError}; -use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; -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 gadgets::utils::scalar_as_base; -use nifs::NIFS; -use r1cs::{ - CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, -}; -use serde::{Deserialize, Serialize}; -use traits::{ - circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, - AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, -}; - -/// A type that holds public parameters of Nova -#[derive(Serialize, Deserialize)] -#[serde(bound = "")] -pub struct PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_circuit_primary: ROConstantsCircuit, - ck_primary: CommitmentKey, - r1cs_shape_primary: R1CSShape, - ro_consts_secondary: ROConstants, - ro_consts_circuit_secondary: ROConstantsCircuit, - ck_secondary: CommitmentKey, - r1cs_shape_secondary: R1CSShape, - augmented_circuit_params_primary: NovaAugmentedCircuitParams, - augmented_circuit_params_secondary: NovaAugmentedCircuitParams, - #[serde(skip, default = "OnceCell::new")] - digest: OnceCell, - _p: PhantomData<(C1, C2)>, -} - -impl SimpleDigestible for PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ -} - -impl PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. - /// - /// # Note - /// - /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. - /// - /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require - /// larger sizes for these parameters. These SNARKs provide a hint for these values by - /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. - /// - /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. - /// - /// # Arguments - /// - /// * `c_primary`: The primary circuit of type `C1`. - /// * `c_secondary`: The secondary circuit of type `C2`. - /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint - /// for the number of generators required in the commitment scheme for the primary circuit. - /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. - /// - /// # Example - /// - /// ```rust - /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; - /// # use nova_snark::provider::ipa_pc::EvaluationEngine; - /// # use nova_snark::provider::{PallasEngine, VestaEngine}; - /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; - /// use nova_snark::PublicParams; - /// - /// type E1 = PallasEngine; - /// type E2 = VestaEngine; - /// type EE = EvaluationEngine; - /// type SPrime = RelaxedR1CSSNARK>; - /// - /// let circuit1 = TrivialCircuit::<::Scalar>::default(); - /// let circuit2 = TrivialCircuit::<::Scalar>::default(); - /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) - /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. - /// let ck_hint1 = &*SPrime::::ck_floor(); - /// let ck_hint2 = &*SPrime::::ck_floor(); - /// - /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); - /// ``` - pub fn setup( - c_primary: &C1, - c_secondary: &C2, - ck_hint1: &CommitmentKeyHint, - ck_hint2: &CommitmentKeyHint, - ) -> Result { - let augmented_circuit_params_primary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); - let augmented_circuit_params_secondary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); - - let ro_consts_primary: ROConstants = ROConstants::::default(); - let ro_consts_secondary: ROConstants = ROConstants::::default(); - - let F_arity_primary = c_primary.arity(); - let F_arity_secondary = c_secondary.arity(); - - // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar - let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); - let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); - - // Initialize ck for the primary - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &augmented_circuit_params_primary, - None, - c_primary, - ro_consts_circuit_primary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_primary.synthesize(&mut cs); - let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); - - // Initialize ck for the secondary - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &augmented_circuit_params_secondary, - None, - c_secondary, - ro_consts_circuit_secondary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_secondary.synthesize(&mut cs); - let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); - - if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { - return Err(NovaError::InvalidStepCircuitIO); - } - - let pp = PublicParams { - F_arity_primary, - F_arity_secondary, - ro_consts_primary, - ro_consts_circuit_primary, - ck_primary, - r1cs_shape_primary, - ro_consts_secondary, - ro_consts_circuit_secondary, - ck_secondary, - r1cs_shape_secondary, - augmented_circuit_params_primary, - augmented_circuit_params_secondary, - digest: OnceCell::new(), - _p: Default::default(), - }; - - // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods - let _ = pp.digest(); - - Ok(pp) - } - - /// Retrieve the digest of the public parameters. - pub fn digest(&self) -> E1::Scalar { - self - .digest - .get_or_try_init(|| DigestComputer::new(self).digest()) - .cloned() - .expect("Failure in retrieving digest") - } - - /// Returns the number of constraints in the primary and secondary circuits - pub const fn num_constraints(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_cons, - self.r1cs_shape_secondary.num_cons, - ) - } - - /// Returns the number of variables in the primary and secondary circuits - pub const fn num_variables(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_vars, - self.r1cs_shape_secondary.num_vars, - ) - } -} - -/// A SNARK that proves the correct execution of an incremental computation -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - z0_primary: Vec, - z0_secondary: Vec, - r_W_primary: RelaxedR1CSWitness, - r_U_primary: RelaxedR1CSInstance, - r_W_secondary: RelaxedR1CSWitness, - r_U_secondary: RelaxedR1CSInstance, - l_w_secondary: R1CSWitness, - l_u_secondary: R1CSInstance, - i: usize, - zi_primary: Vec, - zi_secondary: Vec, - random_layer: Option>, - _p: PhantomData<(C1, C2)>, -} - -/// Final randomized fold -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RandomLayer -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, -{ - l_Ur_primary: RelaxedR1CSInstance, - l_Ur_secondary: RelaxedR1CSInstance, - nifs_Uf_primary: NIFS, - nifs_Uf_secondary: NIFS, - nifs_Ui_prime_primary: NIFS, - nifs_Ui_prime_secondary: NIFS, - r_Wi_prime_primary: RelaxedR1CSWitness, - r_Wi_prime_secondary: RelaxedR1CSWitness, -} - -impl RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Create new instance of recursive SNARK - pub fn new( - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result { - if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { - return Err(NovaError::InvalidInitialInputLength); - } - - // base case for the primary - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::ZERO, - z0_primary.to_vec(), - None, - None, - None, - None, - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - let (u_primary, w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // base case for the secondary - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::ZERO, - z0_secondary.to_vec(), - None, - None, - Some(u_primary.clone()), - None, - ); - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - let (u_secondary, w_secondary) = - cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; - - // IVC proof for the primary circuit - let l_w_primary = w_primary; - let l_u_primary = u_primary; - let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); - let r_U_primary = - RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); - - // IVC proof for the secondary circuit - let l_w_secondary = w_secondary; - let l_u_secondary = u_secondary; - let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); - let r_U_secondary = - RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); - - assert!( - !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), - "Invalid step length" - ); - - let zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - let zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - Ok(Self { - z0_primary: z0_primary.to_vec(), - z0_secondary: z0_secondary.to_vec(), - r_W_primary, - r_U_primary, - r_W_secondary, - r_U_secondary, - l_w_secondary, - l_u_secondary, - i: 0, - zi_primary, - zi_secondary, - random_layer: None, - _p: Default::default(), - }) - } - - /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) - /// by executing a step of the incremental computation - pub fn prove_step( - &mut self, - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - ) -> Result<(), NovaError> { - // first step was already done in the constructor - if self.i == 0 { - self.i = 1; - return Ok(()); - } - - // 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::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::from(self.i as u64), - self.z0_primary.to_vec(), - Some(self.zi_primary.clone()), - Some(self.r_U_secondary.clone()), - Some(self.l_u_secondary.clone()), - Some(nifs_secondary.comm_T), - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - - let (l_u_primary, l_w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // 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, - &self.r_U_primary, - &self.r_W_primary, - &l_u_primary, - &l_w_primary, - )?; - - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::from(self.i as u64), - self.z0_secondary.to_vec(), - Some(self.zi_secondary.clone()), - Some(self.r_U_primary.clone()), - Some(l_u_primary), - Some(nifs_primary.comm_T), - ); - - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - - let (l_u_secondary, l_w_secondary) = cs_secondary - .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) - .map_err(|_e| NovaError::UnSat)?; - - // update the running instances and witnesses - self.zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - self.zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - self.l_u_secondary = l_u_secondary; - self.l_w_secondary = l_w_secondary; - - self.r_U_primary = r_U_primary; - self.r_W_primary = r_W_primary; - - self.i += 1; - - self.r_U_secondary = r_U_secondary; - self.r_W_secondary = r_W_secondary; - - Ok(()) - } - - /// randomize the last step computed - pub fn randomizing_fold( - &mut self, - pp: &PublicParams, - ) -> Result, NovaError> { - if self.i == 0 { - // don't call this until we have something to randomize - return Err(NovaError::InvalidNumSteps); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let (nifs_Uf_secondary, (Uf_secondary, Wf_secondary)) = NIFS::prove_relaxed( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let (ur_secondary, wr_secondary) = pp - .r1cs_shape_secondary - .sample_random_instance_witness(&pp.ck_secondary)?; - - let (nifs_Un_secondary, (Un_secondary, Wn_secondary)) = NIFS::prove_relaxed( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &Uf_secondary, - &Wf_secondary, - &ur_secondary, - &wr_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let (ur_primary, wr_primary) = pp - .r1cs_shape_primary - .sample_random_instance_witness(&pp.ck_primary)?; - - let (nifs_Un_primary, (Un_primary, Wn_primary)) = NIFS::prove_relaxed( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &self.r_U_primary, - &self.r_W_primary, - &ur_primary, - &wr_primary, - )?; - - // 4. output randomized IVC Proof - self.random_layer = Some(Random { - l_Ur_primary, - l_Ur_secondary, - // commitments to cross terms - nifs_Uf_primary, - nifs_Uf_secondary, - nifs_Ui_prime_primary, - nifs_Ui_prime_secondary, - r_Wi_prime_primary, - r_Wi_prime_secondary, - _p: Default::default(), - }) - - Ok(()) - } - - /// Verify the correctness of the `RecursiveSNARK` - pub fn verify( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outpus - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // check the satisfiability of the provided instances - let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( - || { - pp.r1cs_shape_primary - .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) - }, - || { - rayon::join( - || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat( - &pp.ck_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - ) - }, - ) - }, - ); - - // check the returned res objects - res_r_primary?; - res_r_secondary?; - res_l_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } - - /// Get the outputs after the last step of computation. - pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { - (&self.zi_primary, &self.zi_secondary) - } - - /// The number of steps which have been executed thus far. - pub fn num_steps(&self) -> usize { - self.i - } - - /// Verify the correctness of the `RecursiveSNARK` randomizing layer - pub fn verify_randomizing( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - return Err(NovaError::ProofVerifyError); - } - let random_layer = self.random_layer.unwrap(); - - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outpus - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify_relaxed( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - - // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &r_Uf_secondary, - &random_layer.l_ur_secondary, - )?; - - - // fold primary U/W with random inst/wit to get U2/W2 -let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( - &pp.ro_consts_primary, - &pp.digest(), - &self.r_U_primary, - &random_layer.l_ur_primary, - )?; - - // check the satisfiability of U1, U2 - let (res_primary, res_secondary) = rayon::join( - || { - pp.r1cs_shape_primary.is_sat_relaxed( - &pp.ck_primary, - &r_Un_primary, - &random_layer.r_Wn_secondary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat( - &pp.ck_secondary, - &r_Un_secondary, - &random_layer.r_Wn_secondary, - ) - }, - ); - - // check the returned res objects - res_primary?; - res_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } -} - -/// A type that holds the prover key for `CompressedSNARK` -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct ProverKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - pk_primary: S1::ProverKey, - pk_secondary: S2::ProverKey, - _p: PhantomData<(C1, C2)>, -} - -/// A type that holds the verifier key for `CompressedSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct VerifierKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_secondary: ROConstants, - pp_digest: E1::Scalar, - vk_primary: S1::VerifierKey, - vk_secondary: S2::VerifierKey, - _p: PhantomData<(C1, C2)>, -} - -/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - r_U_primary: RelaxedR1CSInstance, - r_W_snark_primary: S1, - - r_U_secondary: RelaxedR1CSInstance, - l_u_secondary: R1CSInstance, - nifs_secondary: NIFS, - f_W_snark_secondary: S2, - - zn_primary: Vec, - zn_secondary: Vec, - - _p: PhantomData<(C1, C2)>, -} - -impl CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - /// Creates prover and verifier keys for `CompressedSNARK` - pub fn setup( - pp: &PublicParams, - ) -> Result< - ( - ProverKey, - VerifierKey, - ), - NovaError, - > { - let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; - let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; - - let pk = ProverKey { - pk_primary, - pk_secondary, - _p: Default::default(), - }; - - let vk = VerifierKey { - 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(), - pp_digest: pp.digest(), - vk_primary, - vk_secondary, - _p: Default::default(), - }; - - Ok((pk, vk)) - } - - /// Create a new `CompressedSNARK` - pub fn prove( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - ) -> Result { - // fold the secondary circuit's instance with its running instance - let (nifs_secondary, (f_U_secondary, f_W_secondary)) = 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, - &recursive_snark.l_u_secondary, - &recursive_snark.l_w_secondary, - )?; - - // create SNARKs proving the knowledge of f_W_primary and f_W_secondary - let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( - || { - S1::prove( - &pp.ck_primary, - &pk.pk_primary, - &pp.r1cs_shape_primary, - &recursive_snark.r_U_primary, - &recursive_snark.r_W_primary, - ) - }, - || { - S2::prove( - &pp.ck_secondary, - &pk.pk_secondary, - &pp.r1cs_shape_secondary, - &f_U_secondary, - &f_W_secondary, - ) - }, - ); - - Ok(Self { - r_U_primary: recursive_snark.r_U_primary.clone(), - r_W_snark_primary: r_W_snark_primary?, - - r_U_secondary: recursive_snark.r_U_secondary.clone(), - l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_secondary, - f_W_snark_secondary: f_W_snark_secondary?, - - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), - - _p: Default::default(), - }) - } - - /// Verify the correctness of the `CompressedSNARK` - pub fn verify( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // the number of steps cannot be zero - if num_steps == 0 { - return Err(NovaError::ProofVerifyError); - } - - // check if the (relaxed) R1CS instances have two public outputs - if self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - vk.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, - ); - hasher.absorb(vk.pp_digest); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zn_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - vk.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(vk.pp_digest)); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zn_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold the secondary's running instance with the last instance to get a folded instance - let f_U_secondary = self.nifs_secondary.verify( - &vk.ro_consts_secondary, - &scalar_as_base::(vk.pp_digest), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // check the satisfiability of the folded instances using - // SNARKs proving the knowledge of their satisfying witnesses - let (res_primary, res_secondary) = rayon::join( - || { - self - .r_W_snark_primary - .verify(&vk.vk_primary, &self.r_U_primary) - }, - || { - self - .f_W_snark_secondary - .verify(&vk.vk_secondary, &f_U_secondary) - }, - ); - - res_primary?; - res_secondary?; - - Ok((self.zn_primary.clone(), self.zn_secondary.clone())) - } -} - -type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; -type Commitment = <::CE as CommitmentEngineTrait>::Commitment; -type CE = ::CE; - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - provider::{ - pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, - GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, - }, - traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, - }; - use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; - use core::{fmt::Write, marker::PhantomData}; - use expect_test::{expect, Expect}; - use ff::PrimeField; - - type EE = provider::ipa_pc::EvaluationEngine; - type EEPrime = provider::hyperkzg::EvaluationEngine; - type S = spartan::snark::RelaxedR1CSSNARK; - type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; - - #[derive(Clone, Debug, Default)] - struct CubicCircuit { - _p: PhantomData, - } - - impl StepCircuit for CubicCircuit { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. - let x = &z[0]; - let x_sq = x.square(cs.namespace(|| "x_sq"))?; - let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) - })?; - - cs.enforce( - || "y = x^3 + x + 5", - |lc| { - lc + x_cu.get_variable() - + x.get_variable() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - }, - |lc| lc + CS::one(), - |lc| lc + y.get_variable(), - ); - - Ok(vec![y]) - } - } - - impl CubicCircuit { - fn output(&self, z: &[F]) -> Vec { - vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] - } - } - - fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - E1::GE: DlogGroup, - E2::GE: DlogGroup, - T1: StepCircuit, - T2: StepCircuit, - // required to use the IPA in the initialization of the commitment key hints below - >::CommitmentKey: CommitmentKeyExtTrait, - >::CommitmentKey: CommitmentKeyExtTrait, - { - // this tests public parameters with a size specifically intended for a spark-compressed SNARK - let ck_hint1 = &*SPrime::>::ck_floor(); - let ck_hint2 = &*SPrime::>::ck_floor(); - let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); - - let digest_str = pp - .digest() - .to_repr() - .as_ref() - .iter() - .fold(String::new(), |mut output, b| { - let _ = write!(output, "{b:02x}"); - output - }); - expected.assert_eq(&digest_str); - } - - #[test] - fn test_pp_digest() { - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["a69d6cf6d014c3a5cc99b77afc86691f7460faa737207dd21b30e8241fae8002"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["b22ab3456df4bd391804a39fae582b37ed4a8d90ace377337940ac956d87f701"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["c8aec89a3ea90317a0ecdc9150f4fc3648ca33f6660924a192cafd82e2939b02"], - ); - } - - fn test_ivc_trivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = TrivialCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ) - .unwrap(); - - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_trivial() { - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - } - - fn test_ivc_nontrivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - - // verify the recursive snark at each step of recursion - let res = recursive_snark.verify( - &pp, - i + 1, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - } - - #[test] - fn test_ivc_nontrivial() { - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - } - - fn test_ivc_nontrivial_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - ); - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - provider::hyperkzg::EvaluationEngine<_>, - EE<_>, - >(); - } - - fn test_ivc_nontrivial_with_spark_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters, which we'll use with a spark-compressed SNARK - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*SPrime::::ck_floor(), - &*SPrime::::ck_floor(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // run the compressed snark with Spark compiler - // produce the prover and verifier keys for compressed snark - let (pk, vk) = - CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( - &pp, - &pk, - &recursive_snark, - ); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - EEPrime<_>, - EE<_>, - >(); - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - ); - } - - fn test_ivc_nondet_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - // y is a non-deterministic advice representing the fifth root of the input at a step. - #[derive(Clone, Debug)] - struct FifthRootCheckingCircuit { - y: F, - } - - impl FifthRootCheckingCircuit { - fn new(num_steps: usize) -> (Vec, Vec) { - let mut powers = Vec::new(); - let rng = &mut rand::rngs::OsRng; - let mut seed = F::random(rng); - for _i in 0..num_steps + 1 { - seed *= seed.clone().square().square(); - - powers.push(Self { y: seed }); - } - - // reverse the powers to get roots - let roots = powers.into_iter().rev().collect::>(); - (vec![roots[0].y], roots[1..].to_vec()) - } - } - - impl StepCircuit for FifthRootCheckingCircuit - where - F: PrimeField, - { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - - // we allocate a variable and set it to the provided non-deterministic advice. - let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); - - // We now check if y = x^{1/5} by checking if y^5 = x - let y_sq = y.square(cs.namespace(|| "y_sq"))?; - let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; - let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; - - cs.enforce( - || "y^5 = x", - |lc| lc + y_pow_5.get_variable(), - |lc| lc + CS::one(), - |lc| lc + x.get_variable(), - ); - - Ok(vec![y]) - } - } - - let circuit_primary = FifthRootCheckingCircuit { - y: ::Scalar::ZERO, - }; - - let circuit_secondary = TrivialCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce non-deterministic advice - let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); - let z0_secondary = vec![::Scalar::ZERO]; - - // produce a recursive SNARK - let mut recursive_snark: RecursiveSNARK< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - > = RecursiveSNARK::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::new( - &pp, - &roots[0], - &circuit_secondary, - &z0_primary, - &z0_secondary, - ) - .unwrap(); - - for circuit_primary in roots.iter().take(num_steps) { - let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nondet_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - } - - fn test_ivc_base_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = CubicCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - // produce a recursive SNARK - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - assert_eq!(zn_primary, vec![::Scalar::ONE]); - assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); - } - - #[test] - fn test_ivc_base() { - test_ivc_base_with::(); - test_ivc_base_with::(); - test_ivc_base_with::(); - } - - fn test_setup_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - #[derive(Clone, Debug, Default)] - struct CircuitWithInputize { - _p: PhantomData, - } - - impl StepCircuit for CircuitWithInputize { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - let y = x.square(cs.namespace(|| "x_sq"))?; - y.inputize(cs.namespace(|| "y"))?; // inputize y - Ok(vec![y]) - } - } - - // produce public parameters with trivial secondary - let circuit = CircuitWithInputize::<::Scalar>::default(); - let pp = - PublicParams::, TrivialCircuit>::setup( - &circuit, - &TrivialCircuit::default(), - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - - // produce public parameters with the trivial primary - let circuit = CircuitWithInputize::::default(); - let pp = - PublicParams::, CircuitWithInputize>::setup( - &TrivialCircuit::default(), - &circuit, - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - } - - #[test] - fn test_setup() { - test_setup_with::(); - } -} From f7d1f43762350df6365bcace54faaca32fc73594 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Wed, 11 Sep 2024 17:20:38 -0400 Subject: [PATCH 08/21] test passes for RecursiveSNARK random fold --- src/lib.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9c828f46..5ac82039 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,6 +396,11 @@ where return Ok(()); } + // already applied randomizing layer + if self.random_layer.is_some() { + return Err(NovaError::ProofVerifyError); + } + // fold the secondary circuit's instance let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( &pp.ck_secondary, @@ -562,6 +567,21 @@ where num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + self.verify_regular(pp, num_steps, z0_primary, z0_secondary) + } else { + self.verify_randomizing(pp, num_steps, z0_primary, z0_secondary) + } + } + + /// Verify the correctness of the `RecursiveSNARK` (no randomizing layer) + fn verify_regular( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { // number of steps cannot be zero let is_num_steps_zero = num_steps == 0; @@ -661,27 +681,14 @@ where Ok((self.zi_primary.clone(), self.zi_secondary.clone())) } - /// Get the outputs after the last step of computation. - pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { - (&self.zi_primary, &self.zi_secondary) - } - - /// The number of steps which have been executed thus far. - pub fn num_steps(&self) -> usize { - self.i - } - /// Verify the correctness of the `RecursiveSNARK` randomizing layer - pub fn verify_randomizing( + fn verify_randomizing( &self, pp: &PublicParams, num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - return Err(NovaError::ProofVerifyError); - } let random_layer = self.random_layer.as_ref().unwrap(); // number of steps cannot be zero @@ -796,6 +803,16 @@ where Ok((self.zi_primary.clone(), self.zi_secondary.clone())) } + + /// Get the outputs after the last step of computation. + pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { + (&self.zi_primary, &self.zi_secondary) + } + + /// The number of steps which have been executed thus far. + pub fn num_steps(&self) -> usize { + self.i + } } /// A type that holds the prover key for `CompressedSNARK` @@ -1312,6 +1329,90 @@ mod tests { test_ivc_nontrivial_with::(); } + fn test_ivc_nontrivial_randomizing_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + // verify the recursive snark at each step of recursion + let res = recursive_snark.verify( + &pp, + i + 1, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + let res = recursive_snark.randomizing_fold(&pp); + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + } + + #[test] + fn test_ivc_nontrivial_randomizing() { + test_ivc_nontrivial_randomizing_with::(); + test_ivc_nontrivial_randomizing_with::(); + test_ivc_nontrivial_randomizing_with::(); + } + fn test_ivc_nontrivial_with_compression_with() where E1: Engine::Scalar>, From 66ddd1e309cd269efc18da7cdf1823879c08b78f Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 12 Sep 2024 13:37:43 -0400 Subject: [PATCH 09/21] draft for prove/verify compressed snark, some refactoring --- examples/and.rs | 2 +- examples/hashchain.rs | 2 +- examples/minroot.rs | 2 +- src/lib.rs | 288 +++++- temp.rs | 2072 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2324 insertions(+), 42 deletions(-) create mode 100644 temp.rs diff --git a/examples/and.rs b/examples/and.rs index 33b50bb7..d38eb3ff 100644 --- a/examples/and.rs +++ b/examples/and.rs @@ -301,7 +301,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/examples/hashchain.rs b/examples/hashchain.rs index d0554568..a08bfad7 100644 --- a/examples/hashchain.rs +++ b/examples/hashchain.rs @@ -196,7 +196,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/examples/minroot.rs b/examples/minroot.rs index 20c7275f..ba8eaa1d 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -261,7 +261,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/src/lib.rs b/src/lib.rs index 5ac82039..311e794b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,7 +253,6 @@ where i: usize, zi_primary: Vec, zi_secondary: Vec, - random_layer: Option>, _p: PhantomData<(C1, C2)>, } @@ -272,6 +271,10 @@ where nifs_Un_secondary: NIFS, r_Wn_primary: RelaxedR1CSWitness, r_Wn_secondary: RelaxedR1CSWitness, + // needed for CompressedSNARK proving, + // but not technically part of RecursiveSNARK proof + r_Un_primary: RelaxedR1CSInstance, + r_Un_secondary: RelaxedR1CSInstance, } impl RecursiveSNARK @@ -377,7 +380,6 @@ where i: 0, zi_primary, zi_secondary, - random_layer: None, _p: Default::default(), }) } @@ -396,11 +398,6 @@ where return Ok(()); } - // already applied randomizing layer - if self.random_layer.is_some() { - return Err(NovaError::ProofVerifyError); - } - // fold the secondary circuit's instance let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( &pp.ck_secondary, @@ -495,7 +492,11 @@ where } /// randomize the last step computed - pub fn randomizing_fold(&mut self, pp: &PublicParams) -> Result<(), NovaError> { + /// if you plan on using a randomized CompressedSNARK, you don't need to call this + fn randomizing_fold( + &self, + pp: &PublicParams, + ) -> Result, NovaError> { if self.i == 0 { // don't call this until we have something to randomize return Err(NovaError::InvalidNumSteps); @@ -518,7 +519,7 @@ where .r1cs_shape_secondary .sample_random_instance_witness(&pp.ck_secondary)?; - let (nifs_Un_secondary, (_r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( + let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( &pp.ck_secondary, &pp.ro_consts_secondary, &scalar_as_base::(pp.digest()), @@ -534,7 +535,7 @@ where .r1cs_shape_primary .sample_random_instance_witness(&pp.ck_primary)?; - let (nifs_Un_primary, (_r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( + let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( &pp.ck_primary, &pp.ro_consts_primary, &pp.digest(), @@ -546,37 +547,25 @@ where )?; // output randomized IVC Proof - self.random_layer = Some(RandomLayer { + Ok(RandomLayer { + // random istances l_ur_primary, l_ur_secondary, // commitments to cross terms nifs_Uf_secondary, nifs_Un_primary, nifs_Un_secondary, + // witnesses r_Wn_primary, r_Wn_secondary, - }); - - Ok(()) - } - - /// Verify the correctness of the `RecursiveSNARK` - pub fn verify( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - self.verify_regular(pp, num_steps, z0_primary, z0_secondary) - } else { - self.verify_randomizing(pp, num_steps, z0_primary, z0_secondary) - } + // needed for CompressedSNARK proving + r_Un_primary, + r_Un_secondary, + }) } /// Verify the correctness of the `RecursiveSNARK` (no randomizing layer) - fn verify_regular( + pub fn verify( &self, pp: &PublicParams, num_steps: usize, @@ -682,15 +671,14 @@ where } /// Verify the correctness of the `RecursiveSNARK` randomizing layer - fn verify_randomizing( + pub fn verify_randomizing( &self, pp: &PublicParams, num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], + random_layer: RandomLayer, ) -> Result<(Vec, Vec), NovaError> { - let random_layer = self.random_layer.as_ref().unwrap(); - // number of steps cannot be zero let is_num_steps_zero = num_steps == 0; @@ -877,9 +865,24 @@ where zn_primary: Vec, zn_secondary: Vec, + random_layer: Option>, _p: PhantomData<(C1, C2)>, } +/// Final randomized fold info for CompressedSNARK +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RandomLayerCompressed +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, +{ + l_ur_primary: RelaxedR1CSInstance, + l_ur_secondary: RelaxedR1CSInstance, + nifs_Un_primary: NIFS, + nifs_Un_secondary: NIFS, +} + impl CompressedSNARK where E1: Engine::Scalar>, @@ -927,6 +930,20 @@ where pp: &PublicParams, pk: &ProverKey, recursive_snark: &RecursiveSNARK, + randomizing: bool, + ) -> Result { + if randomizing { + Self::prove_randomizing(pp, pk, recursive_snark) + } else { + Self::prove_regular(pp, pk, recursive_snark) + } + } + + /// Create a new `CompressedSNARK` + fn prove_regular( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, ) -> Result { // fold the secondary circuit's instance with its running instance let (nifs_secondary, (f_U_secondary, f_W_secondary)) = NIFS::prove( @@ -975,6 +992,64 @@ where zn_secondary: recursive_snark.zi_secondary.clone(), _p: Default::default(), + random_layer: None, + }) + } + + /// Create a new `CompressedSNARK` + fn prove_randomizing( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + ) -> Result { + // prove three foldings + let random_layer = recursive_snark.randomizing_fold(pp)?; + + // create SNARKs proving the knowledge of Wn primary/secondary + let (snark_primary, snark_secondary) = rayon::join( + || { + S1::prove( + &pp.ck_primary, + &pk.pk_primary, + &pp.r1cs_shape_primary, + &random_layer.r_Un_primary, + &random_layer.r_Wn_primary, + ) + }, + || { + S2::prove( + &pp.ck_secondary, + &pk.pk_secondary, + &pp.r1cs_shape_secondary, + &random_layer.r_Un_secondary, + &random_layer.r_Wn_secondary, + ) + }, + ); + + let random_layer_compressed = Some(RandomLayerCompressed { + l_ur_primary: random_layer.l_ur_primary.clone(), + l_ur_secondary: random_layer.l_ur_secondary.clone(), + nifs_Un_primary: random_layer.nifs_Un_primary.clone(), + nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), + }); + + let nifs_secondary = random_layer.nifs_Uf_secondary.clone(); + + Ok(Self { + r_U_primary: recursive_snark.r_U_primary.clone(), + r_W_snark_primary: snark_primary?, + + r_U_secondary: recursive_snark.r_U_secondary.clone(), + l_u_secondary: recursive_snark.l_u_secondary.clone(), + nifs_secondary, + f_W_snark_secondary: snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + + random_layer: random_layer_compressed, + _p: Default::default(), }) } @@ -985,6 +1060,22 @@ where num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + self.verify_regular(vk, num_steps, z0_primary, z0_secondary) + } else { + self.verify_randomizing(vk, num_steps, z0_primary, z0_secondary) + } + // TODO ^^ + } + + /// Verify the correctness of the `CompressedSNARK` + fn verify_regular( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { // the number of steps cannot be zero if num_steps == 0 { @@ -1069,6 +1160,115 @@ where Ok((self.zn_primary.clone(), self.zn_secondary.clone())) } + + // TODO JESS: look at all of the hashing code everywhere and make sure the right crap is hashed + + /// Verify the correctness of the `CompressedSNARK` + fn verify_randomizing( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // the number of steps cannot be zero + if num_steps == 0 { + return Err(NovaError::ProofVerifyError); + } + + // check if the (relaxed) R1CS instances have two public outputs + if self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2 + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + vk.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, + ); + hasher.absorb(vk.pp_digest); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zn_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + vk.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zn_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + let random_layer = self.random_layer.as_ref().unwrap(); + + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = self.nifs_secondary.verify( + //random_layer.nifs_Uf_secondary.verify( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &r_Uf_secondary, + &random_layer.l_ur_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + &vk.ro_consts_primary, + &vk.pp_digest, + &self.r_U_primary, + &random_layer.l_ur_primary, + )?; + + // check the satisfiability of the folded instances using + // SNARKs proving the knowledge of their satisfying witnesses + let (res_primary, res_secondary) = rayon::join( + || self.r_W_snark_primary.verify(&vk.vk_primary, &r_Un_primary), + || { + self + .f_W_snark_secondary + .verify(&vk.vk_secondary, &r_Un_secondary) + }, + ); + + res_primary?; + res_secondary?; + + Ok((self.zn_primary.clone(), self.zn_secondary.clone())) + } } type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; @@ -1385,12 +1585,13 @@ mod tests { let res = recursive_snark.randomizing_fold(&pp); assert!(res.is_ok()); - // verify the recursive SNARK - let res = recursive_snark.verify( + // verify the randomized recursive SNARK + let res = recursive_snark.verify_randomizing( &pp, num_steps, &[::Scalar::ONE], &[::Scalar::ZERO], + res.unwrap(), ); assert!(res.is_ok()); @@ -1483,8 +1684,12 @@ mod tests { let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S, S>::prove( + &pp, + &pk, + &recursive_snark, + false, + ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1589,6 +1794,7 @@ mod tests { &pp, &pk, &recursive_snark, + false, ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1739,8 +1945,12 @@ mod tests { let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S, S>::prove( + &pp, + &pk, + &recursive_snark, + false, + ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); diff --git a/temp.rs b/temp.rs new file mode 100644 index 00000000..260475cb --- /dev/null +++ b/temp.rs @@ -0,0 +1,2072 @@ +//! This library implements Nova, a high-speed recursive SNARK. +#![deny( + warnings, + unused, + future_incompatible, + nonstandard_style, + rust_2018_idioms, + missing_docs +)] +#![allow(non_snake_case)] +#![forbid(unsafe_code)] + +// private modules +mod bellpepper; +mod circuit; +mod constants; +mod digest; +mod nifs; +mod r1cs; + +// public modules +pub mod errors; +pub mod gadgets; +pub mod provider; +pub mod spartan; +pub mod traits; + +use once_cell::sync::OnceCell; + +use crate::bellpepper::{ + r1cs::{NovaShape, NovaWitness}, + shape_cs::ShapeCS, + solver::SatisfyingAssignment, +}; +use crate::digest::{DigestComputer, SimpleDigestible}; +use bellpepper_core::{ConstraintSystem, SynthesisError}; +use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; +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 gadgets::utils::scalar_as_base; +use nifs::NIFS; +use r1cs::{ + CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, +}; +use serde::{Deserialize, Serialize}; +use traits::{ + circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, + AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, +}; + +/// A type that holds public parameters of Nova +#[derive(Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_circuit_primary: ROConstantsCircuit, + ck_primary: CommitmentKey, + r1cs_shape_primary: R1CSShape, + ro_consts_secondary: ROConstants, + ro_consts_circuit_secondary: ROConstantsCircuit, + ck_secondary: CommitmentKey, + r1cs_shape_secondary: R1CSShape, + augmented_circuit_params_primary: NovaAugmentedCircuitParams, + augmented_circuit_params_secondary: NovaAugmentedCircuitParams, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, + _p: PhantomData<(C1, C2)>, +} + +impl SimpleDigestible for PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ +} + +impl PublicParams +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. + /// + /// # Note + /// + /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. + /// + /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require + /// larger sizes for these parameters. These SNARKs provide a hint for these values by + /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. + /// + /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. + /// + /// # Arguments + /// + /// * `c_primary`: The primary circuit of type `C1`. + /// * `c_secondary`: The secondary circuit of type `C2`. + /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint + /// for the number of generators required in the commitment scheme for the primary circuit. + /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. + /// + /// # Example + /// + /// ```rust + /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; + /// # use nova_snark::provider::ipa_pc::EvaluationEngine; + /// # use nova_snark::provider::{PallasEngine, VestaEngine}; + /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; + /// use nova_snark::PublicParams; + /// + /// type E1 = PallasEngine; + /// type E2 = VestaEngine; + /// type EE = EvaluationEngine; + /// type SPrime = RelaxedR1CSSNARK>; + /// + /// let circuit1 = TrivialCircuit::<::Scalar>::default(); + /// let circuit2 = TrivialCircuit::<::Scalar>::default(); + /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) + /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. + /// let ck_hint1 = &*SPrime::::ck_floor(); + /// let ck_hint2 = &*SPrime::::ck_floor(); + /// + /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); + /// ``` + pub fn setup( + c_primary: &C1, + c_secondary: &C2, + ck_hint1: &CommitmentKeyHint, + ck_hint2: &CommitmentKeyHint, + ) -> Result { + let augmented_circuit_params_primary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); + let augmented_circuit_params_secondary = + NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); + + let ro_consts_primary: ROConstants = ROConstants::::default(); + let ro_consts_secondary: ROConstants = ROConstants::::default(); + + let F_arity_primary = c_primary.arity(); + let F_arity_secondary = c_secondary.arity(); + + // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar + let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); + let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); + + // Initialize ck for the primary + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &augmented_circuit_params_primary, + None, + c_primary, + ro_consts_circuit_primary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_primary.synthesize(&mut cs); + let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); + + // Initialize ck for the secondary + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &augmented_circuit_params_secondary, + None, + c_secondary, + ro_consts_circuit_secondary.clone(), + ); + let mut cs: ShapeCS = ShapeCS::new(); + let _ = circuit_secondary.synthesize(&mut cs); + let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); + + if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { + return Err(NovaError::InvalidStepCircuitIO); + } + + let pp = PublicParams { + F_arity_primary, + F_arity_secondary, + ro_consts_primary, + ro_consts_circuit_primary, + ck_primary, + r1cs_shape_primary, + ro_consts_secondary, + ro_consts_circuit_secondary, + ck_secondary, + r1cs_shape_secondary, + augmented_circuit_params_primary, + augmented_circuit_params_secondary, + digest: OnceCell::new(), + _p: Default::default(), + }; + + // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods + let _ = pp.digest(); + + Ok(pp) + } + + /// Retrieve the digest of the public parameters. + pub fn digest(&self) -> E1::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .expect("Failure in retrieving digest") + } + + /// Returns the number of constraints in the primary and secondary circuits + pub const fn num_constraints(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_cons, + self.r1cs_shape_secondary.num_cons, + ) + } + + /// Returns the number of variables in the primary and secondary circuits + pub const fn num_variables(&self) -> (usize, usize) { + ( + self.r1cs_shape_primary.num_vars, + self.r1cs_shape_secondary.num_vars, + ) + } +} + +/// A SNARK that proves the correct execution of an incremental computation +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + z0_primary: Vec, + z0_secondary: Vec, + r_W_primary: RelaxedR1CSWitness, + r_U_primary: RelaxedR1CSInstance, + r_W_secondary: RelaxedR1CSWitness, + r_U_secondary: RelaxedR1CSInstance, + l_w_secondary: R1CSWitness, + l_u_secondary: R1CSInstance, + i: usize, + zi_primary: Vec, + zi_secondary: Vec, + random_layer: Option>, + _p: PhantomData<(C1, C2)>, +} + +/// Final randomized fold +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct RandomLayer +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, +{ + l_ur_primary: RelaxedR1CSInstance, + l_ur_secondary: RelaxedR1CSInstance, + nifs_Uf_secondary: NIFS, + nifs_Un_primary: NIFS, + nifs_Un_secondary: NIFS, + r_Wn_primary: RelaxedR1CSWitness, + r_Wn_secondary: RelaxedR1CSWitness, +} + +impl RecursiveSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ + /// Create new instance of recursive SNARK + pub fn new( + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result { + if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { + return Err(NovaError::InvalidInitialInputLength); + } + + // base case for the primary + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::ZERO, + z0_primary.to_vec(), + None, + None, + None, + None, + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + let (u_primary, w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // base case for the secondary + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::ZERO, + z0_secondary.to_vec(), + None, + None, + Some(u_primary.clone()), + None, + ); + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + let (u_secondary, w_secondary) = + cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; + + // IVC proof for the primary circuit + let l_w_primary = w_primary; + let l_u_primary = u_primary; + let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); + let r_U_primary = + RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); + + // IVC proof for the secondary circuit + let l_w_secondary = w_secondary; + let l_u_secondary = u_secondary; + let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); + let r_U_secondary = + RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); + + assert!( + !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), + "Invalid step length" + ); + + let zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + let zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + Ok(Self { + z0_primary: z0_primary.to_vec(), + z0_secondary: z0_secondary.to_vec(), + r_W_primary, + r_U_primary, + r_W_secondary, + r_U_secondary, + l_w_secondary, + l_u_secondary, + i: 0, + zi_primary, + zi_secondary, + random_layer: None, + _p: Default::default(), + }) + } + + /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) + /// by executing a step of the incremental computation + pub fn prove_step( + &mut self, + pp: &PublicParams, + c_primary: &C1, + c_secondary: &C2, + ) -> Result<(), NovaError> { + // first step was already done in the constructor + if self.i == 0 { + self.i = 1; + return Ok(()); + } + + // already applied randomizing layer + if self.random_layer.is_some() { + return Err(NovaError::ProofVerifyError); + } + + // 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::(pp.digest()), + &pp.r1cs_shape_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + )?; + + let mut cs_primary = SatisfyingAssignment::::new(); + let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + scalar_as_base::(pp.digest()), + E1::Scalar::from(self.i as u64), + self.z0_primary.to_vec(), + Some(self.zi_primary.clone()), + Some(self.r_U_secondary.clone()), + Some(self.l_u_secondary.clone()), + Some(nifs_secondary.comm_T), + ); + + let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_primary, + Some(inputs_primary), + c_primary, + pp.ro_consts_circuit_primary.clone(), + ); + let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; + + let (l_u_primary, l_w_primary) = + cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; + + // 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, + &self.r_U_primary, + &self.r_W_primary, + &l_u_primary, + &l_w_primary, + )?; + + let mut cs_secondary = SatisfyingAssignment::::new(); + let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( + pp.digest(), + E2::Scalar::from(self.i as u64), + self.z0_secondary.to_vec(), + Some(self.zi_secondary.clone()), + Some(self.r_U_primary.clone()), + Some(l_u_primary), + Some(nifs_primary.comm_T), + ); + + let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( + &pp.augmented_circuit_params_secondary, + Some(inputs_secondary), + c_secondary, + pp.ro_consts_circuit_secondary.clone(), + ); + let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; + + let (l_u_secondary, l_w_secondary) = cs_secondary + .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) + .map_err(|_e| NovaError::UnSat)?; + + // update the running instances and witnesses + self.zi_primary = zi_primary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + self.zi_secondary = zi_secondary + .iter() + .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) + .collect::::Scalar>, _>>()?; + + self.l_u_secondary = l_u_secondary; + self.l_w_secondary = l_w_secondary; + + self.r_U_primary = r_U_primary; + self.r_W_primary = r_W_primary; + + self.i += 1; + + self.r_U_secondary = r_U_secondary; + self.r_W_secondary = r_W_secondary; + + Ok(()) + } + + /// randomize the last step computed + /// if you plan on using a randomized CompressedSNARK, you don't need to call this + pub fn randomizing_fold(&mut self, pp: &PublicParams) -> Result<(), NovaError> { + if self.i == 0 { + // don't call this until we have something to randomize + return Err(NovaError::InvalidNumSteps); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let (l_ur_secondary, l_wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; + + let (nifs_Un_secondary, (_r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_ur_secondary, + &l_wr_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let (l_ur_primary, l_wr_primary) = pp + .r1cs_shape_primary + .sample_random_instance_witness(&pp.ck_primary)?; + + let (nifs_Un_primary, (_r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &self.r_U_primary, + &self.r_W_primary, + &l_ur_primary, + &l_wr_primary, + )?; + + // output randomized IVC Proof + self.random_layer = Some(RandomLayer { + l_ur_primary, + l_ur_secondary, + // commitments to cross terms + nifs_Uf_secondary, + nifs_Un_primary, + nifs_Un_secondary, + r_Wn_primary, + r_Wn_secondary, + }); + + Ok(()) + } + + /// Verify the correctness of the `RecursiveSNARK` + pub fn verify( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + self.verify_regular(pp, num_steps, z0_primary, z0_secondary) + } else { + self.verify_randomizing(pp, num_steps, z0_primary, z0_secondary) + } + } + + /// Verify the correctness of the `RecursiveSNARK` (no randomizing layer) + fn verify_regular( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outpus + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + pp.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, + ); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + pp.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // check the satisfiability of the provided instances + let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( + || { + pp.r1cs_shape_primary + .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) + }, + || { + rayon::join( + || { + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &self.r_U_secondary, + &self.r_W_secondary, + ) + }, + || { + pp.r1cs_shape_secondary.is_sat( + &pp.ck_secondary, + &self.l_u_secondary, + &self.l_w_secondary, + ) + }, + ) + }, + ); + + // check the returned res objects + res_r_primary?; + res_r_secondary?; + res_l_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } + + /// Verify the correctness of the `RecursiveSNARK` randomizing layer + fn verify_randomizing( + &self, + pp: &PublicParams, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + let random_layer = self.random_layer.as_ref().unwrap(); + + // number of steps cannot be zero + let is_num_steps_zero = num_steps == 0; + + // check if the provided proof has executed num_steps + let is_num_steps_not_match = self.i != num_steps; + + // check if the initial inputs match + let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; + + // check if the (relaxed) R1CS instances have two public outputs + let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2; + + if is_num_steps_zero + || is_num_steps_not_match + || is_inputs_not_match + || is_instance_has_two_outpus + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + pp.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, + ); + hasher.absorb(pp.digest()); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zi_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + pp.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(pp.digest())); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zi_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &r_Uf_secondary, + &random_layer.l_ur_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + &pp.ro_consts_primary, + &pp.digest(), + &self.r_U_primary, + &random_layer.l_ur_primary, + )?; + + // check the satisfiability of U1, U2 + let (res_primary, res_secondary) = rayon::join( + || { + pp.r1cs_shape_primary.is_sat_relaxed( + &pp.ck_primary, + &r_Un_primary, + &random_layer.r_Wn_primary, + ) + }, + || { + pp.r1cs_shape_secondary.is_sat_relaxed( + &pp.ck_secondary, + &r_Un_secondary, + &random_layer.r_Wn_secondary, + ) + }, + ); + + // check the returned res objects + res_primary?; + res_secondary?; + + Ok((self.zi_primary.clone(), self.zi_secondary.clone())) + } + + /// Get the outputs after the last step of computation. + pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { + (&self.zi_primary, &self.zi_secondary) + } + + /// The number of steps which have been executed thus far. + pub fn num_steps(&self) -> usize { + self.i + } +} + +/// A type that holds the prover key for `CompressedSNARK` +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct ProverKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + pk_primary: S1::ProverKey, + pk_secondary: S2::ProverKey, + _p: PhantomData<(C1, C2)>, +} + +/// A type that holds the verifier key for `CompressedSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct VerifierKey +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + F_arity_primary: usize, + F_arity_secondary: usize, + ro_consts_primary: ROConstants, + ro_consts_secondary: ROConstants, + pp_digest: E1::Scalar, + vk_primary: S1::VerifierKey, + vk_secondary: S2::VerifierKey, + _p: PhantomData<(C1, C2)>, +} + +/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, +{ + r_U_primary: RelaxedR1CSInstance, + r_W_snark_primary: S1, + + r_U_secondary: RelaxedR1CSInstance, + l_u_secondary: R1CSInstance, + nifs_secondary: NIFS, + f_W_snark_secondary: S2, + + zn_primary: Vec, + zn_secondary: Vec, + + _p: PhantomData<(C1, C2)>, +} + +impl CompressedSNARK +where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + S1: RelaxedR1CSSNARKTrait, + S2: RelaxedR1CSSNARKTrait, + random_layer: RandomLayer, +{ + /// Creates prover and verifier keys for `CompressedSNARK` + pub fn setup( + pp: &PublicParams, + ) -> Result< + ( + ProverKey, + VerifierKey, + ), + NovaError, + > { + let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; + let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; + + let pk = ProverKey { + pk_primary, + pk_secondary, + _p: Default::default(), + }; + + let vk = VerifierKey { + 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(), + pp_digest: pp.digest(), + vk_primary, + vk_secondary, + _p: Default::default(), + }; + + Ok((pk, vk)) + } + + /// Create a new `CompressedSNARK` + pub fn prove( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + randomizing: bool, + ) -> Result { + if recursive_snark.random_layer.is_some() { + // don't randomize before, we take care of that here + return Err(NovaError::ProofVerifyError); + } + if randomizing { + self.prove_randomizing(pp, pk, recursive_snark) + } else { + self.prove_regular(pp, pk, recursive_snark) + } + } + + /// Create a new `CompressedSNARK` + fn prove_regular( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + ) -> Result { + // fold the secondary circuit's instance with its running instance + let (nifs_secondary, (f_U_secondary, f_W_secondary)) = 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, + &recursive_snark.l_u_secondary, + &recursive_snark.l_w_secondary, + )?; + + // create SNARKs proving the knowledge of f_W_primary and f_W_secondary + let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( + || { + S1::prove( + &pp.ck_primary, + &pk.pk_primary, + &pp.r1cs_shape_primary, + &recursive_snark.r_U_primary, + &recursive_snark.r_W_primary, + ) + }, + || { + S2::prove( + &pp.ck_secondary, + &pk.pk_secondary, + &pp.r1cs_shape_secondary, + &f_U_secondary, + &f_W_secondary, + ) + }, + ); + + Ok(Self { + r_U_primary: recursive_snark.r_U_primary.clone(), + r_W_snark_primary: r_W_snark_primary?, + + r_U_secondary: recursive_snark.r_U_secondary.clone(), + l_u_secondary: recursive_snark.l_u_secondary.clone(), + nifs_secondary, + f_W_snark_secondary: f_W_snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + + _p: Default::default(), + None, + }) + } + + /// Create a new `CompressedSNARK` + fn prove_randomizing( + pp: &PublicParams, + pk: &ProverKey, + recursive_snark: &RecursiveSNARK, + ) -> Result { + // prove three foldings + let (r_Un_primary, r_Un_secondary) = recursive_snark.randomizing_fold(pp)?; + let random_layer = recursive_snark?; + + // create SNARKs proving the knowledge of Wn primary/secondary + let (snark_primary, snark_secondary) = rayon::join( + || { + S1::prove( + &pp.ck_primary, + &pk.pk_primary, + &pp.r1cs_shape_primary, + &r_Un_primary, + &random_layer.r_Wn_primary, + ) + }, + || { + S2::prove( + &pp.ck_secondary, + &pk.pk_secondary, + &pp.r1cs_shape_secondary, + &random_layer.r_Un_secondary, + &random_layer.r_Wn_secondary, + ) + }, + ); + + Ok(Self { + r_U_primary: recursive_snark.r_U_primary.clone(), + r_W_snark_primary: snark_primary?, + + r_U_secondary: recursive_snark.r_U_secondary.clone(), + l_u_secondary: recursive_snark.l_u_secondary.clone(), + nifs_secondary, + f_W_snark_secondary: f_W_snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + + _p: Default::default(), + }) + } + + /// Verify the correctness of the `CompressedSNARK` + pub fn verify( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + if self.random_layer.is_none() { + self.verify_regular(vk, num_steps, z0_primary, z0_secondary) + } else { + self.verify_randomizing(vk, num_steps, z0_primary, z0_secondary) + } + // TODO ^^ + } + + /// Verify the correctness of the `CompressedSNARK` + fn verify_regular( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // the number of steps cannot be zero + if num_steps == 0 { + return Err(NovaError::ProofVerifyError); + } + + // check if the (relaxed) R1CS instances have two public outputs + if self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2 + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + vk.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, + ); + hasher.absorb(vk.pp_digest); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zn_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + vk.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zn_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold the secondary's running instance with the last instance to get a folded instance + let f_U_secondary = self.nifs_secondary.verify( + &vk.ro_consts_secondary, + &scalar_as_base::(vk.pp_digest), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // check the satisfiability of the folded instances using + // SNARKs proving the knowledge of their satisfying witnesses + let (res_primary, res_secondary) = rayon::join( + || { + self + .r_W_snark_primary + .verify(&vk.vk_primary, &self.r_U_primary) + }, + || { + self + .f_W_snark_secondary + .verify(&vk.vk_secondary, &f_U_secondary) + }, + ); + + res_primary?; + res_secondary?; + + Ok((self.zn_primary.clone(), self.zn_secondary.clone())) + } + + // TODO JESS: look at all of the hashing code everywhere and make sure the right crap is hashed + + /// Verify the correctness of the `CompressedSNARK` + fn verify_randomizing( + &self, + vk: &VerifierKey, + num_steps: usize, + z0_primary: &[E1::Scalar], + z0_secondary: &[E2::Scalar], + ) -> Result<(Vec, Vec), NovaError> { + // the number of steps cannot be zero + if num_steps == 0 { + return Err(NovaError::ProofVerifyError); + } + + // check if the (relaxed) R1CS instances have two public outputs + if self.l_u_secondary.X.len() != 2 + || self.r_U_primary.X.len() != 2 + || self.r_U_secondary.X.len() != 2 + { + return Err(NovaError::ProofVerifyError); + } + + // check if the output hashes in R1CS instances point to the right running instances + let (hash_primary, hash_secondary) = { + let mut hasher = ::RO::new( + vk.ro_consts_secondary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, + ); + hasher.absorb(vk.pp_digest); + hasher.absorb(E1::Scalar::from(num_steps as u64)); + for e in z0_primary { + hasher.absorb(*e); + } + for e in &self.zn_primary { + hasher.absorb(*e); + } + self.r_U_secondary.absorb_in_ro(&mut hasher); + + let mut hasher2 = ::RO::new( + vk.ro_consts_primary.clone(), + NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, + ); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); + hasher2.absorb(E2::Scalar::from(num_steps as u64)); + for e in z0_secondary { + hasher2.absorb(*e); + } + for e in &self.zn_secondary { + hasher2.absorb(*e); + } + self.r_U_primary.absorb_in_ro(&mut hasher2); + + ( + hasher.squeeze(NUM_HASH_BITS), + hasher2.squeeze(NUM_HASH_BITS), + ) + }; + + if hash_primary != self.l_u_secondary.X[0] + || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) + { + return Err(NovaError::ProofVerifyError); + } + + // fold secondary U/W with secondary u/w to get Uf/Wf + let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &self.r_U_secondary, + &self.l_u_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &r_Uf_secondary, + &random_layer.l_ur_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + &pp.ro_consts_primary, + &pp.digest(), + &self.r_U_primary, + &random_layer.l_ur_primary, + )?; + + // check the satisfiability of the folded instances using + // SNARKs proving the knowledge of their satisfying witnesses + let (res_primary, res_secondary) = rayon::join( + || self.r_W_snark_primary.verify(&vk.vk_primary, &r_Un_primary), + || { + self + .f_W_snark_secondary + .verify(&vk.vk_secondary, &r_Un_secondary) + }, + ); + + res_primary?; + res_secondary?; + + Ok((self.zn_primary.clone(), self.zn_secondary.clone())) + } +} + +type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; +type Commitment = <::CE as CommitmentEngineTrait>::Commitment; +type CE = ::CE; + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + provider::{ + pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, + GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, + }, + traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, + }; + use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; + use core::{fmt::Write, marker::PhantomData}; + use expect_test::{expect, Expect}; + use ff::PrimeField; + + type EE = provider::ipa_pc::EvaluationEngine; + type EEPrime = provider::hyperkzg::EvaluationEngine; + type S = spartan::snark::RelaxedR1CSSNARK; + type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; + + #[derive(Clone, Debug, Default)] + struct CubicCircuit { + _p: PhantomData, + } + + impl StepCircuit for CubicCircuit { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. + let x = &z[0]; + let x_sq = x.square(cs.namespace(|| "x_sq"))?; + let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; + let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { + Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) + })?; + + cs.enforce( + || "y = x^3 + x + 5", + |lc| { + lc + x_cu.get_variable() + + x.get_variable() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + + CS::one() + }, + |lc| lc + CS::one(), + |lc| lc + y.get_variable(), + ); + + Ok(vec![y]) + } + } + + impl CubicCircuit { + fn output(&self, z: &[F]) -> Vec { + vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] + } + } + + fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + E1::GE: DlogGroup, + E2::GE: DlogGroup, + T1: StepCircuit, + T2: StepCircuit, + // required to use the IPA in the initialization of the commitment key hints below + >::CommitmentKey: CommitmentKeyExtTrait, + >::CommitmentKey: CommitmentKeyExtTrait, + { + // this tests public parameters with a size specifically intended for a spark-compressed SNARK + let ck_hint1 = &*SPrime::>::ck_floor(); + let ck_hint2 = &*SPrime::>::ck_floor(); + let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); + + let digest_str = pp + .digest() + .to_repr() + .as_ref() + .iter() + .fold(String::new(), |mut output, b| { + let _ = write!(output, "{b:02x}"); + output + }); + expected.assert_eq(&digest_str); + } + + #[test] + fn test_pp_digest() { + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["a69d6cf6d014c3a5cc99b77afc86691f7460faa737207dd21b30e8241fae8002"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["b22ab3456df4bd391804a39fae582b37ed4a8d90ace377337940ac956d87f701"], + ); + + test_pp_digest_with::( + &TrivialCircuit::<_>::default(), + &TrivialCircuit::<_>::default(), + &expect!["c8aec89a3ea90317a0ecdc9150f4fc3648ca33f6660924a192cafd82e2939b02"], + ); + } + + fn test_ivc_trivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = TrivialCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ) + .unwrap(); + + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ZERO], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_trivial() { + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + test_ivc_trivial_with::(); + } + + fn test_ivc_nontrivial_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + // verify the recursive snark at each step of recursion + let res = recursive_snark.verify( + &pp, + i + 1, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + } + + #[test] + fn test_ivc_nontrivial() { + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + test_ivc_nontrivial_with::(); + } + + fn test_ivc_nontrivial_randomizing_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + + // verify the recursive snark at each step of recursion + let res = recursive_snark.verify( + &pp, + i + 1, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + let res = recursive_snark.randomizing_fold(&pp); + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + } + + #[test] + fn test_ivc_nontrivial_randomizing() { + test_ivc_nontrivial_randomizing_with::(); + test_ivc_nontrivial_randomizing_with::(); + test_ivc_nontrivial_randomizing_with::(); + } + + fn test_ivc_nontrivial_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_compression() { + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + ); + test_ivc_nontrivial_with_compression_with::, EE<_>>(); + + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + provider::hyperkzg::EvaluationEngine<_>, + EE<_>, + >(); + } + + fn test_ivc_nontrivial_with_spark_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + let circuit_primary = TrivialCircuit::default(); + let circuit_secondary = CubicCircuit::default(); + + // produce public parameters, which we'll use with a spark-compressed SNARK + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*SPrime::::ck_floor(), + &*SPrime::::ck_floor(), + ) + .unwrap(); + + let num_steps = 3; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &circuit_primary, + &circuit_secondary, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + for _i in 0..num_steps { + let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + // sanity: check the claimed output with a direct computation of the same + assert_eq!(zn_primary, vec![::Scalar::ONE]); + let mut zn_secondary_direct = vec![::Scalar::ZERO]; + for _i in 0..num_steps { + zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); + } + assert_eq!(zn_secondary, zn_secondary_direct); + assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); + + // run the compressed snark with Spark compiler + // produce the prover and verifier keys for compressed snark + let (pk, vk) = + CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( + &pp, + &pk, + &recursive_snark, + ); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify( + &vk, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nontrivial_with_spark_compression() { + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + EEPrime<_>, + EE<_>, + >(); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + ); + } + + fn test_ivc_nondet_with_compression_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + EE1: EvaluationEngineTrait, + EE2: EvaluationEngineTrait, + { + // y is a non-deterministic advice representing the fifth root of the input at a step. + #[derive(Clone, Debug)] + struct FifthRootCheckingCircuit { + y: F, + } + + impl FifthRootCheckingCircuit { + fn new(num_steps: usize) -> (Vec, Vec) { + let mut powers = Vec::new(); + let rng = &mut rand::rngs::OsRng; + let mut seed = F::random(rng); + for _i in 0..num_steps + 1 { + seed *= seed.clone().square().square(); + + powers.push(Self { y: seed }); + } + + // reverse the powers to get roots + let roots = powers.into_iter().rev().collect::>(); + (vec![roots[0].y], roots[1..].to_vec()) + } + } + + impl StepCircuit for FifthRootCheckingCircuit + where + F: PrimeField, + { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + + // we allocate a variable and set it to the provided non-deterministic advice. + let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); + + // We now check if y = x^{1/5} by checking if y^5 = x + let y_sq = y.square(cs.namespace(|| "y_sq"))?; + let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; + let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; + + cs.enforce( + || "y^5 = x", + |lc| lc + y_pow_5.get_variable(), + |lc| lc + CS::one(), + |lc| lc + x.get_variable(), + ); + + Ok(vec![y]) + } + } + + let circuit_primary = FifthRootCheckingCircuit { + y: ::Scalar::ZERO, + }; + + let circuit_secondary = TrivialCircuit::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::setup( + &circuit_primary, + &circuit_secondary, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 3; + + // produce non-deterministic advice + let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); + let z0_secondary = vec![::Scalar::ZERO]; + + // produce a recursive SNARK + let mut recursive_snark: RecursiveSNARK< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + > = RecursiveSNARK::< + E1, + E2, + FifthRootCheckingCircuit<::Scalar>, + TrivialCircuit<::Scalar>, + >::new( + &pp, + &roots[0], + &circuit_secondary, + &z0_primary, + &z0_secondary, + ) + .unwrap(); + + for circuit_primary in roots.iter().take(num_steps) { + let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); + assert!(res.is_ok()); + } + + // verify the recursive SNARK + let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + + // produce the prover and verifier keys for compressed snark + let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); + + // produce a compressed SNARK + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); + assert!(res.is_ok()); + let compressed_snark = res.unwrap(); + + // verify the compressed SNARK + let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); + assert!(res.is_ok()); + } + + #[test] + fn test_ivc_nondet_with_compression() { + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + } + + fn test_ivc_base_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + let test_circuit1 = TrivialCircuit::<::Scalar>::default(); + let test_circuit2 = CubicCircuit::<::Scalar>::default(); + + // produce public parameters + let pp = PublicParams::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::setup( + &test_circuit1, + &test_circuit2, + &*default_ck_hint(), + &*default_ck_hint(), + ) + .unwrap(); + + let num_steps = 1; + + // produce a recursive SNARK + let mut recursive_snark = RecursiveSNARK::< + E1, + E2, + TrivialCircuit<::Scalar>, + CubicCircuit<::Scalar>, + >::new( + &pp, + &test_circuit1, + &test_circuit2, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ) + .unwrap(); + + // produce a recursive SNARK + let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); + + assert!(res.is_ok()); + + // verify the recursive SNARK + let res = recursive_snark.verify( + &pp, + num_steps, + &[::Scalar::ONE], + &[::Scalar::ZERO], + ); + assert!(res.is_ok()); + + let (zn_primary, zn_secondary) = res.unwrap(); + + assert_eq!(zn_primary, vec![::Scalar::ONE]); + assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); + } + + #[test] + fn test_ivc_base() { + test_ivc_base_with::(); + test_ivc_base_with::(); + test_ivc_base_with::(); + } + + fn test_setup_with() + where + E1: Engine::Scalar>, + E2: Engine::Scalar>, + { + #[derive(Clone, Debug, Default)] + struct CircuitWithInputize { + _p: PhantomData, + } + + impl StepCircuit for CircuitWithInputize { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + z: &[AllocatedNum], + ) -> Result>, SynthesisError> { + let x = &z[0]; + let y = x.square(cs.namespace(|| "x_sq"))?; + y.inputize(cs.namespace(|| "y"))?; // inputize y + Ok(vec![y]) + } + } + + // produce public parameters with trivial secondary + let circuit = CircuitWithInputize::<::Scalar>::default(); + let pp = + PublicParams::, TrivialCircuit>::setup( + &circuit, + &TrivialCircuit::default(), + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + + // produce public parameters with the trivial primary + let circuit = CircuitWithInputize::::default(); + let pp = + PublicParams::, CircuitWithInputize>::setup( + &TrivialCircuit::default(), + &circuit, + &*default_ck_hint(), + &*default_ck_hint(), + ); + assert!(pp.is_err()); + assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); + } + + #[test] + fn test_setup() { + test_setup_with::(); + } +} From 114b4b253fea26a9252db4766a93a4cd1663167f Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 12 Sep 2024 13:38:01 -0400 Subject: [PATCH 10/21] clean up --- temp.rs | 2072 ------------------------------------------------------- 1 file changed, 2072 deletions(-) delete mode 100644 temp.rs diff --git a/temp.rs b/temp.rs deleted file mode 100644 index 260475cb..00000000 --- a/temp.rs +++ /dev/null @@ -1,2072 +0,0 @@ -//! This library implements Nova, a high-speed recursive SNARK. -#![deny( - warnings, - unused, - future_incompatible, - nonstandard_style, - rust_2018_idioms, - missing_docs -)] -#![allow(non_snake_case)] -#![forbid(unsafe_code)] - -// private modules -mod bellpepper; -mod circuit; -mod constants; -mod digest; -mod nifs; -mod r1cs; - -// public modules -pub mod errors; -pub mod gadgets; -pub mod provider; -pub mod spartan; -pub mod traits; - -use once_cell::sync::OnceCell; - -use crate::bellpepper::{ - r1cs::{NovaShape, NovaWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, -}; -use crate::digest::{DigestComputer, SimpleDigestible}; -use bellpepper_core::{ConstraintSystem, SynthesisError}; -use circuit::{NovaAugmentedCircuit, NovaAugmentedCircuitInputs, NovaAugmentedCircuitParams}; -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 gadgets::utils::scalar_as_base; -use nifs::NIFS; -use r1cs::{ - CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, -}; -use serde::{Deserialize, Serialize}; -use traits::{ - circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, - AbsorbInROTrait, Engine, ROConstants, ROConstantsCircuit, ROTrait, -}; - -/// A type that holds public parameters of Nova -#[derive(Serialize, Deserialize)] -#[serde(bound = "")] -pub struct PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_circuit_primary: ROConstantsCircuit, - ck_primary: CommitmentKey, - r1cs_shape_primary: R1CSShape, - ro_consts_secondary: ROConstants, - ro_consts_circuit_secondary: ROConstantsCircuit, - ck_secondary: CommitmentKey, - r1cs_shape_secondary: R1CSShape, - augmented_circuit_params_primary: NovaAugmentedCircuitParams, - augmented_circuit_params_secondary: NovaAugmentedCircuitParams, - #[serde(skip, default = "OnceCell::new")] - digest: OnceCell, - _p: PhantomData<(C1, C2)>, -} - -impl SimpleDigestible for PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ -} - -impl PublicParams -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Creates a new `PublicParams` for a pair of circuits `C1` and `C2`. - /// - /// # Note - /// - /// Public parameters set up a number of bases for the homomorphic commitment scheme of Nova. - /// - /// Some final compressing SNARKs, like variants of Spartan, use computation commitments that require - /// larger sizes for these parameters. These SNARKs provide a hint for these values by - /// implementing `RelaxedR1CSSNARKTrait::ck_floor()`, which can be passed to this function. - /// - /// If you're not using such a SNARK, pass `nova_snark::traits::snark::default_ck_hint()` instead. - /// - /// # Arguments - /// - /// * `c_primary`: The primary circuit of type `C1`. - /// * `c_secondary`: The secondary circuit of type `C2`. - /// * `ck_hint1`: A `CommitmentKeyHint` for `G1`, which is a function that provides a hint - /// for the number of generators required in the commitment scheme for the primary circuit. - /// * `ck_hint2`: A `CommitmentKeyHint` for `G2`, similar to `ck_hint1`, but for the secondary circuit. - /// - /// # Example - /// - /// ```rust - /// # use nova_snark::spartan::ppsnark::RelaxedR1CSSNARK; - /// # use nova_snark::provider::ipa_pc::EvaluationEngine; - /// # use nova_snark::provider::{PallasEngine, VestaEngine}; - /// # use nova_snark::traits::{circuit::TrivialCircuit, Engine, snark::RelaxedR1CSSNARKTrait}; - /// use nova_snark::PublicParams; - /// - /// type E1 = PallasEngine; - /// type E2 = VestaEngine; - /// type EE = EvaluationEngine; - /// type SPrime = RelaxedR1CSSNARK>; - /// - /// let circuit1 = TrivialCircuit::<::Scalar>::default(); - /// let circuit2 = TrivialCircuit::<::Scalar>::default(); - /// // Only relevant for a SNARK using computational commitments, pass &(|_| 0) - /// // or &*nova_snark::traits::snark::default_ck_hint() otherwise. - /// let ck_hint1 = &*SPrime::::ck_floor(); - /// let ck_hint2 = &*SPrime::::ck_floor(); - /// - /// let pp = PublicParams::setup(&circuit1, &circuit2, ck_hint1, ck_hint2); - /// ``` - pub fn setup( - c_primary: &C1, - c_secondary: &C2, - ck_hint1: &CommitmentKeyHint, - ck_hint2: &CommitmentKeyHint, - ) -> Result { - let augmented_circuit_params_primary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); - let augmented_circuit_params_secondary = - NovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); - - let ro_consts_primary: ROConstants = ROConstants::::default(); - let ro_consts_secondary: ROConstants = ROConstants::::default(); - - let F_arity_primary = c_primary.arity(); - let F_arity_secondary = c_secondary.arity(); - - // ro_consts_circuit_primary are parameterized by E2 because the type alias uses E2::Base = E1::Scalar - let ro_consts_circuit_primary: ROConstantsCircuit = ROConstantsCircuit::::default(); - let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); - - // Initialize ck for the primary - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &augmented_circuit_params_primary, - None, - c_primary, - ro_consts_circuit_primary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_primary.synthesize(&mut cs); - let (r1cs_shape_primary, ck_primary) = cs.r1cs_shape(ck_hint1); - - // Initialize ck for the secondary - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &augmented_circuit_params_secondary, - None, - c_secondary, - ro_consts_circuit_secondary.clone(), - ); - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit_secondary.synthesize(&mut cs); - let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(ck_hint2); - - if r1cs_shape_primary.num_io != 2 || r1cs_shape_secondary.num_io != 2 { - return Err(NovaError::InvalidStepCircuitIO); - } - - let pp = PublicParams { - F_arity_primary, - F_arity_secondary, - ro_consts_primary, - ro_consts_circuit_primary, - ck_primary, - r1cs_shape_primary, - ro_consts_secondary, - ro_consts_circuit_secondary, - ck_secondary, - r1cs_shape_secondary, - augmented_circuit_params_primary, - augmented_circuit_params_secondary, - digest: OnceCell::new(), - _p: Default::default(), - }; - - // call pp.digest() so the digest is computed here rather than in RecursiveSNARK methods - let _ = pp.digest(); - - Ok(pp) - } - - /// Retrieve the digest of the public parameters. - pub fn digest(&self) -> E1::Scalar { - self - .digest - .get_or_try_init(|| DigestComputer::new(self).digest()) - .cloned() - .expect("Failure in retrieving digest") - } - - /// Returns the number of constraints in the primary and secondary circuits - pub const fn num_constraints(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_cons, - self.r1cs_shape_secondary.num_cons, - ) - } - - /// Returns the number of variables in the primary and secondary circuits - pub const fn num_variables(&self) -> (usize, usize) { - ( - self.r1cs_shape_primary.num_vars, - self.r1cs_shape_secondary.num_vars, - ) - } -} - -/// A SNARK that proves the correct execution of an incremental computation -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - z0_primary: Vec, - z0_secondary: Vec, - r_W_primary: RelaxedR1CSWitness, - r_U_primary: RelaxedR1CSInstance, - r_W_secondary: RelaxedR1CSWitness, - r_U_secondary: RelaxedR1CSInstance, - l_w_secondary: R1CSWitness, - l_u_secondary: R1CSInstance, - i: usize, - zi_primary: Vec, - zi_secondary: Vec, - random_layer: Option>, - _p: PhantomData<(C1, C2)>, -} - -/// Final randomized fold -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RandomLayer -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, -{ - l_ur_primary: RelaxedR1CSInstance, - l_ur_secondary: RelaxedR1CSInstance, - nifs_Uf_secondary: NIFS, - nifs_Un_primary: NIFS, - nifs_Un_secondary: NIFS, - r_Wn_primary: RelaxedR1CSWitness, - r_Wn_secondary: RelaxedR1CSWitness, -} - -impl RecursiveSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, -{ - /// Create new instance of recursive SNARK - pub fn new( - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result { - if z0_primary.len() != pp.F_arity_primary || z0_secondary.len() != pp.F_arity_secondary { - return Err(NovaError::InvalidInitialInputLength); - } - - // base case for the primary - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::ZERO, - z0_primary.to_vec(), - None, - None, - None, - None, - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - let (u_primary, w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // base case for the secondary - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::ZERO, - z0_secondary.to_vec(), - None, - None, - Some(u_primary.clone()), - None, - ); - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - let (u_secondary, w_secondary) = - cs_secondary.r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary)?; - - // IVC proof for the primary circuit - let l_w_primary = w_primary; - let l_u_primary = u_primary; - let r_W_primary = RelaxedR1CSWitness::from_r1cs_witness(&pp.r1cs_shape_primary, &l_w_primary); - let r_U_primary = - RelaxedR1CSInstance::from_r1cs_instance(&pp.ck_primary, &pp.r1cs_shape_primary, &l_u_primary); - - // IVC proof for the secondary circuit - let l_w_secondary = w_secondary; - let l_u_secondary = u_secondary; - let r_W_secondary = RelaxedR1CSWitness::::default(&pp.r1cs_shape_secondary); - let r_U_secondary = - RelaxedR1CSInstance::::default(&pp.ck_secondary, &pp.r1cs_shape_secondary); - - assert!( - !(zi_primary.len() != pp.F_arity_primary || zi_secondary.len() != pp.F_arity_secondary), - "Invalid step length" - ); - - let zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - let zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - Ok(Self { - z0_primary: z0_primary.to_vec(), - z0_secondary: z0_secondary.to_vec(), - r_W_primary, - r_U_primary, - r_W_secondary, - r_U_secondary, - l_w_secondary, - l_u_secondary, - i: 0, - zi_primary, - zi_secondary, - random_layer: None, - _p: Default::default(), - }) - } - - /// Create a new `RecursiveSNARK` (or updates the provided `RecursiveSNARK`) - /// by executing a step of the incremental computation - pub fn prove_step( - &mut self, - pp: &PublicParams, - c_primary: &C1, - c_secondary: &C2, - ) -> Result<(), NovaError> { - // first step was already done in the constructor - if self.i == 0 { - self.i = 1; - return Ok(()); - } - - // already applied randomizing layer - if self.random_layer.is_some() { - return Err(NovaError::ProofVerifyError); - } - - // 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::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - let mut cs_primary = SatisfyingAssignment::::new(); - let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest()), - E1::Scalar::from(self.i as u64), - self.z0_primary.to_vec(), - Some(self.zi_primary.clone()), - Some(self.r_U_secondary.clone()), - Some(self.l_u_secondary.clone()), - Some(nifs_secondary.comm_T), - ); - - let circuit_primary: NovaAugmentedCircuit<'_, E2, C1> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_primary, - Some(inputs_primary), - c_primary, - pp.ro_consts_circuit_primary.clone(), - ); - let zi_primary = circuit_primary.synthesize(&mut cs_primary)?; - - let (l_u_primary, l_w_primary) = - cs_primary.r1cs_instance_and_witness(&pp.r1cs_shape_primary, &pp.ck_primary)?; - - // 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, - &self.r_U_primary, - &self.r_W_primary, - &l_u_primary, - &l_w_primary, - )?; - - let mut cs_secondary = SatisfyingAssignment::::new(); - let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest(), - E2::Scalar::from(self.i as u64), - self.z0_secondary.to_vec(), - Some(self.zi_secondary.clone()), - Some(self.r_U_primary.clone()), - Some(l_u_primary), - Some(nifs_primary.comm_T), - ); - - let circuit_secondary: NovaAugmentedCircuit<'_, E1, C2> = NovaAugmentedCircuit::new( - &pp.augmented_circuit_params_secondary, - Some(inputs_secondary), - c_secondary, - pp.ro_consts_circuit_secondary.clone(), - ); - let zi_secondary = circuit_secondary.synthesize(&mut cs_secondary)?; - - let (l_u_secondary, l_w_secondary) = cs_secondary - .r1cs_instance_and_witness(&pp.r1cs_shape_secondary, &pp.ck_secondary) - .map_err(|_e| NovaError::UnSat)?; - - // update the running instances and witnesses - self.zi_primary = zi_primary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - self.zi_secondary = zi_secondary - .iter() - .map(|v| v.get_value().ok_or(SynthesisError::AssignmentMissing)) - .collect::::Scalar>, _>>()?; - - self.l_u_secondary = l_u_secondary; - self.l_w_secondary = l_w_secondary; - - self.r_U_primary = r_U_primary; - self.r_W_primary = r_W_primary; - - self.i += 1; - - self.r_U_secondary = r_U_secondary; - self.r_W_secondary = r_W_secondary; - - Ok(()) - } - - /// randomize the last step computed - /// if you plan on using a randomized CompressedSNARK, you don't need to call this - pub fn randomizing_fold(&mut self, pp: &PublicParams) -> Result<(), NovaError> { - if self.i == 0 { - // don't call this until we have something to randomize - return Err(NovaError::InvalidNumSteps); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let (l_ur_secondary, l_wr_secondary) = pp - .r1cs_shape_secondary - .sample_random_instance_witness(&pp.ck_secondary)?; - - let (nifs_Un_secondary, (_r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &r_Uf_secondary, - &r_Wf_secondary, - &l_ur_secondary, - &l_wr_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let (l_ur_primary, l_wr_primary) = pp - .r1cs_shape_primary - .sample_random_instance_witness(&pp.ck_primary)?; - - let (nifs_Un_primary, (_r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &self.r_U_primary, - &self.r_W_primary, - &l_ur_primary, - &l_wr_primary, - )?; - - // output randomized IVC Proof - self.random_layer = Some(RandomLayer { - l_ur_primary, - l_ur_secondary, - // commitments to cross terms - nifs_Uf_secondary, - nifs_Un_primary, - nifs_Un_secondary, - r_Wn_primary, - r_Wn_secondary, - }); - - Ok(()) - } - - /// Verify the correctness of the `RecursiveSNARK` - pub fn verify( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - self.verify_regular(pp, num_steps, z0_primary, z0_secondary) - } else { - self.verify_randomizing(pp, num_steps, z0_primary, z0_secondary) - } - } - - /// Verify the correctness of the `RecursiveSNARK` (no randomizing layer) - fn verify_regular( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outpus - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // check the satisfiability of the provided instances - let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join( - || { - pp.r1cs_shape_primary - .is_sat_relaxed(&pp.ck_primary, &self.r_U_primary, &self.r_W_primary) - }, - || { - rayon::join( - || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat( - &pp.ck_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - ) - }, - ) - }, - ); - - // check the returned res objects - res_r_primary?; - res_r_secondary?; - res_l_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } - - /// Verify the correctness of the `RecursiveSNARK` randomizing layer - fn verify_randomizing( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - let random_layer = self.random_layer.as_ref().unwrap(); - - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outpus - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &r_Uf_secondary, - &random_layer.l_ur_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( - &pp.ro_consts_primary, - &pp.digest(), - &self.r_U_primary, - &random_layer.l_ur_primary, - )?; - - // check the satisfiability of U1, U2 - let (res_primary, res_secondary) = rayon::join( - || { - pp.r1cs_shape_primary.is_sat_relaxed( - &pp.ck_primary, - &r_Un_primary, - &random_layer.r_Wn_primary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &r_Un_secondary, - &random_layer.r_Wn_secondary, - ) - }, - ); - - // check the returned res objects - res_primary?; - res_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } - - /// Get the outputs after the last step of computation. - pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { - (&self.zi_primary, &self.zi_secondary) - } - - /// The number of steps which have been executed thus far. - pub fn num_steps(&self) -> usize { - self.i - } -} - -/// A type that holds the prover key for `CompressedSNARK` -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct ProverKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - pk_primary: S1::ProverKey, - pk_secondary: S2::ProverKey, - _p: PhantomData<(C1, C2)>, -} - -/// A type that holds the verifier key for `CompressedSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct VerifierKey -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - F_arity_primary: usize, - F_arity_secondary: usize, - ro_consts_primary: ROConstants, - ro_consts_secondary: ROConstants, - pp_digest: E1::Scalar, - vk_primary: S1::VerifierKey, - vk_secondary: S2::VerifierKey, - _p: PhantomData<(C1, C2)>, -} - -/// A SNARK that proves the knowledge of a valid `RecursiveSNARK` -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, -{ - r_U_primary: RelaxedR1CSInstance, - r_W_snark_primary: S1, - - r_U_secondary: RelaxedR1CSInstance, - l_u_secondary: R1CSInstance, - nifs_secondary: NIFS, - f_W_snark_secondary: S2, - - zn_primary: Vec, - zn_secondary: Vec, - - _p: PhantomData<(C1, C2)>, -} - -impl CompressedSNARK -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - C1: StepCircuit, - C2: StepCircuit, - S1: RelaxedR1CSSNARKTrait, - S2: RelaxedR1CSSNARKTrait, - random_layer: RandomLayer, -{ - /// Creates prover and verifier keys for `CompressedSNARK` - pub fn setup( - pp: &PublicParams, - ) -> Result< - ( - ProverKey, - VerifierKey, - ), - NovaError, - > { - let (pk_primary, vk_primary) = S1::setup(&pp.ck_primary, &pp.r1cs_shape_primary)?; - let (pk_secondary, vk_secondary) = S2::setup(&pp.ck_secondary, &pp.r1cs_shape_secondary)?; - - let pk = ProverKey { - pk_primary, - pk_secondary, - _p: Default::default(), - }; - - let vk = VerifierKey { - 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(), - pp_digest: pp.digest(), - vk_primary, - vk_secondary, - _p: Default::default(), - }; - - Ok((pk, vk)) - } - - /// Create a new `CompressedSNARK` - pub fn prove( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - randomizing: bool, - ) -> Result { - if recursive_snark.random_layer.is_some() { - // don't randomize before, we take care of that here - return Err(NovaError::ProofVerifyError); - } - if randomizing { - self.prove_randomizing(pp, pk, recursive_snark) - } else { - self.prove_regular(pp, pk, recursive_snark) - } - } - - /// Create a new `CompressedSNARK` - fn prove_regular( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - ) -> Result { - // fold the secondary circuit's instance with its running instance - let (nifs_secondary, (f_U_secondary, f_W_secondary)) = 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, - &recursive_snark.l_u_secondary, - &recursive_snark.l_w_secondary, - )?; - - // create SNARKs proving the knowledge of f_W_primary and f_W_secondary - let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( - || { - S1::prove( - &pp.ck_primary, - &pk.pk_primary, - &pp.r1cs_shape_primary, - &recursive_snark.r_U_primary, - &recursive_snark.r_W_primary, - ) - }, - || { - S2::prove( - &pp.ck_secondary, - &pk.pk_secondary, - &pp.r1cs_shape_secondary, - &f_U_secondary, - &f_W_secondary, - ) - }, - ); - - Ok(Self { - r_U_primary: recursive_snark.r_U_primary.clone(), - r_W_snark_primary: r_W_snark_primary?, - - r_U_secondary: recursive_snark.r_U_secondary.clone(), - l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_secondary, - f_W_snark_secondary: f_W_snark_secondary?, - - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), - - _p: Default::default(), - None, - }) - } - - /// Create a new `CompressedSNARK` - fn prove_randomizing( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - ) -> Result { - // prove three foldings - let (r_Un_primary, r_Un_secondary) = recursive_snark.randomizing_fold(pp)?; - let random_layer = recursive_snark?; - - // create SNARKs proving the knowledge of Wn primary/secondary - let (snark_primary, snark_secondary) = rayon::join( - || { - S1::prove( - &pp.ck_primary, - &pk.pk_primary, - &pp.r1cs_shape_primary, - &r_Un_primary, - &random_layer.r_Wn_primary, - ) - }, - || { - S2::prove( - &pp.ck_secondary, - &pk.pk_secondary, - &pp.r1cs_shape_secondary, - &random_layer.r_Un_secondary, - &random_layer.r_Wn_secondary, - ) - }, - ); - - Ok(Self { - r_U_primary: recursive_snark.r_U_primary.clone(), - r_W_snark_primary: snark_primary?, - - r_U_secondary: recursive_snark.r_U_secondary.clone(), - l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_secondary, - f_W_snark_secondary: f_W_snark_secondary?, - - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), - - _p: Default::default(), - }) - } - - /// Verify the correctness of the `CompressedSNARK` - pub fn verify( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - self.verify_regular(vk, num_steps, z0_primary, z0_secondary) - } else { - self.verify_randomizing(vk, num_steps, z0_primary, z0_secondary) - } - // TODO ^^ - } - - /// Verify the correctness of the `CompressedSNARK` - fn verify_regular( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // the number of steps cannot be zero - if num_steps == 0 { - return Err(NovaError::ProofVerifyError); - } - - // check if the (relaxed) R1CS instances have two public outputs - if self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - vk.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, - ); - hasher.absorb(vk.pp_digest); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zn_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - vk.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(vk.pp_digest)); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zn_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold the secondary's running instance with the last instance to get a folded instance - let f_U_secondary = self.nifs_secondary.verify( - &vk.ro_consts_secondary, - &scalar_as_base::(vk.pp_digest), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // check the satisfiability of the folded instances using - // SNARKs proving the knowledge of their satisfying witnesses - let (res_primary, res_secondary) = rayon::join( - || { - self - .r_W_snark_primary - .verify(&vk.vk_primary, &self.r_U_primary) - }, - || { - self - .f_W_snark_secondary - .verify(&vk.vk_secondary, &f_U_secondary) - }, - ); - - res_primary?; - res_secondary?; - - Ok((self.zn_primary.clone(), self.zn_secondary.clone())) - } - - // TODO JESS: look at all of the hashing code everywhere and make sure the right crap is hashed - - /// Verify the correctness of the `CompressedSNARK` - fn verify_randomizing( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // the number of steps cannot be zero - if num_steps == 0 { - return Err(NovaError::ProofVerifyError); - } - - // check if the (relaxed) R1CS instances have two public outputs - if self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - vk.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, - ); - hasher.absorb(vk.pp_digest); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zn_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - - let mut hasher2 = ::RO::new( - vk.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(vk.pp_digest)); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zn_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &r_Uf_secondary, - &random_layer.l_ur_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( - &pp.ro_consts_primary, - &pp.digest(), - &self.r_U_primary, - &random_layer.l_ur_primary, - )?; - - // check the satisfiability of the folded instances using - // SNARKs proving the knowledge of their satisfying witnesses - let (res_primary, res_secondary) = rayon::join( - || self.r_W_snark_primary.verify(&vk.vk_primary, &r_Un_primary), - || { - self - .f_W_snark_secondary - .verify(&vk.vk_secondary, &r_Un_secondary) - }, - ); - - res_primary?; - res_secondary?; - - Ok((self.zn_primary.clone(), self.zn_secondary.clone())) - } -} - -type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; -type Commitment = <::CE as CommitmentEngineTrait>::Commitment; -type CE = ::CE; - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - provider::{ - pedersen::CommitmentKeyExtTrait, traits::DlogGroup, Bn256EngineIPA, Bn256EngineKZG, - GrumpkinEngine, PallasEngine, Secp256k1Engine, Secq256k1Engine, VestaEngine, - }, - traits::{circuit::TrivialCircuit, evaluation::EvaluationEngineTrait, snark::default_ck_hint}, - }; - use ::bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; - use core::{fmt::Write, marker::PhantomData}; - use expect_test::{expect, Expect}; - use ff::PrimeField; - - type EE = provider::ipa_pc::EvaluationEngine; - type EEPrime = provider::hyperkzg::EvaluationEngine; - type S = spartan::snark::RelaxedR1CSSNARK; - type SPrime = spartan::ppsnark::RelaxedR1CSSNARK; - - #[derive(Clone, Debug, Default)] - struct CubicCircuit { - _p: PhantomData, - } - - impl StepCircuit for CubicCircuit { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. - let x = &z[0]; - let x_sq = x.square(cs.namespace(|| "x_sq"))?; - let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), x)?; - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) - })?; - - cs.enforce( - || "y = x^3 + x + 5", - |lc| { - lc + x_cu.get_variable() - + x.get_variable() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - }, - |lc| lc + CS::one(), - |lc| lc + y.get_variable(), - ); - - Ok(vec![y]) - } - } - - impl CubicCircuit { - fn output(&self, z: &[F]) -> Vec { - vec![z[0] * z[0] * z[0] + z[0] + F::from(5u64)] - } - } - - fn test_pp_digest_with(circuit1: &T1, circuit2: &T2, expected: &Expect) - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - E1::GE: DlogGroup, - E2::GE: DlogGroup, - T1: StepCircuit, - T2: StepCircuit, - // required to use the IPA in the initialization of the commitment key hints below - >::CommitmentKey: CommitmentKeyExtTrait, - >::CommitmentKey: CommitmentKeyExtTrait, - { - // this tests public parameters with a size specifically intended for a spark-compressed SNARK - let ck_hint1 = &*SPrime::>::ck_floor(); - let ck_hint2 = &*SPrime::>::ck_floor(); - let pp = PublicParams::::setup(circuit1, circuit2, ck_hint1, ck_hint2).unwrap(); - - let digest_str = pp - .digest() - .to_repr() - .as_ref() - .iter() - .fold(String::new(), |mut output, b| { - let _ = write!(output, "{b:02x}"); - output - }); - expected.assert_eq(&digest_str); - } - - #[test] - fn test_pp_digest() { - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["a69d6cf6d014c3a5cc99b77afc86691f7460faa737207dd21b30e8241fae8002"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["b22ab3456df4bd391804a39fae582b37ed4a8d90ace377337940ac956d87f701"], - ); - - test_pp_digest_with::( - &TrivialCircuit::<_>::default(), - &TrivialCircuit::<_>::default(), - &expect!["c8aec89a3ea90317a0ecdc9150f4fc3648ca33f6660924a192cafd82e2939b02"], - ); - } - - fn test_ivc_trivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = TrivialCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ) - .unwrap(); - - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ZERO], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_trivial() { - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - test_ivc_trivial_with::(); - } - - fn test_ivc_nontrivial_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - - // verify the recursive snark at each step of recursion - let res = recursive_snark.verify( - &pp, - i + 1, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - } - - #[test] - fn test_ivc_nontrivial() { - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - test_ivc_nontrivial_with::(); - } - - fn test_ivc_nontrivial_randomizing_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - - // verify the recursive snark at each step of recursion - let res = recursive_snark.verify( - &pp, - i + 1, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - let res = recursive_snark.randomizing_fold(&pp); - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - } - - #[test] - fn test_ivc_nontrivial_randomizing() { - test_ivc_nontrivial_randomizing_with::(); - test_ivc_nontrivial_randomizing_with::(); - test_ivc_nontrivial_randomizing_with::(); - } - - fn test_ivc_nontrivial_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - ); - test_ivc_nontrivial_with_compression_with::, EE<_>>(); - - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - provider::hyperkzg::EvaluationEngine<_>, - EE<_>, - >(); - } - - fn test_ivc_nontrivial_with_spark_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters, which we'll use with a spark-compressed SNARK - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*SPrime::::ck_floor(), - &*SPrime::::ck_floor(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for _i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = CubicCircuit::default().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - - // run the compressed snark with Spark compiler - // produce the prover and verifier keys for compressed snark - let (pk, vk) = - CompressedSNARK::<_, _, _, _, SPrime, SPrime>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = CompressedSNARK::<_, _, _, _, SPrime, SPrime>::prove( - &pp, - &pk, - &recursive_snark, - ); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify( - &vk, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nontrivial_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - EEPrime<_>, - EE<_>, - >(); - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - ); - } - - fn test_ivc_nondet_with_compression_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - EE1: EvaluationEngineTrait, - EE2: EvaluationEngineTrait, - { - // y is a non-deterministic advice representing the fifth root of the input at a step. - #[derive(Clone, Debug)] - struct FifthRootCheckingCircuit { - y: F, - } - - impl FifthRootCheckingCircuit { - fn new(num_steps: usize) -> (Vec, Vec) { - let mut powers = Vec::new(); - let rng = &mut rand::rngs::OsRng; - let mut seed = F::random(rng); - for _i in 0..num_steps + 1 { - seed *= seed.clone().square().square(); - - powers.push(Self { y: seed }); - } - - // reverse the powers to get roots - let roots = powers.into_iter().rev().collect::>(); - (vec![roots[0].y], roots[1..].to_vec()) - } - } - - impl StepCircuit for FifthRootCheckingCircuit - where - F: PrimeField, - { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - - // we allocate a variable and set it to the provided non-deterministic advice. - let y = AllocatedNum::alloc_infallible(cs.namespace(|| "y"), || self.y); - - // We now check if y = x^{1/5} by checking if y^5 = x - let y_sq = y.square(cs.namespace(|| "y_sq"))?; - let y_quad = y_sq.square(cs.namespace(|| "y_quad"))?; - let y_pow_5 = y_quad.mul(cs.namespace(|| "y_fifth"), &y)?; - - cs.enforce( - || "y^5 = x", - |lc| lc + y_pow_5.get_variable(), - |lc| lc + CS::one(), - |lc| lc + x.get_variable(), - ); - - Ok(vec![y]) - } - } - - let circuit_primary = FifthRootCheckingCircuit { - y: ::Scalar::ZERO, - }; - - let circuit_secondary = TrivialCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce non-deterministic advice - let (z0_primary, roots) = FifthRootCheckingCircuit::new(num_steps); - let z0_secondary = vec![::Scalar::ZERO]; - - // produce a recursive SNARK - let mut recursive_snark: RecursiveSNARK< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - > = RecursiveSNARK::< - E1, - E2, - FifthRootCheckingCircuit<::Scalar>, - TrivialCircuit<::Scalar>, - >::new( - &pp, - &roots[0], - &circuit_secondary, - &z0_primary, - &z0_secondary, - ) - .unwrap(); - - for circuit_primary in roots.iter().take(num_steps) { - let res = recursive_snark.prove_step(&pp, circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - } - - // verify the recursive SNARK - let res = recursive_snark.verify(&pp, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - - // produce the prover and verifier keys for compressed snark - let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); - - // produce a compressed SNARK - let res = - CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); - assert!(res.is_ok()); - let compressed_snark = res.unwrap(); - - // verify the compressed SNARK - let res = compressed_snark.verify(&vk, num_steps, &z0_primary, &z0_secondary); - assert!(res.is_ok()); - } - - #[test] - fn test_ivc_nondet_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - } - - fn test_ivc_base_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let test_circuit1 = TrivialCircuit::<::Scalar>::default(); - let test_circuit2 = CubicCircuit::<::Scalar>::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &test_circuit1, - &test_circuit2, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 1; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &test_circuit1, - &test_circuit2, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - // produce a recursive SNARK - let res = recursive_snark.prove_step(&pp, &test_circuit1, &test_circuit2); - - assert!(res.is_ok()); - - // verify the recursive SNARK - let res = recursive_snark.verify( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - assert_eq!(zn_primary, vec![::Scalar::ONE]); - assert_eq!(zn_secondary, vec![::Scalar::from(5u64)]); - } - - #[test] - fn test_ivc_base() { - test_ivc_base_with::(); - test_ivc_base_with::(); - test_ivc_base_with::(); - } - - fn test_setup_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - #[derive(Clone, Debug, Default)] - struct CircuitWithInputize { - _p: PhantomData, - } - - impl StepCircuit for CircuitWithInputize { - fn arity(&self) -> usize { - 1 - } - - fn synthesize>( - &self, - cs: &mut CS, - z: &[AllocatedNum], - ) -> Result>, SynthesisError> { - let x = &z[0]; - let y = x.square(cs.namespace(|| "x_sq"))?; - y.inputize(cs.namespace(|| "y"))?; // inputize y - Ok(vec![y]) - } - } - - // produce public parameters with trivial secondary - let circuit = CircuitWithInputize::<::Scalar>::default(); - let pp = - PublicParams::, TrivialCircuit>::setup( - &circuit, - &TrivialCircuit::default(), - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - - // produce public parameters with the trivial primary - let circuit = CircuitWithInputize::::default(); - let pp = - PublicParams::, CircuitWithInputize>::setup( - &TrivialCircuit::default(), - &circuit, - &*default_ck_hint(), - &*default_ck_hint(), - ); - assert!(pp.is_err()); - assert_eq!(pp.err(), Some(NovaError::InvalidStepCircuitIO)); - } - - #[test] - fn test_setup() { - test_setup_with::(); - } -} From 78152922b6d4ea3effcf8922c286bf92aea416ff Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 12 Sep 2024 14:12:39 -0400 Subject: [PATCH 11/21] tests pass for compressedSNARK --- benches/compressed-snark.rs | 70 +++++++++++++++++++++++++++++--- src/circuit.rs | 52 ------------------------ src/lib.rs | 79 ++++++++++++++++++++++++++++++------- 3 files changed, 129 insertions(+), 72 deletions(-) diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index 7da57365..a5c0d736 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -37,13 +37,14 @@ cfg_if::cfg_if! { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); - targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_randomizing_compressed_snark, bench_compressed_snark_with_computational_commitments, bench_randomizing_compressed_snark_with_computational_commitments } } else { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)); - targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_randomizing_compressed_snark, bench_compressed_snark_with_computational_commitments, +bench_randomizing_compressed_snark_with_computational_commitments } } } @@ -59,9 +60,11 @@ const NUM_SAMPLES: usize = 10; /// Parameters /// - `group``: the criterion benchmark group /// - `num_cons`: the number of constraints in the step circuit +/// - `randomizing`: if there is a zk layer added before compression fn bench_compressed_snark_internal, S2: RelaxedR1CSSNARKTrait>( group: &mut BenchmarkGroup<'_, WallTime>, num_cons: usize, + randomizing: bool, ) { let c_primary = NonTrivialCircuit::new(num_cons); let c_secondary = TrivialCircuit::default(); @@ -109,12 +112,13 @@ fn bench_compressed_snark_internal, S2: RelaxedR1C assert!(CompressedSNARK::<_, _, _, _, S1, S2>::prove( black_box(&pp), black_box(&pk), - black_box(&recursive_snark) + black_box(&recursive_snark), + black_box(randomizing) ) .is_ok()); }) }); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, randomizing); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -153,7 +157,7 @@ fn bench_compressed_snark(c: &mut Criterion) { let mut group = c.benchmark_group(format!("CompressedSNARK-StepCircuitSize-{num_cons}")); group.sample_size(NUM_SAMPLES); - bench_compressed_snark_internal::(&mut group, num_cons); + bench_compressed_snark_internal::(&mut group, num_cons, false); group.finish(); } @@ -181,7 +185,61 @@ fn bench_compressed_snark_with_computational_commitments(c: &mut Criterion) { .sampling_mode(SamplingMode::Flat) .sample_size(NUM_SAMPLES); - bench_compressed_snark_internal::(&mut group, num_cons); + bench_compressed_snark_internal::(&mut group, num_cons, false); + + group.finish(); + } +} + +fn bench_randomizing_compressed_snark(c: &mut Criterion) { + // we vary the number of constraints in the step circuit + for &num_cons_in_augmented_circuit in [ + NUM_CONS_VERIFIER_CIRCUIT_PRIMARY, + 16384, + 32768, + 65536, + 131072, + 262144, + 524288, + 1048576, + ] + .iter() + { + // number of constraints in the step circuit + let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY; + + let mut group = c.benchmark_group(format!("CompressedSNARK-StepCircuitSize-{num_cons}")); + group.sample_size(NUM_SAMPLES); + + bench_compressed_snark_internal::(&mut group, num_cons, true); + + group.finish(); + } +} + +fn bench_randomizing_compressed_snark_with_computational_commitments(c: &mut Criterion) { + // we vary the number of constraints in the step circuit + for &num_cons_in_augmented_circuit in [ + NUM_CONS_VERIFIER_CIRCUIT_PRIMARY, + 16384, + 32768, + 65536, + 131072, + 262144, + ] + .iter() + { + // number of constraints in the step circuit + let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY; + + let mut group = c.benchmark_group(format!( + "CompressedSNARK-Commitments-StepCircuitSize-{num_cons}" + )); + group + .sampling_mode(SamplingMode::Flat) + .sample_size(NUM_SAMPLES); + + bench_compressed_snark_internal::(&mut group, num_cons, true); group.finish(); } diff --git a/src/circuit.rs b/src/circuit.rs index e9d2afb6..1402e934 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -252,58 +252,6 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { Ok((U_fold, check_pass)) } - - /* /// Synthesizes non base case and returns the new relaxed `R1CSInstance` - /// And a boolean indicating if all checks pass - fn synthesize_last_random_case::Base>>( - &self, - mut cs: CS, - params: &AllocatedNum, - i: &AllocatedNum, - z_0: &[AllocatedNum], - z_i: &[AllocatedNum], - U: &AllocatedRelaxedR1CSInstance, - u: &AllocatedR1CSInstance, - T: &AllocatedPoint, - arity: usize, - ) -> Result<(AllocatedRelaxedR1CSInstance, AllocatedBit), SynthesisError> { - // Check that u.x[0] = Hash(params, U, i, z0, zi) - let mut ro = E::ROCircuit::new( - self.ro_consts.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * arity, - ); - ro.absorb(params); - ro.absorb(i); - for e in z_0 { - ro.absorb(e); - } - for e in z_i { - ro.absorb(e); - } - U.absorb_in_ro(cs.namespace(|| "absorb U"), &mut ro)?; - - let hash_bits = ro.squeeze(cs.namespace(|| "Input hash"), NUM_HASH_BITS)?; - let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), &hash_bits)?; - let check_pass = alloc_num_equals( - cs.namespace(|| "check consistency of u.X[0] with H(params, U, i, z0, zi)"), - &u.X0, - &hash, - )?; - - // Run NIFS Verifier - let U_fold = U.fold_with_r1cs( - cs.namespace(|| "compute fold of U and u"), - params, - u, - T, - self.ro_consts.clone(), - self.params.limb_width, - self.params.n_limbs, - )?; - - Ok((U_fold, check_pass)) - } - */ } impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { diff --git a/src/lib.rs b/src/lib.rs index 311e794b..94b6bbdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1614,7 +1614,7 @@ mod tests { test_ivc_nontrivial_randomizing_with::(); } - fn test_ivc_nontrivial_with_compression_with() + fn test_ivc_nontrivial_with_compression_with(randomizing: bool) where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -1688,7 +1688,7 @@ mod tests { &pp, &pk, &recursive_snark, - false, + randomizing, ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1705,20 +1705,41 @@ mod tests { #[test] fn test_ivc_nontrivial_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_compression_with::, EE<_>>(false); test_ivc_nontrivial_with_compression_with::, EE<_>>( + false, + ); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + false, ); - test_ivc_nontrivial_with_compression_with::, EE<_>>(); test_ivc_nontrivial_with_spark_compression_with::< Bn256EngineKZG, GrumpkinEngine, provider::hyperkzg::EvaluationEngine<_>, EE<_>, - >(); + >(false); } - fn test_ivc_nontrivial_with_spark_compression_with() + #[test] + fn test_ivc_nontrivial_randomizing_with_compression() { + test_ivc_nontrivial_with_compression_with::, EE<_>>(true); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + true, + ); + test_ivc_nontrivial_with_compression_with::, EE<_>>( + true, + ); + + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + provider::hyperkzg::EvaluationEngine<_>, + EE<_>, + >(true); + } + + fn test_ivc_nontrivial_with_spark_compression_with(randomizing: bool) where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -1794,7 +1815,7 @@ mod tests { &pp, &pk, &recursive_snark, - false, + randomizing, ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1811,18 +1832,37 @@ mod tests { #[test] fn test_ivc_nontrivial_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + false, + ); test_ivc_nontrivial_with_spark_compression_with::< Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>, - >(); + >(false); test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + false, ); } - fn test_ivc_nondet_with_compression_with() + #[test] + fn test_ivc_nontrivial_randomizing_with_spark_compression() { + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + true, + ); + test_ivc_nontrivial_with_spark_compression_with::< + Bn256EngineKZG, + GrumpkinEngine, + EEPrime<_>, + EE<_>, + >(true); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( + true, + ); + } + + fn test_ivc_nondet_with_compression_with(randomizing: bool) where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -1949,7 +1989,7 @@ mod tests { &pp, &pk, &recursive_snark, - false, + randomizing, ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1961,9 +2001,20 @@ mod tests { #[test] fn test_ivc_nondet_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); - test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(false); + test_ivc_nondet_with_compression_with::, EE<_>>( + false, + ); + test_ivc_nondet_with_compression_with::, EE<_>>(false); + } + + #[test] + fn test_ivc_nondet_randomizing_with_compression() { + test_ivc_nondet_with_compression_with::, EE<_>>(true); + test_ivc_nondet_with_compression_with::, EE<_>>( + true, + ); + test_ivc_nondet_with_compression_with::, EE<_>>(true); } fn test_ivc_base_with() From 2ea8972eb006b3a55915b5a97b90937ab5c35a7b Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 12 Sep 2024 15:17:51 -0400 Subject: [PATCH 12/21] comments, clarity --- src/lib.rs | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 94b6bbdb..0127ff55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -256,14 +256,15 @@ where _p: PhantomData<(C1, C2)>, } -/// Final randomized fold +/// Final randomized fold of `RecursiveSNARK` #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] -pub struct RandomLayer +pub struct RandomLayerRecursive where E1: Engine::Scalar>, E2: Engine::Scalar>, { + last_i: usize, l_ur_primary: RelaxedR1CSInstance, l_ur_secondary: RelaxedR1CSInstance, nifs_Uf_secondary: NIFS, @@ -491,16 +492,17 @@ where Ok(()) } - /// randomize the last step computed + /// add a randomizing fold to a `RecursiveSNARK` /// if you plan on using a randomized CompressedSNARK, you don't need to call this fn randomizing_fold( &self, pp: &PublicParams, - ) -> Result, NovaError> { + ) -> Result, NovaError> { if self.i == 0 { // don't call this until we have something to randomize return Err(NovaError::InvalidNumSteps); } + let last_i = self.i; // fold secondary U/W with secondary u/w to get Uf/Wf let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( @@ -547,7 +549,8 @@ where )?; // output randomized IVC Proof - Ok(RandomLayer { + Ok(RandomLayerRecursive { + last_i, // random istances l_ur_primary, l_ur_secondary, @@ -677,11 +680,16 @@ where num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], - random_layer: RandomLayer, + random_layer: RandomLayerRecursive, ) -> Result<(Vec, Vec), NovaError> { // number of steps cannot be zero let is_num_steps_zero = num_steps == 0; + // there should not have been more folds after randomizing layer + if random_layer.last_i != self.i { + return Err(NovaError::ProofVerifyError); + } + // check if the provided proof has executed num_steps let is_num_steps_not_match = self.i != num_steps; @@ -691,7 +699,11 @@ where // check if the (relaxed) R1CS instances have two public outputs let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2; + || self.r_U_secondary.X.len() != 2 + || random_layer.l_ur_primary.X.len() != 2 + || random_layer.l_ur_secondary.X.len() != 2 + || random_layer.r_Un_primary.X.len() != 2 + || random_layer.r_Un_secondary.X.len() != 2; if is_num_steps_zero || is_num_steps_not_match @@ -869,7 +881,7 @@ where _p: PhantomData<(C1, C2)>, } -/// Final randomized fold info for CompressedSNARK +/// Final randomized fold info for `CompressedSNARK` #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub struct RandomLayerCompressed @@ -939,7 +951,7 @@ where } } - /// Create a new `CompressedSNARK` + /// Create a new `CompressedSNARK` (without randomizing layer) fn prove_regular( pp: &PublicParams, pk: &ProverKey, @@ -996,7 +1008,7 @@ where }) } - /// Create a new `CompressedSNARK` + /// Create a new `CompressedSNARK` (with randomizing layer) fn prove_randomizing( pp: &PublicParams, pk: &ProverKey, @@ -1066,10 +1078,9 @@ where } else { self.verify_randomizing(vk, num_steps, z0_primary, z0_secondary) } - // TODO ^^ } - /// Verify the correctness of the `CompressedSNARK` + /// Verify the correctness of the `CompressedSNARK` (without randomizing layer) fn verify_regular( &self, vk: &VerifierKey, @@ -1161,9 +1172,7 @@ where Ok((self.zn_primary.clone(), self.zn_secondary.clone())) } - // TODO JESS: look at all of the hashing code everywhere and make sure the right crap is hashed - - /// Verify the correctness of the `CompressedSNARK` + /// Verify the correctness of the `CompressedSNARK` (with randomizing layer) fn verify_randomizing( &self, vk: &VerifierKey, @@ -1171,6 +1180,8 @@ where z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { + let random_layer = self.random_layer.as_ref().unwrap(); + // the number of steps cannot be zero if num_steps == 0 { return Err(NovaError::ProofVerifyError); @@ -1180,6 +1191,8 @@ where if self.l_u_secondary.X.len() != 2 || self.r_U_primary.X.len() != 2 || self.r_U_secondary.X.len() != 2 + || random_layer.l_ur_primary.X.len() != 2 + || random_layer.l_ur_secondary.X.len() != 2 { return Err(NovaError::ProofVerifyError); } @@ -1226,8 +1239,6 @@ where return Err(NovaError::ProofVerifyError); } - let random_layer = self.random_layer.as_ref().unwrap(); - // fold secondary U/W with secondary u/w to get Uf/Wf let r_Uf_secondary = self.nifs_secondary.verify( //random_layer.nifs_Uf_secondary.verify( From a9666be7e35b43ac509ffd8002247e115b7a64cf Mon Sep 17 00:00:00 2001 From: jkwoods Date: Wed, 2 Oct 2024 12:49:58 -0400 Subject: [PATCH 13/21] public method --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0127ff55..23059008 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -494,7 +494,7 @@ where /// add a randomizing fold to a `RecursiveSNARK` /// if you plan on using a randomized CompressedSNARK, you don't need to call this - fn randomizing_fold( + pub fn randomizing_fold( &self, pp: &PublicParams, ) -> Result, NovaError> { From df1e784b21edbbfa25dc0c5ce32480a949b1292c Mon Sep 17 00:00:00 2001 From: jkwoods Date: Fri, 4 Oct 2024 16:49:57 -0400 Subject: [PATCH 14/21] add blinds draft --- src/nifs.rs | 64 ++++++++++++++++++++--------------- src/provider/hyperkzg.rs | 19 ++++++++++- src/provider/ipa_pc.rs | 2 +- src/provider/pedersen.rs | 68 ++++++++++++++++++++++++++++++++++--- src/r1cs/mod.rs | 72 +++++++++++++++++++++++++++++----------- src/traits/commitment.rs | 16 ++++++++- 6 files changed, 188 insertions(+), 53 deletions(-) diff --git a/src/nifs.rs b/src/nifs.rs index e7dc0872..59167ae5 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -9,6 +9,8 @@ use crate::{ traits::{AbsorbInROTrait, Engine, ROTrait}, Commitment, CommitmentKey, }; +use ff::Field; +use rand_core::OsRng; use serde::{Deserialize, Serialize}; /// A SNARK that holds the proof of a step of an incremental computation @@ -55,7 +57,8 @@ impl NIFS { U2.absorb_in_ro(&mut ro); // compute a commitment to the cross-term - let (T, comm_T) = S.commit_T(ck, U1, W1, U2, W2)?; + let r_T = E::Scalar::random(&mut OsRng); + let (T, comm_T) = S.commit_T(ck, U1, W1, U2, W2, &r_T)?; // append `comm_T` to the transcript and obtain a challenge comm_T.absorb_in_ro(&mut ro); @@ -67,7 +70,7 @@ impl NIFS { let U = U1.fold(U2, &comm_T, &r); // fold the witness using `r` and `T` - let W = W1.fold(W2, &T, &r)?; + let W = W1.fold(W2, &T, &r_T, &r)?; // return the folded instance and witness Ok((Self { comm_T }, (U, W))) @@ -94,7 +97,8 @@ impl NIFS { U2.absorb_in_ro(&mut ro); // compute a commitment to the cross-term - let (T, comm_T) = S.commit_T_relaxed(ck, U1, W1, U2, W2)?; + let r_T = E::Scalar::random(&mut OsRng); + let (T, comm_T) = S.commit_T_relaxed(ck, U1, W1, U2, W2, &r_T)?; // append `comm_T` to the transcript and obtain a challenge comm_T.absorb_in_ro(&mut ro); @@ -106,7 +110,7 @@ impl NIFS { let U = U1.fold_relaxed(U2, &comm_T, &r); // fold the witness using `r` and `T` - let W = W1.fold_relaxed(W2, &T, &r)?; + let W = W1.fold_relaxed(W2, &T, &r_T, &r)?; // return the folded instance and witness Ok((Self { comm_T }, (U, W))) @@ -282,43 +286,47 @@ mod tests { W2: &R1CSWitness, ) { // produce a default running instance - let mut r_W = RelaxedR1CSWitness::default(shape); - let mut r_U = RelaxedR1CSInstance::default(ck, shape); + let mut running_W = RelaxedR1CSWitness::default(shape); + let mut running_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, pp_digest, shape, &r_U, &r_W, U1, W1); + let res = NIFS::prove( + ck, ro_consts, pp_digest, shape, &running_U, &running_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, pp_digest, &r_U, U1); + let res = nifs.verify(ro_consts, pp_digest, &running_U, U1); assert!(res.is_ok()); let U = res.unwrap(); assert_eq!(U, _U); // update the running witness and instance - r_W = W; - r_U = U; + running_W = W; + running_U = U; // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair - let res = NIFS::prove(ck, ro_consts, pp_digest, shape, &r_U, &r_W, U2, W2); + let res = NIFS::prove( + ck, ro_consts, pp_digest, shape, &running_U, &running_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, pp_digest, &r_U, U2); + let res = nifs.verify(ro_consts, pp_digest, &running_U, U2); assert!(res.is_ok()); let U = res.unwrap(); assert_eq!(U, _U); // update the running witness and instance - r_W = W; - r_U = U; + running_W = W; + running_U = U; // check if the running instance is satisfiable - assert!(shape.is_sat_relaxed(ck, &r_U, &r_W).is_ok()); + assert!(shape.is_sat_relaxed(ck, &running_U, &running_W).is_ok()); } fn execute_sequence_relaxed( @@ -332,43 +340,47 @@ mod tests { W2: &RelaxedR1CSWitness, ) { // produce a default running instance - let mut r_W = RelaxedR1CSWitness::default(shape); - let mut r_U = RelaxedR1CSInstance::default(ck, shape); + let mut running_W = RelaxedR1CSWitness::default(shape); + let mut running_U = RelaxedR1CSInstance::default(ck, shape); // produce a step SNARK with (W1, U1) as the first incoming witness-instance pair - let res = NIFS::prove_relaxed(ck, ro_consts, pp_digest, shape, &r_U, &r_W, U1, W1); + let res = NIFS::prove_relaxed( + ck, ro_consts, pp_digest, shape, &running_U, &running_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_relaxed(ro_consts, pp_digest, &r_U, U1); + let res = nifs.verify_relaxed(ro_consts, pp_digest, &running_U, U1); assert!(res.is_ok()); let U = res.unwrap(); assert_eq!(U, _U); // update the running witness and instance - r_W = W; - r_U = U; + running_W = W; + running_U = U; // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair - let res = NIFS::prove_relaxed(ck, ro_consts, pp_digest, shape, &r_U, &r_W, U2, W2); + let res = NIFS::prove_relaxed( + ck, ro_consts, pp_digest, shape, &running_U, &running_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_relaxed(ro_consts, pp_digest, &r_U, U2); + let res = nifs.verify_relaxed(ro_consts, pp_digest, &running_U, U2); assert!(res.is_ok()); let U = res.unwrap(); assert_eq!(U, _U); // update the running witness and instance - r_W = W; - r_U = U; + running_W = W; + running_U = U; // check if the running instance is satisfiable - assert!(shape.is_sat_relaxed(ck, &r_U, &r_W).is_ok()); + assert!(shape.is_sat_relaxed(ck, &running_U, &running_W).is_ok()); } fn test_tiny_r1cs_relaxed_with() { diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 18ef7924..c2469058 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -185,7 +185,7 @@ where type Commitment = Commitment; type CommitmentKey = CommitmentKey; - fn setup(_label: &'static [u8], n: usize) -> Self::CommitmentKey { + fn setup(_label: &'static [u8], _blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey { // NOTE: this is for testing purposes and should not be used in production // TODO: we need to decide how to generate load/store parameters let tau = E::Scalar::random(OsRng); @@ -210,10 +210,27 @@ where fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment { assert!(ck.ck.len() >= v.len()); + Commitment { comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()]), } } + + fn commit_with_blinding( + _ck: &Self::CommitmentKey, + _v: &[E::Scalar], + _r: &E::Scalar, + ) -> Self::Commitment { + unimplemented!() + } + + fn derandomize( + _ck: &Self::CommitmentKey, + _commit: &Self::Commitment, + _r: &E::Scalar, + ) -> Self::Commitment { + unimplemented!() + } } /// Provides an implementation of generators for proving evaluations diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index cfe5e876..22cb29d7 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -49,7 +49,7 @@ where fn setup( ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, ) -> (Self::ProverKey, Self::VerifierKey) { - let ck_c = E::CE::setup(b"ipa", 1); + let ck_c = E::CE::setup(b"ipa", b"no blind", 1); let pk = ProverKey { ck_s: ck_c.clone() }; let vk = VerifierKey { diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index dc8579e4..2b3060f4 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -24,6 +24,7 @@ where E::GE: DlogGroup, { ck: Vec<::AffineGroupElement>, + h: Option<::AffineGroupElement>, } impl Len for CommitmentKey @@ -166,9 +167,13 @@ where type CommitmentKey = CommitmentKey; type Commitment = Commitment; - fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey { + fn setup(label: &'static [u8], blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey { + let blinding = E::GE::from_label(blinding_label, 1); + let h = blinding.first().unwrap().clone(); + Self::CommitmentKey { ck: E::GE::from_label(label, n.next_power_of_two()), + h: Some(h), } } @@ -178,6 +183,41 @@ where comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()]), } } + + fn commit_with_blinding( + ck: &Self::CommitmentKey, + v: &[E::Scalar], + r: &E::Scalar, + ) -> Self::Commitment { + assert!(ck.ck.len() >= v.len()); + assert!(ck.h.is_some()); + + let mut scalars: Vec = v.to_vec(); + scalars.push(*r); + let mut bases = ck.ck[..v.len()].to_vec(); + bases.push(ck.h.as_ref().unwrap().clone()); + + Commitment { + comm: E::GE::vartime_multiscalar_mul(&scalars, &bases), + } + } + + fn derandomize( + ck: &Self::CommitmentKey, + commit: &Self::Commitment, + r: &E::Scalar, + ) -> Self::Commitment { + assert!(ck.h.is_some()); + // g^m * h^r * h^(-r) + let neg_r = *r * (E::Scalar::from(0) - E::Scalar::from(1)); + + Commitment { + comm: E::GE::vartime_multiscalar_mul( + &[E::Scalar::from(1), neg_r], + &[commit.comm.affine(), ck.h.as_ref().unwrap().clone()], + ), + } + } } /// A trait listing properties of a commitment key that can be managed in a divide-and-conquer fashion @@ -217,9 +257,11 @@ where ( CommitmentKey { ck: self.ck[0..n].to_vec(), + h: self.h.clone(), }, CommitmentKey { ck: self.ck[n..].to_vec(), + h: self.h.clone(), }, ) } @@ -230,7 +272,10 @@ where c.extend(other.ck.clone()); c }; - CommitmentKey { ck } + CommitmentKey { + ck, + h: self.h.clone(), + } } // combines the left and right halves of `self` using `w1` and `w2` as the weights @@ -246,7 +291,10 @@ where }) .collect(); - CommitmentKey { ck } + CommitmentKey { + ck, + h: self.h.clone(), + } } /// Scales each element in `self` by `r` @@ -258,7 +306,10 @@ where .map(|g| E::GE::vartime_multiscalar_mul(&[*r], &[g]).affine()) .collect(); - CommitmentKey { ck: ck_scaled } + CommitmentKey { + ck: ck_scaled, + h: self.h.clone(), + } } /// reinterprets a vector of commitments as a set of generators @@ -267,6 +318,13 @@ where .into_par_iter() .map(|i| c[i].comm.affine()) .collect(); - Ok(CommitmentKey { ck }) + + // cmt is derandomized by the point that this is called + Ok(CommitmentKey { + ck, + h: None, // this is okay, since this method is used in IPA only, + // and we only use `commit` after, not `commit_with_blind` + // bc we don't use ZK IPA + }) } } diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 5b339d84..57b477d9 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -48,6 +48,7 @@ impl SimpleDigestible for R1CSShape {} #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct R1CSWitness { W: Vec, + r_W: E::Scalar, } /// A type that holds an R1CS instance @@ -62,7 +63,9 @@ pub struct R1CSInstance { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RelaxedR1CSWitness { pub(crate) W: Vec, + pub(crate) r_W: E::Scalar, pub(crate) E: Vec, + pub(crate) r_E: E::Scalar, } /// A type that holds a Relaxed R1CS instance @@ -93,7 +96,7 @@ impl R1CS { let num_cons = S.num_cons; let num_vars = S.num_vars; let ck_hint = ck_floor(S); - E::CE::setup(b"ck", max(max(num_cons, num_vars), ck_hint)) + E::CE::setup(b"ck", b"ck blinding", max(max(num_cons, num_vars), ck_hint)) } } @@ -197,8 +200,10 @@ impl R1CSShape { // verify if comm_E and comm_W are commitments to E and W let res_comm = { - let (comm_W, comm_E) = - rayon::join(|| CE::::commit(ck, &W.W), || CE::::commit(ck, &W.E)); + let (comm_W, comm_E) = rayon::join( + || CE::::commit_with_blinding(ck, &W.W, &W.r_W), + || CE::::commit_with_blinding(ck, &W.E, &W.r_E), + ); U.comm_W == comm_W && U.comm_E == comm_E }; @@ -231,7 +236,7 @@ impl R1CSShape { }; // verify if comm_W is a commitment to W - let res_comm = U.comm_W == CE::::commit(ck, &W.W); + let res_comm = U.comm_W == CE::::commit_with_blinding(ck, &W.W, &W.r_W); if res_eq && res_comm { Ok(()) @@ -249,6 +254,7 @@ impl R1CSShape { W1: &RelaxedR1CSWitness, U2: &R1CSInstance, W2: &R1CSWitness, + r_T: &E::Scalar, ) -> Result<(Vec, Commitment), NovaError> { let Z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat(); let Z2 = [W2.W.clone(), vec![E::Scalar::ONE], U2.X.clone()].concat(); @@ -272,7 +278,7 @@ impl R1CSShape { .map(|(((az, bz), cz), e)| *az * *bz - u * *cz - *e) .collect::>(); - let comm_T = CE::::commit(ck, &T); + let comm_T = CE::::commit_with_blinding(ck, &T, r_T); Ok((T, comm_T)) } @@ -286,6 +292,7 @@ impl R1CSShape { W1: &RelaxedR1CSWitness, U2: &RelaxedR1CSInstance, W2: &RelaxedR1CSWitness, + r_T: &E::Scalar, ) -> Result<(Vec, Commitment), NovaError> { let Z1 = [W1.W.clone(), vec![U1.u], U1.X.clone()].concat(); let Z2 = [W2.W.clone(), vec![U2.u], U2.X.clone()].concat(); @@ -310,7 +317,7 @@ impl R1CSShape { .map(|((((az, bz), cz), e1), e2)| *az * *bz - u * *cz - *e1 - *e2) .collect::>(); - let comm_T = CE::::commit(ck, &T); + let comm_T = CE::::commit_with_blinding(ck, &T, r_T); Ok((T, comm_T)) } @@ -395,6 +402,9 @@ impl R1CSShape { let u = E::Scalar::random(&mut OsRng); + let r_W = E::Scalar::random(&mut OsRng); + let r_E = E::Scalar::random(&mut OsRng); + // Z let Z = [W.clone(), vec![u], X.clone()].concat(); @@ -409,8 +419,8 @@ impl R1CSShape { .collect::>(); // compute commitments to W,E - let comm_W = CE::::commit(ck, &W); - let comm_E = CE::::commit(ck, &E); + let comm_W = CE::::commit_with_blinding(ck, &W, &r_W); + let comm_E = CE::::commit_with_blinding(ck, &E, &r_E); Ok(( RelaxedR1CSInstance { @@ -419,7 +429,7 @@ impl R1CSShape { u, X, }, - RelaxedR1CSWitness { W, E }, + RelaxedR1CSWitness { W, r_W, E, r_E }, )) } } @@ -430,13 +440,16 @@ impl R1CSWitness { if S.num_vars != W.len() { Err(NovaError::InvalidWitnessLength) } else { - Ok(R1CSWitness { W: W.to_owned() }) + Ok(R1CSWitness { + W: W.to_owned(), + r_W: E::Scalar::random(&mut OsRng), + }) } } /// Commits to the witness using the supplied generators pub fn commit(&self, ck: &CommitmentKey) -> Commitment { - CE::::commit(ck, &self.W) + CE::::commit_with_blinding(ck, &self.W, &self.r_W) } } @@ -472,7 +485,9 @@ impl RelaxedR1CSWitness { pub fn default(S: &R1CSShape) -> RelaxedR1CSWitness { RelaxedR1CSWitness { W: vec![E::Scalar::ZERO; S.num_vars], + r_W: E::Scalar::ZERO, E: vec![E::Scalar::ZERO; S.num_cons], + r_E: E::Scalar::ZERO, } } @@ -480,13 +495,18 @@ impl RelaxedR1CSWitness { pub fn from_r1cs_witness(S: &R1CSShape, witness: &R1CSWitness) -> RelaxedR1CSWitness { RelaxedR1CSWitness { W: witness.W.clone(), + r_W: witness.r_W, E: vec![E::Scalar::ZERO; S.num_cons], + r_E: E::Scalar::ZERO, } } /// Commits to the witness using the supplied generators pub fn commit(&self, ck: &CommitmentKey) -> (Commitment, Commitment) { - (CE::::commit(ck, &self.W), CE::::commit(ck, &self.E)) + ( + CE::::commit_with_blinding(ck, &self.W, &self.r_W), + CE::::commit_with_blinding(ck, &self.E, &self.r_E), + ) } /// Folds an incoming `R1CSWitness` into the current one @@ -494,10 +514,11 @@ impl RelaxedR1CSWitness { &self, W2: &R1CSWitness, T: &[E::Scalar], + r_T: &E::Scalar, r: &E::Scalar, ) -> Result, NovaError> { - let (W1, E1) = (&self.W, &self.E); - let W2 = &W2.W; + let (W1, r_W1, E1, r_E1) = (&self.W, &self.r_W, &self.E, &self.r_E); + let (W2, r_W2) = (&W2.W, &W2.r_W); if W1.len() != W2.len() { return Err(NovaError::InvalidWitnessLength); @@ -513,7 +534,11 @@ impl RelaxedR1CSWitness { .zip(T) .map(|(a, b)| *a + *r * *b) .collect::>(); - Ok(RelaxedR1CSWitness { W, E }) + + let r_W = *r_W1 + *r * r_W2; + let r_E = *r_E1 + *r * r_T; + + Ok(RelaxedR1CSWitness { W, r_W, E, r_E }) } /// Folds an incoming `RelaxedR1CSWitness` into the current one @@ -522,10 +547,11 @@ impl RelaxedR1CSWitness { &self, W2: &RelaxedR1CSWitness, T: &[E::Scalar], + r_T: &E::Scalar, r: &E::Scalar, ) -> Result, NovaError> { - let (W1, E1) = (&self.W, &self.E); - let (W2, E2) = (&W2.W, &W2.E); + let (W1, r_W1, E1, r_E1) = (&self.W, &self.r_W, &self.E, &self.r_E); + let (W2, r_W2, E2, r_E2) = (&W2.W, &W2.r_W, &W2.E, &W2.r_E); if W1.len() != W2.len() { return Err(NovaError::InvalidWitnessLength); @@ -543,7 +569,10 @@ impl RelaxedR1CSWitness { .map(|((a, b), c)| *a + *r * *b + *r * *r * *c) .collect::>(); - Ok(RelaxedR1CSWitness { W, E }) + let r_W = *r_W1 + *r * r_W2; + let r_E = *r_E1 + *r * r_T + *r * *r * *r_E2; + + Ok(RelaxedR1CSWitness { W, r_W, E, r_E }) } /// Pads the provided witness to the correct length @@ -554,7 +583,12 @@ impl RelaxedR1CSWitness { let mut E = self.E.clone(); E.extend(vec![E::Scalar::ZERO; S.num_cons - E.len()]); - Self { W, E } + Self { + W, + r_W: self.r_W, + E, + r_E: self.r_E, + } } } diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index f23182eb..683ac3a8 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -51,8 +51,22 @@ pub trait CommitmentEngineTrait: Clone + Send + Sync { type Commitment: CommitmentTrait; /// Samples a new commitment key of a specified size - fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey; + fn setup(label: &'static [u8], blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey; /// Commits to the provided vector using the provided generators fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment; + + /// Commits to the provided vector using the provided generators and random blind + fn commit_with_blinding( + ck: &Self::CommitmentKey, + v: &[E::Scalar], + r: &E::Scalar, + ) -> Self::Commitment; + + /// Remove given blind from commitment + fn derandomize( + ck: &Self::CommitmentKey, + commit: &Self::Commitment, + r: &E::Scalar, + ) -> Self::Commitment; } From a251b59ab42be2ec993c34832478c207937b1856 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 10 Oct 2024 14:53:45 -0400 Subject: [PATCH 15/21] blinding and deblinding r1cs instances --- src/lib.rs | 86 +++++++++++++++++++++++++++++++++------- src/nifs.rs | 38 ++++++++++++++---- src/provider/hyperkzg.rs | 16 ++++---- src/r1cs/mod.rs | 35 ++++++++++++++++ src/spartan/direct.rs | 31 ++++++++++++--- 5 files changed, 172 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 23059008..8711c67f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -851,6 +851,8 @@ where pp_digest: E1::Scalar, vk_primary: S1::VerifierKey, vk_secondary: S2::VerifierKey, + vk_primary_commitment_key: CommitmentKey, + vk_secondary_commitment_key: CommitmentKey, _p: PhantomData<(C1, C2)>, } @@ -877,6 +879,10 @@ where zn_primary: Vec, zn_secondary: Vec, + primary_blind_r_W: E1::Scalar, + primary_blind_r_E: E1::Scalar, + secondary_blind_r_W: E2::Scalar, + secondary_blind_r_E: E2::Scalar, random_layer: Option>, _p: PhantomData<(C1, C2)>, } @@ -931,6 +937,8 @@ where pp_digest: pp.digest(), vk_primary, vk_secondary, + vk_primary_commitment_key: pp.ck_primary.clone(), + vk_secondary_commitment_key: pp.ck_secondary.clone(), _p: Default::default(), }; @@ -969,6 +977,13 @@ where &recursive_snark.l_w_secondary, )?; + // derandomize/unblind commitments + let (derandom_r_U_primary, derandom_r_W_primary) = recursive_snark + .r_U_primary + .derandomize_commits_and_witnesses(&pp.ck_primary, &recursive_snark.r_W_primary); + let (derandom_f_U_secondary, derandom_f_W_secondary) = + f_U_secondary.derandomize_commits_and_witnesses(&pp.ck_secondary, &f_W_secondary); + // create SNARKs proving the knowledge of f_W_primary and f_W_secondary let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( || { @@ -976,8 +991,8 @@ where &pp.ck_primary, &pk.pk_primary, &pp.r1cs_shape_primary, - &recursive_snark.r_U_primary, - &recursive_snark.r_W_primary, + &derandom_r_U_primary, + &derandom_r_W_primary, ) }, || { @@ -985,8 +1000,8 @@ where &pp.ck_secondary, &pk.pk_secondary, &pp.r1cs_shape_secondary, - &f_U_secondary, - &f_W_secondary, + &derandom_f_U_secondary, + &derandom_f_W_secondary, ) }, ); @@ -1003,8 +1018,12 @@ where zn_primary: recursive_snark.zi_primary.clone(), zn_secondary: recursive_snark.zi_secondary.clone(), - _p: Default::default(), + primary_blind_r_W: recursive_snark.r_W_primary.r_W, + primary_blind_r_E: recursive_snark.r_W_primary.r_E, + secondary_blind_r_W: f_W_secondary.r_W, + secondary_blind_r_E: f_W_secondary.r_E, random_layer: None, + _p: Default::default(), }) } @@ -1017,6 +1036,14 @@ where // prove three foldings let random_layer = recursive_snark.randomizing_fold(pp)?; + // derandomize/unblind commitments + let (derandom_r_Un_primary, derandom_r_Wn_primary) = random_layer + .r_Un_primary + .derandomize_commits_and_witnesses(&pp.ck_primary, &random_layer.r_Wn_primary); + let (derandom_r_Un_secondary, derandom_r_Wn_secondary) = random_layer + .r_Un_secondary + .derandomize_commits_and_witnesses(&pp.ck_secondary, &random_layer.r_Wn_secondary); + // create SNARKs proving the knowledge of Wn primary/secondary let (snark_primary, snark_secondary) = rayon::join( || { @@ -1024,8 +1051,8 @@ where &pp.ck_primary, &pk.pk_primary, &pp.r1cs_shape_primary, - &random_layer.r_Un_primary, - &random_layer.r_Wn_primary, + &derandom_r_Un_primary, + &derandom_r_Wn_primary, ) }, || { @@ -1033,8 +1060,8 @@ where &pp.ck_secondary, &pk.pk_secondary, &pp.r1cs_shape_secondary, - &random_layer.r_Un_secondary, - &random_layer.r_Wn_secondary, + &derandom_r_Un_secondary, + &derandom_r_Wn_secondary, ) }, ); @@ -1060,6 +1087,10 @@ where zn_primary: recursive_snark.zi_primary.clone(), zn_secondary: recursive_snark.zi_secondary.clone(), + primary_blind_r_W: random_layer.r_Wn_primary.r_W, + primary_blind_r_E: random_layer.r_Wn_primary.r_E, + secondary_blind_r_W: random_layer.r_Wn_secondary.r_W, + secondary_blind_r_E: random_layer.r_Wn_secondary.r_E, random_layer: random_layer_compressed, _p: Default::default(), }) @@ -1151,18 +1182,30 @@ where &self.l_u_secondary, )?; + // derandomize/unblind commitments + let derandom_r_U_primary = self.r_U_primary.derandomize_commits( + &vk.vk_primary_commitment_key, + &self.primary_blind_r_W, + &self.primary_blind_r_E, + ); + let derandom_f_U_secondary = f_U_secondary.derandomize_commits( + &vk.vk_secondary_commitment_key, + &self.secondary_blind_r_W, + &self.secondary_blind_r_E, + ); + // check the satisfiability of the folded instances using // SNARKs proving the knowledge of their satisfying witnesses let (res_primary, res_secondary) = rayon::join( || { self .r_W_snark_primary - .verify(&vk.vk_primary, &self.r_U_primary) + .verify(&vk.vk_primary, &derandom_r_U_primary) }, || { self .f_W_snark_secondary - .verify(&vk.vk_secondary, &f_U_secondary) + .verify(&vk.vk_secondary, &derandom_f_U_secondary) }, ); @@ -1241,7 +1284,6 @@ where // fold secondary U/W with secondary u/w to get Uf/Wf let r_Uf_secondary = self.nifs_secondary.verify( - //random_layer.nifs_Uf_secondary.verify( &vk.ro_consts_secondary, &scalar_as_base::(vk.pp_digest), &self.r_U_secondary, @@ -1264,14 +1306,30 @@ where &random_layer.l_ur_primary, )?; + // derandomize/unblind commitments + let derandom_r_Un_primary = r_Un_primary.derandomize_commits( + &vk.vk_primary_commitment_key, + &self.primary_blind_r_W, + &self.primary_blind_r_E, + ); + let derandom_r_Un_secondary = r_Un_secondary.derandomize_commits( + &vk.vk_secondary_commitment_key, + &self.secondary_blind_r_W, + &self.secondary_blind_r_E, + ); + // check the satisfiability of the folded instances using // SNARKs proving the knowledge of their satisfying witnesses let (res_primary, res_secondary) = rayon::join( - || self.r_W_snark_primary.verify(&vk.vk_primary, &r_Un_primary), + || { + self + .r_W_snark_primary + .verify(&vk.vk_primary, &derandom_r_Un_primary) + }, || { self .f_W_snark_secondary - .verify(&vk.vk_secondary, &r_Un_secondary) + .verify(&vk.vk_secondary, &derandom_r_Un_secondary) }, ); diff --git a/src/nifs.rs b/src/nifs.rs index 59167ae5..b84d7863 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -98,6 +98,7 @@ impl NIFS { // compute a commitment to the cross-term let r_T = E::Scalar::random(&mut OsRng); + E::Scalar::random(&mut OsRng); let (T, comm_T) = S.commit_T_relaxed(ck, U1, W1, U2, W2, &r_T)?; // append `comm_T` to the transcript and obtain a challenge @@ -338,7 +339,7 @@ mod tests { W1: &RelaxedR1CSWitness, U2: &RelaxedR1CSInstance, W2: &RelaxedR1CSWitness, - ) { + ) -> (RelaxedR1CSInstance, RelaxedR1CSWitness) { // produce a default running instance let mut running_W = RelaxedR1CSWitness::default(shape); let mut running_U = RelaxedR1CSInstance::default(ck, shape); @@ -381,9 +382,34 @@ mod tests { // check if the running instance is satisfiable assert!(shape.is_sat_relaxed(ck, &running_U, &running_W).is_ok()); + + (running_U, running_W) + } + + fn test_tiny_r1cs_relaxed_derandomize_with() { + let (ck, S, final_U, final_W) = test_tiny_r1cs_relaxed_with::(); + assert!(S.is_sat_relaxed(&ck, &final_U, &final_W).is_ok()); + + let (derandom_final_U, derandom_final_W) = + final_U.derandomize_commits_and_witnesses(&ck, &final_W); + assert!(S + .is_sat_relaxed(&ck, &derandom_final_U, &derandom_final_W) + .is_ok()); } - fn test_tiny_r1cs_relaxed_with() { + #[test] + fn test_tiny_r1cs_relaxed_derandomize() { + test_tiny_r1cs_relaxed_derandomize_with::(); + test_tiny_r1cs_relaxed_derandomize_with::(); + test_tiny_r1cs_relaxed_derandomize_with::(); + } + + fn test_tiny_r1cs_relaxed_with() -> ( + CommitmentKey, + R1CSShape, + RelaxedR1CSInstance, + RelaxedR1CSWitness, + ) { let one = ::ONE; let (num_cons, num_vars, num_io, A, B, C) = { let num_cons = 4; @@ -496,12 +522,10 @@ mod tests { let (_O, U1, W1) = rand_inst_witness_generator(&ck, &I); let (U2, W2) = S.sample_random_instance_witness(&ck).unwrap(); // random fold - //let (_O, U2, W2) = rand_inst_witness_generator(&ck, &O); - println!("INSTANCE {:#?}", U1.clone()); // execute a sequence of folds - execute_sequence_relaxed( + let (final_U, final_W) = execute_sequence_relaxed( &ck, &ro_consts, &::Scalar::ZERO, @@ -510,9 +534,9 @@ mod tests { &RelaxedR1CSWitness::from_r1cs_witness(&S, &W1), &U2, &W2, - //&RelaxedR1CSInstance::from_r1cs_instance(&ck, &S, &U2), - //&RelaxedR1CSWitness::from_r1cs_witness(&S, &W2), ); + + (ck, S, final_U, final_W) } #[test] diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index c2469058..1d9b6778 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -217,19 +217,19 @@ where } fn commit_with_blinding( - _ck: &Self::CommitmentKey, - _v: &[E::Scalar], + ck: &Self::CommitmentKey, + v: &[E::Scalar], _r: &E::Scalar, ) -> Self::Commitment { - unimplemented!() + Self::commit(ck, v) } fn derandomize( _ck: &Self::CommitmentKey, - _commit: &Self::Commitment, + commit: &Self::Commitment, _r: &E::Scalar, ) -> Self::Commitment { - unimplemented!() + commit.clone() } } @@ -658,7 +658,7 @@ mod tests { fn test_hyperkzg_eval() { // Test with poly(X1, X2) = 1 + X1 + X2 + X1*X2 let n = 4; - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); let (pk, vk): (ProverKey, VerifierKey) = EvaluationEngine::setup(&ck); // poly is in eval. representation; evaluated at [(0,0), (0,1), (1,0), (1,1)] @@ -718,7 +718,7 @@ mod tests { // eval = 28 let eval = Fr::from(28); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment @@ -776,7 +776,7 @@ mod tests { let point = (0..ell).map(|_| Fr::random(&mut rng)).collect::>(); let eval = MultilinearPolynomial::evaluate_with(&poly, &point); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 57b477d9..d87a50a1 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -688,6 +688,41 @@ impl RelaxedR1CSInstance { u, } } + + pub fn derandomize_commits( + &self, + ck: &CommitmentKey, + r_W: &E::Scalar, + r_E: &E::Scalar, + ) -> RelaxedR1CSInstance { + RelaxedR1CSInstance { + comm_W: CE::::derandomize(ck, &self.comm_W, &r_W), + comm_E: CE::::derandomize(ck, &self.comm_E, &r_E), + X: self.X.clone(), + u: self.u.clone(), + } + } + + pub fn derandomize_commits_and_witnesses( + &self, + ck: &CommitmentKey, + wit: &RelaxedR1CSWitness, + ) -> (RelaxedR1CSInstance, RelaxedR1CSWitness) { + ( + RelaxedR1CSInstance { + comm_W: CE::::derandomize(ck, &self.comm_W, &wit.r_W), + comm_E: CE::::derandomize(ck, &self.comm_E, &wit.r_E), + X: self.X.clone(), + u: self.u.clone(), + }, + RelaxedR1CSWitness { + W: wit.W.clone(), + r_W: E::Scalar::ZERO, + E: wit.E.clone(), + r_E: E::Scalar::ZERO, + }, + ) + } } impl TranscriptReprTrait for RelaxedR1CSInstance { diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 83cad2c3..71e215d0 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -11,6 +11,7 @@ use crate::{ r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, traits::{ circuit::StepCircuit, + commitment::CommitmentEngineTrait, snark::{DigestHelperTrait, RelaxedR1CSSNARKTrait}, Engine, }, @@ -76,6 +77,7 @@ where E: Engine, S: RelaxedR1CSSNARKTrait, { + ck: CommitmentKey, vk: S::VerifierKey, } @@ -96,7 +98,8 @@ where C: StepCircuit, { comm_W: Commitment, // commitment to the witness - snark: S, // snark proving the witness is satisfying + blind_r_W: E::Scalar, + snark: S, // snark proving the witness is satisfying _p: PhantomData, } @@ -113,9 +116,13 @@ impl, C: StepCircuit> DirectSN let (pk, vk) = S::setup(&ck, &shape)?; - let pk = ProverKey { S: shape, ck, pk }; + let pk = ProverKey { + S: shape, + ck: ck.clone(), + pk, + }; - let vk = VerifierKey { vk }; + let vk = VerifierKey { ck, vk }; Ok((pk, vk)) } @@ -140,11 +147,22 @@ impl, C: StepCircuit> DirectSN RelaxedR1CSWitness::from_r1cs_witness(&pk.S, &w), ); + // derandomize/unblind commitments + let (derandom_u_relaxed, derandom_w_relaxed) = + u_relaxed.derandomize_commits_and_witnesses(&pk.ck, &w_relaxed); + // prove the instance using Spartan - let snark = S::prove(&pk.ck, &pk.pk, &pk.S, &u_relaxed, &w_relaxed)?; + let snark = S::prove( + &pk.ck, + &pk.pk, + &pk.S, + &derandom_u_relaxed, + &derandom_w_relaxed, + )?; Ok(DirectSNARK { comm_W: u.comm_W, + blind_r_W: w_relaxed.r_W, snark, _p: PhantomData, }) @@ -152,8 +170,11 @@ impl, C: StepCircuit> DirectSN /// Verifies a proof of satisfiability pub fn verify(&self, vk: &VerifierKey, io: &[E::Scalar]) -> Result<(), NovaError> { + // derandomize/unblind commitments + let comm_W = E::CE::derandomize(&vk.ck, &self.comm_W, &self.blind_r_W); + // construct an instance using the provided commitment to the witness and z_i and z_{i+1} - let u_relaxed = RelaxedR1CSInstance::from_r1cs_instance_unchecked(&self.comm_W, io); + let u_relaxed = RelaxedR1CSInstance::from_r1cs_instance_unchecked(&comm_W, io); // verify the snark using the constructed instance self.snark.verify(&vk.vk, &u_relaxed)?; From 3c9a6e629833b5363a0c91099f670689f971656b Mon Sep 17 00:00:00 2001 From: jkwoods Date: Tue, 15 Oct 2024 16:39:11 -0400 Subject: [PATCH 16/21] rand in hashing --- src/circuit.rs | 30 ++++++++++++++++++++++++++++-- src/constants.rs | 2 +- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index 1402e934..337cdd16 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -53,6 +53,8 @@ pub struct NovaAugmentedCircuitInputs { z0: Vec, zi: Option>, U: Option>, + ri: Option, + r_next: E::Base, u: Option>, T: Option>, } @@ -65,6 +67,8 @@ impl NovaAugmentedCircuitInputs { z0: Vec, zi: Option>, U: Option>, + ri: Option, + r_next: E::Base, u: Option>, T: Option>, ) -> Self { @@ -74,6 +78,8 @@ impl NovaAugmentedCircuitInputs { z0, zi, U, + ri, + r_next, u, T, } @@ -117,6 +123,8 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { Vec>, Vec>, AllocatedRelaxedR1CSInstance, + AllocatedNum, + AllocatedNum, AllocatedR1CSInstance, AllocatedPoint, ), @@ -158,6 +166,14 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { self.params.n_limbs, )?; + // Allocate ri + let r_i = AllocatedNum::alloc(cs.namespace(|| "ri"), || { + Ok(self.inputs.get()?.ri.unwrap_or(E::Base::ZERO)) + })?; + + // Allocate r_i+1 + let r_next = AllocatedNum::alloc(cs.namespace(|| "r_i+1"), || Ok(self.inputs.get()?.r_next))?; + // Allocate the instance to be folded in let u = AllocatedR1CSInstance::alloc( cs.namespace(|| "allocate instance u to fold"), @@ -174,7 +190,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { )?; T.check_on_curve(cs.namespace(|| "check T on curve"))?; - Ok((params, i, z_0, z_i, U, u, T)) + Ok((params, i, z_0, z_i, U, r_i, r_next, u, T)) } /// Synthesizes base case and returns the new relaxed `R1CSInstance` @@ -212,6 +228,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { z_0: &[AllocatedNum], z_i: &[AllocatedNum], U: &AllocatedRelaxedR1CSInstance, + r_i: &AllocatedNum, u: &AllocatedR1CSInstance, T: &AllocatedPoint, arity: usize, @@ -230,6 +247,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { ro.absorb(e); } U.absorb_in_ro(cs.namespace(|| "absorb U"), &mut ro)?; + ro.absorb(r_i); let hash_bits = ro.squeeze(cs.namespace(|| "Input hash"), NUM_HASH_BITS)?; let hash = le_bits_to_num(cs.namespace(|| "bits to hash"), &hash_bits)?; @@ -263,7 +281,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { let arity = self.step_circuit.arity(); // Allocate all witnesses - let (params, i, z_0, z_i, U, u, T) = + let (params, i, z_0, z_i, U, r_i, r_next, u, T) = self.alloc_witness(cs.namespace(|| "allocate the circuit witness"), arity)?; // Compute variable indicating if this is the base case @@ -282,6 +300,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { &z_0, &z_i, &U, + &r_i, &u, &T, arity, @@ -347,6 +366,7 @@ impl<'a, E: Engine, SC: StepCircuit> NovaAugmentedCircuit<'a, E, SC> { ro.absorb(e); } Unew.absorb_in_ro(cs.namespace(|| "absorb U_new"), &mut ro)?; + ro.absorb(&r_next); let hash_bits = ro.squeeze(cs.namespace(|| "output hash bits"), NUM_HASH_BITS)?; let hash = le_bits_to_num(cs.namespace(|| "convert hash to num"), &hash_bits)?; @@ -410,6 +430,7 @@ mod tests { // Execute the base case for the primary let zero1 = <::Base as Field>::ZERO; + let ri_1 = <::Base as Field>::ZERO; let mut cs1 = SatisfyingAssignment::::new(); let inputs1: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( scalar_as_base::(zero1), // pass zero for testing @@ -418,6 +439,8 @@ mod tests { None, None, None, + ri_1, + None, None, ); let circuit1: NovaAugmentedCircuit<'_, E2, TrivialCircuit<::Base>> = @@ -429,6 +452,7 @@ mod tests { // Execute the base case for the secondary let zero2 = <::Base as Field>::ZERO; + let ri_2 = <::Base as Field>::ZERO; let mut cs2 = SatisfyingAssignment::::new(); let inputs2: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( scalar_as_base::(zero2), // pass zero for testing @@ -436,6 +460,8 @@ mod tests { vec![zero2], None, None, + None, + ri_2, Some(inst1), None, ); diff --git a/src/constants.rs b/src/constants.rs index 65a132dd..b0465cfd 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,6 +2,6 @@ pub(crate) const NUM_CHALLENGE_BITS: usize = 128; pub(crate) const NUM_HASH_BITS: usize = 250; pub(crate) const BN_LIMB_WIDTH: usize = 64; pub(crate) const BN_N_LIMBS: usize = 4; -pub(crate) const NUM_FE_WITHOUT_IO_FOR_CRHF: usize = 17; +pub(crate) const NUM_FE_WITHOUT_IO_FOR_CRHF: usize = 18; pub(crate) const NUM_FE_FOR_RO: usize = 9; pub(crate) const NUM_FE_FOR_RO_RELAXED: usize = 19; diff --git a/src/lib.rs b/src/lib.rs index 8711c67f..0d630090 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ use nifs::NIFS; use r1cs::{ CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, }; +use rand_core::OsRng; use serde::{Deserialize, Serialize}; use traits::{ circuit::StepCircuit, commitment::CommitmentEngineTrait, snark::RelaxedR1CSSNARKTrait, @@ -246,8 +247,10 @@ where z0_secondary: Vec, r_W_primary: RelaxedR1CSWitness, r_U_primary: RelaxedR1CSInstance, + ri_primary: E1::Scalar, r_W_secondary: RelaxedR1CSWitness, r_U_secondary: RelaxedR1CSInstance, + ri_secondary: E2::Scalar, l_w_secondary: R1CSWitness, l_u_secondary: R1CSInstance, i: usize, @@ -297,6 +300,9 @@ where return Err(NovaError::InvalidInitialInputLength); } + let ri_primary = E1::Scalar::random(&mut OsRng); + let ri_secondary = E2::Scalar::random(&mut OsRng); + // base case for the primary let mut cs_primary = SatisfyingAssignment::::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( @@ -306,6 +312,8 @@ where None, None, None, + ri_primary, // "r next" + None, None, ); @@ -327,6 +335,8 @@ where z0_secondary.to_vec(), None, None, + None, + ri_secondary, // "r next" Some(u_primary.clone()), None, ); @@ -374,8 +384,10 @@ where z0_secondary: z0_secondary.to_vec(), r_W_primary, r_U_primary, + ri_primary, r_W_secondary, r_U_secondary, + ri_secondary, l_w_secondary, l_u_secondary, i: 0, @@ -411,6 +423,8 @@ where &self.l_w_secondary, )?; + let r_next_primary = E1::Scalar::random(&mut OsRng); + let mut cs_primary = SatisfyingAssignment::::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( scalar_as_base::(pp.digest()), @@ -418,6 +432,8 @@ where self.z0_primary.to_vec(), Some(self.zi_primary.clone()), Some(self.r_U_secondary.clone()), + Some(self.ri_primary), + r_next_primary, Some(self.l_u_secondary.clone()), Some(nifs_secondary.comm_T), ); @@ -445,6 +461,8 @@ where &l_w_primary, )?; + let r_next_secondary = E2::Scalar::random(&mut OsRng); + let mut cs_secondary = SatisfyingAssignment::::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( pp.digest(), @@ -452,6 +470,8 @@ where self.z0_secondary.to_vec(), Some(self.zi_secondary.clone()), Some(self.r_U_primary.clone()), + Some(self.ri_secondary), + r_next_secondary, Some(l_u_primary), Some(nifs_primary.comm_T), ); @@ -489,6 +509,9 @@ where self.r_U_secondary = r_U_secondary; self.r_W_secondary = r_W_secondary; + self.ri_primary = r_next_primary; + self.ri_secondary = r_next_secondary; + Ok(()) } @@ -612,6 +635,7 @@ where hasher.absorb(*e); } self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); let mut hasher2 = ::RO::new( pp.ro_consts_primary.clone(), @@ -626,6 +650,7 @@ where hasher2.absorb(*e); } self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); ( hasher.squeeze(NUM_HASH_BITS), @@ -728,6 +753,7 @@ where hasher.absorb(*e); } self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); let mut hasher2 = ::RO::new( pp.ro_consts_primary.clone(), @@ -742,6 +768,7 @@ where hasher2.absorb(*e); } self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); ( hasher.squeeze(NUM_HASH_BITS), @@ -869,9 +896,11 @@ where S2: RelaxedR1CSSNARKTrait, { r_U_primary: RelaxedR1CSInstance, + ri_primary: E1::Scalar, r_W_snark_primary: S1, r_U_secondary: RelaxedR1CSInstance, + ri_secondary: E2::Scalar, l_u_secondary: R1CSInstance, nifs_secondary: NIFS, f_W_snark_secondary: S2, @@ -1008,9 +1037,11 @@ where Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), + ri_primary: recursive_snark.ri_primary.clone(), r_W_snark_primary: r_W_snark_primary?, r_U_secondary: recursive_snark.r_U_secondary.clone(), + ri_secondary: recursive_snark.ri_secondary.clone(), l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_secondary, f_W_snark_secondary: f_W_snark_secondary?, @@ -1077,9 +1108,11 @@ where Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), + ri_primary: recursive_snark.ri_primary.clone(), r_W_snark_primary: snark_primary?, r_U_secondary: recursive_snark.r_U_secondary.clone(), + ri_secondary: recursive_snark.ri_secondary.clone(), l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_secondary, f_W_snark_secondary: snark_secondary?, @@ -1147,6 +1180,7 @@ where hasher.absorb(*e); } self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); let mut hasher2 = ::RO::new( vk.ro_consts_primary.clone(), @@ -1161,6 +1195,7 @@ where hasher2.absorb(*e); } self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); ( hasher.squeeze(NUM_HASH_BITS), @@ -1255,6 +1290,7 @@ where hasher.absorb(*e); } self.r_U_secondary.absorb_in_ro(&mut hasher); + hasher.absorb(self.ri_primary); let mut hasher2 = ::RO::new( vk.ro_consts_primary.clone(), @@ -1269,6 +1305,7 @@ where hasher2.absorb(*e); } self.r_U_primary.absorb_in_ro(&mut hasher2); + hasher2.absorb(self.ri_secondary); ( hasher.squeeze(NUM_HASH_BITS), From b58876fc683134894d5bc4eff0b998d1c1fdb10a Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 17 Oct 2024 13:30:30 -0400 Subject: [PATCH 17/21] hashing tests, clippy --- src/lib.rs | 14 +++++++------- src/provider/hyperkzg.rs | 2 +- src/r1cs/mod.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0d630090..be29d35d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1037,11 +1037,11 @@ where Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), - ri_primary: recursive_snark.ri_primary.clone(), + ri_primary: recursive_snark.ri_primary, r_W_snark_primary: r_W_snark_primary?, r_U_secondary: recursive_snark.r_U_secondary.clone(), - ri_secondary: recursive_snark.ri_secondary.clone(), + ri_secondary: recursive_snark.ri_secondary, l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_secondary, f_W_snark_secondary: f_W_snark_secondary?, @@ -1108,11 +1108,11 @@ where Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), - ri_primary: recursive_snark.ri_primary.clone(), + ri_primary: recursive_snark.ri_primary, r_W_snark_primary: snark_primary?, r_U_secondary: recursive_snark.r_U_secondary.clone(), - ri_secondary: recursive_snark.ri_secondary.clone(), + ri_secondary: recursive_snark.ri_secondary, l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_secondary, f_W_snark_secondary: snark_secondary?, @@ -1483,19 +1483,19 @@ mod tests { test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["a69d6cf6d014c3a5cc99b77afc86691f7460faa737207dd21b30e8241fae8002"], + &expect!["cfd726c95effd42b68bb22dda539eaefe4483601e207aa92f4970e7d6d215702"], ); test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["b22ab3456df4bd391804a39fae582b37ed4a8d90ace377337940ac956d87f701"], + &expect!["c90a30a806b9b320bc7777cfadc21a9d133c196d9ab50d2237c5f15edcbd3a03"], ); test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["c8aec89a3ea90317a0ecdc9150f4fc3648ca33f6660924a192cafd82e2939b02"], + &expect!["88fc7d72e9a6e6e872bd08135ecbdbb13f4b829976e026c81f8feae89cf57802"], ); } diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 1d9b6778..e08e46a5 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -229,7 +229,7 @@ where commit: &Self::Commitment, _r: &E::Scalar, ) -> Self::Commitment { - commit.clone() + *commit } } diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index d87a50a1..fe380723 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -696,10 +696,10 @@ impl RelaxedR1CSInstance { r_E: &E::Scalar, ) -> RelaxedR1CSInstance { RelaxedR1CSInstance { - comm_W: CE::::derandomize(ck, &self.comm_W, &r_W), - comm_E: CE::::derandomize(ck, &self.comm_E, &r_E), + comm_W: CE::::derandomize(ck, &self.comm_W, r_W), + comm_E: CE::::derandomize(ck, &self.comm_E, r_E), X: self.X.clone(), - u: self.u.clone(), + u: self.u, } } @@ -713,7 +713,7 @@ impl RelaxedR1CSInstance { comm_W: CE::::derandomize(ck, &self.comm_W, &wit.r_W), comm_E: CE::::derandomize(ck, &self.comm_E, &wit.r_E), X: self.X.clone(), - u: self.u.clone(), + u: self.u, }, RelaxedR1CSWitness { W: wit.W.clone(), From 068249a21c13946126a94c3b2ac84dc84ce437f5 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 17 Oct 2024 15:09:55 -0400 Subject: [PATCH 18/21] requested simplifying changes --- benches/compressed-snark.rs | 68 +---- examples/and.rs | 2 +- examples/hashchain.rs | 2 +- examples/minroot.rs | 2 +- src/constants.rs | 2 +- src/lib.rs | 569 +++--------------------------------- src/nifs.rs | 12 +- src/provider/hyperkzg.rs | 20 +- src/provider/ipa_pc.rs | 15 +- src/provider/pedersen.rs | 34 +-- src/r1cs/mod.rs | 20 +- src/spartan/ppsnark.rs | 25 +- src/traits/commitment.rs | 9 +- 13 files changed, 114 insertions(+), 666 deletions(-) diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index a5c0d736..fae15282 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -37,14 +37,13 @@ cfg_if::cfg_if! { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)).with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); - targets = bench_compressed_snark, bench_randomizing_compressed_snark, bench_compressed_snark_with_computational_commitments, bench_randomizing_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments } } else { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)); - targets = bench_compressed_snark, bench_randomizing_compressed_snark, bench_compressed_snark_with_computational_commitments, -bench_randomizing_compressed_snark_with_computational_commitments + targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments, } } } @@ -60,11 +59,9 @@ const NUM_SAMPLES: usize = 10; /// Parameters /// - `group``: the criterion benchmark group /// - `num_cons`: the number of constraints in the step circuit -/// - `randomizing`: if there is a zk layer added before compression fn bench_compressed_snark_internal, S2: RelaxedR1CSSNARKTrait>( group: &mut BenchmarkGroup<'_, WallTime>, num_cons: usize, - randomizing: bool, ) { let c_primary = NonTrivialCircuit::new(num_cons); let c_secondary = TrivialCircuit::default(); @@ -113,12 +110,11 @@ fn bench_compressed_snark_internal, S2: RelaxedR1C black_box(&pp), black_box(&pk), black_box(&recursive_snark), - black_box(randomizing) ) .is_ok()); }) }); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, randomizing); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -157,7 +153,7 @@ fn bench_compressed_snark(c: &mut Criterion) { let mut group = c.benchmark_group(format!("CompressedSNARK-StepCircuitSize-{num_cons}")); group.sample_size(NUM_SAMPLES); - bench_compressed_snark_internal::(&mut group, num_cons, false); + bench_compressed_snark_internal::(&mut group, num_cons); group.finish(); } @@ -185,61 +181,7 @@ fn bench_compressed_snark_with_computational_commitments(c: &mut Criterion) { .sampling_mode(SamplingMode::Flat) .sample_size(NUM_SAMPLES); - bench_compressed_snark_internal::(&mut group, num_cons, false); - - group.finish(); - } -} - -fn bench_randomizing_compressed_snark(c: &mut Criterion) { - // we vary the number of constraints in the step circuit - for &num_cons_in_augmented_circuit in [ - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY, - 16384, - 32768, - 65536, - 131072, - 262144, - 524288, - 1048576, - ] - .iter() - { - // number of constraints in the step circuit - let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY; - - let mut group = c.benchmark_group(format!("CompressedSNARK-StepCircuitSize-{num_cons}")); - group.sample_size(NUM_SAMPLES); - - bench_compressed_snark_internal::(&mut group, num_cons, true); - - group.finish(); - } -} - -fn bench_randomizing_compressed_snark_with_computational_commitments(c: &mut Criterion) { - // we vary the number of constraints in the step circuit - for &num_cons_in_augmented_circuit in [ - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY, - 16384, - 32768, - 65536, - 131072, - 262144, - ] - .iter() - { - // number of constraints in the step circuit - let num_cons = num_cons_in_augmented_circuit - NUM_CONS_VERIFIER_CIRCUIT_PRIMARY; - - let mut group = c.benchmark_group(format!( - "CompressedSNARK-Commitments-StepCircuitSize-{num_cons}" - )); - group - .sampling_mode(SamplingMode::Flat) - .sample_size(NUM_SAMPLES); - - bench_compressed_snark_internal::(&mut group, num_cons, true); + bench_compressed_snark_internal::(&mut group, num_cons); group.finish(); } diff --git a/examples/and.rs b/examples/and.rs index d38eb3ff..33b50bb7 100644 --- a/examples/and.rs +++ b/examples/and.rs @@ -301,7 +301,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/examples/hashchain.rs b/examples/hashchain.rs index a08bfad7..d0554568 100644 --- a/examples/hashchain.rs +++ b/examples/hashchain.rs @@ -196,7 +196,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/examples/minroot.rs b/examples/minroot.rs index ba8eaa1d..20c7275f 100644 --- a/examples/minroot.rs +++ b/examples/minroot.rs @@ -261,7 +261,7 @@ fn main() { let start = Instant::now(); - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark, false); + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), diff --git a/src/constants.rs b/src/constants.rs index b0465cfd..6c6180a8 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,4 +4,4 @@ pub(crate) const BN_LIMB_WIDTH: usize = 64; pub(crate) const BN_N_LIMBS: usize = 4; pub(crate) const NUM_FE_WITHOUT_IO_FOR_CRHF: usize = 18; pub(crate) const NUM_FE_FOR_RO: usize = 9; -pub(crate) const NUM_FE_FOR_RO_RELAXED: usize = 19; +pub(crate) const NUM_FE_FOR_RO_RELAXED: usize = 34; diff --git a/src/lib.rs b/src/lib.rs index be29d35d..42287fbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -516,8 +516,7 @@ where } /// add a randomizing fold to a `RecursiveSNARK` - /// if you plan on using a randomized CompressedSNARK, you don't need to call this - pub fn randomizing_fold( + fn randomizing_fold( &self, pp: &PublicParams, ) -> Result, NovaError> { @@ -698,139 +697,6 @@ where Ok((self.zi_primary.clone(), self.zi_secondary.clone())) } - /// Verify the correctness of the `RecursiveSNARK` randomizing layer - pub fn verify_randomizing( - &self, - pp: &PublicParams, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - random_layer: RandomLayerRecursive, - ) -> Result<(Vec, Vec), NovaError> { - // number of steps cannot be zero - let is_num_steps_zero = num_steps == 0; - - // there should not have been more folds after randomizing layer - if random_layer.last_i != self.i { - return Err(NovaError::ProofVerifyError); - } - - // check if the provided proof has executed num_steps - let is_num_steps_not_match = self.i != num_steps; - - // check if the initial inputs match - let is_inputs_not_match = self.z0_primary != z0_primary || self.z0_secondary != z0_secondary; - - // check if the (relaxed) R1CS instances have two public outputs - let is_instance_has_two_outpus = self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - || random_layer.l_ur_primary.X.len() != 2 - || random_layer.l_ur_secondary.X.len() != 2 - || random_layer.r_Un_primary.X.len() != 2 - || random_layer.r_Un_secondary.X.len() != 2; - - if is_num_steps_zero - || is_num_steps_not_match - || is_inputs_not_match - || is_instance_has_two_outpus - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - pp.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, - ); - hasher.absorb(pp.digest()); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zi_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - hasher.absorb(self.ri_primary); - - let mut hasher2 = ::RO::new( - pp.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(pp.digest())); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zi_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - hasher2.absorb(self.ri_secondary); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = random_layer.nifs_Uf_secondary.verify( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &r_Uf_secondary, - &random_layer.l_ur_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( - &pp.ro_consts_primary, - &pp.digest(), - &self.r_U_primary, - &random_layer.l_ur_primary, - )?; - - // check the satisfiability of U1, U2 - let (res_primary, res_secondary) = rayon::join( - || { - pp.r1cs_shape_primary.is_sat_relaxed( - &pp.ck_primary, - &r_Un_primary, - &random_layer.r_Wn_primary, - ) - }, - || { - pp.r1cs_shape_secondary.is_sat_relaxed( - &pp.ck_secondary, - &r_Un_secondary, - &random_layer.r_Wn_secondary, - ) - }, - ); - - // check the returned res objects - res_primary?; - res_secondary?; - - Ok((self.zi_primary.clone(), self.zi_secondary.clone())) - } - /// Get the outputs after the last step of computation. pub fn outputs(&self) -> (&[E1::Scalar], &[E2::Scalar]) { (&self.zi_primary, &self.zi_secondary) @@ -902,7 +768,7 @@ where r_U_secondary: RelaxedR1CSInstance, ri_secondary: E2::Scalar, l_u_secondary: R1CSInstance, - nifs_secondary: NIFS, + nifs_Uf_secondary: NIFS, f_W_snark_secondary: S2, zn_primary: Vec, @@ -912,22 +778,14 @@ where primary_blind_r_E: E1::Scalar, secondary_blind_r_W: E2::Scalar, secondary_blind_r_E: E2::Scalar, - random_layer: Option>, - _p: PhantomData<(C1, C2)>, -} -/// Final randomized fold info for `CompressedSNARK` -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RandomLayerCompressed -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, -{ + // randomizing layer l_ur_primary: RelaxedR1CSInstance, l_ur_secondary: RelaxedR1CSInstance, nifs_Un_primary: NIFS, nifs_Un_secondary: NIFS, + + _p: PhantomData<(C1, C2)>, } impl CompressedSNARK @@ -974,92 +832,8 @@ where Ok((pk, vk)) } - /// Create a new `CompressedSNARK` - pub fn prove( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - randomizing: bool, - ) -> Result { - if randomizing { - Self::prove_randomizing(pp, pk, recursive_snark) - } else { - Self::prove_regular(pp, pk, recursive_snark) - } - } - - /// Create a new `CompressedSNARK` (without randomizing layer) - fn prove_regular( - pp: &PublicParams, - pk: &ProverKey, - recursive_snark: &RecursiveSNARK, - ) -> Result { - // fold the secondary circuit's instance with its running instance - let (nifs_secondary, (f_U_secondary, f_W_secondary)) = 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, - &recursive_snark.l_u_secondary, - &recursive_snark.l_w_secondary, - )?; - - // derandomize/unblind commitments - let (derandom_r_U_primary, derandom_r_W_primary) = recursive_snark - .r_U_primary - .derandomize_commits_and_witnesses(&pp.ck_primary, &recursive_snark.r_W_primary); - let (derandom_f_U_secondary, derandom_f_W_secondary) = - f_U_secondary.derandomize_commits_and_witnesses(&pp.ck_secondary, &f_W_secondary); - - // create SNARKs proving the knowledge of f_W_primary and f_W_secondary - let (r_W_snark_primary, f_W_snark_secondary) = rayon::join( - || { - S1::prove( - &pp.ck_primary, - &pk.pk_primary, - &pp.r1cs_shape_primary, - &derandom_r_U_primary, - &derandom_r_W_primary, - ) - }, - || { - S2::prove( - &pp.ck_secondary, - &pk.pk_secondary, - &pp.r1cs_shape_secondary, - &derandom_f_U_secondary, - &derandom_f_W_secondary, - ) - }, - ); - - Ok(Self { - r_U_primary: recursive_snark.r_U_primary.clone(), - ri_primary: recursive_snark.ri_primary, - r_W_snark_primary: r_W_snark_primary?, - - r_U_secondary: recursive_snark.r_U_secondary.clone(), - ri_secondary: recursive_snark.ri_secondary, - l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_secondary, - f_W_snark_secondary: f_W_snark_secondary?, - - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), - - primary_blind_r_W: recursive_snark.r_W_primary.r_W, - primary_blind_r_E: recursive_snark.r_W_primary.r_E, - secondary_blind_r_W: f_W_secondary.r_W, - secondary_blind_r_E: f_W_secondary.r_E, - random_layer: None, - _p: Default::default(), - }) - } - /// Create a new `CompressedSNARK` (with randomizing layer) - fn prove_randomizing( + pub fn prove( pp: &PublicParams, pk: &ProverKey, recursive_snark: &RecursiveSNARK, @@ -1097,15 +871,6 @@ where }, ); - let random_layer_compressed = Some(RandomLayerCompressed { - l_ur_primary: random_layer.l_ur_primary.clone(), - l_ur_secondary: random_layer.l_ur_secondary.clone(), - nifs_Un_primary: random_layer.nifs_Un_primary.clone(), - nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), - }); - - let nifs_secondary = random_layer.nifs_Uf_secondary.clone(); - Ok(Self { r_U_primary: recursive_snark.r_U_primary.clone(), ri_primary: recursive_snark.ri_primary, @@ -1114,7 +879,7 @@ where r_U_secondary: recursive_snark.r_U_secondary.clone(), ri_secondary: recursive_snark.ri_secondary, l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_secondary, + nifs_Uf_secondary: random_layer.nifs_Uf_secondary.clone(), f_W_snark_secondary: snark_secondary?, zn_primary: recursive_snark.zi_primary.clone(), @@ -1124,142 +889,23 @@ where primary_blind_r_E: random_layer.r_Wn_primary.r_E, secondary_blind_r_W: random_layer.r_Wn_secondary.r_W, secondary_blind_r_E: random_layer.r_Wn_secondary.r_E, - random_layer: random_layer_compressed, + + l_ur_primary: random_layer.l_ur_primary.clone(), + l_ur_secondary: random_layer.l_ur_secondary.clone(), + nifs_Un_primary: random_layer.nifs_Un_primary.clone(), + nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), _p: Default::default(), }) } - /// Verify the correctness of the `CompressedSNARK` - pub fn verify( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - if self.random_layer.is_none() { - self.verify_regular(vk, num_steps, z0_primary, z0_secondary) - } else { - self.verify_randomizing(vk, num_steps, z0_primary, z0_secondary) - } - } - - /// Verify the correctness of the `CompressedSNARK` (without randomizing layer) - fn verify_regular( - &self, - vk: &VerifierKey, - num_steps: usize, - z0_primary: &[E1::Scalar], - z0_secondary: &[E2::Scalar], - ) -> Result<(Vec, Vec), NovaError> { - // the number of steps cannot be zero - if num_steps == 0 { - return Err(NovaError::ProofVerifyError); - } - - // check if the (relaxed) R1CS instances have two public outputs - if self.l_u_secondary.X.len() != 2 - || self.r_U_primary.X.len() != 2 - || self.r_U_secondary.X.len() != 2 - { - return Err(NovaError::ProofVerifyError); - } - - // check if the output hashes in R1CS instances point to the right running instances - let (hash_primary, hash_secondary) = { - let mut hasher = ::RO::new( - vk.ro_consts_secondary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, - ); - hasher.absorb(vk.pp_digest); - hasher.absorb(E1::Scalar::from(num_steps as u64)); - for e in z0_primary { - hasher.absorb(*e); - } - for e in &self.zn_primary { - hasher.absorb(*e); - } - self.r_U_secondary.absorb_in_ro(&mut hasher); - hasher.absorb(self.ri_primary); - - let mut hasher2 = ::RO::new( - vk.ro_consts_primary.clone(), - NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, - ); - hasher2.absorb(scalar_as_base::(vk.pp_digest)); - hasher2.absorb(E2::Scalar::from(num_steps as u64)); - for e in z0_secondary { - hasher2.absorb(*e); - } - for e in &self.zn_secondary { - hasher2.absorb(*e); - } - self.r_U_primary.absorb_in_ro(&mut hasher2); - hasher2.absorb(self.ri_secondary); - - ( - hasher.squeeze(NUM_HASH_BITS), - hasher2.squeeze(NUM_HASH_BITS), - ) - }; - - if hash_primary != self.l_u_secondary.X[0] - || hash_secondary != scalar_as_base::(self.l_u_secondary.X[1]) - { - return Err(NovaError::ProofVerifyError); - } - - // fold the secondary's running instance with the last instance to get a folded instance - let f_U_secondary = self.nifs_secondary.verify( - &vk.ro_consts_secondary, - &scalar_as_base::(vk.pp_digest), - &self.r_U_secondary, - &self.l_u_secondary, - )?; - - // derandomize/unblind commitments - let derandom_r_U_primary = self.r_U_primary.derandomize_commits( - &vk.vk_primary_commitment_key, - &self.primary_blind_r_W, - &self.primary_blind_r_E, - ); - let derandom_f_U_secondary = f_U_secondary.derandomize_commits( - &vk.vk_secondary_commitment_key, - &self.secondary_blind_r_W, - &self.secondary_blind_r_E, - ); - - // check the satisfiability of the folded instances using - // SNARKs proving the knowledge of their satisfying witnesses - let (res_primary, res_secondary) = rayon::join( - || { - self - .r_W_snark_primary - .verify(&vk.vk_primary, &derandom_r_U_primary) - }, - || { - self - .f_W_snark_secondary - .verify(&vk.vk_secondary, &derandom_f_U_secondary) - }, - ); - - res_primary?; - res_secondary?; - - Ok((self.zn_primary.clone(), self.zn_secondary.clone())) - } - /// Verify the correctness of the `CompressedSNARK` (with randomizing layer) - fn verify_randomizing( + pub fn verify( &self, vk: &VerifierKey, num_steps: usize, z0_primary: &[E1::Scalar], z0_secondary: &[E2::Scalar], ) -> Result<(Vec, Vec), NovaError> { - let random_layer = self.random_layer.as_ref().unwrap(); - // the number of steps cannot be zero if num_steps == 0 { return Err(NovaError::ProofVerifyError); @@ -1269,8 +915,8 @@ where if self.l_u_secondary.X.len() != 2 || self.r_U_primary.X.len() != 2 || self.r_U_secondary.X.len() != 2 - || random_layer.l_ur_primary.X.len() != 2 - || random_layer.l_ur_secondary.X.len() != 2 + || self.l_ur_primary.X.len() != 2 + || self.l_ur_secondary.X.len() != 2 { return Err(NovaError::ProofVerifyError); } @@ -1320,7 +966,7 @@ where } // fold secondary U/W with secondary u/w to get Uf/Wf - let r_Uf_secondary = self.nifs_secondary.verify( + let r_Uf_secondary = self.nifs_Uf_secondary.verify( &vk.ro_consts_secondary, &scalar_as_base::(vk.pp_digest), &self.r_U_secondary, @@ -1328,19 +974,19 @@ where )?; // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = random_layer.nifs_Un_secondary.verify_relaxed( + let r_Un_secondary = self.nifs_Un_secondary.verify_relaxed( &vk.ro_consts_secondary, &scalar_as_base::(vk.pp_digest), &r_Uf_secondary, - &random_layer.l_ur_secondary, + &self.l_ur_secondary, )?; // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = random_layer.nifs_Un_primary.verify_relaxed( + let r_Un_primary = self.nifs_Un_primary.verify_relaxed( &vk.ro_consts_primary, &vk.pp_digest, &self.r_U_primary, - &random_layer.l_ur_primary, + &self.l_ur_primary, )?; // derandomize/unblind commitments @@ -1635,92 +1281,7 @@ mod tests { test_ivc_nontrivial_with::(); } - fn test_ivc_nontrivial_randomizing_with() - where - E1: Engine::Scalar>, - E2: Engine::Scalar>, - { - let circuit_primary = TrivialCircuit::default(); - let circuit_secondary = CubicCircuit::default(); - - // produce public parameters - let pp = PublicParams::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::setup( - &circuit_primary, - &circuit_secondary, - &*default_ck_hint(), - &*default_ck_hint(), - ) - .unwrap(); - - let num_steps = 3; - - // produce a recursive SNARK - let mut recursive_snark = RecursiveSNARK::< - E1, - E2, - TrivialCircuit<::Scalar>, - CubicCircuit<::Scalar>, - >::new( - &pp, - &circuit_primary, - &circuit_secondary, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ) - .unwrap(); - - for i in 0..num_steps { - let res = recursive_snark.prove_step(&pp, &circuit_primary, &circuit_secondary); - assert!(res.is_ok()); - - // verify the recursive snark at each step of recursion - let res = recursive_snark.verify( - &pp, - i + 1, - &[::Scalar::ONE], - &[::Scalar::ZERO], - ); - assert!(res.is_ok()); - } - - let res = recursive_snark.randomizing_fold(&pp); - assert!(res.is_ok()); - - // verify the randomized recursive SNARK - let res = recursive_snark.verify_randomizing( - &pp, - num_steps, - &[::Scalar::ONE], - &[::Scalar::ZERO], - res.unwrap(), - ); - assert!(res.is_ok()); - - let (zn_primary, zn_secondary) = res.unwrap(); - - // sanity: check the claimed output with a direct computation of the same - assert_eq!(zn_primary, vec![::Scalar::ONE]); - let mut zn_secondary_direct = vec![::Scalar::ZERO]; - for _i in 0..num_steps { - zn_secondary_direct = circuit_secondary.clone().output(&zn_secondary_direct); - } - assert_eq!(zn_secondary, zn_secondary_direct); - assert_eq!(zn_secondary, vec![::Scalar::from(2460515u64)]); - } - - #[test] - fn test_ivc_nontrivial_randomizing() { - test_ivc_nontrivial_randomizing_with::(); - test_ivc_nontrivial_randomizing_with::(); - test_ivc_nontrivial_randomizing_with::(); - } - - fn test_ivc_nontrivial_with_compression_with(randomizing: bool) + fn test_ivc_nontrivial_with_compression_with() where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -1790,12 +1351,8 @@ mod tests { let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); // produce a compressed SNARK - let res = CompressedSNARK::<_, _, _, _, S, S>::prove( - &pp, - &pk, - &recursive_snark, - randomizing, - ); + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1811,41 +1368,20 @@ mod tests { #[test] fn test_ivc_nontrivial_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(false); + test_ivc_nontrivial_with_compression_with::, EE<_>>(); test_ivc_nontrivial_with_compression_with::, EE<_>>( - false, - ); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - false, ); + test_ivc_nontrivial_with_compression_with::, EE<_>>(); test_ivc_nontrivial_with_spark_compression_with::< Bn256EngineKZG, GrumpkinEngine, provider::hyperkzg::EvaluationEngine<_>, EE<_>, - >(false); + >(); } - #[test] - fn test_ivc_nontrivial_randomizing_with_compression() { - test_ivc_nontrivial_with_compression_with::, EE<_>>(true); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - true, - ); - test_ivc_nontrivial_with_compression_with::, EE<_>>( - true, - ); - - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - provider::hyperkzg::EvaluationEngine<_>, - EE<_>, - >(true); - } - - fn test_ivc_nontrivial_with_spark_compression_with(randomizing: bool) + fn test_ivc_nontrivial_with_spark_compression_with() where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -1921,7 +1457,6 @@ mod tests { &pp, &pk, &recursive_snark, - randomizing, ); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -1938,37 +1473,18 @@ mod tests { #[test] fn test_ivc_nontrivial_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - false, - ); + test_ivc_nontrivial_with_spark_compression_with::, EE<_>>(); test_ivc_nontrivial_with_spark_compression_with::< Bn256EngineKZG, GrumpkinEngine, EEPrime<_>, EE<_>, - >(false); + >(); test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - false, ); } - #[test] - fn test_ivc_nontrivial_randomizing_with_spark_compression() { - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - true, - ); - test_ivc_nontrivial_with_spark_compression_with::< - Bn256EngineKZG, - GrumpkinEngine, - EEPrime<_>, - EE<_>, - >(true); - test_ivc_nontrivial_with_spark_compression_with::, EE<_>>( - true, - ); - } - - fn test_ivc_nondet_with_compression_with(randomizing: bool) + fn test_ivc_nondet_with_compression_with() where E1: Engine::Scalar>, E2: Engine::Scalar>, @@ -2091,12 +1607,8 @@ mod tests { let (pk, vk) = CompressedSNARK::<_, _, _, _, S, S>::setup(&pp).unwrap(); // produce a compressed SNARK - let res = CompressedSNARK::<_, _, _, _, S, S>::prove( - &pp, - &pk, - &recursive_snark, - randomizing, - ); + let res = + CompressedSNARK::<_, _, _, _, S, S>::prove(&pp, &pk, &recursive_snark); assert!(res.is_ok()); let compressed_snark = res.unwrap(); @@ -2107,20 +1619,9 @@ mod tests { #[test] fn test_ivc_nondet_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(false); - test_ivc_nondet_with_compression_with::, EE<_>>( - false, - ); - test_ivc_nondet_with_compression_with::, EE<_>>(false); - } - - #[test] - fn test_ivc_nondet_randomizing_with_compression() { - test_ivc_nondet_with_compression_with::, EE<_>>(true); - test_ivc_nondet_with_compression_with::, EE<_>>( - true, - ); - test_ivc_nondet_with_compression_with::, EE<_>>(true); + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); + test_ivc_nondet_with_compression_with::, EE<_>>(); } fn test_ivc_base_with() diff --git a/src/nifs.rs b/src/nifs.rs index b84d7863..93ce7416 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -93,7 +93,11 @@ impl NIFS { // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); - // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) + // append U1 to transcript + // (this function is only used when folding in random instance) + U1.absorb_in_ro(&mut ro); + + // append U2 to transcript (randomized instance) U2.absorb_in_ro(&mut ro); // compute a commitment to the cross-term @@ -165,7 +169,11 @@ impl NIFS { // append the digest of pp to the transcript ro.absorb(scalar_as_base::(*pp_digest)); - // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) + // append U1 to transcript + // (this function is only used when folding in random instance) + U1.absorb_in_ro(&mut ro); + + // append U2 to transcript U2.absorb_in_ro(&mut ro); // append `comm_T` to the transcript and obtain a challenge diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index e08e46a5..0123e5cd 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -208,7 +208,7 @@ where Self::CommitmentKey { ck, tau_H } } - fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment { + fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar], _r: &E::Scalar) -> Self::Commitment { assert!(ck.ck.len() >= v.len()); Commitment { @@ -216,14 +216,6 @@ where } } - fn commit_with_blinding( - ck: &Self::CommitmentKey, - v: &[E::Scalar], - _r: &E::Scalar, - ) -> Self::Commitment { - Self::commit(ck, v) - } - fn derandomize( _ck: &Self::CommitmentKey, commit: &Self::Commitment, @@ -380,7 +372,7 @@ where let h = compute_witness_polynomial(f, u); - E::CE::commit(ck, &h).comm.affine() + E::CE::commit(ck, &h, &E::Scalar::ZERO).comm.affine() }; let kzg_open_batch = |f: &[Vec], @@ -479,7 +471,7 @@ where // Compute commitments in parallel let com: Vec> = (1..polys.len()) .into_par_iter() - .map(|i| E::CE::commit(ck, &polys[i]).comm.affine()) + .map(|i| E::CE::commit(ck, &polys[i], &E::Scalar::ZERO).comm.affine()) .collect(); // Phase 2 @@ -664,7 +656,7 @@ mod tests { // poly is in eval. representation; evaluated at [(0,0), (0,1), (1,0), (1,1)] let poly = vec![Fr::from(1), Fr::from(2), Fr::from(2), Fr::from(4)]; - let C = CommitmentEngine::commit(&ck, &poly); + let C = CommitmentEngine::commit(&ck, &poly, &::Scalar::ZERO); let test_inner = |point: Vec, eval: Fr| -> Result<(), NovaError> { let mut tr = Keccak256Transcript::new(b"TestEval"); @@ -722,7 +714,7 @@ mod tests { let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment - let C = CommitmentEngine::commit(&ck, &poly); + let C = CommitmentEngine::commit(&ck, &poly, &::Scalar::ZERO); // prove an evaluation let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); @@ -780,7 +772,7 @@ mod tests { let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment - let C = CommitmentEngine::commit(&ck, &poly); + let C = CommitmentEngine::commit(&ck, &poly, &::Scalar::ZERO); // prove an evaluation let mut prover_transcript = Keccak256Transcript::new(b"TestEval"); diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 22cb29d7..8be62aed 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -220,6 +220,7 @@ where .chain(iter::once(&c_L)) .copied() .collect::>(), + &E::Scalar::ZERO, ); let R = CE::::commit( &ck_L.combine(&ck_c), @@ -228,6 +229,7 @@ where .chain(iter::once(&c_R)) .copied() .collect::>(), + &E::Scalar::ZERO, ); transcript.absorb(b"L", &L); @@ -306,7 +308,7 @@ where let r = transcript.squeeze(b"r")?; let ck_c = ck_c.scale(&r); - let P = U.comm_a_vec + CE::::commit(&ck_c, &[U.c]); + let P = U.comm_a_vec + CE::::commit(&ck_c, &[U.c], &E::Scalar::ZERO); let batch_invert = |v: &[E::Scalar]| -> Result, NovaError> { let mut products = vec![E::Scalar::ZERO; v.len()]; @@ -372,7 +374,7 @@ where }; let ck_hat = { - let c = CE::::commit(&ck, &s); + let c = CE::::commit(&ck, &s, &E::Scalar::ZERO); CommitmentKey::::reinterpret_commitments_as_ck(&[c])? }; @@ -394,10 +396,17 @@ where .chain(iter::once(&E::Scalar::ONE)) .copied() .collect::>(), + &E::Scalar::ZERO, ) }; - if P_hat == CE::::commit(&ck_hat.combine(&ck_c), &[self.a_hat, self.a_hat * b_hat]) { + if P_hat + == CE::::commit( + &ck_hat.combine(&ck_c), + &[self.a_hat, self.a_hat * b_hat], + &E::Scalar::ZERO, + ) + { Ok(()) } else { Err(NovaError::InvalidPCS) diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 2b3060f4..bb9aeed5 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -177,28 +177,24 @@ where } } - fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment { + fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar], r: &E::Scalar) -> Self::Commitment { assert!(ck.ck.len() >= v.len()); - Commitment { - comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()]), - } - } - fn commit_with_blinding( - ck: &Self::CommitmentKey, - v: &[E::Scalar], - r: &E::Scalar, - ) -> Self::Commitment { - assert!(ck.ck.len() >= v.len()); - assert!(ck.h.is_some()); + if ck.h.is_some() { + let mut scalars: Vec = v.to_vec(); + scalars.push(*r); + let mut bases = ck.ck[..v.len()].to_vec(); + bases.push(ck.h.as_ref().unwrap().clone()); - let mut scalars: Vec = v.to_vec(); - scalars.push(*r); - let mut bases = ck.ck[..v.len()].to_vec(); - bases.push(ck.h.as_ref().unwrap().clone()); + Commitment { + comm: E::GE::vartime_multiscalar_mul(&scalars, &bases), + } + } else { + assert_eq!(*r, E::Scalar::ZERO); - Commitment { - comm: E::GE::vartime_multiscalar_mul(&scalars, &bases), + Commitment { + comm: E::GE::vartime_multiscalar_mul(v, &ck.ck[..v.len()]), + } } } @@ -323,7 +319,7 @@ where Ok(CommitmentKey { ck, h: None, // this is okay, since this method is used in IPA only, - // and we only use `commit` after, not `commit_with_blind` + // and we only use non-blinding commits afterwards // bc we don't use ZK IPA }) } diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index fe380723..d352436c 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -201,8 +201,8 @@ impl R1CSShape { // verify if comm_E and comm_W are commitments to E and W let res_comm = { let (comm_W, comm_E) = rayon::join( - || CE::::commit_with_blinding(ck, &W.W, &W.r_W), - || CE::::commit_with_blinding(ck, &W.E, &W.r_E), + || CE::::commit(ck, &W.W, &W.r_W), + || CE::::commit(ck, &W.E, &W.r_E), ); U.comm_W == comm_W && U.comm_E == comm_E }; @@ -236,7 +236,7 @@ impl R1CSShape { }; // verify if comm_W is a commitment to W - let res_comm = U.comm_W == CE::::commit_with_blinding(ck, &W.W, &W.r_W); + let res_comm = U.comm_W == CE::::commit(ck, &W.W, &W.r_W); if res_eq && res_comm { Ok(()) @@ -278,7 +278,7 @@ impl R1CSShape { .map(|(((az, bz), cz), e)| *az * *bz - u * *cz - *e) .collect::>(); - let comm_T = CE::::commit_with_blinding(ck, &T, r_T); + let comm_T = CE::::commit(ck, &T, r_T); Ok((T, comm_T)) } @@ -317,7 +317,7 @@ impl R1CSShape { .map(|((((az, bz), cz), e1), e2)| *az * *bz - u * *cz - *e1 - *e2) .collect::>(); - let comm_T = CE::::commit_with_blinding(ck, &T, r_T); + let comm_T = CE::::commit(ck, &T, r_T); Ok((T, comm_T)) } @@ -419,8 +419,8 @@ impl R1CSShape { .collect::>(); // compute commitments to W,E - let comm_W = CE::::commit_with_blinding(ck, &W, &r_W); - let comm_E = CE::::commit_with_blinding(ck, &E, &r_E); + let comm_W = CE::::commit(ck, &W, &r_W); + let comm_E = CE::::commit(ck, &E, &r_E); Ok(( RelaxedR1CSInstance { @@ -449,7 +449,7 @@ impl R1CSWitness { /// Commits to the witness using the supplied generators pub fn commit(&self, ck: &CommitmentKey) -> Commitment { - CE::::commit_with_blinding(ck, &self.W, &self.r_W) + CE::::commit(ck, &self.W, &self.r_W) } } @@ -504,8 +504,8 @@ impl RelaxedR1CSWitness { /// Commits to the witness using the supplied generators pub fn commit(&self, ck: &CommitmentKey) -> (Commitment, Commitment) { ( - CE::::commit_with_blinding(ck, &self.W, &self.r_W), - CE::::commit_with_blinding(ck, &self.E, &self.r_E), + CE::::commit(ck, &self.W, &self.r_W), + CE::::commit(ck, &self.E, &self.r_E), ) } diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 04f7a233..42b85d1e 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -185,7 +185,7 @@ impl R1CSShapeSparkRepr { &self.ts_col, ] .par_iter() - .map(|v| E::CE::commit(ck, v)) + .map(|v| E::CE::commit(ck, v, &E::Scalar::ZERO)) .collect(); R1CSShapeSparkCommitment { @@ -482,14 +482,14 @@ impl MemorySumcheckInstance { ) = rayon::join( || { rayon::join( - || E::CE::commit(ck, &t_plus_r_inv_row), - || E::CE::commit(ck, &w_plus_r_inv_row), + || E::CE::commit(ck, &t_plus_r_inv_row, &E::Scalar::ZERO), + || E::CE::commit(ck, &w_plus_r_inv_row, &E::Scalar::ZERO), ) }, || { rayon::join( - || E::CE::commit(ck, &t_plus_r_inv_col), - || E::CE::commit(ck, &w_plus_r_inv_col), + || E::CE::commit(ck, &t_plus_r_inv_col, &E::Scalar::ZERO), + || E::CE::commit(ck, &w_plus_r_inv_col, &E::Scalar::ZERO), ) }, ); @@ -1122,8 +1122,13 @@ impl> RelaxedR1CSSNARKTrait for Relax // commit to Az, Bz, Cz let (comm_Az, (comm_Bz, comm_Cz)) = rayon::join( - || E::CE::commit(ck, &Az), - || rayon::join(|| E::CE::commit(ck, &Bz), || E::CE::commit(ck, &Cz)), + || E::CE::commit(ck, &Az, &E::Scalar::ZERO), + || { + rayon::join( + || E::CE::commit(ck, &Bz, &E::Scalar::ZERO), + || E::CE::commit(ck, &Cz, &E::Scalar::ZERO), + ) + }, ); transcript.absorb(b"c", &[comm_Az, comm_Bz, comm_Cz].as_slice()); @@ -1155,8 +1160,10 @@ impl> RelaxedR1CSSNARKTrait for Relax // L_row(i) = eq(tau, row(i)) for all i // L_col(i) = z(col(i)) for all i let (mem_row, mem_col, L_row, L_col) = pk.S_repr.evaluation_oracles(&S, &tau, &z); - let (comm_L_row, comm_L_col) = - rayon::join(|| E::CE::commit(ck, &L_row), || E::CE::commit(ck, &L_col)); + let (comm_L_row, comm_L_col) = rayon::join( + || E::CE::commit(ck, &L_row, &E::Scalar::ZERO), + || E::CE::commit(ck, &L_col, &E::Scalar::ZERO), + ); // since all the three polynomials are opened at tau, // we can combine them into a single polynomial opened at tau diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index 683ac3a8..ffe52b1b 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -53,15 +53,8 @@ pub trait CommitmentEngineTrait: Clone + Send + Sync { /// Samples a new commitment key of a specified size fn setup(label: &'static [u8], blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey; - /// Commits to the provided vector using the provided generators - fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar]) -> Self::Commitment; - /// Commits to the provided vector using the provided generators and random blind - fn commit_with_blinding( - ck: &Self::CommitmentKey, - v: &[E::Scalar], - r: &E::Scalar, - ) -> Self::Commitment; + fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar], r: &E::Scalar) -> Self::Commitment; /// Remove given blind from commitment fn derandomize( From 2a5cee6f222c651511faa4ddbc4746de57c4e0d2 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Thu, 17 Oct 2024 15:28:10 -0400 Subject: [PATCH 19/21] clarity refactoring --- src/lib.rs | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 42287fbc..87cbf2b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -761,29 +761,29 @@ where S1: RelaxedR1CSSNARKTrait, S2: RelaxedR1CSSNARKTrait, { - r_U_primary: RelaxedR1CSInstance, - ri_primary: E1::Scalar, - r_W_snark_primary: S1, - r_U_secondary: RelaxedR1CSInstance, ri_secondary: E2::Scalar, l_u_secondary: R1CSInstance, nifs_Uf_secondary: NIFS, - f_W_snark_secondary: S2, - zn_primary: Vec, - zn_secondary: Vec, + l_ur_secondary: RelaxedR1CSInstance, + nifs_Un_secondary: NIFS, + + r_U_primary: RelaxedR1CSInstance, + ri_primary: E1::Scalar, + l_ur_primary: RelaxedR1CSInstance, + nifs_Un_primary: NIFS, primary_blind_r_W: E1::Scalar, primary_blind_r_E: E1::Scalar, secondary_blind_r_W: E2::Scalar, secondary_blind_r_E: E2::Scalar, - // randomizing layer - l_ur_primary: RelaxedR1CSInstance, - l_ur_secondary: RelaxedR1CSInstance, - nifs_Un_primary: NIFS, - nifs_Un_secondary: NIFS, + snark_primary: S1, + snark_secondary: S2, + + zn_primary: Vec, + zn_secondary: Vec, _p: PhantomData<(C1, C2)>, } @@ -872,28 +872,30 @@ where ); Ok(Self { - r_U_primary: recursive_snark.r_U_primary.clone(), - ri_primary: recursive_snark.ri_primary, - r_W_snark_primary: snark_primary?, - r_U_secondary: recursive_snark.r_U_secondary.clone(), ri_secondary: recursive_snark.ri_secondary, l_u_secondary: recursive_snark.l_u_secondary.clone(), nifs_Uf_secondary: random_layer.nifs_Uf_secondary.clone(), - f_W_snark_secondary: snark_secondary?, - zn_primary: recursive_snark.zi_primary.clone(), - zn_secondary: recursive_snark.zi_secondary.clone(), + l_ur_secondary: random_layer.l_ur_secondary.clone(), + nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), + + r_U_primary: recursive_snark.r_U_primary.clone(), + ri_primary: recursive_snark.ri_primary, + l_ur_primary: random_layer.l_ur_primary.clone(), + nifs_Un_primary: random_layer.nifs_Un_primary.clone(), primary_blind_r_W: random_layer.r_Wn_primary.r_W, primary_blind_r_E: random_layer.r_Wn_primary.r_E, secondary_blind_r_W: random_layer.r_Wn_secondary.r_W, secondary_blind_r_E: random_layer.r_Wn_secondary.r_E, - l_ur_primary: random_layer.l_ur_primary.clone(), - l_ur_secondary: random_layer.l_ur_secondary.clone(), - nifs_Un_primary: random_layer.nifs_Un_primary.clone(), - nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), + snark_primary: snark_primary?, + snark_secondary: snark_secondary?, + + zn_primary: recursive_snark.zi_primary.clone(), + zn_secondary: recursive_snark.zi_secondary.clone(), + _p: Default::default(), }) } @@ -1006,12 +1008,12 @@ where let (res_primary, res_secondary) = rayon::join( || { self - .r_W_snark_primary + .snark_primary .verify(&vk.vk_primary, &derandom_r_Un_primary) }, || { self - .f_W_snark_secondary + .snark_secondary .verify(&vk.vk_secondary, &derandom_r_Un_secondary) }, ); From fa59e2cccd78f18db1e28506626ace920a9b28a5 Mon Sep 17 00:00:00 2001 From: jkwoods Date: Fri, 18 Oct 2024 16:49:42 -0400 Subject: [PATCH 20/21] no blinding label, better derandom funcs, benchmark space --- benches/compressed-snark.rs | 2 +- src/lib.rs | 51 ++++++++++++--------- src/nifs.rs | 90 ++++++++++++++++++++++--------------- src/provider/hyperkzg.rs | 2 +- src/provider/ipa_pc.rs | 2 +- src/provider/pedersen.rs | 11 ++--- src/r1cs/mod.rs | 38 +++++++--------- src/spartan/direct.rs | 4 +- src/traits/commitment.rs | 2 +- 9 files changed, 110 insertions(+), 92 deletions(-) diff --git a/benches/compressed-snark.rs b/benches/compressed-snark.rs index fae15282..1a866d7d 100644 --- a/benches/compressed-snark.rs +++ b/benches/compressed-snark.rs @@ -43,7 +43,7 @@ cfg_if::cfg_if! { criterion_group! { name = compressed_snark; config = Criterion::default().warm_up_time(Duration::from_millis(3000)); - targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments, + targets = bench_compressed_snark, bench_compressed_snark_with_computational_commitments, } } } diff --git a/src/lib.rs b/src/lib.rs index 87cbf2b7..21fb051b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ use core::marker::PhantomData; use errors::NovaError; use ff::Field; use gadgets::utils::scalar_as_base; -use nifs::NIFS; +use nifs::{NIFSRelaxed, NIFS}; use r1cs::{ CommitmentKeyHint, R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness, }; @@ -271,8 +271,8 @@ where l_ur_primary: RelaxedR1CSInstance, l_ur_secondary: RelaxedR1CSInstance, nifs_Uf_secondary: NIFS, - nifs_Un_primary: NIFS, - nifs_Un_secondary: NIFS, + nifs_Un_primary: NIFSRelaxed, + nifs_Un_secondary: NIFSRelaxed, r_Wn_primary: RelaxedR1CSWitness, r_Wn_secondary: RelaxedR1CSWitness, // needed for CompressedSNARK proving, @@ -543,7 +543,7 @@ where .r1cs_shape_secondary .sample_random_instance_witness(&pp.ck_secondary)?; - let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFS::prove_relaxed( + let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove( &pp.ck_secondary, &pp.ro_consts_secondary, &scalar_as_base::(pp.digest()), @@ -559,7 +559,7 @@ where .r1cs_shape_primary .sample_random_instance_witness(&pp.ck_primary)?; - let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFS::prove_relaxed( + let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove( &pp.ck_primary, &pp.ro_consts_primary, &pp.digest(), @@ -767,12 +767,12 @@ where nifs_Uf_secondary: NIFS, l_ur_secondary: RelaxedR1CSInstance, - nifs_Un_secondary: NIFS, + nifs_Un_secondary: NIFSRelaxed, r_U_primary: RelaxedR1CSInstance, ri_primary: E1::Scalar, l_ur_primary: RelaxedR1CSInstance, - nifs_Un_primary: NIFS, + nifs_Un_primary: NIFSRelaxed, primary_blind_r_W: E1::Scalar, primary_blind_r_E: E1::Scalar, @@ -842,12 +842,21 @@ where let random_layer = recursive_snark.randomizing_fold(pp)?; // derandomize/unblind commitments - let (derandom_r_Un_primary, derandom_r_Wn_primary) = random_layer - .r_Un_primary - .derandomize_commits_and_witnesses(&pp.ck_primary, &random_layer.r_Wn_primary); - let (derandom_r_Un_secondary, derandom_r_Wn_secondary) = random_layer - .r_Un_secondary - .derandomize_commits_and_witnesses(&pp.ck_secondary, &random_layer.r_Wn_secondary); + let (derandom_r_Wn_primary, r_Wn_primary_blind_W, r_Wn_primary_blind_E) = + random_layer.r_Wn_primary.derandomize(); + let derandom_r_Un_primary = random_layer.r_Un_primary.derandomize( + &pp.ck_primary, + &r_Wn_primary_blind_W, + &r_Wn_primary_blind_E, + ); + + let (derandom_r_Wn_secondary, r_Wn_secondary_blind_W, r_Wn_secondary_blind_E) = + random_layer.r_Wn_secondary.derandomize(); + let derandom_r_Un_secondary = random_layer.r_Un_secondary.derandomize( + &pp.ck_secondary, + &r_Wn_secondary_blind_W, + &r_Wn_secondary_blind_E, + ); // create SNARKs proving the knowledge of Wn primary/secondary let (snark_primary, snark_secondary) = rayon::join( @@ -885,10 +894,10 @@ where l_ur_primary: random_layer.l_ur_primary.clone(), nifs_Un_primary: random_layer.nifs_Un_primary.clone(), - primary_blind_r_W: random_layer.r_Wn_primary.r_W, - primary_blind_r_E: random_layer.r_Wn_primary.r_E, - secondary_blind_r_W: random_layer.r_Wn_secondary.r_W, - secondary_blind_r_E: random_layer.r_Wn_secondary.r_E, + primary_blind_r_W: r_Wn_primary_blind_W, + primary_blind_r_E: r_Wn_primary_blind_E, + secondary_blind_r_W: r_Wn_secondary_blind_W, + secondary_blind_r_E: r_Wn_secondary_blind_E, snark_primary: snark_primary?, snark_secondary: snark_secondary?, @@ -976,7 +985,7 @@ where )?; // fold Uf/Wf with random inst/wit to get U1/W1 - let r_Un_secondary = self.nifs_Un_secondary.verify_relaxed( + let r_Un_secondary = self.nifs_Un_secondary.verify( &vk.ro_consts_secondary, &scalar_as_base::(vk.pp_digest), &r_Uf_secondary, @@ -984,7 +993,7 @@ where )?; // fold primary U/W with random inst/wit to get U2/W2 - let r_Un_primary = self.nifs_Un_primary.verify_relaxed( + let r_Un_primary = self.nifs_Un_primary.verify( &vk.ro_consts_primary, &vk.pp_digest, &self.r_U_primary, @@ -992,12 +1001,12 @@ where )?; // derandomize/unblind commitments - let derandom_r_Un_primary = r_Un_primary.derandomize_commits( + let derandom_r_Un_primary = r_Un_primary.derandomize( &vk.vk_primary_commitment_key, &self.primary_blind_r_W, &self.primary_blind_r_E, ); - let derandom_r_Un_secondary = r_Un_secondary.derandomize_commits( + let derandom_r_Un_secondary = r_Un_secondary.derandomize( &vk.vk_secondary_commitment_key, &self.secondary_blind_r_W, &self.secondary_blind_r_E, diff --git a/src/nifs.rs b/src/nifs.rs index 93ce7416..9a8c4d99 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -76,8 +76,52 @@ impl NIFS { Ok((Self { comm_T }, (U, W))) } + /// Takes as input a relaxed R1CS instance `U1` and R1CS instance `U2` + /// with the same shape and defined with respect to the same parameters, + /// and outputs a folded instance `U` with the same shape, + /// with the guarantee that the folded instance `U` + /// if and only if `U1` and `U2` are satisfiable. + pub fn verify( + &self, + ro_consts: &ROConstants, + pp_digest: &E::Scalar, + U1: &RelaxedR1CSInstance, + U2: &R1CSInstance, + ) -> Result, NovaError> { + // initialize a new RO + let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); + + // append the digest of pp to the transcript + ro.absorb(scalar_as_base::(*pp_digest)); + + // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) + U2.absorb_in_ro(&mut ro); + + // append `comm_T` to the transcript and obtain a challenge + self.comm_T.absorb_in_ro(&mut ro); + + // compute a challenge from the RO + let r = ro.squeeze(NUM_CHALLENGE_BITS); + + // fold the instance using `r` and `comm_T` + let U = U1.fold(U2, &self.comm_T, &r); + + // return the folded instance + Ok(U) + } +} + +/// A SNARK that holds the proof of a step of an incremental computation +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct NIFSRelaxed { + pub(crate) comm_T: Commitment, +} + +impl NIFSRelaxed { /// Same as `prove`, but takes two Relaxed R1CS Instance/Witness pairs - pub fn prove_relaxed( + pub fn prove( ck: &CommitmentKey, ro_consts: &ROConstants, pp_digest: &E::Scalar, @@ -86,7 +130,13 @@ impl NIFS { W1: &RelaxedR1CSWitness, U2: &RelaxedR1CSInstance, W2: &RelaxedR1CSWitness, - ) -> Result<(NIFS, (RelaxedR1CSInstance, RelaxedR1CSWitness)), NovaError> { + ) -> Result< + ( + NIFSRelaxed, + (RelaxedR1CSInstance, RelaxedR1CSWitness), + ), + NovaError, + > { // initialize a new RO let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO_RELAXED); @@ -121,42 +171,8 @@ impl NIFS { Ok((Self { comm_T }, (U, W))) } - /// Takes as input a relaxed R1CS instance `U1` and R1CS instance `U2` - /// with the same shape and defined with respect to the same parameters, - /// and outputs a folded instance `U` with the same shape, - /// with the guarantee that the folded instance `U` - /// if and only if `U1` and `U2` are satisfiable. + /// Same as `verify`, but takes two Relaxed R1CS Instance/Witness pairs pub fn verify( - &self, - ro_consts: &ROConstants, - pp_digest: &E::Scalar, - U1: &RelaxedR1CSInstance, - U2: &R1CSInstance, - ) -> Result, NovaError> { - // initialize a new RO - let mut ro = E::RO::new(ro_consts.clone(), NUM_FE_FOR_RO); - - // append the digest of pp to the transcript - ro.absorb(scalar_as_base::(*pp_digest)); - - // append U2 to transcript, U1 does not need to absorbed since U2.X[0] = Hash(params, U1, i, z0, zi) - U2.absorb_in_ro(&mut ro); - - // append `comm_T` to the transcript and obtain a challenge - self.comm_T.absorb_in_ro(&mut ro); - - // compute a challenge from the RO - let r = ro.squeeze(NUM_CHALLENGE_BITS); - - // fold the instance using `r` and `comm_T` - let U = U1.fold(U2, &self.comm_T, &r); - - // return the folded instance - Ok(U) - } - - /// Same as `prove`, but takes two Relaxed R1CS Instance/Witness pairs - pub fn verify_relaxed( &self, ro_consts: &ROConstants, pp_digest: &E::Scalar, diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 0123e5cd..349fd5a2 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -185,7 +185,7 @@ where type Commitment = Commitment; type CommitmentKey = CommitmentKey; - fn setup(_label: &'static [u8], _blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey { + fn setup(_label: &'static [u8], n: usize) -> Self::CommitmentKey { // NOTE: this is for testing purposes and should not be used in production // TODO: we need to decide how to generate load/store parameters let tau = E::Scalar::random(OsRng); diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 8be62aed..63680dca 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -49,7 +49,7 @@ where fn setup( ck: &<::CE as CommitmentEngineTrait>::CommitmentKey, ) -> (Self::ProverKey, Self::VerifierKey) { - let ck_c = E::CE::setup(b"ipa", b"no blind", 1); + let ck_c = E::CE::setup(b"ipa", 1); let pk = ProverKey { ck_s: ck_c.clone() }; let vk = VerifierKey { diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index bb9aeed5..640652ea 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -167,13 +167,14 @@ where type CommitmentKey = CommitmentKey; type Commitment = Commitment; - fn setup(label: &'static [u8], blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey { - let blinding = E::GE::from_label(blinding_label, 1); - let h = blinding.first().unwrap().clone(); + fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey { + let gens = E::GE::from_label(label, n.next_power_of_two() + 1); + + let (h, ck) = gens.split_first().unwrap(); Self::CommitmentKey { - ck: E::GE::from_label(label, n.next_power_of_two()), - h: Some(h), + ck: ck.to_vec(), + h: Some(h.clone()), } } diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index d352436c..ab67c5b9 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -96,7 +96,7 @@ impl R1CS { let num_cons = S.num_cons; let num_vars = S.num_vars; let ck_hint = ck_floor(S); - E::CE::setup(b"ck", b"ck blinding", max(max(num_cons, num_vars), ck_hint)) + E::CE::setup(b"ck", max(max(num_cons, num_vars), ck_hint)) } } @@ -590,6 +590,19 @@ impl RelaxedR1CSWitness { r_E: self.r_E, } } + + pub fn derandomize(&self) -> (Self, E::Scalar, E::Scalar) { + ( + RelaxedR1CSWitness { + W: self.W.clone(), + r_W: E::Scalar::ZERO, + E: self.E.clone(), + r_E: E::Scalar::ZERO, + }, + self.r_W.clone(), + self.r_E.clone(), + ) + } } impl RelaxedR1CSInstance { @@ -689,7 +702,7 @@ impl RelaxedR1CSInstance { } } - pub fn derandomize_commits( + pub fn derandomize( &self, ck: &CommitmentKey, r_W: &E::Scalar, @@ -702,27 +715,6 @@ impl RelaxedR1CSInstance { u: self.u, } } - - pub fn derandomize_commits_and_witnesses( - &self, - ck: &CommitmentKey, - wit: &RelaxedR1CSWitness, - ) -> (RelaxedR1CSInstance, RelaxedR1CSWitness) { - ( - RelaxedR1CSInstance { - comm_W: CE::::derandomize(ck, &self.comm_W, &wit.r_W), - comm_E: CE::::derandomize(ck, &self.comm_E, &wit.r_E), - X: self.X.clone(), - u: self.u, - }, - RelaxedR1CSWitness { - W: wit.W.clone(), - r_W: E::Scalar::ZERO, - E: wit.E.clone(), - r_E: E::Scalar::ZERO, - }, - ) - } } impl TranscriptReprTrait for RelaxedR1CSInstance { diff --git a/src/spartan/direct.rs b/src/spartan/direct.rs index 71e215d0..0f5da7d0 100644 --- a/src/spartan/direct.rs +++ b/src/spartan/direct.rs @@ -148,8 +148,8 @@ impl, C: StepCircuit> DirectSN ); // derandomize/unblind commitments - let (derandom_u_relaxed, derandom_w_relaxed) = - u_relaxed.derandomize_commits_and_witnesses(&pk.ck, &w_relaxed); + let (derandom_w_relaxed, blind_W, blind_E) = w_relaxed.derandomize(); + let derandom_u_relaxed = u_relaxed.derandomize(&pk.ck, &blind_W, &blind_E); // prove the instance using Spartan let snark = S::prove( diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index ffe52b1b..8937a3d7 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -51,7 +51,7 @@ pub trait CommitmentEngineTrait: Clone + Send + Sync { type Commitment: CommitmentTrait; /// Samples a new commitment key of a specified size - fn setup(label: &'static [u8], blinding_label: &'static [u8], n: usize) -> Self::CommitmentKey; + fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey; /// Commits to the provided vector using the provided generators and random blind fn commit(ck: &Self::CommitmentKey, v: &[E::Scalar], r: &E::Scalar) -> Self::Commitment; From 6dc90f6d8f0f146825c9152d857baaa33fca3d8c Mon Sep 17 00:00:00 2001 From: jkwoods Date: Mon, 21 Oct 2024 21:25:30 -0400 Subject: [PATCH 21/21] inline randomizing inside CompressedSNARK, clippy, tests --- src/lib.rs | 172 +++++++++++++-------------------------- src/nifs.rs | 13 +-- src/provider/hyperkzg.rs | 6 +- src/r1cs/mod.rs | 4 +- 4 files changed, 70 insertions(+), 125 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 21fb051b..aa157d19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -259,28 +259,6 @@ where _p: PhantomData<(C1, C2)>, } -/// Final randomized fold of `RecursiveSNARK` -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(bound = "")] -pub struct RandomLayerRecursive -where - E1: Engine::Scalar>, - E2: Engine::Scalar>, -{ - last_i: usize, - l_ur_primary: RelaxedR1CSInstance, - l_ur_secondary: RelaxedR1CSInstance, - nifs_Uf_secondary: NIFS, - nifs_Un_primary: NIFSRelaxed, - nifs_Un_secondary: NIFSRelaxed, - r_Wn_primary: RelaxedR1CSWitness, - r_Wn_secondary: RelaxedR1CSWitness, - // needed for CompressedSNARK proving, - // but not technically part of RecursiveSNARK proof - r_Un_primary: RelaxedR1CSInstance, - r_Un_secondary: RelaxedR1CSInstance, -} - impl RecursiveSNARK where E1: Engine::Scalar>, @@ -515,81 +493,7 @@ where Ok(()) } - /// add a randomizing fold to a `RecursiveSNARK` - fn randomizing_fold( - &self, - pp: &PublicParams, - ) -> Result, NovaError> { - if self.i == 0 { - // don't call this until we have something to randomize - return Err(NovaError::InvalidNumSteps); - } - let last_i = self.i; - - // fold secondary U/W with secondary u/w to get Uf/Wf - let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = NIFS::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &self.r_U_secondary, - &self.r_W_secondary, - &self.l_u_secondary, - &self.l_w_secondary, - )?; - - // fold Uf/Wf with random inst/wit to get U1/W1 - let (l_ur_secondary, l_wr_secondary) = pp - .r1cs_shape_secondary - .sample_random_instance_witness(&pp.ck_secondary)?; - - let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove( - &pp.ck_secondary, - &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest()), - &pp.r1cs_shape_secondary, - &r_Uf_secondary, - &r_Wf_secondary, - &l_ur_secondary, - &l_wr_secondary, - )?; - - // fold primary U/W with random inst/wit to get U2/W2 - let (l_ur_primary, l_wr_primary) = pp - .r1cs_shape_primary - .sample_random_instance_witness(&pp.ck_primary)?; - - let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove( - &pp.ck_primary, - &pp.ro_consts_primary, - &pp.digest(), - &pp.r1cs_shape_primary, - &self.r_U_primary, - &self.r_W_primary, - &l_ur_primary, - &l_wr_primary, - )?; - - // output randomized IVC Proof - Ok(RandomLayerRecursive { - last_i, - // random istances - l_ur_primary, - l_ur_secondary, - // commitments to cross terms - nifs_Uf_secondary, - nifs_Un_primary, - nifs_Un_secondary, - // witnesses - r_Wn_primary, - r_Wn_secondary, - // needed for CompressedSNARK proving - r_Un_primary, - r_Un_secondary, - }) - } - - /// Verify the correctness of the `RecursiveSNARK` (no randomizing layer) + /// Verify the correctness of the `RecursiveSNARK` pub fn verify( &self, pp: &PublicParams, @@ -839,20 +743,60 @@ where recursive_snark: &RecursiveSNARK, ) -> Result { // prove three foldings - let random_layer = recursive_snark.randomizing_fold(pp)?; + + // fold secondary U/W with secondary u/w to get Uf/Wf + let (nifs_Uf_secondary, (r_Uf_secondary, r_Wf_secondary)) = 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, + &recursive_snark.l_u_secondary, + &recursive_snark.l_w_secondary, + )?; + + // fold Uf/Wf with random inst/wit to get U1/W1 + let (l_ur_secondary, l_wr_secondary) = pp + .r1cs_shape_secondary + .sample_random_instance_witness(&pp.ck_secondary)?; + + let (nifs_Un_secondary, (r_Un_secondary, r_Wn_secondary)) = NIFSRelaxed::prove( + &pp.ck_secondary, + &pp.ro_consts_secondary, + &scalar_as_base::(pp.digest()), + &pp.r1cs_shape_secondary, + &r_Uf_secondary, + &r_Wf_secondary, + &l_ur_secondary, + &l_wr_secondary, + )?; + + // fold primary U/W with random inst/wit to get U2/W2 + let (l_ur_primary, l_wr_primary) = pp + .r1cs_shape_primary + .sample_random_instance_witness(&pp.ck_primary)?; + + let (nifs_Un_primary, (r_Un_primary, r_Wn_primary)) = NIFSRelaxed::prove( + &pp.ck_primary, + &pp.ro_consts_primary, + &pp.digest(), + &pp.r1cs_shape_primary, + &recursive_snark.r_U_primary, + &recursive_snark.r_W_primary, + &l_ur_primary, + &l_wr_primary, + )?; // derandomize/unblind commitments let (derandom_r_Wn_primary, r_Wn_primary_blind_W, r_Wn_primary_blind_E) = - random_layer.r_Wn_primary.derandomize(); - let derandom_r_Un_primary = random_layer.r_Un_primary.derandomize( - &pp.ck_primary, - &r_Wn_primary_blind_W, - &r_Wn_primary_blind_E, - ); + r_Wn_primary.derandomize(); + let derandom_r_Un_primary = + r_Un_primary.derandomize(&pp.ck_primary, &r_Wn_primary_blind_W, &r_Wn_primary_blind_E); let (derandom_r_Wn_secondary, r_Wn_secondary_blind_W, r_Wn_secondary_blind_E) = - random_layer.r_Wn_secondary.derandomize(); - let derandom_r_Un_secondary = random_layer.r_Un_secondary.derandomize( + r_Wn_secondary.derandomize(); + let derandom_r_Un_secondary = r_Un_secondary.derandomize( &pp.ck_secondary, &r_Wn_secondary_blind_W, &r_Wn_secondary_blind_E, @@ -884,15 +828,15 @@ where r_U_secondary: recursive_snark.r_U_secondary.clone(), ri_secondary: recursive_snark.ri_secondary, l_u_secondary: recursive_snark.l_u_secondary.clone(), - nifs_Uf_secondary: random_layer.nifs_Uf_secondary.clone(), + nifs_Uf_secondary: nifs_Uf_secondary.clone(), - l_ur_secondary: random_layer.l_ur_secondary.clone(), - nifs_Un_secondary: random_layer.nifs_Un_secondary.clone(), + l_ur_secondary: l_ur_secondary.clone(), + nifs_Un_secondary: nifs_Un_secondary.clone(), r_U_primary: recursive_snark.r_U_primary.clone(), ri_primary: recursive_snark.ri_primary, - l_ur_primary: random_layer.l_ur_primary.clone(), - nifs_Un_primary: random_layer.nifs_Un_primary.clone(), + l_ur_primary: l_ur_primary.clone(), + nifs_Un_primary: nifs_Un_primary.clone(), primary_blind_r_W: r_Wn_primary_blind_W, primary_blind_r_E: r_Wn_primary_blind_E, @@ -1140,19 +1084,19 @@ mod tests { test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["cfd726c95effd42b68bb22dda539eaefe4483601e207aa92f4970e7d6d215702"], + &expect!["ba7ff40bc60f95f7157350608b2f1892dc33b2470ccf52c3fae0464c61db9501"], ); test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["c90a30a806b9b320bc7777cfadc21a9d133c196d9ab50d2237c5f15edcbd3a03"], + &expect!["e0d75ecff901aee5b22223a4be82af30d7988a5f2cbd40815fda88dd79a22a01"], ); test_pp_digest_with::( &TrivialCircuit::<_>::default(), &TrivialCircuit::<_>::default(), - &expect!["88fc7d72e9a6e6e872bd08135ecbdbb13f4b829976e026c81f8feae89cf57802"], + &expect!["ee4bd444ffe1f1be8224a09dae09bdf4532035655fd3f25e70955eaa13c48d03"], ); } diff --git a/src/nifs.rs b/src/nifs.rs index 9a8c4d99..4209ea62 100644 --- a/src/nifs.rs +++ b/src/nifs.rs @@ -369,14 +369,14 @@ mod tests { let mut running_U = RelaxedR1CSInstance::default(ck, shape); // produce a step SNARK with (W1, U1) as the first incoming witness-instance pair - let res = NIFS::prove_relaxed( + let res = NIFSRelaxed::prove( ck, ro_consts, pp_digest, shape, &running_U, &running_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_relaxed(ro_consts, pp_digest, &running_U, U1); + let res = nifs.verify(ro_consts, pp_digest, &running_U, U1); assert!(res.is_ok()); let U = res.unwrap(); @@ -387,14 +387,14 @@ mod tests { running_U = U; // produce a step SNARK with (W2, U2) as the second incoming witness-instance pair - let res = NIFS::prove_relaxed( + let res = NIFSRelaxed::prove( ck, ro_consts, pp_digest, shape, &running_U, &running_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_relaxed(ro_consts, pp_digest, &running_U, U2); + let res = nifs.verify(ro_consts, pp_digest, &running_U, U2); assert!(res.is_ok()); let U = res.unwrap(); @@ -414,8 +414,9 @@ mod tests { let (ck, S, final_U, final_W) = test_tiny_r1cs_relaxed_with::(); assert!(S.is_sat_relaxed(&ck, &final_U, &final_W).is_ok()); - let (derandom_final_U, derandom_final_W) = - final_U.derandomize_commits_and_witnesses(&ck, &final_W); + let (derandom_final_W, final_blind_W, final_blind_E) = final_W.derandomize(); + let derandom_final_U = final_U.derandomize(&ck, &final_blind_W, &final_blind_E); + assert!(S .is_sat_relaxed(&ck, &derandom_final_U, &derandom_final_W) .is_ok()); diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index 349fd5a2..9017ff0f 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -650,7 +650,7 @@ mod tests { fn test_hyperkzg_eval() { // Test with poly(X1, X2) = 1 + X1 + X2 + X1*X2 let n = 4; - let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); let (pk, vk): (ProverKey, VerifierKey) = EvaluationEngine::setup(&ck); // poly is in eval. representation; evaluated at [(0,0), (0,1), (1,0), (1,1)] @@ -710,7 +710,7 @@ mod tests { // eval = 28 let eval = Fr::from(28); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment @@ -768,7 +768,7 @@ mod tests { let point = (0..ell).map(|_| Fr::random(&mut rng)).collect::>(); let eval = MultilinearPolynomial::evaluate_with(&poly, &point); - let ck: CommitmentKey = CommitmentEngine::setup(b"test", b"test blind", n); + let ck: CommitmentKey = CommitmentEngine::setup(b"test", n); let (pk, vk) = EvaluationEngine::setup(&ck); // make a commitment diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index ab67c5b9..0232ed0d 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -599,8 +599,8 @@ impl RelaxedR1CSWitness { E: self.E.clone(), r_E: E::Scalar::ZERO, }, - self.r_W.clone(), - self.r_E.clone(), + self.r_W, + self.r_E, ) } }