Browse Source

Add IVCProof to the existing folding schemes (Nova,HyperNova,ProtoGalaxy) (#167)

* Add IVCProof to the existing folding schemes (Nova,HyperNova,ProtoGalaxy)

* Implement `from_ivc_proof` for the FoldingSchemes trait (and Nova,
HyperNova, ProtoGalaxy), so that the FoldingScheme IVC's instance can be
constructed from the given parameters and the last IVCProof, which
allows to sent the IVCProof between different parties, so that they can
continue iterating the IVC from the received IVCProof.  Also the
serializers allow for the IVCProof to be sent to a verifier that can
deserialize it and verify it.

This allows to remove the logic from the file
[folding/nova/serialize.rs](f1d82418ba/folding-schemes/src/folding/nova/serialize.rs)
and
[folding/hypernova/serialize.rs](f1d82418ba/folding-schemes/src/folding/hypernova/serialize.rs)
(removing the whole files), which is now covered by the `IVCProof`
generated serializers (generated by macro instead of handwritten), and
the test that the file contained is now abstracted and applied to all
the 3 existing folding schemes (Nova, HyperNova, ProtoGalaxy) at the
folding/mod.rs file.

* update Nova VerifierParams serializers to avoid serializing the R1CS to save big part of the old serialized size

* rm .instances() since it's not needed

* add nova params serialization to nova's ivc test to ensure that IVC verification works with deserialized data

* Add unified FS::ProverParam & VerifierParam serialization & deserialization (for all Nova, HyperNova and ProtoGalaxy), without serializing the R1CS/CCS and thus saving substantial serialized bytes space.

* rm CanonicalDeserialize warnings msgs for VerifierParams
main
arnaucube 1 month ago
committed by GitHub
parent
commit
cb1b8e37aa
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
20 changed files with 1021 additions and 1027 deletions
  1. +2
    -0
      .gitignore
  2. +10
    -1
      examples/circom_full_flow.rs
  3. +3
    -9
      examples/external_inputs.rs
  4. +3
    -9
      examples/multi_inputs.rs
  5. +9
    -1
      examples/noir_full_flow.rs
  6. +10
    -1
      examples/noname_full_flow.rs
  7. +3
    -9
      examples/sha256.rs
  8. +1
    -1
      folding-schemes/src/folding/hypernova/cccs.rs
  9. +5
    -21
      folding-schemes/src/folding/hypernova/decider_eth.rs
  10. +3
    -16
      folding-schemes/src/folding/hypernova/decider_eth_circuit.rs
  11. +276
    -99
      folding-schemes/src/folding/hypernova/mod.rs
  12. +0
    -420
      folding-schemes/src/folding/hypernova/serialize.rs
  13. +167
    -0
      folding-schemes/src/folding/mod.rs
  14. +3
    -13
      folding-schemes/src/folding/nova/decider_circuits.rs
  15. +9
    -9
      folding-schemes/src/folding/nova/decider_eth.rs
  16. +3
    -13
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  17. +220
    -75
      folding-schemes/src/folding/nova/mod.rs
  18. +0
    -268
      folding-schemes/src/folding/nova/serialize.rs
  19. +250
    -38
      folding-schemes/src/folding/protogalaxy/mod.rs
  20. +44
    -24
      folding-schemes/src/lib.rs

+ 2
- 0
.gitignore

@ -14,3 +14,5 @@ solidity-verifiers/generated
examples/*.sol
examples/*.calldata
examples/*.inputs
*.serialized
*/*.serialized

+ 10
- 1
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());

+ 3
- 9
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();
}

+ 3
- 9
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();
}

+ 9
- 1
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();

+ 10
- 1
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());

+ 3
- 9
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();
}

+ 1
- 1
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<C: CurveGroup> {
// Commitment to witness
pub C: C,

+ 5
- 21
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<Projective2>,
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<Projective2>,
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();

+ 3
- 16
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::<

+ 276
- 99
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<CCS<C1::ScalarField>>,
}
impl<
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&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<C1, C2, CS1, CS2, const H: bool> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&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<C1, C2, CS1, CS2, const H: bool> VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
@ -157,6 +200,23 @@ where
}
}
#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)]
pub struct IVCProof<C1, C2>
where
C1: CurveGroup,
C2: CurveGroup,
{
pub i: C1::ScalarField,
pub z_0: Vec<C1::ScalarField>,
pub z_i: Vec<C1::ScalarField>,
pub W_i: Witness<C1::ScalarField>,
pub U_i: LCCCS<C1>,
pub w_i: Witness<C1::ScalarField>,
pub u_i: CCCS<C1>,
pub cf_W_i: CycleFoldWitness<C2>,
pub cf_U_i: CycleFoldCommittedInstance<C2>,
}
/// 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<Self::RunningInstance>, Vec<Self::IncomingInstance>);
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
type IVCProof = IVCProof<C1, C2>;
fn pp_deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params,
) -> Result<Self::ProverParam, Error> {
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
// 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::<C1, C2, GC2, FC, MU, NU>::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<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params,
) -> Result<Self::VerifierParam, Error> {
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
// 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::<C1, C2, GC2, FC, MU, NU>::empty(
&poseidon_config,
f_circuit.clone(),
None,
)?;
let ccs = augmented_F_circuit.ccs;
// CycleFold circuit R1CS
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(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::<C1::ScalarField>::new(&self.poseidon_config);
let other_instances = other_instances.ok_or(Error::MissingOtherInstances)?;
#[allow(clippy::type_complexity)]
let (lcccs, cccs): (
Vec<(LCCCS<C1>, Witness<C1::ScalarField>)>,
Vec<(CCCS<C1>, Witness<C1::ScalarField>)>,
) = 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<C1>, Witness<C1::ScalarField>)>,
Vec<(CCCS<C1>, Witness<C1::ScalarField>)>,
) = 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<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) = lcccs.into_iter().unzip();
let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip();
let (Us, Ws): (Vec<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) =
lcccs.into_iter().unzip();
let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip();
(Some(Us), Some(Ws), Some(us), Some(ws))
} else {
(None, None, None, None)
};
let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>;
@ -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<C1::ScalarField> =
PoseidonSponge::<C1::ScalarField>::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::<C1, PoseidonSponge<C1::ScalarField>>::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::<Vec<_>>(),
all_us.iter().map(|us_i| us_i.C).collect::<Vec<_>>(),
]
.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<Self, Error> {
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::<C1, C2, GC2, FC, MU, NU>::empty(
&pp.poseidon_config,
f_circuit.clone(),
None,
)?;
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
let ccs = augmented_f_circuit.ccs.clone();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(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<C1::ScalarField>, // initial state
z_i: Vec<C1::ScalarField>, // 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::<C1::ScalarField>::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<Fr>,
F_circuit: CubicFCircuit<Fr>,
) -> (
HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, 2, 3, H>,
(
ProverParams<Projective, Projective2, CS1, CS2, H>,
VerifierParams<Projective, Projective2, CS1, CS2, H>,
),
(LCCCS<Projective>, Witness<Fr>),
(CCCS<Projective>, Witness<Fr>),
(
CycleFoldCommittedInstance<Projective2>,
CycleFoldWitness<Projective2>,
),
) {
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,
)
}
}

+ 0
- 420
folding-schemes/src/folding/hypernova/serialize.rs

@ -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<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
CanonicalSerialize for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn serialize_compressed<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField, Params = ()>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
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<R: std::io::prelude::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
poseidon_config: PoseidonConfig<C1::ScalarField>,
cs_pp: CS1::ProverParams,
cs_vp: CS1::VerifierParams,
cf_cs_pp: CS2::ProverParams,
cf_cs_vp: CS2::VerifierParams,
) -> Result<Self, SerializationError> {
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::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let z_i = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let W_i =
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let U_i = LCCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let w_i =
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let u_i = CCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_W_i =
CycleFoldWitness::<C2>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_U_i = CycleFoldCommittedInstance::<C2>::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<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
{
fn serialize_compressed<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> ProverParams<C1, C2, CS1, CS2, H>
{
pub fn deserialize_prover_params<R: std::io::prelude::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
ccs: &Option<CCS<C1::ScalarField>>,
poseidon_config: &PoseidonConfig<C1::ScalarField>,
) -> Result<Self, SerializationError> {
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<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
{
fn serialize_compressed<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<W: std::io::prelude::Write>(
&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<C1, H>,
CS2: CommitmentScheme<C2, H>,
const H: bool,
> VerifierParams<C1, C2, CS1, CS2, H>
{
pub fn deserialize_verifier_params<R: std::io::Read>(
mut reader: R,
compress: Compress,
validate: Validate,
ccs: &CCS<C1::ScalarField>,
poseidon_config: &PoseidonConfig<C1::ScalarField>,
) -> Result<Self, SerializationError> {
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::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let (mut hn, (_, verifier_params), _, _, _) = test_ivc_opt::<
KZG<Bn254>,
Pedersen<Projective2>,
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<Fr>,
KZG<Bn254>,
Pedersen<Projective2>,
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);
}
}

+ 167
- 0
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::<Fr>();
type FC = CubicFCircuit<Fr>;
let f_circuit = FC::new(()).unwrap();
// test Nova
type N = Nova<G1, GVar1, G2, GVar2, FC, Pedersen<G1>, Pedersen<G2>, false>;
let prep_param = NovaPreprocessorParam::new(poseidon_config.clone(), f_circuit);
test_serialize_ivc_opt::<G1, G2, FC, N>("nova".to_string(), prep_param.clone()).unwrap();
// test HyperNova
type HN = HyperNova<
G1,
GVar1,
G2,
GVar2,
FC,
Pedersen<G1>,
Pedersen<G2>,
1, // mu
1, // nu
false,
>;
test_serialize_ivc_opt::<G1, G2, FC, HN>("hypernova".to_string(), prep_param).unwrap();
// test ProtoGalaxy
type P = ProtoGalaxy<G1, GVar1, G2, GVar2, FC, Pedersen<G1>, Pedersen<G2>>;
let prep_param = (poseidon_config, f_circuit);
test_serialize_ivc_opt::<G1, G2, FC, P>("protogalaxy".to_string(), prep_param).unwrap();
}
fn test_serialize_ivc_opt<
C1: CurveGroup,
C2: CurveGroup,
FC: FCircuit<C1::ScalarField, Params = ()>,
FS: FoldingScheme<C1, C2, FC>,
>(
name: String,
prep_param: FS::PreprocessorParam,
) -> Result<(), Error>
where
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
C2::BaseField: PrimeField,
FC: FCircuit<C1::ScalarField>,
{
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(())
}
}

+ 3
- 13
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 =

+ 9
- 9
folding-schemes/src/folding/nova/decider_eth.rs

@ -75,8 +75,8 @@ impl DeciderTrait
for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS>
where
C1: CurveGroup,
C2: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
// 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 = <N as FoldingScheme<
Projective,
Projective2,
KZG<'static, Bn254>,
Pedersen<Projective2>,
>::deserialize_compressed(
&mut nova_vp_serialized.as_slice()
CubicFCircuit<Fr>,
>>::vp_deserialize_with_mode(
&mut nova_vp_serialized.as_slice(),
ark_serialize::Compress::Yes,
ark_serialize::Validate::Yes,
(), // fcircuit_params
)
.unwrap();

+ 3
- 13
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::<

+ 220
- 75
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<C2, H>,
{
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<C1, C2, CS1, CS2, const H: bool> CanonicalDeserialize for VerifierParams<C1, C2, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
fn deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
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::<C1::ScalarField>(),
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<C1, C2>
where
C1: CurveGroup,
C2: CurveGroup,
{
// current step of the IVC
pub i: C1::ScalarField,
// initial state
pub z_0: Vec<C1::ScalarField>,
// current state
pub z_i: Vec<C1::ScalarField>,
// running instance
pub W_i: Witness<C1>,
pub U_i: CommittedInstance<C1>,
// incoming instance
pub w_i: Witness<C1>,
pub u_i: CommittedInstance<C1>,
// CycleFold instances
pub cf_W_i: CycleFoldWitness<C2>,
pub cf_U_i: CycleFoldCommittedInstance<C2>,
}
/// 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<C1>, Witness<C1>);
type MultiCommittedInstanceWithWitness = ();
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
type IVCProof = IVCProof<C1, C2>;
fn pp_deserialize_with_mode<R: std::io::prelude::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
_fc_params: FC::Params, // FCircuit params
) -> Result<Self::ProverParam, Error> {
Ok(Self::ProverParam::deserialize_with_mode(
reader, compress, validate,
)?)
}
fn vp_deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params,
) -> Result<Self::VerifierParam, Error> {
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
// 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::<C1::ScalarField>::new_ref();
let augmented_F_circuit =
AugmentedFCircuit::<C1, C2, GC2, FC>::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::<C1::ScalarField>(&cs);
// CycleFold circuit R1CS
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
cf_circuit.generate_constraints(cs2.clone())?;
cs2.finalize();
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let cf_r1cs = extract_r1cs::<C1::BaseField>(&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<C1, C2>,
fcircuit_params: FC::Params,
params: (Self::ProverParam, Self::VerifierParam),
) -> Result<Self, Error> {
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::<C1::ScalarField>::new_ref();
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let augmented_F_circuit =
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&pp.poseidon_config, f_circuit.clone());
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
augmented_F_circuit.generate_constraints(cs.clone())?;
cs.finalize();
let cs = cs.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let r1cs = extract_r1cs::<C1::ScalarField>(&cs);
cf_circuit.generate_constraints(cs2.clone())?;
cs2.finalize();
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let cf_r1cs = extract_r1cs::<C1::BaseField>(&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<C1::ScalarField>, // initial state
z_i: Vec<C1::ScalarField>, // 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::<C1::ScalarField>::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::<Projective, Projective2, CS1, CS2, H>::deserialize_compressed(
&mut nova_pp_serialized.as_slice(),
)
.unwrap();
let nova_vp_deserialized = Nova::<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
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 = <Nova::<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
CS1,
CS2,
H,
> as FoldingScheme<Projective,Projective2, CubicFCircuit<Fr>>>::IVCProof::deserialize_compressed(
ivc_proof_serialized.as_slice()
)
.unwrap();
// verify the deserialized IVCProof with the deserialized VerifierParams
Nova::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, 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();

+ 0
- 268
folding-schemes/src/folding/nova/serialize.rs

@ -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<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> CanonicalSerialize
for Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
GC1: CurveVar<C1, <C2 as Group>::ScalarField>,
GC1: ToConstraintFieldGadget<<C2 as Group>::ScalarField>,
GC2: CurveVar<C2, <C2 as CurveGroup>::BaseField>,
{
fn serialize_with_mode<W: Write>(
&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<W: Write>(
&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<W: Write>(
&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<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> Nova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
where
C1: CurveGroup,
C2: CurveGroup,
FC: FCircuit<CF1<C1>, Params = ()>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
<C1 as Group>::ScalarField: Absorb,
<C2 as Group>::ScalarField: Absorb,
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
GC1: CurveVar<C1, <C2 as Group>::ScalarField>,
GC1: ToConstraintFieldGadget<<C2 as Group>::ScalarField>,
GC2: CurveVar<C2, CF2<C2>>,
GC2: ToConstraintFieldGadget<<C2 as CurveGroup>::BaseField>,
{
pub fn deserialize_nova<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
prover_params: ProverParams<C1, C2, CS1, CS2, H>,
poseidon_config: PoseidonConfig<C1::ScalarField>,
) -> Result<Self, ark_serialize::SerializationError> {
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::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let z_i = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
let w_i = Witness::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let u_i = CommittedInstance::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let W_i = Witness::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let U_i = CommittedInstance::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_W_i = Witness::<C2>::deserialize_with_mode(&mut reader, compress, validate)?;
let cf_U_i =
CommittedInstance::<C2>::deserialize_with_mode(&mut reader, compress, validate)?;
let f_circuit = FC::new(()).unwrap();
let cs = ConstraintSystem::<C1::ScalarField>::new_ref();
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let augmented_F_circuit =
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&poseidon_config, f_circuit.clone());
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::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::<C1::ScalarField>(&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::<C1::BaseField>(&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::<Fr>();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
// Initialize nova and make multiple `prove_step()`
type N = Nova<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
KZG<'static, Bn254>,
Pedersen<Projective2>,
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<Fr>,
KZG<Bn254>,
Pedersen<Projective2>,
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);
}
}

+ 250
- 38
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
/// We use `TYPE` to distinguish between incoming and running instances, as
/// they have slightly different structures (e.g., length of `betas`) and
/// behaviors (e.g., in satisfiability checks).
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CommittedInstance<C: CurveGroup, const TYPE: bool> {
phi: C,
betas: Vec<C::ScalarField>,
@ -207,7 +209,7 @@ impl CommittedInstanceVarOps for CommittedIn
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Witness<F: PrimeField> {
w: Vec<F>,
r_w: F,
@ -313,6 +315,68 @@ where
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_params: CS2::ProverParams,
}
impl<C1, C2, CS1, CS2> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, false>,
CS2: CommitmentScheme<C2, false>,
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&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<C1, C2, CS1, CS2> Valid for ProverParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
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<C1, C2, CS1, CS2> CanonicalDeserialize for ProverParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1, false>,
CS2: CommitmentScheme<C2, false>,
{
fn deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
) -> Result<Self, ark_serialize::SerializationError> {
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::<C1::ScalarField>(),
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<C1, C2, CS1, CS2> Valid for VerifierParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
fn check(&self) -> Result<(), ark_serialize::SerializationError> {
self.cs_vp.check()?;
self.cf_cs_vp.check()?;
Ok(())
}
}
impl<C1, C2, CS1, CS2> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
C2: CurveGroup,
CS1: CommitmentScheme<C1>,
CS2: CommitmentScheme<C2>,
{
fn serialize_with_mode<W: std::io::prelude::Write>(
&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<C1, C2, CS1, CS2> VerifierParams<C1, C2, CS1, CS2>
where
C1: CurveGroup,
@ -357,6 +455,23 @@ where
}
}
#[derive(PartialEq, Eq, Debug, Clone, CanonicalSerialize, CanonicalDeserialize)]
pub struct IVCProof<C1, C2>
where
C1: CurveGroup,
C2: CurveGroup,
{
pub i: C1::ScalarField,
pub z_0: Vec<C1::ScalarField>,
pub z_i: Vec<C1::ScalarField>,
pub W_i: Witness<C1::ScalarField>,
pub U_i: CommittedInstance<C1, true>,
pub w_i: Witness<C1::ScalarField>,
pub u_i: CommittedInstance<C1, false>,
pub cf_W_i: CycleFoldWitness<C2>,
pub cf_U_i: CycleFoldCommittedInstance<C2>,
}
/// Implements ProtoGalaxy+CycleFold's IVC, described in [ProtoGalaxy] and
/// [CycleFold], following the FoldingScheme trait
///
@ -536,6 +651,68 @@ where
type MultiCommittedInstanceWithWitness =
(CommittedInstance<C1, false>, Witness<C1::ScalarField>);
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
type IVCProof = IVCProof<C1, C2>;
fn pp_deserialize_with_mode<R: std::io::prelude::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
_fc_params: FC::Params, // FCircuit params
) -> Result<Self::ProverParam, Error> {
Ok(Self::ProverParam::deserialize_with_mode(
reader, compress, validate,
)?)
}
fn vp_deserialize_with_mode<R: std::io::prelude::Read>(
mut reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params,
) -> Result<Self::VerifierParam, Error> {
let poseidon_config = poseidon_canonical_config::<C1::ScalarField>();
// 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::<C1::ScalarField>::new_ref();
let augmented_F_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::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::<C1::ScalarField>(&cs);
// CycleFold circuit R1CS
let cs2 = ConstraintSystem::<C1::BaseField>::new_ref();
let cf_circuit = ProtoGalaxyCycleFoldCircuit::<C1, GC1>::empty();
cf_circuit.generate_constraints(cs2.clone())?;
cs2.finalize();
let cs2 = cs2.into_inner().ok_or(Error::NoInnerConstraintSystem)?;
let cf_r1cs = extract_r1cs::<C1::BaseField>(&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<C1::ScalarField> {
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<Self, Error> {
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<C1::ScalarField>, // initial state
z_i: Vec<C1::ScalarField>, // last state
num_steps: C1::ScalarField,
running_instance: Self::RunningInstance,
incoming_instance: Self::IncomingInstance,
cyclefold_instance: Self::CFInstance,
) -> Result<(), Error> {
let sponge = PoseidonSponge::<C1::ScalarField>::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::<C1::ScalarField>::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::<CS1, CS2>::verify(
params.1,
z_0,
protogalaxy.z_i,
protogalaxy.i,
running_instance,
incoming_instance,
cyclefold_instance,
)
.unwrap();
let ivc_proof = protogalaxy.ivc_proof();
PG::<CS1, CS2>::verify(params.1, ivc_proof).unwrap();
}
#[ignore]

+ 44
- 24
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<C1::ScalarField>,
{
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<R: std::io::prelude::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params, // FCircuit params
) -> Result<Self::ProverParam, Error>;
/// 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<R: std::io::prelude::Read>(
reader: R,
compress: ark_serialize::Compress,
validate: ark_serialize::Validate,
fc_params: FC::Params, // FCircuit params
) -> Result<Self::VerifierParam, Error>;
fn preprocess(
rng: impl RngCore,
@ -149,29 +175,23 @@ where
other_instances: Option<Self::MultiCommittedInstanceWithWitness>,
) -> Result<(), Error>;
// returns the state at the current step
/// returns the state at the current step
fn state(&self) -> Vec<C1::ScalarField>;
// 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<C1::ScalarField>, // initial state
z_i: Vec<C1::ScalarField>, // 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<Self, Error>;
fn verify(vp: Self::VerifierParam, ivc_proof: Self::IVCProof) -> Result<(), Error>;
}
/// Trait with auxiliary methods for multi-folding schemes (ie. HyperNova, ProtoGalaxy, etc),

Loading…
Cancel
Save