Browse Source

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
update-nifs-interface
Pierre 5 months ago
committed by GitHub
parent
commit
bdfaa66ecb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
7 changed files with 318 additions and 9 deletions
  1. +2
    -2
      .github/workflows/ci.yml
  2. +2
    -1
      folding-schemes/src/ccs/r1cs.rs
  3. +38
    -0
      folding-schemes/src/commitment/kzg.rs
  4. +2
    -1
      folding-schemes/src/commitment/pedersen.rs
  5. +6
    -4
      folding-schemes/src/folding/nova/mod.rs
  6. +266
    -0
      folding-schemes/src/folding/nova/serialize.rs
  7. +2
    -1
      folding-schemes/src/utils/vec.rs

+ 2
- 2
.github/workflows/ci.yml

@ -60,7 +60,7 @@ jobs:
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 @@ jobs:
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

+ 2
- 1
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>,

+ 38
- 0
folding-schemes/src/commitment/kzg.rs

@ -14,6 +14,7 @@ use ark_poly::{
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 @@ pub struct ProverKey<'a, C: CurveGroup> {
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,

+ 2
- 1
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 struct Proof {
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>,

+ 6
- 4
folding-schemes/src/folding/nova/mod.rs

@ -7,11 +7,12 @@ use ark_crypto_primitives::{
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 @@ use traits::NovaR1CS;
#[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 @@ where
}
}
#[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,

+ 266
- 0
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);
}
}

+ 2
- 1
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,

Loading…
Cancel
Save