Generalized CycleFold (#120)

* Support randomness of arbitrary length

* Rename `N_BITS_RO` to `NOVA_N_BITS_RO`

* Compute `r_nonnat` inside `NIFSFullGadget::fold_committed_instance`

* Format

* Use `CycleFold{CommittedInstance, Witness}` in the context of cyclefold

* Format

* Fix the creation of dummy witness

* Make clippy happy

* Improve docs
This commit is contained in:
winderica
2024-08-05 11:11:49 +01:00
committed by GitHub
parent 18a3e0aa93
commit ecaecd483c
13 changed files with 554 additions and 486 deletions

View File

@@ -20,11 +20,12 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace,
use ark_std::{fmt::Debug, One, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{CommittedInstance, NOVA_CF_N_POINTS};
use crate::constants::N_BITS_RO;
use super::{CommittedInstance, NovaCycleFoldConfig};
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::{
cyclefold::{
cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget,
CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
CycleFoldConfig, NIFSFullGadget,
},
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
CF1, CF2,
@@ -196,7 +197,7 @@ where
transcript.absorb(&U_i);
transcript.absorb(&u_i);
transcript.absorb_nonnative(&cmT);
transcript.squeeze_bits(N_BITS_RO)
transcript.squeeze_bits(NOVA_N_BITS_RO)
}
// compatible with the native get_challenge_native
@@ -211,13 +212,22 @@ where
transcript.absorb(&U_i_vec)?;
transcript.absorb(&u_i)?;
transcript.absorb_nonnative(&cmT)?;
transcript.squeeze_bits(N_BITS_RO)
transcript.squeeze_bits(NOVA_N_BITS_RO)
}
}
/// AugmentedFCircuit implements the F' circuit (augmented F) defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf) together with the extra constraints defined in
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
/// `AugmentedFCircuit` enhances the original step function `F`, so that it can
/// be used in recursive arguments such as IVC.
///
/// The method for converting `F` to `AugmentedFCircuit` (`F'`) is defined in
/// [Nova](https://eprint.iacr.org/2021/370.pdf), where `AugmentedFCircuit` not
/// only invokes `F`, but also adds additional constraints for verifying the
/// correct folding of primary instances (i.e., Nova's `CommittedInstance`s over
/// `C1`).
///
/// Furthermore, to reduce circuit size over `C2`, we implement the constraints
/// defined in [CycleFold](https://eprint.iacr.org/2023/1192.pdf). These extra
/// constraints verify the correct folding of CycleFold instances.
#[derive(Debug, Clone)]
pub struct AugmentedFCircuit<
C1: CurveGroup,
@@ -246,9 +256,9 @@ pub struct AugmentedFCircuit<
// 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<CommittedInstance<C2>>, // input
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])
@@ -337,7 +347,7 @@ where
let cmT =
NonNativeAffineVar::new_witness(cs.clone(), || Ok(self.cmT.unwrap_or_else(C1::zero)))?;
let cf_u_dummy = CommittedInstance::dummy(cf_io_len(NOVA_CF_N_POINTS));
let cf_u_dummy = CycleFoldCommittedInstance::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone()))
})?;
@@ -481,19 +491,9 @@ where
cf1_u_i.clone(),
cf1_cmT.clone(),
)?;
// Convert cf1_r_bits to a `NonNativeFieldVar`
let cf1_r_nonnat = {
let mut bits = cf1_r_bits.clone();
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
};
// Fold cf1_u_i & cf_U_i into cf1_U_{i+1}
let cf1_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
cf1_r_bits,
cf1_r_nonnat,
cf1_cmT,
cf_U_i,
cf1_u_i,
cf1_r_bits, cf1_cmT, cf_U_i, cf1_u_i,
)?;
// same for cf2_r:
@@ -504,16 +504,8 @@ where
cf2_u_i.clone(),
cf2_cmT.clone(),
)?;
let cf2_r_nonnat = {
let mut bits = cf2_r_bits.clone();
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
};
let cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
cf2_r_bits,
cf2_r_nonnat,
cf2_cmT,
cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
cf2_r_bits, cf2_cmT, cf1_U_i1, // the output from NIFS.V(cf1_r, cf_U, cfE_u)
cf2_u_i,
)?;
@@ -566,7 +558,7 @@ pub mod tests {
assert_eq!(ciVar.x.value().unwrap(), ci.x);
// the values cmE and cmW are checked in the CycleFold's circuit
// CommittedInstanceInCycleFoldVar in
// nova::cyclefold::tests::test_committed_instance_cyclefold_var
// cyclefold::tests::test_committed_instance_cyclefold_var
}
#[test]

View File

@@ -22,14 +22,18 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace,
use ark_std::{log2, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{circuits::ChallengeGadget, nifs::NIFS};
use super::{
circuits::{ChallengeGadget, CommittedInstanceVar},
nifs::NIFS,
CommittedInstance, Nova, Witness,
};
use crate::arith::r1cs::R1CS;
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
use crate::folding::circuits::{
cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
CF1, CF2,
};
use crate::folding::nova::{circuits::CommittedInstanceVar, CommittedInstance, Nova, Witness};
use crate::frontend::FCircuit;
use crate::transcript::{Transcript, TranscriptVar};
use crate::utils::{
@@ -156,7 +160,7 @@ where
}
}
/// In-circuit representation of the Witness associated to the CommittedInstance, but with
/// In-circuit representation of the Witness associated to the CycleFoldCommittedInstance, but with
/// non-native representation, since it is used to represent the CycleFold witness.
#[derive(Debug, Clone)]
pub struct CycleFoldWitnessVar<C: CurveGroup> {
@@ -166,12 +170,12 @@ pub struct CycleFoldWitnessVar<C: CurveGroup> {
pub rW: NonNativeUintVar<CF2<C>>,
}
impl<C> AllocVar<Witness<C>, CF2<C>> for CycleFoldWitnessVar<C>
impl<C> AllocVar<CycleFoldWitness<C>, CF2<C>> for CycleFoldWitnessVar<C>
where
C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
{
fn new_variable<T: Borrow<Witness<C>>>(
fn new_variable<T: Borrow<CycleFoldWitness<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
@@ -237,8 +241,8 @@ where
pub cmT: Option<C1>,
pub r: Option<C1::ScalarField>,
/// CycleFold running instance
pub cf_U_i: Option<CommittedInstance<C2>>,
pub cf_W_i: Option<Witness<C2>>,
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>,
pub cf_W_i: Option<CycleFoldWitness<C2>>,
/// KZG challenges
pub kzg_c_W: Option<C1::ScalarField>,
@@ -447,14 +451,19 @@ where
{
// imports here instead of at the top of the file, so we avoid having multiple
// `#[cfg(not(test))]`
use super::NOVA_CF_N_POINTS;
use crate::commitment::pedersen::PedersenGadget;
use crate::folding::circuits::cyclefold::{cf_io_len, CycleFoldCommittedInstanceVar};
use crate::folding::{
circuits::cyclefold::{CycleFoldCommittedInstanceVar, CycleFoldConfig},
nova::NovaCycleFoldConfig,
};
use ark_r1cs_std::ToBitsGadget;
let cf_u_dummy_native = CommittedInstance::<C2>::dummy(cf_io_len(NOVA_CF_N_POINTS));
let w_dummy_native =
Witness::<C2>::dummy(self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l, self.cf_E_len);
let cf_u_dummy_native =
CycleFoldCommittedInstance::<C2>::dummy(NovaCycleFoldConfig::<C1>::IO_LEN);
let w_dummy_native = CycleFoldWitness::<C2>::dummy(
self.cf_r1cs.A.n_cols - 1 - self.cf_r1cs.l,
self.cf_E_len,
);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or_else(|| cf_u_dummy_native.clone()))
})?;

View File

@@ -4,7 +4,7 @@ use ark_crypto_primitives::sponge::{
poseidon::{PoseidonConfig, PoseidonSponge},
Absorb, CryptographicSponge,
};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
@@ -15,7 +15,10 @@ use ark_std::{One, UniformRand, Zero};
use core::marker::PhantomData;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit};
use crate::folding::circuits::cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
};
use crate::folding::circuits::CF2;
use crate::frontend::FCircuit;
use crate::transcript::{AbsorbNonNative, Transcript};
@@ -24,6 +27,7 @@ use crate::Error;
use crate::FoldingScheme;
use crate::{
arith::r1cs::{extract_r1cs, extract_w_x, R1CS},
constants::NOVA_N_BITS_RO,
utils::{get_cm_coordinates, pp_hash},
};
@@ -33,13 +37,23 @@ pub mod decider_eth_circuit;
pub mod nifs;
pub mod serialize;
pub mod traits;
use circuits::{AugmentedFCircuit, ChallengeGadget};
use nifs::NIFS;
use traits::NovaR1CS;
/// Number of points to be folded in the CycleFold circuit, in Nova's case, this is a fixed amount:
/// 2 points to be folded.
const NOVA_CF_N_POINTS: usize = 2_usize;
struct NovaCycleFoldConfig<C: CurveGroup> {
_c: PhantomData<C>,
}
impl<C: CurveGroup> CycleFoldConfig for NovaCycleFoldConfig<C> {
const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO;
const N_INPUT_POINTS: usize = 2;
type C = C;
type F = C::BaseField;
}
type NovaCycleFoldCircuit<C, GC> = CycleFoldCircuit<NovaCycleFoldConfig<C>, GC>;
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct CommittedInstance<C: CurveGroup> {
@@ -84,30 +98,6 @@ where
}
}
impl<C: CurveGroup> AbsorbNonNative<C::BaseField> for CommittedInstance<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
{
// Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements`
// in `cyclefold.rs`.
fn to_native_sponge_field_elements(&self, dest: &mut Vec<C::BaseField>) {
[self.u].to_native_sponge_field_elements(dest);
self.x.to_native_sponge_field_elements(dest);
let (cmE_x, cmE_y) = match self.cmE.into_affine().xy() {
Some((&x, &y)) => (x, y),
None => (C::BaseField::zero(), C::BaseField::zero()),
};
let (cmW_x, cmW_y) = match self.cmW.into_affine().xy() {
Some((&x, &y)) => (x, y),
None => (C::BaseField::zero(), C::BaseField::zero()),
};
cmE_x.to_sponge_field_elements(dest);
cmE_y.to_sponge_field_elements(dest);
cmW_x.to_sponge_field_elements(dest);
cmW_y.to_sponge_field_elements(dest);
}
}
impl<C: CurveGroup> CommittedInstance<C>
where
<C as Group>::ScalarField: Absorb,
@@ -135,25 +125,6 @@ where
}
}
impl<C: CurveGroup> CommittedInstance<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
{
/// hash_cyclefold implements the committed instance hash compatible with the gadget implemented in
/// nova/cyclefold.rs::CycleFoldCommittedInstanceVar.hash.
/// Returns `H(U_i)`, where `U_i` is the `CommittedInstance` for CycleFold.
pub fn hash_cyclefold<T: Transcript<C::BaseField>>(
&self,
sponge: &T,
pp_hash: C::BaseField, // public params hash
) -> C::BaseField {
let mut sponge = sponge.clone();
sponge.absorb(&pp_hash);
sponge.absorb_nonnative(self);
sponge.squeeze_field_elements(1)[0]
}
}
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
pub struct Witness<C: CurveGroup> {
pub E: Vec<C::ScalarField>,
@@ -342,8 +313,8 @@ where
pub U_i: CommittedInstance<C1>,
/// CycleFold running instance
pub cf_W_i: Witness<C2>,
pub cf_U_i: CommittedInstance<C2>,
pub cf_W_i: CycleFoldWitness<C2>,
pub cf_U_i: CycleFoldCommittedInstance<C2>,
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> FoldingScheme<C1, C2, FC>
@@ -370,7 +341,7 @@ where
type RunningInstance = (CommittedInstance<C1>, Witness<C1>);
type IncomingInstance = (CommittedInstance<C1>, Witness<C1>);
type MultiCommittedInstanceWithWitness = ();
type CFInstance = (CommittedInstance<C2>, Witness<C2>);
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
fn preprocess(
mut rng: impl RngCore,
@@ -428,7 +399,7 @@ where
let augmented_F_circuit =
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(&pp.poseidon_config, F.clone());
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS);
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
augmented_F_circuit.generate_constraints(cs.clone())?;
cs.finalize();
@@ -619,16 +590,14 @@ where
]
.concat();
let cfW_circuit = CycleFoldCircuit::<C1, GC1> {
let cfW_circuit = NovaCycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
n_points: NOVA_CF_N_POINTS,
r_bits: Some(vec![r_bits.clone()]),
points: Some(vec![self.U_i.clone().cmW, self.u_i.clone().cmW]),
x: Some(cfW_u_i_x.clone()),
};
let cfE_circuit = CycleFoldCircuit::<C1, GC1> {
let cfE_circuit = NovaCycleFoldCircuit::<C1, GC1> {
_gc: PhantomData,
n_points: NOVA_CF_N_POINTS,
r_bits: Some(vec![r_bits.clone()]),
points: Some(vec![self.U_i.clone().cmE, cmT]),
x: Some(cfE_u_i_x.clone()),
@@ -855,24 +824,23 @@ where
fn fold_cyclefold_circuit<T: Transcript<C1::ScalarField>>(
&self,
transcript: &mut T,
cf_W_i: Witness<C2>, // witness of the running instance
cf_U_i: CommittedInstance<C2>, // running instance
cf_W_i: CycleFoldWitness<C2>, // witness of the running instance
cf_U_i: CycleFoldCommittedInstance<C2>, // running instance
cf_u_i_x: Vec<C2::ScalarField>,
cf_circuit: CycleFoldCircuit<C1, GC1>,
cf_circuit: NovaCycleFoldCircuit<C1, GC1>,
rng: &mut impl RngCore,
) -> Result<
(
Witness<C2>,
CommittedInstance<C2>, // u_i
Witness<C2>, // W_i1
CommittedInstance<C2>, // U_i1
C2, // cmT
C2::ScalarField, // r_Fq
CycleFoldWitness<C2>,
CycleFoldCommittedInstance<C2>, // u_i
CycleFoldWitness<C2>, // W_i1
CycleFoldCommittedInstance<C2>, // U_i1
C2, // cmT
C2::ScalarField, // r_Fq
),
Error,
> {
fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2, H>(
NOVA_CF_N_POINTS,
fold_cyclefold_circuit::<NovaCycleFoldConfig<C1>, C1, GC1, C2, GC2, CS2, H>(
transcript,
self.cf_r1cs.clone(),
self.cf_cs_pp.clone(),
@@ -920,7 +888,7 @@ where
{
let augmented_F_circuit =
AugmentedFCircuit::<C1, C2, GC2, FC>::empty(poseidon_config, F_circuit);
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(NOVA_CF_N_POINTS);
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
let r1cs = get_r1cs_from_cs::<C1::ScalarField>(augmented_F_circuit)?;
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
Ok((r1cs, cf_r1cs))

View File

@@ -6,6 +6,7 @@ use std::marker::PhantomData;
use super::{CommittedInstance, Witness};
use crate::arith::r1cs::R1CS;
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness};
use crate::transcript::Transcript;
use crate::utils::vec::{hadamard, mat_vec_mul, vec_add, vec_scalar_mul, vec_sub};
use crate::Error;
@@ -110,10 +111,10 @@ where
pub fn compute_cyclefold_cmT(
cs_prover_params: &CS::ProverParams,
r1cs: &R1CS<C::ScalarField>, // R1CS over C2.Fr=C1.Fq (here C=C2)
w1: &Witness<C>,
ci1: &CommittedInstance<C>,
w2: &Witness<C>,
ci2: &CommittedInstance<C>,
w1: &CycleFoldWitness<C>,
ci1: &CycleFoldCommittedInstance<C>,
w2: &CycleFoldWitness<C>,
ci2: &CycleFoldCommittedInstance<C>,
) -> Result<(Vec<C::ScalarField>, C), Error>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,

View File

@@ -10,14 +10,14 @@ use ark_relations::r1cs::ConstraintSystem;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError, Write};
use std::marker::PhantomData;
use super::{circuits::AugmentedFCircuit, Nova, ProverParams};
use super::{CommittedInstance, Witness};
use crate::folding::{
circuits::{cyclefold::CycleFoldCircuit, CF2},
nova::NOVA_CF_N_POINTS,
use super::{
circuits::AugmentedFCircuit, CommittedInstance, Nova, NovaCycleFoldCircuit, ProverParams,
Witness,
};
use crate::{
arith::r1cs::extract_r1cs, commitment::CommitmentScheme, folding::circuits::CF1,
arith::r1cs::extract_r1cs,
commitment::CommitmentScheme,
folding::circuits::{CF1, CF2},
frontend::FCircuit,
};
@@ -138,7 +138,7 @@ where
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(NOVA_CF_N_POINTS);
let cf_circuit = NovaCycleFoldCircuit::<C1, GC1>::empty();
augmented_F_circuit
.generate_constraints(cs.clone())