diff --git a/.gitignore b/.gitignore index d38766b0..089ab2a1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ solidity-verifiers/generated examples/*.sol examples/*.calldata examples/*.inputs +*.serialized 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..2843610c 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth.rs @@ -377,7 +377,7 @@ pub mod tests { KZG<'static, Bn254>, Pedersen, false, - >::deserialize_prover_params( + >::deserialize_with_mode( hypernova_pp_serialized.as_slice(), Compress::Yes, Validate::No, diff --git a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs index 1485f8b2..256b0cb0 100644 --- a/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs +++ b/folding-schemes/src/folding/hypernova/decider_eth_circuit.rs @@ -582,9 +582,7 @@ 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_proof = hypernova.ivc_proof(); HN::verify(hn_params.1, ivc_proof).unwrap(); diff --git a/folding-schemes/src/folding/hypernova/mod.rs b/folding-schemes/src/folding/hypernova/mod.rs index 748c8ee9..76698a85 100644 --- a/folding-schemes/src/folding/hypernova/mod.rs +++ b/folding-schemes/src/folding/hypernova/mod.rs @@ -6,7 +6,9 @@ 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, Validate, +}; use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero}; pub mod cccs; @@ -15,7 +17,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; @@ -117,6 +118,108 @@ 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) + } +} + +impl< + C1: CurveGroup, + C2: CurveGroup, + CS1: CommitmentScheme, + CS2: CommitmentScheme, + const H: bool, + > ProverParams +{ + pub fn deserialize_with_mode( + 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_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, + }) + } +} + /// Verification parameters for HyperNova-based IVC #[derive(Debug, Clone)] pub struct VerifierParams< @@ -157,7 +260,7 @@ where } } -#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct IVCProof where C1: CurveGroup, @@ -584,36 +687,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; @@ -691,9 +800,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), @@ -709,14 +818,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 @@ -743,12 +869,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), @@ -760,10 +886,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(), ), @@ -804,9 +928,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), @@ -895,6 +1019,58 @@ where } } + 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, ivc_proof: Self::IVCProof) -> Result<(), Error> { 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..91855b53 100644 --- a/folding-schemes/src/folding/mod.rs +++ b/folding-schemes/src/folding/mod.rs @@ -3,3 +3,139 @@ 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; + + #[test] + fn test_serialize_ivc() { + let poseidon_config = poseidon_canonical_config::(); + let f_circuit = CubicFCircuit::::new(()).unwrap(); + + // test Nova + type N = Nova, Pedersen, Pedersen, false>; + let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit); + test_serialize_ivc_opt::, N>( + "nova".to_string(), + prep_param.clone(), + ) + .unwrap(); + + // test HyperNova + type HN = HyperNova< + G1, + GVar1, + G2, + GVar2, + CubicFCircuit, + Pedersen, + Pedersen, + 1, // mu + 1, // nu + false, + >; + test_serialize_ivc_opt::, HN>( + "hypernova".to_string(), + prep_param, + ) + .unwrap(); + + // test ProtoGalaxy + type P = ProtoGalaxy, Pedersen, Pedersen>; + let prep_param = (poseidon_config, f_circuit); + test_serialize_ivc_opt::, P>( + "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(); + + // 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()); + + // verify the last IVCProof from the recovered from serialization FS + let ivc_proof: FS::IVCProof = new_fs.ivc_proof(); + FS::verify(fs_params.1.clone(), ivc_proof.clone()).unwrap(); + + Ok(()) + } +} diff --git a/folding-schemes/src/folding/nova/mod.rs b/folding-schemes/src/folding/nova/mod.rs index 049a56ab..2b2ac284 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; @@ -429,7 +428,7 @@ where } } -#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct IVCProof where C1: CurveGroup, @@ -926,6 +925,63 @@ where } } + 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, ivc_proof: Self::IVCProof) -> Result<(), Error> { 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 d967423e..d12c9041 100644 --- a/folding-schemes/src/folding/protogalaxy/mod.rs +++ b/folding-schemes/src/folding/protogalaxy/mod.rs @@ -208,7 +208,7 @@ impl CommittedInstanceVarOps for CommittedIn } } -#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness { w: Vec, r_w: F, @@ -358,7 +358,7 @@ where } } -#[derive(Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct IVCProof where C1: CurveGroup, @@ -945,6 +945,49 @@ where } } + 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 Pro.clone()toGalaxy+CycleFold fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error> { let Self::IVCProof { diff --git a/folding-schemes/src/lib.rs b/folding-schemes/src/lib.rs index d1d7e9d3..6909cd8b 100644 --- a/folding-schemes/src/lib.rs +++ b/folding-schemes/src/lib.rs @@ -108,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 @@ -131,7 +131,7 @@ where 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: Debug + CanonicalSerialize + CanonicalDeserialize; + type IVCProof: PartialEq + Eq + Clone + Debug + CanonicalSerialize + CanonicalDeserialize; fn preprocess( rng: impl RngCore, @@ -167,6 +167,16 @@ where /// returns the last IVC state proof, which can be verified in the `verify` method fn ivc_proof(&self) -> Self::IVCProof; + /// 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>; }