diff --git a/.gitignore b/.gitignore index d38766b0..d3067b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ solidity-verifiers/generated examples/*.sol examples/*.calldata examples/*.inputs +*.serialized +*/*.serialized diff --git a/examples/circom_full_flow.rs b/examples/circom_full_flow.rs index 10a79ba2..dccf0d8e 100644 --- a/examples/circom_full_flow.rs +++ b/examples/circom_full_flow.rs @@ -89,7 +89,8 @@ fn main() { let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap(); + let (decider_pp, decider_vp) = + D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap(); // run n steps of the folding iteration for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { @@ -99,6 +100,14 @@ fn main() { println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } + // verify the last IVC proof + let ivc_proof = nova.ivc_proof(); + N::verify( + nova_params.1, // Nova's verifier params + ivc_proof, + ) + .unwrap(); + let start = Instant::now(); let proof = D::prove(rng, decider_pp, nova.clone()).unwrap(); println!("generated Decider proof: {:?}", start.elapsed()); diff --git a/examples/external_inputs.rs b/examples/external_inputs.rs index 2f708091..f4a6af1f 100644 --- a/examples/external_inputs.rs +++ b/examples/external_inputs.rs @@ -207,17 +207,11 @@ fn main() { folding_scheme.state() ); - let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances(); - println!("Run the Nova's IVC verifier"); + let ivc_proof = folding_scheme.ivc_proof(); N::verify( - nova_params.1, - initial_state.clone(), - folding_scheme.state(), // latest state - Fr::from(num_steps as u32), - running_instance, - incoming_instance, - cyclefold_instance, + nova_params.1, // Nova's verifier params + ivc_proof, ) .unwrap(); } diff --git a/examples/multi_inputs.rs b/examples/multi_inputs.rs index f7819525..a337c894 100644 --- a/examples/multi_inputs.rs +++ b/examples/multi_inputs.rs @@ -154,17 +154,11 @@ fn main() { println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } - let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances(); - println!("Run the Nova's IVC verifier"); + let ivc_proof = folding_scheme.ivc_proof(); N::verify( - nova_params.1, - initial_state.clone(), - folding_scheme.state(), // latest state - Fr::from(num_steps as u32), - running_instance, - incoming_instance, - cyclefold_instance, + nova_params.1, // Nova's verifier params + ivc_proof, ) .unwrap(); } diff --git a/examples/noir_full_flow.rs b/examples/noir_full_flow.rs index ec5107a9..64531098 100644 --- a/examples/noir_full_flow.rs +++ b/examples/noir_full_flow.rs @@ -79,7 +79,8 @@ fn main() { let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap(); + let (decider_pp, decider_vp) = + D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap(); // run n steps of the folding iteration for i in 0..5 { @@ -87,6 +88,13 @@ fn main() { nova.prove_step(rng, vec![], None).unwrap(); println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } + // verify the last IVC proof + let ivc_proof = nova.ivc_proof(); + N::verify( + nova_params.1, // Nova's verifier params + ivc_proof, + ) + .unwrap(); let start = Instant::now(); let proof = D::prove(rng, decider_pp, nova.clone()).unwrap(); diff --git a/examples/noname_full_flow.rs b/examples/noname_full_flow.rs index 73a1a673..00dccbfb 100644 --- a/examples/noname_full_flow.rs +++ b/examples/noname_full_flow.rs @@ -89,7 +89,8 @@ fn main() { let mut nova = N::init(&nova_params, f_circuit.clone(), z_0).unwrap(); // prepare the Decider prover & verifier params - let (decider_pp, decider_vp) = D::preprocess(&mut rng, nova_params, nova.clone()).unwrap(); + let (decider_pp, decider_vp) = + D::preprocess(&mut rng, nova_params.clone(), nova.clone()).unwrap(); // run n steps of the folding iteration for (i, external_inputs_at_step) in external_inputs.iter().enumerate() { @@ -99,6 +100,14 @@ fn main() { println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } + // verify the last IVC proof + let ivc_proof = nova.ivc_proof(); + N::verify( + nova_params.1, // Nova's verifier params + ivc_proof, + ) + .unwrap(); + let start = Instant::now(); let proof = D::prove(rng, decider_pp, nova.clone()).unwrap(); println!("generated Decider proof: {:?}", start.elapsed()); diff --git a/examples/sha256.rs b/examples/sha256.rs index 705dcb8d..d974d650 100644 --- a/examples/sha256.rs +++ b/examples/sha256.rs @@ -138,17 +138,11 @@ fn main() { println!("Nova::prove_step {}: {:?}", i, start.elapsed()); } - let (running_instance, incoming_instance, cyclefold_instance) = folding_scheme.instances(); - println!("Run the Nova's IVC verifier"); + let ivc_proof = folding_scheme.ivc_proof(); N::verify( - nova_params.1, - initial_state, - folding_scheme.state(), // latest state - Fr::from(num_steps as u32), - running_instance, - incoming_instance, - cyclefold_instance, + nova_params.1, // Nova's verifier params + ivc_proof, ) .unwrap(); } diff --git a/folding-schemes/src/folding/hypernova/cccs.rs b/folding-schemes/src/folding/hypernova/cccs.rs index 5378a639..9e42bd9d 100644 --- a/folding-schemes/src/folding/hypernova/cccs.rs +++ b/folding-schemes/src/folding/hypernova/cccs.rs @@ -18,7 +18,7 @@ use crate::utils::virtual_polynomial::{build_eq_x_r_vec, VirtualPolynomial}; use crate::Error; /// Committed CCS instance -#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct CCCS { // Commitment to witness pub C: C, diff --git a/folding-schemes/src/folding/hypernova/decider_eth.rs b/folding-schemes/src/folding/hypernova/decider_eth.rs index f897efff..f6b01188 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth.rs @@ -234,9 +234,7 @@ pub mod tests { use super::*; use crate::commitment::{kzg::KZG, pedersen::Pedersen}; use crate::folding::hypernova::cccs::CCCS; - use crate::folding::hypernova::{ - PreprocessorParam, ProverParams, VerifierParams as HyperNovaVerifierParams, - }; + use crate::folding::hypernova::PreprocessorParam; use crate::folding::nova::decider_eth::VerifierParam; use crate::frontend::utils::CubicFCircuit; use crate::transcript::poseidon::poseidon_canonical_config; @@ -371,33 +369,19 @@ pub mod tests { .serialize_compressed(&mut hypernova_vp_serialized) .unwrap(); - let hypernova_pp_deserialized = ProverParams::< - Projective, - Projective2, - KZG<'static, Bn254>, - Pedersen, - false, - >::deserialize_prover_params( + let hypernova_pp_deserialized = HN::pp_deserialize_with_mode( hypernova_pp_serialized.as_slice(), Compress::Yes, Validate::No, - &hypernova_params.0.ccs, - &poseidon_config, + (), // FCircuit's Params ) .unwrap(); - let hypernova_vp_deserialized = HyperNovaVerifierParams::< - Projective, - Projective2, - KZG<'static, Bn254>, - Pedersen, - false, - >::deserialize_verifier_params( + let hypernova_vp_deserialized = HN::vp_deserialize_with_mode( hypernova_vp_serialized.as_slice(), Compress::Yes, Validate::No, - &hypernova_params.0.ccs.unwrap(), - &poseidon_config, + (), // FCircuit's Params ) .unwrap(); diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index 1fdad883..256b0cb0 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -509,7 +509,6 @@ pub mod tests { use ark_bn254::{constraints::GVar, Fr, G1Projective as Projective}; use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; use ark_relations::r1cs::ConstraintSystem; - use ark_std::One; use ark_std::{test_rng, UniformRand}; use super::*; @@ -583,22 +582,10 @@ pub mod tests { // generate a Nova instance and do a step of it let mut hypernova = HN::init(&hn_params, F_circuit, z_0.clone()).unwrap(); - hypernova - .prove_step(&mut rng, vec![], Some((vec![], vec![]))) - .unwrap(); + hypernova.prove_step(&mut rng, vec![], None).unwrap(); - let ivc_v = hypernova.clone(); - let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances(); - HN::verify( - hn_params.1, // HN's verifier_params - z_0, - ivc_v.z_i, - Fr::one(), - running_instance, - incoming_instance, - cyclefold_instance, - ) - .unwrap(); + let ivc_proof = hypernova.ivc_proof(); + HN::verify(hn_params.1, ivc_proof).unwrap(); // load the DeciderEthCircuit from the generated Nova instance let decider_circuit = DeciderEthCircuit::< diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index f476b211..2034b792 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -6,7 +6,7 @@ use ark_crypto_primitives::sponge::{ use ark_ec::{CurveGroup, Group}; use ark_ff::{BigInteger, PrimeField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError}; use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero}; pub mod cccs; @@ -15,7 +15,6 @@ pub mod decider_eth; pub mod decider_eth_circuit; pub mod lcccs; pub mod nimfs; -pub mod serialize; pub mod utils; use cccs::CCCS; @@ -38,6 +37,7 @@ use crate::folding::{ traits::{CommittedInstanceOps, Dummy, WitnessOps}, }; use crate::frontend::FCircuit; +use crate::transcript::poseidon::poseidon_canonical_config; use crate::utils::{get_cm_coordinates, pp_hash}; use crate::Error; use crate::{ @@ -117,6 +117,28 @@ where pub ccs: Option>, } +impl< + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, + const H: bool, + > CanonicalSerialize for ProverParams +{ + fn serialize_with_mode( + &self, + mut writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + self.cs_pp.serialize_with_mode(&mut writer, compress)?; + self.cf_cs_pp.serialize_with_mode(&mut writer, compress) + } + + fn serialized_size(&self, compress: Compress) -> usize { + self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress) + } +} + /// Verification parameters for HyperNova-based IVC #[derive(Debug, Clone)] pub struct VerifierParams< @@ -138,6 +160,27 @@ pub struct VerifierParams< pub cf_cs_vp: CS2::VerifierParams, } +impl CanonicalSerialize for VerifierParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn serialize_with_mode( + &self, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), ark_serialize::SerializationError> { + self.cs_vp.serialize_with_mode(&mut writer, compress)?; + self.cf_cs_vp.serialize_with_mode(&mut writer, compress) + } + + fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { + self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) + } +} + impl VerifierParams where C1: CurveGroup, @@ -157,6 +200,23 @@ where } } +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct IVCProof +where + C1: CurveGroup, + C2: CurveGroup, +{ + pub i: C1::ScalarField, + pub z_0: Vec, + pub z_i: Vec, + pub W_i: Witness, + pub U_i: LCCCS, + pub w_i: Witness, + pub u_i: CCCS, + pub cf_W_i: CycleFoldWitness, + pub cf_U_i: CycleFoldCommittedInstance, +} + /// Implements HyperNova+CycleFold's IVC, described in /// [HyperNova](https://eprint.iacr.org/2023/573.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait @@ -419,6 +479,74 @@ where type MultiCommittedInstanceWithWitness = (Vec, Vec); type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); + type IVCProof = IVCProof; + + fn pp_deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, + ) -> Result { + let poseidon_config = poseidon_canonical_config::(); + + // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing + // to serialize them, saving significant space in the VerifierParams serialized size. + + // main circuit R1CS: + let f_circuit = FC::new(fc_params)?; + let augmented_F_circuit = AugmentedFCircuit::::empty( + &poseidon_config, + f_circuit.clone(), + None, + )?; + let ccs = augmented_F_circuit.ccs; + + let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; + let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; + + Ok(ProverParams { + poseidon_config, + cs_pp, + cf_cs_pp, + ccs: Some(ccs), + }) + } + + fn vp_deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, + ) -> Result { + let poseidon_config = poseidon_canonical_config::(); + + // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing + // to serialize them, saving significant space in the VerifierParams serialized size. + + // main circuit R1CS: + let f_circuit = FC::new(fc_params)?; + let augmented_F_circuit = AugmentedFCircuit::::empty( + &poseidon_config, + f_circuit.clone(), + None, + )?; + let ccs = augmented_F_circuit.ccs; + + // CycleFold circuit R1CS + let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; + + let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + + Ok(VerifierParams { + poseidon_config, + ccs, + cf_r1cs, + cs_vp, + cf_cs_vp, + }) + } fn preprocess( mut rng: impl RngCore, @@ -566,36 +694,42 @@ where // `sponge` is for digest computation. let sponge = PoseidonSponge::::new(&self.poseidon_config); - let other_instances = other_instances.ok_or(Error::MissingOtherInstances)?; - - #[allow(clippy::type_complexity)] - let (lcccs, cccs): ( - Vec<(LCCCS, Witness)>, - Vec<(CCCS, Witness)>, - ) = other_instances; - - // recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the - // running and incoming instances that are not part of the 'other_instances', hence the +1 - // in the couple of following checks. - if lcccs.len() + 1 != MU { - return Err(Error::NotSameLength( - "other_instances.lcccs.len()".to_string(), - lcccs.len(), - "hypernova.mu".to_string(), - MU, - )); - } - if cccs.len() + 1 != NU { - return Err(Error::NotSameLength( - "other_instances.cccs.len()".to_string(), - cccs.len(), - "hypernova.nu".to_string(), - NU, - )); - } + let (Us, Ws, us, ws) = if MU > 1 || NU > 1 { + let other_instances = other_instances.ok_or(Error::MissingOtherInstances(MU, NU))?; + + #[allow(clippy::type_complexity)] + let (lcccs, cccs): ( + Vec<(LCCCS, Witness)>, + Vec<(CCCS, Witness)>, + ) = other_instances; + + // recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the + // running and incoming instances that are not part of the 'other_instances', hence the +1 + // in the couple of following checks. + if lcccs.len() + 1 != MU { + return Err(Error::NotSameLength( + "other_instances.lcccs.len()".to_string(), + lcccs.len(), + "hypernova.mu".to_string(), + MU, + )); + } + if cccs.len() + 1 != NU { + return Err(Error::NotSameLength( + "other_instances.cccs.len()".to_string(), + cccs.len(), + "hypernova.nu".to_string(), + NU, + )); + } - let (Us, Ws): (Vec>, Vec>) = lcccs.into_iter().unzip(); - let (us, ws): (Vec>, Vec>) = cccs.into_iter().unzip(); + let (Us, Ws): (Vec>, Vec>) = + lcccs.into_iter().unzip(); + let (us, ws): (Vec>, Vec>) = cccs.into_iter().unzip(); + (Some(Us), Some(Ws), Some(us), Some(ws)) + } else { + (None, None, None, None) + }; let augmented_f_circuit: AugmentedFCircuit; @@ -673,9 +807,9 @@ where z_i: Some(self.z_i.clone()), external_inputs: Some(external_inputs.clone()), U_i: Some(self.U_i.clone()), - Us: Some(Us.clone()), + Us: Us.clone(), u_i_C: Some(self.u_i.C), - us: Some(us.clone()), + us: us.clone(), U_i1_C: Some(U_i1.C), F: self.F.clone(), x: Some(u_i1_x), @@ -691,14 +825,31 @@ where let mut transcript_p: PoseidonSponge = PoseidonSponge::::new(&self.poseidon_config); transcript_p.absorb(&self.pp_hash); + + let (all_Us, all_us, all_Ws, all_ws) = if MU > 1 || NU > 1 { + ( + [vec![self.U_i.clone()], Us.clone().unwrap()].concat(), + [vec![self.u_i.clone()], us.clone().unwrap()].concat(), + [vec![self.W_i.clone()], Ws.unwrap()].concat(), + [vec![self.w_i.clone()], ws.unwrap()].concat(), + ) + } else { + ( + vec![self.U_i.clone()], + vec![self.u_i.clone()], + vec![self.W_i.clone()], + vec![self.w_i.clone()], + ) + }; + let (rho, nimfs_proof); (nimfs_proof, U_i1, W_i1, rho) = NIMFS::>::prove( &mut transcript_p, &self.ccs, - &[vec![self.U_i.clone()], Us.clone()].concat(), - &[vec![self.u_i.clone()], us.clone()].concat(), - &[vec![self.W_i.clone()], Ws].concat(), - &[vec![self.w_i.clone()], ws].concat(), + &all_Us, + &all_us, + &all_Ws, + &all_ws, )?; // sanity check: check the folded instance relation @@ -725,12 +876,12 @@ where // where each p_i is in fact p_i.to_constraint_field() let cf_u_i_x = [ vec![rho_Fq], - get_cm_coordinates(&self.U_i.C), - Us.iter() + all_Us + .iter() .flat_map(|Us_i| get_cm_coordinates(&Us_i.C)) .collect(), - get_cm_coordinates(&self.u_i.C), - us.iter() + all_us + .iter() .flat_map(|us_i| get_cm_coordinates(&us_i.C)) .collect(), get_cm_coordinates(&U_i1.C), @@ -742,10 +893,8 @@ where r_bits: Some(rho_bits.clone()), points: Some( [ - vec![self.U_i.clone().C], - Us.iter().map(|Us_i| Us_i.C).collect(), - vec![self.u_i.clone().C], - us.iter().map(|us_i| us_i.C).collect(), + all_Us.iter().map(|Us_i| Us_i.C).collect::>(), + all_us.iter().map(|us_i| us_i.C).collect::>(), ] .concat(), ), @@ -786,9 +935,9 @@ where z_i: Some(self.z_i.clone()), external_inputs: Some(external_inputs), U_i: Some(self.U_i.clone()), - Us: Some(Us.clone()), + Us: Us.clone(), u_i_C: Some(self.u_i.C), - us: Some(us.clone()), + us: us.clone(), U_i1_C: Some(U_i1.C), F: self.F.clone(), x: Some(u_i1_x), @@ -849,31 +998,87 @@ where self.z_i.clone() } - fn instances( - &self, - ) -> ( - Self::RunningInstance, - Self::IncomingInstance, - Self::CFInstance, - ) { - ( - (self.U_i.clone(), self.W_i.clone()), - (self.u_i.clone(), self.w_i.clone()), - (self.cf_U_i.clone(), self.cf_W_i.clone()), - ) + fn ivc_proof(&self) -> Self::IVCProof { + Self::IVCProof { + i: self.i, + z_0: self.z_0.clone(), + z_i: self.z_i.clone(), + W_i: self.W_i.clone(), + U_i: self.U_i.clone(), + w_i: self.w_i.clone(), + u_i: self.u_i.clone(), + cf_W_i: self.cf_W_i.clone(), + cf_U_i: self.cf_U_i.clone(), + } } - /// Implements IVC.V of HyperNova+CycleFold. Notice that this method does not include the + fn from_ivc_proof( + ivc_proof: Self::IVCProof, + fcircuit_params: FC::Params, + params: (Self::ProverParam, Self::VerifierParam), + ) -> Result { + let IVCProof { + i, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; + let (pp, vp) = params; + + let f_circuit = FC::new(fcircuit_params).unwrap(); + let augmented_f_circuit = AugmentedFCircuit::::empty( + &pp.poseidon_config, + f_circuit.clone(), + None, + )?; + let cf_circuit = HyperNovaCycleFoldCircuit::::empty(); + + let ccs = augmented_f_circuit.ccs.clone(); + let cf_r1cs = get_r1cs_from_cs::(cf_circuit)?; + + Ok(Self { + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + ccs, + cf_r1cs, + poseidon_config: pp.poseidon_config, + cs_pp: pp.cs_pp, + cf_cs_pp: pp.cf_cs_pp, + F: f_circuit, + pp_hash: vp.pp_hash()?, + i, + z_0, + z_i, + w_i, + u_i, + W_i, + U_i, + cf_W_i, + cf_U_i, + }) + } + + /// Implements IVC.V of Hyp.clone()erNova+CycleFold. Notice that this method does not include the /// commitments verification, which is done in the Decider. - fn verify( - vp: Self::VerifierParam, - z_0: Vec, // initial state - z_i: Vec, // last state - num_steps: C1::ScalarField, - running_instance: Self::RunningInstance, - incoming_instance: Self::IncomingInstance, - cyclefold_instance: Self::CFInstance, - ) -> Result<(), Error> { + fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { + let Self::IVCProof { + i: num_steps, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; + if num_steps == C1::ScalarField::zero() { if z_0 != z_i { return Err(Error::IVCVerificationFail); @@ -883,9 +1088,6 @@ where // `sponge` is for digest computation. let sponge = PoseidonSponge::::new(&vp.poseidon_config); - let (U_i, W_i) = running_instance; - let (u_i, w_i) = incoming_instance; - let (cf_U_i, cf_W_i) = cyclefold_instance; if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); } @@ -959,18 +1161,6 @@ mod tests { >( poseidon_config: PoseidonConfig, F_circuit: CubicFCircuit, - ) -> ( - HyperNova, CS1, CS2, 2, 3, H>, - ( - ProverParams, - VerifierParams, - ), - (LCCCS, Witness), - (CCCS, Witness), - ( - CycleFoldCommittedInstance, - CycleFoldWitness, - ), ) { let mut rng = ark_std::test_rng(); @@ -1024,24 +1214,11 @@ mod tests { } assert_eq!(Fr::from(num_steps as u32), hypernova.i); - let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances(); + let ivc_proof = hypernova.ivc_proof(); HN::verify( hypernova_params.1.clone(), // verifier_params - z_0, - hypernova.z_i.clone(), - hypernova.i, - running_instance.clone(), - incoming_instance.clone(), - cyclefold_instance.clone(), + ivc_proof, ) .unwrap(); - - ( - hypernova, - hypernova_params, - running_instance, - incoming_instance, - cyclefold_instance, - ) } } diff --git a/folding-schemes/src/folding/hypernova/serialize.rs b/folding-schemes/src/folding/hypernova/serialize.rs deleted file mode 100644 index a7aa6d0c..00000000 --- a/folding-schemes/src/folding/hypernova/serialize.rs +++ /dev/null @@ -1,420 +0,0 @@ -use crate::arith::ccs::CCS; -use crate::arith::r1cs::R1CS; -use crate::folding::hypernova::ProverParams; -use crate::folding::hypernova::VerifierParams; -use ark_crypto_primitives::sponge::poseidon::PoseidonConfig; -use ark_crypto_primitives::sponge::Absorb; -use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; -use ark_r1cs_std::groups::{CurveVar, GroupOpsBounds}; -use ark_r1cs_std::ToConstraintFieldGadget; -use ark_serialize::CanonicalDeserialize; -use ark_serialize::{CanonicalSerialize, Compress, SerializationError, Validate}; -use ark_std::marker::PhantomData; - -use crate::folding::hypernova::cccs::CCCS; -use crate::folding::hypernova::lcccs::LCCCS; -use crate::folding::hypernova::Witness; -use crate::folding::nova::{ - CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness, -}; -use crate::FoldingScheme; -use crate::{ - commitment::CommitmentScheme, - folding::{circuits::CF2, nova::PreprocessorParam}, - frontend::FCircuit, -}; - -use super::HyperNova; - -impl - CanonicalSerialize for HyperNova -where - C1: CurveGroup, - GC1: CurveVar> + ToConstraintFieldGadget>, - C2: CurveGroup, - GC2: CurveVar>, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn serialize_compressed( - &self, - writer: W, - ) -> Result<(), ark_serialize::SerializationError> { - self.serialize_with_mode(writer, ark_serialize::Compress::Yes) - } - - fn compressed_size(&self) -> usize { - self.serialized_size(ark_serialize::Compress::Yes) - } - - fn serialize_uncompressed( - &self, - writer: W, - ) -> Result<(), ark_serialize::SerializationError> { - self.serialize_with_mode(writer, ark_serialize::Compress::No) - } - - fn uncompressed_size(&self) -> usize { - self.serialized_size(ark_serialize::Compress::No) - } - - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.pp_hash.serialize_with_mode(&mut writer, compress)?; - self.i.serialize_with_mode(&mut writer, compress)?; - self.z_0.serialize_with_mode(&mut writer, compress)?; - self.z_i.serialize_with_mode(&mut writer, compress)?; - self.W_i.serialize_with_mode(&mut writer, compress)?; - self.U_i.serialize_with_mode(&mut writer, compress)?; - self.w_i.serialize_with_mode(&mut writer, compress)?; - self.u_i.serialize_with_mode(&mut writer, compress)?; - self.cf_W_i.serialize_with_mode(&mut writer, compress)?; - self.cf_U_i.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.pp_hash.serialized_size(compress) - + self.i.serialized_size(compress) - + self.z_0.serialized_size(compress) - + self.z_i.serialized_size(compress) - + self.W_i.serialized_size(compress) - + self.U_i.serialized_size(compress) - + self.w_i.serialized_size(compress) - + self.u_i.serialized_size(compress) - + self.cf_W_i.serialized_size(compress) - + self.cf_U_i.serialized_size(compress) - } -} - -impl - HyperNova -where - C1: CurveGroup, - GC1: CurveVar> + ToConstraintFieldGadget>, - C2: CurveGroup, - GC2: CurveVar> + ToConstraintFieldGadget>, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - ::ScalarField: Absorb, - ::ScalarField: Absorb, - C1: CurveGroup, - for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, - for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, -{ - #[allow(clippy::too_many_arguments)] - pub fn deserialize_hypernova( - mut reader: R, - compress: Compress, - validate: Validate, - poseidon_config: PoseidonConfig, - cs_pp: CS1::ProverParams, - cs_vp: CS1::VerifierParams, - cf_cs_pp: CS2::ProverParams, - cf_cs_vp: CS2::VerifierParams, - ) -> Result { - let f_circuit = FC::new(()).unwrap(); - let prep_param = PreprocessorParam { - poseidon_config: poseidon_config.clone(), - F: f_circuit.clone(), - cs_pp: Some(cs_pp.clone()), - cs_vp: Some(cs_vp.clone()), - cf_cs_pp: Some(cf_cs_pp.clone()), - cf_cs_vp: Some(cf_cs_vp.clone()), - }; - // `test_rng` won't be used in `preprocess`, since parameters have already been initialized - let (prover_params, verifier_params) = Self::preprocess(ark_std::test_rng(), &prep_param) - .or(Err(SerializationError::InvalidData))?; - let pp_hash = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?; - let i = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?; - let z_0 = Vec::::deserialize_with_mode(&mut reader, compress, validate)?; - let z_i = Vec::::deserialize_with_mode(&mut reader, compress, validate)?; - let W_i = - Witness::::deserialize_with_mode(&mut reader, compress, validate)?; - let U_i = LCCCS::::deserialize_with_mode(&mut reader, compress, validate)?; - let w_i = - Witness::::deserialize_with_mode(&mut reader, compress, validate)?; - let u_i = CCCS::::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_W_i = - CycleFoldWitness::::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_U_i = CycleFoldCommittedInstance::::deserialize_with_mode( - &mut reader, - compress, - validate, - )?; - let ccs = prover_params.ccs.ok_or(SerializationError::InvalidData)?; - - Ok(HyperNova { - _gc1: PhantomData, - _c2: PhantomData, - _gc2: PhantomData, - ccs, - cf_r1cs: verifier_params.cf_r1cs, - poseidon_config, - cs_pp, - cf_cs_pp, - F: f_circuit, - pp_hash, - i, - z_0, - z_i, - W_i, - U_i, - w_i, - u_i, - cf_W_i, - cf_U_i, - }) - } -} - -impl< - C1: CurveGroup, - C2: CurveGroup, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > CanonicalSerialize for ProverParams -{ - fn serialize_compressed( - &self, - writer: W, - ) -> Result<(), SerializationError> { - self.serialize_with_mode(writer, Compress::Yes) - } - - fn compressed_size(&self) -> usize { - self.serialized_size(Compress::Yes) - } - - fn serialize_uncompressed( - &self, - writer: W, - ) -> Result<(), SerializationError> { - self.serialize_with_mode(writer, Compress::No) - } - - fn uncompressed_size(&self) -> usize { - self.serialized_size(Compress::No) - } - - fn serialize_with_mode( - &self, - mut writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - self.cs_pp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_pp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: Compress) -> usize { - self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress) - } -} - -impl< - C1: CurveGroup, - C2: CurveGroup, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > ProverParams -{ - pub fn deserialize_prover_params( - mut reader: R, - compress: Compress, - validate: Validate, - ccs: &Option>, - poseidon_config: &PoseidonConfig, - ) -> Result { - let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; - - Ok(ProverParams { - cs_pp, - cf_cs_pp, - ccs: ccs.clone(), - poseidon_config: poseidon_config.clone(), - }) - } -} - -impl< - C1: CurveGroup, - C2: CurveGroup, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > CanonicalSerialize for VerifierParams -{ - fn serialize_compressed( - &self, - writer: W, - ) -> Result<(), SerializationError> { - self.serialize_with_mode(writer, Compress::Yes) - } - - fn compressed_size(&self) -> usize { - self.serialized_size(Compress::Yes) - } - - fn serialize_uncompressed( - &self, - writer: W, - ) -> Result<(), SerializationError> { - self.serialize_with_mode(writer, Compress::No) - } - - fn uncompressed_size(&self) -> usize { - self.serialized_size(Compress::No) - } - - fn serialize_with_mode( - &self, - mut writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - self.cf_r1cs.serialize_with_mode(&mut writer, compress)?; - self.cs_vp.serialize_with_mode(&mut writer, compress)?; - self.cf_cs_vp.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: Compress) -> usize { - self.cf_r1cs.serialized_size(compress) - + self.cs_vp.serialized_size(compress) - + self.cf_cs_vp.serialized_size(compress) - } -} - -impl< - C1: CurveGroup, - C2: CurveGroup, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - const H: bool, - > VerifierParams -{ - pub fn deserialize_verifier_params( - mut reader: R, - compress: Compress, - validate: Validate, - ccs: &CCS, - poseidon_config: &PoseidonConfig, - ) -> Result { - let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?; - let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - Ok(VerifierParams { - ccs: ccs.clone(), - poseidon_config: poseidon_config.clone(), - cf_r1cs, - cs_vp, - cf_cs_vp, - }) - } -} - -#[cfg(test)] -pub mod tests { - use crate::FoldingScheme; - use crate::MultiFolding; - use ark_serialize::{Compress, Validate, Write}; - use std::fs; - - use crate::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::hypernova::{tests::test_ivc_opt, HyperNova}, - frontend::{utils::CubicFCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, - }; - use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; - use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; - use ark_serialize::CanonicalSerialize; - - #[test] - fn test_serde_hypernova() { - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(()).unwrap(); - let (mut hn, (_, verifier_params), _, _, _) = test_ivc_opt::< - KZG, - Pedersen, - false, - >(poseidon_config.clone(), F_circuit); - - let mut writer = vec![]; - assert!(hn.serialize_compressed(&mut writer).is_ok()); - let mut writer = vec![]; - assert!(hn.serialize_uncompressed(&mut writer).is_ok()); - - let mut file = fs::OpenOptions::new() - .create(true) - .write(true) - .open("./hypernova.serde") - .unwrap(); - - file.write_all(&writer).unwrap(); - - let bytes = fs::read("./hypernova.serde").unwrap(); - - let mut hn_deserialized = HyperNova::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - KZG, - Pedersen, - 2, - 3, - false, - >::deserialize_hypernova( - bytes.as_slice(), - Compress::No, - Validate::No, - poseidon_config, - hn.cs_pp.clone(), - verifier_params.cs_vp, - hn.cf_cs_pp.clone(), - verifier_params.cf_cs_vp, - ) - .unwrap(); - - assert_eq!(hn.i, hn_deserialized.i); - - let mut rng = ark_std::test_rng(); - for _ in 0..3 { - // prepare some new instances to fold in the multifolding step - let mut lcccs = vec![]; - for j in 0..1 { - let instance_state = vec![Fr::from(j as u32 + 85_u32)]; - let (U, W) = hn - .new_running_instance(&mut rng, instance_state, vec![]) - .unwrap(); - lcccs.push((U, W)); - } - let mut cccs = vec![]; - for j in 0..2 { - let instance_state = vec![Fr::from(j as u32 + 15_u32)]; - let (u, w) = hn - .new_incoming_instance(&mut rng, instance_state, vec![]) - .unwrap(); - cccs.push((u, w)); - } - - hn.prove_step(&mut rng, vec![], Some((lcccs.clone(), cccs.clone()))) - .unwrap(); - hn_deserialized - .prove_step(&mut rng, vec![], Some((lcccs, cccs))) - .unwrap(); - } - - assert_eq!(hn.z_i, hn_deserialized.z_i); - } -} diff --git a/folding-schemes/src/folding/mod.rs b/folding-schemes/src/folding/mod.rs index 0f3a66fd..32342886 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -3,3 +3,170 @@ pub mod hypernova; pub mod nova; pub mod protogalaxy; pub mod traits; + +#[cfg(test)] +pub mod tests { + use ark_ec::CurveGroup; + use ark_ff::PrimeField; + use ark_pallas::{constraints::GVar as GVar1, Fr, Projective as G1}; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + use ark_vesta::{constraints::GVar as GVar2, Projective as G2}; + use std::io::Write; + + use crate::commitment::pedersen::Pedersen; + use crate::folding::{ + hypernova::HyperNova, + nova::{Nova, PreprocessorParam as NovaPreprocessorParam}, + protogalaxy::ProtoGalaxy, + }; + use crate::frontend::utils::CubicFCircuit; + use crate::frontend::FCircuit; + use crate::transcript::poseidon::poseidon_canonical_config; + use crate::Error; + use crate::FoldingScheme; + + /// tests the IVC proofs and its serializers for the 3 implemented IVCs: Nova, HyperNova and + /// ProtoGalaxy. + #[test] + fn test_serialize_ivc_nova_hypernova_protogalaxy() { + let poseidon_config = poseidon_canonical_config::(); + type FC = CubicFCircuit; + let f_circuit = FC::new(()).unwrap(); + + // test Nova + type N = Nova, Pedersen, false>; + let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit); + test_serialize_ivc_opt::("nova".to_string(), prep_param.clone()).unwrap(); + + // test HyperNova + type HN = HyperNova< + G1, + GVar1, + G2, + GVar2, + FC, + Pedersen, + Pedersen, + 1, // mu + 1, // nu + false, + >; + test_serialize_ivc_opt::("hypernova".to_string(), prep_param).unwrap(); + + // test ProtoGalaxy + type P = ProtoGalaxy, Pedersen>; + let prep_param = (poseidon_config, f_circuit); + test_serialize_ivc_opt::("protogalaxy".to_string(), prep_param).unwrap(); + } + + fn test_serialize_ivc_opt< + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit, + FS: FoldingScheme, + >( + name: String, + prep_param: FS::PreprocessorParam, + ) -> Result<(), Error> + where + C1: CurveGroup, + C2::BaseField: PrimeField, + FC: FCircuit, + { + let mut rng = ark_std::test_rng(); + let F_circuit = FC::new(())?; + + let fs_params = FS::preprocess(&mut rng, &prep_param)?; + + let z_0 = vec![C1::ScalarField::from(3_u32)]; + let mut fs = FS::init(&fs_params, F_circuit, z_0.clone()).unwrap(); + + // perform multiple IVC steps (internally folding) + let num_steps: usize = 3; + for _ in 0..num_steps { + fs.prove_step(&mut rng, vec![], None).unwrap(); + } + + // verify the IVCProof + let ivc_proof: FS::IVCProof = fs.ivc_proof(); + FS::verify(fs_params.1.clone(), ivc_proof.clone()).unwrap(); + + // serialize the IVCProof and store it in a file + let mut writer = vec![]; + assert!(ivc_proof.serialize_compressed(&mut writer).is_ok()); + + let mut file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .open(format!("./ivc_proof-{}.serialized", name)) + .unwrap(); + file.write_all(&writer).unwrap(); + + // read the IVCProof from the file deserializing it + let bytes = std::fs::read(format!("./ivc_proof-{}.serialized", name)).unwrap(); + let deserialized_ivc_proof = + FS::IVCProof::deserialize_compressed(bytes.as_slice()).unwrap(); + // verify deserialized IVCProof + FS::verify(fs_params.1.clone(), deserialized_ivc_proof.clone()).unwrap(); + + // build the FS from the given IVCProof, FC::Params, ProverParams and VerifierParams + let mut new_fs = FS::from_ivc_proof(deserialized_ivc_proof, (), fs_params.clone()).unwrap(); + + // serialize the Nova params + let mut fs_pp_serialized = vec![]; + fs_params + .0 + .serialize_compressed(&mut fs_pp_serialized) + .unwrap(); + let mut fs_vp_serialized = vec![]; + fs_params + .1 + .serialize_compressed(&mut fs_vp_serialized) + .unwrap(); + + // deserialize the Nova params. This would be done by the client reading from a file + let _fs_pp_deserialized = FS::pp_deserialize_with_mode( + &mut fs_pp_serialized.as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + (), // FCircuit's Params + ) + .unwrap(); + + // perform several IVC steps on both the original FS instance and the recovered from the + // serialization new FS instance + let num_steps: usize = 3; + for _ in 0..num_steps { + new_fs.prove_step(&mut rng, vec![], None).unwrap(); + fs.prove_step(&mut rng, vec![], None).unwrap(); + } + + // check that the IVCProofs from both FS instances are equal + assert_eq!(new_fs.ivc_proof(), fs.ivc_proof()); + + let fs_vp_deserialized = FS::vp_deserialize_with_mode( + &mut fs_vp_serialized.as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + (), // fcircuit_params + ) + .unwrap(); + + // get the IVCProof + let ivc_proof: FS::IVCProof = new_fs.ivc_proof(); + + // serialize IVCProof + let mut ivc_proof_serialized = vec![]; + assert!(ivc_proof + .serialize_compressed(&mut ivc_proof_serialized) + .is_ok()); + // deserialize IVCProof + let ivc_proof_deserialized = + FS::IVCProof::deserialize_compressed(ivc_proof_serialized.as_slice()).unwrap(); + + // verify the last IVCProof from the recovered from serialization FS + FS::verify(fs_vp_deserialized.clone(), ivc_proof_deserialized).unwrap(); + + Ok(()) + } +} diff --git a/folding-schemes/src/folding/nova/decider_circuits.rs b/folding-schemes/src/folding/nova/decider_circuits.rs index 0db2106d..cfd56c1b 100644 --- a/folding-schemes/src/folding/nova/decider_circuits.rs +++ b/folding-schemes/src/folding/nova/decider_circuits.rs @@ -491,7 +491,6 @@ where pub mod tests { use ark_pallas::{constraints::GVar, Fq, Fr, Projective}; use ark_relations::r1cs::ConstraintSystem; - use ark_std::One; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; use super::*; @@ -533,18 +532,9 @@ pub mod tests { // generate a Nova instance and do a step of it let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); nova.prove_step(&mut rng, vec![], None).unwrap(); - let ivc_v = nova.clone(); - let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances(); - N::verify( - nova_params.1, // verifier_params - z_0, - ivc_v.z_i, - Fr::one(), - running_instance, - incoming_instance, - cyclefold_instance, - ) - .unwrap(); + // verify the IVC + let ivc_proof = nova.ivc_proof(); + N::verify(nova_params.1, ivc_proof).unwrap(); // load the DeciderCircuit 1 & 2 from the Nova instance let decider_circuit1 = diff --git a/folding-schemes/src/folding/nova/decider_eth.rs b/folding-schemes/src/folding/nova/decider_eth.rs index 8ef02519..24d27edb 100644 --- a/folding-schemes/src/folding/nova/decider_eth.rs +++ b/folding-schemes/src/folding/nova/decider_eth.rs @@ -75,8 +75,8 @@ impl DeciderTrait for Decider where C1: CurveGroup, - C2: CurveGroup, GC1: CurveVar> + ToConstraintFieldGadget>, + C2: CurveGroup, GC2: CurveVar> + ToConstraintFieldGadget>, FC: FCircuit, // CS1 is a KZG commitment, where challenge is C1::Fr elem @@ -343,9 +343,7 @@ pub mod tests { use super::*; use crate::commitment::pedersen::Pedersen; - use crate::folding::nova::{ - PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams, - }; + use crate::folding::nova::{PreprocessorParam, ProverParams as NovaProverParams}; use crate::frontend::utils::CubicFCircuit; use crate::transcript::poseidon::poseidon_canonical_config; @@ -490,13 +488,15 @@ pub mod tests { &mut nova_pp_serialized.as_slice() ) .unwrap(); - let nova_vp_deserialized = NovaVerifierParams::< + let nova_vp_deserialized = , - Pedersen, - >::deserialize_compressed( - &mut nova_vp_serialized.as_slice() + CubicFCircuit, + >>::vp_deserialize_with_mode( + &mut nova_vp_serialized.as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + (), // fcircuit_params ) .unwrap(); diff --git a/folding-schemes/src/folding/nova/decider_eth_circuit.rs b/folding-schemes/src/folding/nova/decider_eth_circuit.rs index 37a5a67d..a1dd8091 100644 --- a/folding-schemes/src/folding/nova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/nova/decider_eth_circuit.rs @@ -589,7 +589,7 @@ pub mod tests { use ark_relations::r1cs::ConstraintSystem; use ark_std::{ rand::{thread_rng, Rng}, - One, UniformRand, + UniformRand, }; use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2}; @@ -810,18 +810,8 @@ pub mod tests { // generate a Nova instance and do a step of it let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); nova.prove_step(&mut rng, vec![], None).unwrap(); - let ivc_v = nova.clone(); - let (running_instance, incoming_instance, cyclefold_instance) = ivc_v.instances(); - N::verify( - nova_params.1, // verifier_params - z_0, - ivc_v.z_i, - Fr::one(), - running_instance, - incoming_instance, - cyclefold_instance, - ) - .unwrap(); + let ivc_proof = nova.ivc_proof(); + N::verify(nova_params.1, ivc_proof).unwrap(); // load the DeciderEthCircuit from the Nova instance let decider_eth_circuit = DeciderEthCircuit::< diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 45ee51ac..d67c33b9 100644 --- a/folding-schemes/src/folding/nova/mod.rs +++ b/folding-schemes/src/folding/nova/mod.rs @@ -38,7 +38,6 @@ use crate::{arith::Arith, commitment::CommitmentScheme}; pub mod circuits; pub mod nifs; pub mod ova; -pub mod serialize; pub mod traits; pub mod zk; @@ -345,15 +344,6 @@ where CS2: CommitmentScheme, { fn check(&self) -> Result<(), ark_serialize::SerializationError> { - self.poseidon_config.full_rounds.check()?; - self.poseidon_config.partial_rounds.check()?; - self.poseidon_config.alpha.check()?; - self.poseidon_config.ark.check()?; - self.poseidon_config.mds.check()?; - self.poseidon_config.rate.check()?; - self.poseidon_config.capacity.check()?; - self.r1cs.check()?; - self.cf_r1cs.check()?; self.cs_vp.check()?; self.cf_cs_vp.check()?; Ok(()) @@ -371,42 +361,12 @@ where mut writer: W, compress: ark_serialize::Compress, ) -> Result<(), ark_serialize::SerializationError> { - self.r1cs.serialize_with_mode(&mut writer, compress)?; - self.cf_r1cs.serialize_with_mode(&mut writer, compress)?; self.cs_vp.serialize_with_mode(&mut writer, compress)?; self.cf_cs_vp.serialize_with_mode(&mut writer, compress) } fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.r1cs.serialized_size(compress) - + self.cf_r1cs.serialized_size(compress) - + self.cs_vp.serialized_size(compress) - + self.cf_cs_vp.serialized_size(compress) - } -} -impl CanonicalDeserialize for VerifierParams -where - C1: CurveGroup, - C2: CurveGroup, - CS1: CommitmentScheme, - CS2: CommitmentScheme, -{ - fn deserialize_with_mode( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - ) -> Result { - let r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?; - let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; - Ok(VerifierParams { - poseidon_config: poseidon_canonical_config::(), - r1cs, - cf_r1cs, - cs_vp, - cf_cs_vp, - }) + self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) } } @@ -429,6 +389,29 @@ where } } +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct IVCProof +where + C1: CurveGroup, + C2: CurveGroup, +{ + // current step of the IVC + pub i: C1::ScalarField, + // initial state + pub z_0: Vec, + // current state + pub z_i: Vec, + // running instance + pub W_i: Witness, + pub U_i: CommittedInstance, + // incoming instance + pub w_i: Witness, + pub u_i: CommittedInstance, + // CycleFold instances + pub cf_W_i: CycleFoldWitness, + pub cf_U_i: CycleFoldCommittedInstance, +} + /// Implements Nova+CycleFold's IVC, described in [Nova](https://eprint.iacr.org/2021/370.pdf) and /// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait /// The `H` const generic specifies whether the homorphic commitment scheme is blinding @@ -500,6 +483,58 @@ where type IncomingInstance = (CommittedInstance, Witness); type MultiCommittedInstanceWithWitness = (); type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); + type IVCProof = IVCProof; + + fn pp_deserialize_with_mode( + reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + _fc_params: FC::Params, // FCircuit params + ) -> Result { + Ok(Self::ProverParam::deserialize_with_mode( + reader, compress, validate, + )?) + } + fn vp_deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, + ) -> Result { + let poseidon_config = poseidon_canonical_config::(); + + // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing + // to serialize them, saving significant space in the VerifierParams serialized size. + + // main circuit R1CS: + let f_circuit = FC::new(fc_params)?; + let cs = ConstraintSystem::::new_ref(); + let augmented_F_circuit = + AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); + augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize(); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let r1cs = extract_r1cs::(&cs); + + // CycleFold circuit R1CS + let cs2 = ConstraintSystem::::new_ref(); + let cf_circuit = NovaCycleFoldCircuit::::empty(); + cf_circuit.generate_constraints(cs2.clone())?; + cs2.finalize(); + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let cf_r1cs = extract_r1cs::(&cs2); + + let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + + Ok(Self::VerifierParam { + poseidon_config, + r1cs, + cf_r1cs, + cs_vp, + cf_cs_vp, + }) + } fn preprocess( mut rng: impl RngCore, @@ -874,31 +909,93 @@ where self.z_i.clone() } - fn instances( - &self, - ) -> ( - Self::RunningInstance, - Self::IncomingInstance, - Self::CFInstance, - ) { - ( - (self.U_i.clone(), self.W_i.clone()), - (self.u_i.clone(), self.w_i.clone()), - (self.cf_U_i.clone(), self.cf_W_i.clone()), - ) + fn ivc_proof(&self) -> Self::IVCProof { + Self::IVCProof { + i: self.i, + z_0: self.z_0.clone(), + z_i: self.z_i.clone(), + W_i: self.W_i.clone(), + U_i: self.U_i.clone(), + w_i: self.w_i.clone(), + u_i: self.u_i.clone(), + cf_W_i: self.cf_W_i.clone(), + cf_U_i: self.cf_U_i.clone(), + } } - /// Implements IVC.V of Nova+CycleFold. Notice that this method does not include the + fn from_ivc_proof( + ivc_proof: IVCProof, + fcircuit_params: FC::Params, + params: (Self::ProverParam, Self::VerifierParam), + ) -> Result { + let IVCProof { + i, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; + let (pp, vp) = params; + + let f_circuit = FC::new(fcircuit_params).unwrap(); + let cs = ConstraintSystem::::new_ref(); + let cs2 = ConstraintSystem::::new_ref(); + let augmented_F_circuit = + AugmentedFCircuit::::empty(&pp.poseidon_config, f_circuit.clone()); + let cf_circuit = NovaCycleFoldCircuit::::empty(); + + augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize(); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let r1cs = extract_r1cs::(&cs); + + cf_circuit.generate_constraints(cs2.clone())?; + cs2.finalize(); + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let cf_r1cs = extract_r1cs::(&cs2); + + Ok(Self { + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + r1cs, + cf_r1cs, + poseidon_config: pp.poseidon_config, + cs_pp: pp.cs_pp, + cf_cs_pp: pp.cf_cs_pp, + F: f_circuit, + pp_hash: vp.pp_hash()?, + i, + z_0, + z_i, + w_i, + u_i, + W_i, + U_i, + cf_W_i, + cf_U_i, + }) + } + + /// Implements IVC.V of Nov.clone()a+CycleFold. Notice that this method does not include the /// commitments verification, which is done in the Decider. - fn verify( - vp: Self::VerifierParam, - z_0: Vec, // initial state - z_i: Vec, // last state - num_steps: C1::ScalarField, - running_instance: Self::RunningInstance, - incoming_instance: Self::IncomingInstance, - cyclefold_instance: Self::CFInstance, - ) -> Result<(), Error> { + fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { + let Self::IVCProof { + i: num_steps, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; + let sponge = PoseidonSponge::::new(&vp.poseidon_config); if num_steps == C1::ScalarField::zero() { @@ -908,10 +1005,6 @@ where return Ok(()); } - let (U_i, W_i) = running_instance; - let (u_i, w_i) = incoming_instance; - let (cf_U_i, cf_W_i) = cyclefold_instance; - if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); } @@ -1175,15 +1268,67 @@ pub mod tests { } assert_eq!(Fr::from(num_steps as u32), nova.i); - let (running_instance, incoming_instance, cyclefold_instance) = nova.instances(); + // serialize the Nova Prover & Verifier params. These params are the trusted setup of the commitment schemes used + let mut nova_pp_serialized = vec![]; + nova_params + .0 + .serialize_compressed(&mut nova_pp_serialized) + .unwrap(); + let mut nova_vp_serialized = vec![]; + nova_params + .1 + .serialize_compressed(&mut nova_vp_serialized) + .unwrap(); + + // deserialize the Nova params + let _nova_pp_deserialized = + ProverParams::::deserialize_compressed( + &mut nova_pp_serialized.as_slice(), + ) + .unwrap(); + let nova_vp_deserialized = Nova::< + Projective, + GVar, + Projective2, + GVar2, + CubicFCircuit, + CS1, + CS2, + H, + >::vp_deserialize_with_mode( + &mut nova_vp_serialized.as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + (), // fcircuit_params + ) + .unwrap(); + + let ivc_proof = nova.ivc_proof(); + + // serialize IVCProof + let mut ivc_proof_serialized = vec![]; + assert!(ivc_proof + .serialize_compressed(&mut ivc_proof_serialized) + .is_ok()); + // deserialize IVCProof + let ivc_proof_deserialized = , + CS1, + CS2, + H, + > as FoldingScheme>>::IVCProof::deserialize_compressed( + ivc_proof_serialized.as_slice() + ) + .unwrap(); + + // verify the deserialized IVCProof with the deserialized VerifierParams Nova::, CS1, CS2, H>::verify( - nova_params.1, // Nova's verifier params - z_0.clone(), - nova.z_i.clone(), - nova.i, - running_instance, - incoming_instance, - cyclefold_instance, + nova_vp_deserialized, // Nova's verifier params + ivc_proof_deserialized, ) .unwrap(); diff --git a/folding-schemes/src/folding/nova/serialize.rs b/folding-schemes/src/folding/nova/serialize.rs deleted file mode 100644 index e5e53827..00000000 --- a/folding-schemes/src/folding/nova/serialize.rs +++ /dev/null @@ -1,268 +0,0 @@ -use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; -use ark_ec::{CurveGroup, Group}; -use ark_ff::PrimeField; -use ark_r1cs_std::{ - groups::{CurveVar, GroupOpsBounds}, - ToConstraintFieldGadget, -}; -use ark_relations::r1cs::ConstraintSynthesizer; -use ark_relations::r1cs::ConstraintSystem; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError, Write}; -use std::marker::PhantomData; - -use super::{ - circuits::AugmentedFCircuit, CommittedInstance, Nova, NovaCycleFoldCircuit, ProverParams, - Witness, -}; -use crate::{ - arith::r1cs::extract_r1cs, - commitment::CommitmentScheme, - folding::circuits::{CF1, CF2}, - frontend::FCircuit, -}; - -impl CanonicalSerialize - for Nova -where - C1: CurveGroup, - C2: CurveGroup, - FC: FCircuit, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - ::ScalarField: Absorb, - ::ScalarField: Absorb, - C1: CurveGroup, - for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, - for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, - GC1: CurveVar::ScalarField>, - GC1: ToConstraintFieldGadget<::ScalarField>, - GC2: CurveVar::BaseField>, -{ - fn serialize_with_mode( - &self, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), ark_serialize::SerializationError> { - self.pp_hash.serialize_with_mode(&mut writer, compress)?; - self.i.serialize_with_mode(&mut writer, compress)?; - self.z_0.serialize_with_mode(&mut writer, compress)?; - self.z_i.serialize_with_mode(&mut writer, compress)?; - self.w_i.serialize_with_mode(&mut writer, compress)?; - self.u_i.serialize_with_mode(&mut writer, compress)?; - self.W_i.serialize_with_mode(&mut writer, compress)?; - self.U_i.serialize_with_mode(&mut writer, compress)?; - self.cf_W_i.serialize_with_mode(&mut writer, compress)?; - self.cf_U_i.serialize_with_mode(&mut writer, compress) - } - - fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { - self.pp_hash.serialized_size(compress) - + self.i.serialized_size(compress) - + self.z_0.serialized_size(compress) - + self.z_i.serialized_size(compress) - + self.w_i.serialized_size(compress) - + self.u_i.serialized_size(compress) - + self.W_i.serialized_size(compress) - + self.U_i.serialized_size(compress) - + self.cf_W_i.serialized_size(compress) - + self.cf_U_i.serialized_size(compress) - } - - fn serialize_compressed( - &self, - writer: W, - ) -> Result<(), ark_serialize::SerializationError> { - self.serialize_with_mode(writer, ark_serialize::Compress::Yes) - } - - fn compressed_size(&self) -> usize { - self.serialized_size(ark_serialize::Compress::Yes) - } - - fn serialize_uncompressed( - &self, - writer: W, - ) -> Result<(), ark_serialize::SerializationError> { - self.serialize_with_mode(writer, ark_serialize::Compress::No) - } - - fn uncompressed_size(&self) -> usize { - self.serialized_size(ark_serialize::Compress::No) - } -} - -// Note that we can't derive or implement `CanonicalDeserialize` directly. -// This is because `CurveVar` notably does not implement the `Sync` trait. -impl Nova -where - C1: CurveGroup, - C2: CurveGroup, - FC: FCircuit, Params = ()>, - CS1: CommitmentScheme, - CS2: CommitmentScheme, - ::BaseField: PrimeField, - ::BaseField: PrimeField, - ::ScalarField: Absorb, - ::ScalarField: Absorb, - C1: CurveGroup, - for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>, - for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>, - GC1: CurveVar::ScalarField>, - GC1: ToConstraintFieldGadget<::ScalarField>, - GC2: CurveVar>, - GC2: ToConstraintFieldGadget<::BaseField>, -{ - pub fn deserialize_nova( - mut reader: R, - compress: ark_serialize::Compress, - validate: ark_serialize::Validate, - prover_params: ProverParams, - poseidon_config: PoseidonConfig, - ) -> Result { - let pp_hash = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?; - let i = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?; - let z_0 = Vec::::deserialize_with_mode(&mut reader, compress, validate)?; - let z_i = Vec::::deserialize_with_mode(&mut reader, compress, validate)?; - let w_i = Witness::::deserialize_with_mode(&mut reader, compress, validate)?; - let u_i = CommittedInstance::::deserialize_with_mode(&mut reader, compress, validate)?; - let W_i = Witness::::deserialize_with_mode(&mut reader, compress, validate)?; - let U_i = CommittedInstance::::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_W_i = Witness::::deserialize_with_mode(&mut reader, compress, validate)?; - let cf_U_i = - CommittedInstance::::deserialize_with_mode(&mut reader, compress, validate)?; - - let f_circuit = FC::new(()).unwrap(); - let cs = ConstraintSystem::::new_ref(); - let cs2 = ConstraintSystem::::new_ref(); - let augmented_F_circuit = - AugmentedFCircuit::::empty(&poseidon_config, f_circuit.clone()); - let cf_circuit = NovaCycleFoldCircuit::::empty(); - - augmented_F_circuit - .generate_constraints(cs.clone()) - .map_err(|_| SerializationError::InvalidData)?; - cs.finalize(); - let cs = cs.into_inner().ok_or(SerializationError::InvalidData)?; - let r1cs = extract_r1cs::(&cs); - - cf_circuit - .generate_constraints(cs2.clone()) - .map_err(|_| SerializationError::InvalidData)?; - cs2.finalize(); - let cs2 = cs2.into_inner().ok_or(SerializationError::InvalidData)?; - let cf_r1cs = extract_r1cs::(&cs2); - Ok(Nova { - _gc1: PhantomData, - _c2: PhantomData, - _gc2: PhantomData, - r1cs, - cf_r1cs, - poseidon_config, - cs_pp: prover_params.cs_pp, - cf_cs_pp: prover_params.cf_cs_pp, - F: f_circuit, - pp_hash, - i, - z_0, - z_i, - w_i, - u_i, - W_i, - U_i, - cf_W_i, - cf_U_i, - }) - } -} - -#[cfg(test)] -pub mod tests { - use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; - use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; - use ark_serialize::{CanonicalSerialize, Compress, Validate}; - use std::{fs, io::Write}; - - use crate::{ - commitment::{kzg::KZG, pedersen::Pedersen}, - folding::nova::{Nova, PreprocessorParam}, - frontend::{utils::CubicFCircuit, FCircuit}, - transcript::poseidon::poseidon_canonical_config, - FoldingScheme, - }; - - #[test] - fn test_serde_nova() { - let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_canonical_config::(); - let F_circuit = CubicFCircuit::::new(()).unwrap(); - - // Initialize nova and make multiple `prove_step()` - type N = Nova< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - KZG<'static, Bn254>, - Pedersen, - false, - >; - let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit); - let nova_params = N::preprocess(&mut rng, &prep_param).unwrap(); - - let z_0 = vec![Fr::from(3_u32)]; - let mut nova = N::init(&nova_params, F_circuit, z_0.clone()).unwrap(); - - let num_steps: usize = 3; - for _ in 0..num_steps { - nova.prove_step(&mut rng, vec![], None).unwrap(); - } - - let mut writer = vec![]; - assert!(nova - .serialize_with_mode(&mut writer, ark_serialize::Compress::No) - .is_ok()); - - let mut file = fs::OpenOptions::new() - .create(true) - .write(true) - .open("./nova.serde") - .unwrap(); - - file.write_all(&writer).unwrap(); - - let bytes = fs::read("./nova.serde").unwrap(); - - let mut deserialized_nova = Nova::< - Projective, - GVar, - Projective2, - GVar2, - CubicFCircuit, - KZG, - Pedersen, - false, - >::deserialize_nova( - bytes.as_slice(), - Compress::No, - Validate::No, - nova_params.0, // Nova's prover params - poseidon_config, - ) - .unwrap(); - - assert_eq!(nova.i, deserialized_nova.i); - - let num_steps: usize = 3; - for _ in 0..num_steps { - deserialized_nova - .prove_step(&mut rng, vec![], None) - .unwrap(); - nova.prove_step(&mut rng, vec![], None).unwrap(); - } - - assert_eq!(deserialized_nova.w_i, nova.w_i); - } -} diff --git a/folding-schemes/src/folding/protogalaxy/mod.rs b/folding-schemes/src/folding/protogalaxy/mod.rs index be9eb1f1..d17655d8 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -15,6 +15,7 @@ use ark_r1cs_std::{ use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, Namespace, SynthesisError, }; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; use ark_std::{ borrow::Borrow, cmp::max, fmt::Debug, log2, marker::PhantomData, rand::RngCore, One, Zero, }; @@ -36,6 +37,7 @@ use crate::{ CF1, CF2, }, frontend::{utils::DummyCircuit, FCircuit}, + transcript::poseidon::poseidon_canonical_config, utils::{get_cm_coordinates, pp_hash}, Error, FoldingScheme, }; @@ -74,7 +76,7 @@ pub type ProtoGalaxyCycleFoldCircuit = CycleFoldCircuit { phi: C, betas: Vec, @@ -207,7 +209,7 @@ impl CommittedInstanceVarOps for CommittedIn } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness { w: Vec, r_w: F, @@ -313,6 +315,68 @@ where /// Proving parameters of the underlying commitment scheme over C2 pub cf_cs_params: CS2::ProverParams, } +impl CanonicalSerialize for ProverParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn serialize_with_mode( + &self, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), ark_serialize::SerializationError> { + self.cs_params.serialize_with_mode(&mut writer, compress)?; + self.cf_cs_params.serialize_with_mode(&mut writer, compress) + } + + fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { + self.cs_params.serialized_size(compress) + self.cf_cs_params.serialized_size(compress) + } +} +impl Valid for ProverParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn check(&self) -> Result<(), ark_serialize::SerializationError> { + self.poseidon_config.full_rounds.check()?; + self.poseidon_config.partial_rounds.check()?; + self.poseidon_config.alpha.check()?; + self.poseidon_config.ark.check()?; + self.poseidon_config.mds.check()?; + self.poseidon_config.rate.check()?; + self.poseidon_config.capacity.check()?; + self.cs_params.check()?; + self.cf_cs_params.check()?; + Ok(()) + } +} +impl CanonicalDeserialize for ProverParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + ) -> Result { + let cs_params = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; + let cf_cs_params = + CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?; + Ok(ProverParams { + poseidon_config: poseidon_canonical_config::(), + cs_params, + cf_cs_params, + }) + } +} /// Verification parameters for ProtoGalaxy-based IVC #[derive(Debug, Clone)] @@ -335,6 +399,40 @@ where pub cf_cs_vp: CS2::VerifierParams, } +impl Valid for VerifierParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn check(&self) -> Result<(), ark_serialize::SerializationError> { + self.cs_vp.check()?; + self.cf_cs_vp.check()?; + Ok(()) + } +} +impl CanonicalSerialize for VerifierParams +where + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, +{ + fn serialize_with_mode( + &self, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), ark_serialize::SerializationError> { + self.cs_vp.serialize_with_mode(&mut writer, compress)?; + self.cf_cs_vp.serialize_with_mode(&mut writer, compress) + } + + fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { + self.cs_vp.serialized_size(compress) + self.cf_cs_vp.serialized_size(compress) + } +} + impl VerifierParams where C1: CurveGroup, @@ -357,6 +455,23 @@ where } } +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +pub struct IVCProof +where + C1: CurveGroup, + C2: CurveGroup, +{ + pub i: C1::ScalarField, + pub z_0: Vec, + pub z_i: Vec, + pub W_i: Witness, + pub U_i: CommittedInstance, + pub w_i: Witness, + pub u_i: CommittedInstance, + pub cf_W_i: CycleFoldWitness, + pub cf_U_i: CycleFoldCommittedInstance, +} + /// Implements ProtoGalaxy+CycleFold's IVC, described in [ProtoGalaxy] and /// [CycleFold], following the FoldingScheme trait /// @@ -536,6 +651,68 @@ where type MultiCommittedInstanceWithWitness = (CommittedInstance, Witness); type CFInstance = (CycleFoldCommittedInstance, CycleFoldWitness); + type IVCProof = IVCProof; + + fn pp_deserialize_with_mode( + reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + _fc_params: FC::Params, // FCircuit params + ) -> Result { + Ok(Self::ProverParam::deserialize_with_mode( + reader, compress, validate, + )?) + } + + fn vp_deserialize_with_mode( + mut reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, + ) -> Result { + let poseidon_config = poseidon_canonical_config::(); + + // generate the r1cs & cf_r1cs needed for the VerifierParams. In this way we avoid needing + // to serialize them, saving significant space in the VerifierParams serialized size. + + let f_circuit = FC::new(fc_params)?; + let k = 1; + let d = 2; + let t = Self::compute_t(&poseidon_config, &f_circuit, d, k)?; + + // main circuit R1CS: + let cs = ConstraintSystem::::new_ref(); + let augmented_F_circuit = AugmentedFCircuit::::empty( + &poseidon_config, + f_circuit.clone(), + t, + d, + k, + ); + augmented_F_circuit.generate_constraints(cs.clone())?; + cs.finalize(); + let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let r1cs = extract_r1cs::(&cs); + + // CycleFold circuit R1CS + let cs2 = ConstraintSystem::::new_ref(); + let cf_circuit = ProtoGalaxyCycleFoldCircuit::::empty(); + cf_circuit.generate_constraints(cs2.clone())?; + cs2.finalize(); + let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?; + let cf_r1cs = extract_r1cs::(&cs2); + + let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?; + + Ok(Self::VerifierParam { + poseidon_config, + r1cs, + cf_r1cs, + cs_vp, + cf_cs_vp, + }) + } fn preprocess( mut rng: impl RngCore, @@ -898,35 +1075,79 @@ where fn state(&self) -> Vec { self.z_i.clone() } - fn instances( - &self, - ) -> ( - Self::RunningInstance, - Self::IncomingInstance, - Self::CFInstance, - ) { - ( - (self.U_i.clone(), self.W_i.clone()), - (self.u_i.clone(), self.w_i.clone()), - (self.cf_U_i.clone(), self.cf_W_i.clone()), - ) + + fn ivc_proof(&self) -> Self::IVCProof { + Self::IVCProof { + i: self.i, + z_0: self.z_0.clone(), + z_i: self.z_i.clone(), + W_i: self.W_i.clone(), + U_i: self.U_i.clone(), + w_i: self.w_i.clone(), + u_i: self.u_i.clone(), + cf_W_i: self.cf_W_i.clone(), + cf_U_i: self.cf_U_i.clone(), + } + } + + fn from_ivc_proof( + ivc_proof: Self::IVCProof, + fcircuit_params: FC::Params, + params: (Self::ProverParam, Self::VerifierParam), + ) -> Result { + let IVCProof { + i, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; + let (pp, vp) = params; + + let f_circuit = FC::new(fcircuit_params).unwrap(); + + Ok(Self { + _gc1: PhantomData, + _c2: PhantomData, + _gc2: PhantomData, + r1cs: vp.r1cs.clone(), + cf_r1cs: vp.cf_r1cs.clone(), + poseidon_config: pp.poseidon_config, + cs_params: pp.cs_params, + cf_cs_params: pp.cf_cs_params, + F: f_circuit, + pp_hash: vp.pp_hash()?, + i, + z_0, + z_i, + w_i, + u_i, + W_i, + U_i, + cf_W_i, + cf_U_i, + }) } /// Implements IVC.V of ProtoGalaxy+CycleFold - fn verify( - vp: Self::VerifierParam, - z_0: Vec, // initial state - z_i: Vec, // last state - num_steps: C1::ScalarField, - running_instance: Self::RunningInstance, - incoming_instance: Self::IncomingInstance, - cyclefold_instance: Self::CFInstance, - ) -> Result<(), Error> { - let sponge = PoseidonSponge::::new(&vp.poseidon_config); + fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { + let Self::IVCProof { + i: num_steps, + z_0, + z_i, + W_i, + U_i, + w_i, + u_i, + cf_W_i, + cf_U_i, + } = ivc_proof; - let (U_i, W_i) = running_instance; - let (u_i, w_i) = incoming_instance; - let (cf_U_i, cf_W_i) = cyclefold_instance; + let sponge = PoseidonSponge::::new(&vp.poseidon_config); if u_i.x.len() != 2 || U_i.x.len() != 2 { return Err(Error::IVCVerificationFail); @@ -1067,17 +1288,8 @@ mod tests { } assert_eq!(Fr::from(num_steps as u32), protogalaxy.i); - let (running_instance, incoming_instance, cyclefold_instance) = protogalaxy.instances(); - PG::::verify( - params.1, - z_0, - protogalaxy.z_i, - protogalaxy.i, - running_instance, - incoming_instance, - cyclefold_instance, - ) - .unwrap(); + let ivc_proof = protogalaxy.ivc_proof(); + PG::::verify(params.1, ivc_proof).unwrap(); } #[ignore] diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index 6ed08054..419d1baa 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -4,6 +4,7 @@ use ark_ec::{pairing::Pairing, CurveGroup}; use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::rand::CryptoRng; use ark_std::{fmt::Debug, rand::RngCore}; use thiserror::Error; @@ -107,8 +108,8 @@ pub enum Error { JSONSerdeError(String), #[error("Multi instances folding not supported in this scheme")] NoMultiInstances, - #[error("Missing 'other' instances, since this is a multi-instances folding scheme")] - MissingOtherInstances, + #[error("Missing 'other' instances, since this is a multi-instances folding scheme. Expected number of instances, mu:{0}, nu:{1}")] + MissingOtherInstances(usize, usize), } /// FoldingScheme defines trait that is implemented by the diverse folding schemes. It is defined @@ -124,12 +125,37 @@ where FC: FCircuit, { type PreprocessorParam: Debug + Clone; - type ProverParam: Debug + Clone; - type VerifierParam: Debug + Clone; + type ProverParam: Debug + Clone + CanonicalSerialize; + type VerifierParam: Debug + Clone + CanonicalSerialize; type RunningInstance: Debug; // contains the CommittedInstance + Witness type IncomingInstance: Debug; // contains the CommittedInstance + Witness type MultiCommittedInstanceWithWitness: Debug; // type used for the extra instances in the multi-instance folding setting type CFInstance: Debug; // CycleFold CommittedInstance & Witness + type IVCProof: PartialEq + Eq + Clone + Debug + CanonicalSerialize + CanonicalDeserialize; + + /// deserialize Self::ProverParam and recover the not serialized data that is recomputed on the + /// fly to save serialized bytes. + /// Internally it generates the r1cs/ccs & cf_r1cs needed for the VerifierParams. In this way + /// we avoid needing to serialize them, saving significant space in the VerifierParams + /// serialized size. + fn pp_deserialize_with_mode( + reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, // FCircuit params + ) -> Result; + + /// deserialize Self::VerifierParam and recover the not serialized data that is recomputed on + /// the fly to save serialized bytes. + /// Internally it generates the r1cs/ccs & cf_r1cs needed for the VerifierParams. In this way + /// we avoid needing to serialize them, saving significant space in the VerifierParams + /// serialized size. + fn vp_deserialize_with_mode( + reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + fc_params: FC::Params, // FCircuit params + ) -> Result; fn preprocess( rng: impl RngCore, @@ -149,29 +175,23 @@ where other_instances: Option, ) -> Result<(), Error>; - // returns the state at the current step + /// returns the state at the current step fn state(&self) -> Vec; - // returns the instances at the current step, in the following order: - // (running_instance, incoming_instance, cyclefold_instance) - fn instances( - &self, - ) -> ( - Self::RunningInstance, - Self::IncomingInstance, - Self::CFInstance, - ); + /// returns the last IVC state proof, which can be verified in the `verify` method + fn ivc_proof(&self) -> Self::IVCProof; - fn verify( - vp: Self::VerifierParam, - z_0: Vec, // initial state - z_i: Vec, // last state - // number of steps between the initial state and the last state - num_steps: C1::ScalarField, - running_instance: Self::RunningInstance, - incoming_instance: Self::IncomingInstance, - cyclefold_instance: Self::CFInstance, - ) -> Result<(), Error>; + /// constructs the FoldingScheme instance from the given IVCProof, ProverParams, VerifierParams + /// and PoseidonConfig. + /// This method is useful for when the IVCProof is sent between different parties, so that they + /// can continue iterating the IVC from the received IVCProof. + fn from_ivc_proof( + ivc_proof: Self::IVCProof, + fcircuit_params: FC::Params, + params: (Self::ProverParam, Self::VerifierParam), + ) -> Result; + + fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error>; } /// Trait with auxiliary methods for multi-folding schemes (ie. HyperNova, ProtoGalaxy, etc),