Protogalaxy based IVC (#123)

* Parallelize vector and matrix operations

* Implement convenient methods for `NonNativeAffineVar`

* Return `L_X_evals` and intermediate `phi_star`s from ProtoGalaxy prover.

These values will be used as hints to the augmented circuit

* Correctly use number of variables, number of constraints, and `t`

* Fix the size of `F_coeffs` and `K_coeffs` for in-circuit consistency

* Improve prover's performance

* Make `prepare_inputs` generic

* Remove redundant parameters in verifier

* Move `eval_f` to arith

* `u` is unnecessary in ProtoGalaxy

* Convert `RelaxedR1CS` to a trait that can be used in both Nova and ProtoGalaxy

* Implement several traits for ProtoGalaxy

* Move `FCircuit` impls to `utils.rs` and add `DummyCircuit`

* `AugmentedFCircuit` and ProtoGalaxy-based IVC

* Add explanations about IVC prover and in-circuit operations

* Avoid using unstable features

* Rename `PROTOGALAXY` to `PG` to make clippy happy

* Fix merge conflicts in `RelaxedR1CS::sample`

* Fix merge conflicts in `CycleFoldCircuit`

* Swap `m` and `n` for protogalaxy

* Add `#[cfg(test)]` to test-only util circuits

* Prefer unit struct over empty struct

* Add documents to `AugmentedFCircuit` for ProtoGalaxy

* Fix the names for CycleFold cricuits in ProtoGalaxy

* Fix usize conversion when targeting wasm

* Restrict the visibility of fields in `AugmentedFCircuit` to `pub(super)`

* Make CycleFold circuits and configs public

* Add docs for `ProverParams` and `VerifierParams`

* Refactor `pow_i`

* Fix imports

* Remove lint reasons

* Fix type inference
This commit is contained in:
winderica
2024-09-12 15:08:53 +01:00
committed by GitHub
parent 0ad54576ec
commit 1322767a1e
26 changed files with 2222 additions and 695 deletions

View File

@@ -237,31 +237,31 @@ pub struct AugmentedFCircuit<
> where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
pub _gc2: PhantomData<GC2>,
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub pp_hash: Option<CF1<C1>>,
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
pub z_i: Option<Vec<C1::ScalarField>>,
pub external_inputs: Option<Vec<C1::ScalarField>>,
pub u_i_cmW: Option<C1>,
pub U_i: Option<CommittedInstance<C1>>,
pub U_i1_cmE: Option<C1>,
pub U_i1_cmW: Option<C1>,
pub cmT: Option<C1>,
pub F: FC, // F circuit
pub x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
pub(super) _gc2: PhantomData<GC2>,
pub(super) poseidon_config: PoseidonConfig<CF1<C1>>,
pub(super) pp_hash: Option<CF1<C1>>,
pub(super) i: Option<CF1<C1>>,
pub(super) i_usize: Option<usize>,
pub(super) z_0: Option<Vec<C1::ScalarField>>,
pub(super) z_i: Option<Vec<C1::ScalarField>>,
pub(super) external_inputs: Option<Vec<C1::ScalarField>>,
pub(super) u_i_cmW: Option<C1>,
pub(super) U_i: Option<CommittedInstance<C1>>,
pub(super) U_i1_cmE: Option<C1>,
pub(super) U_i1_cmW: Option<C1>,
pub(super) cmT: Option<C1>,
pub(super) F: FC, // F circuit
pub(super) x: Option<CF1<C1>>, // public input (u_{i+1}.x[0])
// cyclefold verifier on C1
// Here 'cf1, cf2' are for each of the CycleFold circuits, corresponding to the fold of cmW and
// cmE respectively
pub cf1_u_i_cmW: Option<C2>, // input
pub cf2_u_i_cmW: Option<C2>, // input
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
pub cf1_cmT: Option<C2>,
pub cf2_cmT: Option<C2>,
pub cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
pub(super) cf1_u_i_cmW: Option<C2>, // input
pub(super) cf2_u_i_cmW: Option<C2>, // input
pub(super) cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input
pub(super) cf1_cmT: Option<C2>,
pub(super) cf2_cmT: Option<C2>,
pub(super) cf_x: Option<CF1<C1>>, // public input (u_{i+1}.x[1])
}
impl<C1: CurveGroup, C2: CurveGroup, GC2: CurveVar<C2, CF2<C2>>, FC: FCircuit<CF1<C1>>>

View File

@@ -342,7 +342,7 @@ pub mod tests {
use crate::folding::nova::{
PreprocessorParam, ProverParams as NovaProverParams, VerifierParams as NovaVerifierParams,
};
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
#[test]

View File

@@ -577,6 +577,8 @@ where
#[cfg(test)]
pub mod tests {
use std::cmp::max;
use ark_crypto_primitives::crh::{
sha256::{
constraints::{Sha256Gadget, UnitVar},
@@ -587,34 +589,61 @@ pub mod tests {
use ark_pallas::{constraints::GVar, Fq, Fr, Projective};
use ark_r1cs_std::bits::uint8::UInt8;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::{One, UniformRand};
use ark_std::{
rand::{thread_rng, Rng},
One, UniformRand,
};
use ark_vesta::{constraints::GVar as GVar2, Projective as Projective2};
use super::*;
use crate::arith::{
r1cs::{
extract_r1cs, extract_w_x,
tests::{get_test_r1cs, get_test_z},
{extract_r1cs, extract_w_x},
RelaxedR1CS,
},
Arith,
};
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::PreprocessorParam;
use crate::frontend::tests::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::frontend::utils::{CubicFCircuit, CustomFCircuit, WrapperCircuit};
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::FoldingScheme;
fn prepare_instances<C: CurveGroup, CS: CommitmentScheme<C>, R: Rng>(
mut rng: R,
r1cs: &R1CS<C::ScalarField>,
z: &[C::ScalarField],
) -> (Witness<C>, CommittedInstance<C>)
where
C::ScalarField: Absorb,
{
let (w, x) = r1cs.split_z(z);
let (cs_pp, _) = CS::setup(&mut rng, max(w.len(), r1cs.A.n_rows)).unwrap();
let mut w = Witness::new::<false>(w, r1cs.A.n_rows, &mut rng);
w.E = r1cs.eval_relation(z).unwrap();
let mut u = w.commit::<CS, false>(&cs_pp, x).unwrap();
u.u = z[0];
(w, u)
}
#[test]
fn test_relaxed_r1cs_small_gadget_handcrafted() {
let rng = &mut thread_rng();
let r1cs: R1CS<Fr> = get_test_r1cs();
let rel_r1cs = r1cs.clone().relax();
let z = get_test_z(3);
let mut z = get_test_z(3);
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(rel_r1cs.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@@ -624,6 +653,8 @@ pub mod tests {
// gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
// initialized.
fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fr>::new_ref();
circuit.generate_constraints(cs.clone()).unwrap();
@@ -634,18 +665,19 @@ pub mod tests {
let r1cs = extract_r1cs::<Fr>(&cs);
let (w, x) = extract_w_x::<Fr>(&cs);
let z = [vec![Fr::one()], x, w].concat();
let mut z = [vec![Fr::one()], x, w].concat();
r1cs.check_relation(&z).unwrap();
let relaxed_r1cs = r1cs.clone().relax();
relaxed_r1cs.check_relation(&z).unwrap();
z[0] = Fr::rand(rng);
let (w, u) = prepare_instances::<_, Pedersen<Projective>, _>(rng, &r1cs, &z);
r1cs.check_relaxed_relation(&w, &u).unwrap();
// set new CS for the circuit that checks the RelaxedR1CS of our original circuit
let cs = ConstraintSystem::<Fr>::new_ref();
// prepare the inputs for our circuit
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@@ -709,6 +741,8 @@ pub mod tests {
#[test]
fn test_relaxed_r1cs_nonnative_circuit() {
let rng = &mut thread_rng();
let cs = ConstraintSystem::<Fq>::new_ref();
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
@@ -725,16 +759,15 @@ pub mod tests {
let cs = cs.into_inner().unwrap();
let r1cs = extract_r1cs::<Fq>(&cs);
let (w, x) = extract_w_x::<Fq>(&cs);
let z = [vec![Fq::one()], x, w].concat();
let z = [vec![Fq::rand(rng)], x, w].concat();
let relaxed_r1cs = r1cs.clone().relax();
let (w, u) = prepare_instances::<_, Pedersen<Projective2>, _>(rng, &r1cs, &z);
// natively
let cs = ConstraintSystem::<Fq>::new_ref();
let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap();
let EVar =
Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.clone().E)).unwrap();
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(w.E.clone())).unwrap();
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar =
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
@@ -742,8 +775,8 @@ pub mod tests {
// non-natively
let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let EVar = Vec::new_witness(cs.clone(), || Ok(w.E)).unwrap();
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(u.u)).unwrap();
let r1csVar =
R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap();

View File

@@ -14,7 +14,6 @@ use ark_std::rand::RngCore;
use ark_std::{One, UniformRand, Zero};
use core::marker::PhantomData;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
@@ -25,6 +24,7 @@ use crate::transcript::{poseidon::poseidon_canonical_config, AbsorbNonNative, Tr
use crate::utils::vec::is_zero_vec;
use crate::Error;
use crate::FoldingScheme;
use crate::{arith::r1cs::RelaxedR1CS, commitment::CommitmentScheme};
use crate::{
arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
constants::NOVA_N_BITS_RO,
@@ -40,8 +40,8 @@ pub mod traits;
pub mod zk;
use circuits::{AugmentedFCircuit, ChallengeGadget};
use nifs::NIFS;
use traits::NovaR1CS;
/// Configuration for Nova's CycleFold circuit
pub struct NovaCycleFoldConfig<C: CurveGroup> {
_c: PhantomData<C>,
}
@@ -56,7 +56,9 @@ impl<C: CurveGroup> CycleFoldConfig for NovaCycleFoldConfig<C> {
type F = C::BaseField;
}
type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
/// CycleFold circuit for computing random linear combinations of group elements
/// in Nova instances.
pub type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CommittedInstance<C: CurveGroup> {
@@ -136,10 +138,7 @@ pub struct Witness<C: CurveGroup> {
pub rW: C::ScalarField,
}
impl<C: CurveGroup> Witness<C>
where
<C as Group>::ScalarField: Absorb,
{
impl<C: CurveGroup> Witness<C> {
pub fn new<const H: bool>(w: Vec<C::ScalarField>, e_len: usize, mut rng: impl RngCore) -> Self {
let (rW, rE) = if H {
(
@@ -227,6 +226,7 @@ where
}
}
/// Proving parameters for Nova-based IVC
#[derive(Debug, Clone)]
pub struct ProverParams<C1, C2, CS1, CS2, const H: bool = false>
where
@@ -235,8 +235,11 @@ where
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// Proving parameters of the underlying commitment scheme over C1
pub cs_pp: CS1::ProverParams,
/// Proving parameters of the underlying commitment scheme over C2
pub cf_cs_pp: CS2::ProverParams,
}
@@ -302,6 +305,7 @@ where
}
}
/// Verification parameters for Nova-based IVC
#[derive(Debug, Clone)]
pub struct VerifierParams<C1, C2, CS1, CS2, const H: bool = false>
where
@@ -310,10 +314,15 @@ where
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
{
/// Poseidon sponge configuration
pub poseidon_config: PoseidonConfig<C1::ScalarField>,
/// R1CS of the Augmented step circuit
pub r1cs: R1CS<C1::ScalarField>,
/// R1CS of the CycleFold circuit
pub cf_r1cs: R1CS<C2::ScalarField>,
/// Verification parameters of the underlying commitment scheme over C1
pub cs_vp: CS1::VerifierParams,
/// Verification parameters of the underlying commitment scheme over C2
pub cf_cs_vp: CS2::VerifierParams,
}
@@ -553,8 +562,9 @@ where
let pp_hash = vp.pp_hash()?;
// setup the dummy instances
let (w_dummy, u_dummy) = r1cs.dummy_instance();
let (cf_w_dummy, cf_u_dummy) = cf_r1cs.dummy_instance();
let (W_dummy, U_dummy) = r1cs.dummy_running_instance();
let (w_dummy, u_dummy) = r1cs.dummy_incoming_instance();
let (cf_W_dummy, cf_U_dummy) = cf_r1cs.dummy_running_instance();
// W_dummy=W_0 is a 'dummy witness', all zeroes, but with the size corresponding to the
// R1CS that we're working with.
@@ -572,13 +582,13 @@ where
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
z_i: z_0,
w_i: w_dummy.clone(),
u_i: u_dummy.clone(),
W_i: w_dummy,
U_i: u_dummy,
w_i: w_dummy,
u_i: u_dummy,
W_i: W_dummy,
U_i: U_dummy,
// cyclefold running instance
cf_W_i: cf_w_dummy.clone(),
cf_U_i: cf_u_dummy.clone(),
cf_W_i: cf_W_dummy,
cf_U_i: cf_U_dummy,
})
}
@@ -805,10 +815,10 @@ where
#[cfg(test)]
{
self.cf_r1cs.check_instance_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_instance_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs.check_tight_relation(&_cfW_w_i, &cfW_u_i)?;
self.cf_r1cs.check_tight_relation(&_cfE_w_i, &cfE_u_i)?;
self.cf_r1cs
.check_relaxed_instance_relation(&self.cf_W_i, &self.cf_U_i)?;
.check_relaxed_relation(&self.cf_W_i, &self.cf_U_i)?;
}
}
@@ -840,9 +850,8 @@ where
#[cfg(test)]
{
self.r1cs.check_instance_relation(&self.w_i, &self.u_i)?;
self.r1cs
.check_relaxed_instance_relation(&self.W_i, &self.U_i)?;
self.r1cs.check_tight_relation(&self.w_i, &self.u_i)?;
self.r1cs.check_relaxed_relation(&self.W_i, &self.U_i)?;
}
Ok(())
@@ -908,19 +917,13 @@ where
return Err(Error::IVCVerificationFail);
}
// check u_i.cmE==0, u_i.u==1 (=u_i is a un-relaxed instance)
if !u_i.cmE.is_zero() || !u_i.u.is_one() {
return Err(Error::IVCVerificationFail);
}
// check R1CS satisfiability
vp.r1cs.check_instance_relation(&w_i, &u_i)?;
// check R1CS satisfiability, which also enforces u_i.cmE==0, u_i.u==1
vp.r1cs.check_tight_relation(&w_i, &u_i)?;
// check RelaxedR1CS satisfiability
vp.r1cs.check_relaxed_instance_relation(&W_i, &U_i)?;
vp.r1cs.check_relaxed_relation(&W_i, &U_i)?;
// check CycleFold RelaxedR1CS satisfiability
vp.cf_r1cs
.check_relaxed_instance_relation(&cf_W_i, &cf_U_i)?;
vp.cf_r1cs.check_relaxed_relation(&cf_W_i, &cf_U_i)?;
Ok(())
}
@@ -1077,7 +1080,7 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
/// This test tests the Nova+CycleFold IVC, and by consequence it is also testing the

View File

@@ -210,10 +210,12 @@ pub mod tests {
use ark_pallas::{Fr, Projective};
use ark_std::{ops::Mul, test_rng, UniformRand};
use crate::arith::r1cs::tests::{get_test_r1cs, get_test_z};
use crate::arith::r1cs::{
tests::{get_test_r1cs, get_test_z},
RelaxedR1CS,
};
use crate::commitment::pedersen::{Params as PedersenParams, Pedersen};
use crate::folding::nova::circuits::ChallengeGadget;
use crate::folding::nova::traits::NovaR1CS;
use crate::transcript::poseidon::poseidon_canonical_config;
#[allow(clippy::type_complexity)]
@@ -316,8 +318,8 @@ pub mod tests {
let u_i = u_dummy.clone();
let W_i = w_dummy.clone();
let U_i = u_dummy.clone();
r1cs.check_relaxed_instance_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_instance_relation(&W_i, &U_i).unwrap();
r1cs.check_relaxed_relation(&w_i, &u_i).unwrap();
r1cs.check_relaxed_relation(&W_i, &U_i).unwrap();
let r_Fr = Fr::from(3_u32);
@@ -334,7 +336,7 @@ pub mod tests {
r_Fr, &w_i, &u_i, &W_i, &U_i, &T, cmT,
)
.unwrap();
r1cs.check_relaxed_instance_relation(&W_i1, &U_i1).unwrap();
r1cs.check_relaxed_relation(&W_i1, &U_i1).unwrap();
}
// fold 2 instances into one
@@ -348,9 +350,9 @@ pub mod tests {
assert_eq!(ci3_v, ci3);
// check that relations hold for the 2 inputted instances and the folded one
r1cs.check_relaxed_instance_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_instance_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_instance_relation(&w3, &ci3).unwrap();
r1cs.check_relaxed_relation(&w1, &ci1).unwrap();
r1cs.check_relaxed_relation(&w2, &ci2).unwrap();
r1cs.check_relaxed_relation(&w3, &ci3).unwrap();
// check that folded commitments from folded instance (ci) are equal to folding the
// use folded rE, rW to commit w3
@@ -425,7 +427,7 @@ pub mod tests {
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap();
r1cs.check_relaxed_instance_relation(&running_instance_w, &running_committed_instance)
r1cs.check_relaxed_relation(&running_instance_w, &running_committed_instance)
.unwrap();
let num_iters = 10;
@@ -438,11 +440,8 @@ pub mod tests {
let incoming_committed_instance = incoming_instance_w
.commit::<Pedersen<Projective>, false>(&pedersen_params, x)
.unwrap();
r1cs.check_relaxed_instance_relation(
&incoming_instance_w,
&incoming_committed_instance,
)
.unwrap();
r1cs.check_relaxed_relation(&incoming_instance_w, &incoming_committed_instance)
.unwrap();
let r = Fr::rand(&mut rng); // folding challenge would come from the RO
@@ -475,7 +474,7 @@ pub mod tests {
&cmT,
);
r1cs.check_relaxed_instance_relation(&folded_w, &folded_committed_instance)
r1cs.check_relaxed_relation(&folded_w, &folded_committed_instance)
.unwrap();
// set running_instance for next loop iteration

View File

@@ -187,7 +187,7 @@ pub mod tests {
use crate::{
commitment::{kzg::KZG, pedersen::Pedersen},
folding::nova::{Nova, PreprocessorParam},
frontend::{tests::CubicFCircuit, FCircuit},
frontend::{utils::CubicFCircuit, FCircuit},
transcript::poseidon::poseidon_canonical_config,
FoldingScheme,
};

View File

@@ -1,69 +1,90 @@
use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group};
use ark_std::One;
use ark_ec::CurveGroup;
use ark_std::{rand::RngCore, One, UniformRand};
use super::{CommittedInstance, Witness};
use crate::arith::{r1cs::R1CS, Arith};
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
use crate::Error;
/// NovaR1CS extends R1CS methods with Nova specific methods
pub trait NovaR1CS<C: CurveGroup> {
/// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>);
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
fn check_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error>;
/// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness
/// and CommittedInstance.
fn check_relaxed_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error>;
}
impl<C: CurveGroup> NovaR1CS<C> for R1CS<C::ScalarField>
where
<C as Group>::ScalarField: Absorb,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C>, CommittedInstance<C>> for R1CS<C::ScalarField> {
fn dummy_running_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
let w_len = self.A.n_cols - 1 - self.l;
let w_dummy = Witness::<C>::dummy(w_len, self.A.n_rows);
let u_dummy = CommittedInstance::<C>::dummy(self.l);
(w_dummy, u_dummy)
}
// notice that this method does not check the commitment correctness
fn check_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error> {
if U.cmE != C::zero() || U.u != C::ScalarField::one() {
return Err(Error::R1CSUnrelaxedFail);
}
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
self.check_relation(&Z)
fn dummy_incoming_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
self.dummy_running_instance()
}
// notice that this method does not check the commitment correctness
fn check_relaxed_instance_relation(
&self,
W: &Witness<C>,
U: &CommittedInstance<C>,
) -> Result<(), Error> {
let mut rel_r1cs = self.clone().relax();
rel_r1cs.u = U.u;
rel_r1cs.E = W.E.clone();
fn is_relaxed(_w: &Witness<C>, u: &CommittedInstance<C>) -> bool {
u.cmE != C::zero() || u.u != C::ScalarField::one()
}
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
rel_r1cs.check_relation(&Z)
fn extract_z(w: &Witness<C>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
[&[u.u][..], &u.x, &w.W].concat()
}
fn check_error_terms(
w: &Witness<C>,
_u: &CommittedInstance<C>,
e: Vec<C::ScalarField>,
) -> Result<(), Error> {
if w.E == e {
Ok(())
} else {
Err(Error::NotSatisfied)
}
}
fn sample<CS>(
&self,
params: &CS::ProverParams,
mut rng: impl RngCore,
) -> Result<(Witness<C>, CommittedInstance<C>), Error>
where
CS: crate::commitment::CommitmentScheme<C, true>,
{
// Implements sampling a (committed) RelaxedR1CS
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
let u = C::ScalarField::rand(&mut rng);
let rE = C::ScalarField::rand(&mut rng);
let rW = C::ScalarField::rand(&mut rng);
let W = (0..self.A.n_cols - self.l - 1)
.map(|_| C::ScalarField::rand(&mut rng))
.collect();
let x = (0..self.l)
.map(|_| C::ScalarField::rand(&mut rng))
.collect::<Vec<C::ScalarField>>();
let mut z = vec![u];
z.extend(&x);
z.extend(&W);
let E = <Self as RelaxedR1CS<C, Witness<C>, CommittedInstance<C>>>::compute_E(
&self.A, &self.B, &self.C, &z, &u,
)?;
debug_assert!(
z.len() == self.A.n_cols,
"Length of z is {}, while A has {} columns.",
z.len(),
self.A.n_cols
);
let witness = Witness { E, rE, W, rW };
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
// witness.commit() sets u to 1, we set it to the sampled u value
cm_witness.u = u;
debug_assert!(
self.check_relaxed_relation(&witness, &cm_witness).is_ok(),
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
u,
witness.E
);
Ok((witness, cm_witness))
}
}

View File

@@ -30,7 +30,6 @@
/// paper).
/// And the Use-case-2 would require a modified version of the Decider circuits.
///
use crate::folding::nova::traits::NovaR1CS;
use ark_crypto_primitives::sponge::CryptographicSponge;
use ark_ff::{BigInteger, PrimeField};
use ark_std::{One, Zero};
@@ -141,9 +140,8 @@ where
// d. Store folding proof
let pi = FoldingProof { cmT };
// 2. Sample a satisfying relaxed R1CS instance-witness pair (U_r, W_r)
let relaxed_instance = nova.r1cs.clone().relax();
let (U_r, W_r) = relaxed_instance.sample::<C1, CS1>(&nova.cs_pp, &mut rng)?;
// 2. Sample a satisfying relaxed R1CS instance-witness pair (W_r, U_r)
let (W_r, U_r) = nova.r1cs.sample::<CS1>(&nova.cs_pp, &mut rng)?;
// 3. Fold the instance-witness pair (U_f, W_f) with (U_r, W_r)
// a. Compute T
@@ -280,21 +278,10 @@ where
);
// 5. Check that W^{\prime}_i is a satisfying witness
let mut z = vec![U_i_prime.u];
z.extend(&U_i_prime.x);
z.extend(&proof.W_i_prime.W);
let relaxed_r1cs = RelaxedR1CS {
l: r1cs.l,
A: r1cs.A.clone(),
B: r1cs.B.clone(),
C: r1cs.C.clone(),
u: U_i_prime.u,
E: proof.W_i_prime.E.clone(),
};
relaxed_r1cs.check_relation(&z)?;
r1cs.check_relaxed_relation(&proof.W_i_prime, &U_i_prime)?;
// 6. Check that the cyclefold instance-witness pair satisfies the cyclefold relaxed r1cs
cf_r1cs.check_relaxed_instance_relation(&proof.cf_W_i, &proof.cf_U_i)?;
cf_r1cs.check_relaxed_relation(&proof.cf_W_i, &proof.cf_U_i)?;
Ok(())
}
@@ -305,7 +292,7 @@ pub mod tests {
use super::*;
use crate::commitment::pedersen::Pedersen;
use crate::folding::nova::tests::test_ivc_opt;
use crate::frontend::tests::CubicFCircuit;
use crate::frontend::utils::CubicFCircuit;
use crate::transcript::poseidon::poseidon_canonical_config;
use ark_bn254::{Fr, G1Projective as Projective};
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
@@ -380,11 +367,9 @@ pub mod tests {
F_circuit,
3,
);
let (sampled_committed_instance, _) = nova
let (_, sampled_committed_instance) = nova
.r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// proof verification fails with incorrect running instance
@@ -419,11 +404,9 @@ pub mod tests {
F_circuit,
3,
);
let (_, sampled_committed_witness) = nova
let (sampled_committed_witness, _) = nova
.r1cs
.clone()
.relax()
.sample::<Projective, Pedersen<Projective, true>>(&nova.cs_pp, rng)
.sample::<Pedersen<Projective, true>>(&nova.cs_pp, rng)
.unwrap();
// proof generation fails with incorrect running witness