Add serde capabilites to `Nova` (#107) * feat: `Nova` can be serialized and deserialized * chore: (temp) allow dead code as serde is not yet used * fix: require trait in `where` to not increase restrictions on `CommitmentScheme` * feat: add file with nova serialization methods * fix: change call to get poseidon config and chore: update traits for serde * chore: remove clang install from CI, move tests and remove unnecessary allow * feat: remove serializing r1cs and cs params and provide them at deserialization time * chore: initialize r1cs within deserialization function directly
Pierre pdaixmoreux@gmail.com
Mon, 10 Jun 2024 11:24:01 +0200
7 files changed,
318 insertions(+),
9 deletions(-)
M
.github/workflows/ci.yml
→
.github/workflows/ci.yml
@@ -60,7 +60,7 @@ run: |
curl -sSfL https://github.com/ethereum/solidity/releases/download/v0.8.4/solc-static-linux -o /usr/local/bin/solc chmod +x /usr/local/bin/solc - name: Execute compile.sh to generate .r1cs and .wasm from .circom - run: bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh + run: ./folding-schemes/src/frontend/circom/test_folder/compile.sh - name: Build # This build will be reused by nextest, # and also checks (--all-targets) that benches don't bit-rot@@ -90,7 +90,7 @@ run: |
curl -sSfL https://github.com/ethereum/solidity/releases/download/v0.8.4/solc-static-linux -o /usr/local/bin/solc chmod +x /usr/local/bin/solc - name: Execute compile.sh to generate .r1cs and .wasm from .circom - run: bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh + run: ./folding-schemes/src/frontend/circom/test_folder/compile.sh - name: Run examples tests run: cargo test --examples - name: Run examples
M
folding-schemes/src/ccs/r1cs.rs
→
folding-schemes/src/ccs/r1cs.rs
@@ -4,8 +4,9 @@ use ark_std::rand::Rng;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, SparseMatrix}; use crate::Error; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct R1CS<F: PrimeField> { pub l: usize, // io len pub A: SparseMatrix<F>,
M
folding-schemes/src/commitment/kzg.rs
→
folding-schemes/src/commitment/kzg.rs
@@ -14,6 +14,7 @@ };
use ark_poly_commit::kzg10::{ Commitment as KZG10Commitment, Proof as KZG10Proof, VerifierKey, KZG10, }; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Valid}; use ark_std::rand::RngCore; use ark_std::{borrow::Cow, fmt::Debug}; use ark_std::{One, Zero};@@ -33,6 +34,42 @@ /// Group elements of the form `β^i G`, for different values of `i`.
pub powers_of_g: Cow<'a, [C::Affine]>, } +impl<'a, C: CurveGroup> CanonicalSerialize for ProverKey<'a, C> { + fn serialize_with_mode<W: std::io::prelude::Write>( + &self, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), ark_serialize::SerializationError> { + self.powers_of_g.serialize_with_mode(&mut writer, compress) + } + + fn serialized_size(&self, compress: ark_serialize::Compress) -> usize { + self.powers_of_g.serialized_size(compress) + } +} + +impl<'a, C: CurveGroup> CanonicalDeserialize for ProverKey<'a, C> { + fn deserialize_with_mode<R: std::io::prelude::Read>( + reader: R, + compress: ark_serialize::Compress, + validate: ark_serialize::Validate, + ) -> Result<Self, ark_serialize::SerializationError> { + let powers_of_g_vec = Vec::deserialize_with_mode(reader, compress, validate)?; + Ok(ProverKey { + powers_of_g: ark_std::borrow::Cow::Owned(powers_of_g_vec), + }) + } +} + +impl<'a, C: CurveGroup> Valid for ProverKey<'a, C> { + fn check(&self) -> Result<(), ark_serialize::SerializationError> { + match self.powers_of_g.clone() { + Cow::Borrowed(powers) => powers.to_vec().check(), + Cow::Owned(powers) => powers.check(), + } + } +} + #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct Proof<C: CurveGroup> { pub eval: C::ScalarField,@@ -45,6 +82,7 @@ pub struct KZG<'a, E: Pairing, const H: bool = false> {
_a: PhantomData<&'a ()>, _e: PhantomData<E>, } + impl<'a, E, const H: bool> CommitmentScheme<E::G1, H> for KZG<'a, E, H> where E: Pairing,
M
folding-schemes/src/commitment/pedersen.rs
→
folding-schemes/src/commitment/pedersen.rs
@@ -2,6 +2,7 @@ use ark_ec::CurveGroup;
use ark_ff::Field; use ark_r1cs_std::{boolean::Boolean, groups::GroupOpsBounds, prelude::CurveVar}; use ark_relations::r1cs::SynthesisError; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::Zero; use ark_std::{rand::RngCore, UniformRand}; use core::marker::PhantomData;@@ -18,7 +19,7 @@ pub u: Vec<C::ScalarField>,
pub r_u: C::ScalarField, // blind } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Params<C: CurveGroup> { pub h: C, pub generators: Vec<C::Affine>,
M
folding-schemes/src/folding/nova/mod.rs
→
folding-schemes/src/folding/nova/mod.rs
@@ -7,11 +7,12 @@ };
use ark_ec::{AffineRepr, CurveGroup, Group}; use ark_ff::{BigInteger, Field, PrimeField, ToConstraintField}; use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::fmt::Debug; use ark_std::{One, Zero}; use core::marker::PhantomData; - -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; +use std::usize; use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS}; use crate::commitment::CommitmentScheme;@@ -31,6 +32,7 @@ pub mod cyclefold;
pub mod decider_eth; pub mod decider_eth_circuit; pub mod nifs; +pub mod serialize; pub mod traits; use circuits::{AugmentedFCircuit, ChallengeGadget};@@ -41,7 +43,7 @@
#[cfg(test)] use cyclefold::CF_IO_LEN; -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct CommittedInstance<C: CurveGroup> { pub cmE: C, pub u: C::ScalarField,@@ -148,7 +150,7 @@ .map_err(|e| Error::Other(e.to_string()))
} } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct Witness<C: CurveGroup> { pub E: Vec<C::ScalarField>, pub rE: C::ScalarField,
A
folding-schemes/src/folding/nova/serialize.rs
@@ -0,0 +1,266 @@
+use super::{circuits::AugmentedFCircuit, cyclefold::CycleFoldCircuit, Nova, ProverParams}; +pub use super::{CommittedInstance, Witness}; +pub use crate::folding::circuits::CF2; +use crate::{ + ccs::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1, + frontend::FCircuit, +}; +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; + +impl<C1, GC1, C2, GC2, FC, CS1, CS2> CanonicalSerialize for Nova<C1, GC1, C2, GC2, FC, CS1, CS2> +where + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit<C1::ScalarField>, + CS1: CommitmentScheme<C1>, + CS2: CommitmentScheme<C2>, + <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.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.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> Nova<C1, GC1, C2, GC2, FC, CS1, CS2> +where + C1: CurveGroup, + C2: CurveGroup, + FC: FCircuit<CF1<C1>, Params = ()>, + CS1: CommitmentScheme<C1>, + CS2: CommitmentScheme<C2>, + <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>, + poseidon_config: PoseidonConfig<C1::ScalarField>, + ) -> Result<Self, ark_serialize::SerializationError> { + 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 = CycleFoldCircuit::<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, + cs_params: prover_params.cs_params, + cf_cs_params: prover_params.cf_cs_params, + i, + z_0, + z_i, + w_i, + u_i, + W_i, + U_i, + cf_W_i, + cf_U_i, + r1cs, + cf_r1cs, + poseidon_config, + F: f_circuit, + }) + } +} + +#[cfg(test)] +pub mod tests { + use crate::{ + commitment::{ + kzg::{ProverKey as KZGProverKey, KZG}, + pedersen::Pedersen, + CommitmentScheme, + }, + folding::nova::{get_cs_params_len, Nova, ProverParams}, + frontend::{tests::CubicFCircuit, FCircuit}, + transcript::poseidon::poseidon_canonical_config, + FoldingScheme, + }; + use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective}; + use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2}; + use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey; + use ark_serialize::{CanonicalSerialize, Compress, Validate}; + use std::{fs, io::Write}; + + #[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(); + let (cs_len, cf_cs_len) = + get_cs_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>( + &poseidon_config, + F_circuit, + ) + .unwrap(); + let (kzg_pk, _): (KZGProverKey<Projective>, KZGVerifierKey<Bn254>) = + KZG::<Bn254>::setup(&mut rng, cs_len).unwrap(); + let (cf_pedersen_params, _) = Pedersen::<Projective2>::setup(&mut rng, cf_cs_len).unwrap(); + + // Initialize nova and make multiple `prove_step()` + type NOVA<CS1, CS2> = + Nova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2>; + let prover_params = + ProverParams::<Projective, Projective2, KZG<Bn254>, Pedersen<Projective2>> { + poseidon_config: poseidon_config.clone(), + cs_params: kzg_pk.clone(), + cf_cs_params: cf_pedersen_params.clone(), + }; + + let z_0 = vec![Fr::from(3_u32)]; + let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap(); + + let num_steps: usize = 3; + for _ in 0..num_steps { + nova.prove_step(vec![]).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>, + >::deserialize_nova( + bytes.as_slice(), + Compress::No, + Validate::No, + 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(vec![]).unwrap(); + nova.prove_step(vec![]).unwrap(); + } + + assert_eq!(deserialized_nova.w_i, nova.w_i); + } +}
M
folding-schemes/src/utils/vec.rs
→
folding-schemes/src/utils/vec.rs
@@ -3,13 +3,14 @@ use ark_poly::{
univariate::DensePolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, }; pub use ark_relations::r1cs::Matrix as R1CSMatrix; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::cfg_iter; use ark_std::rand::Rng; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use crate::Error; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)] pub struct SparseMatrix<F: PrimeField> { pub n_rows: usize, pub n_cols: usize,