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

@@ -26,20 +26,21 @@ use super::{
cccs::CCCS,
lcccs::LCCCS,
nimfs::{NIMFSProof, NIMFS},
Witness,
HyperNovaCycleFoldConfig, Witness,
};
use crate::constants::N_BITS_RO;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::{
circuits::cyclefold::{
cf_io_len, CycleFoldChallengeGadget, CycleFoldCommittedInstanceVar, NIFSFullGadget,
},
circuits::{
cyclefold::{
CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
CycleFoldConfig, NIFSFullGadget,
},
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
sum_check::{IOPProofVar, SumCheckVerifierGadget, VPAuxInfoVar},
utils::EqEvalGadget,
CF1, CF2,
},
nova::{get_r1cs_from_cs, CommittedInstance},
nova::get_r1cs_from_cs,
};
use crate::frontend::FCircuit;
use crate::utils::virtual_polynomial::VPAuxInfo;
@@ -313,7 +314,7 @@ where
let rho_scalar_raw = C::ScalarField::from_le_bytes_mod_order(b"rho");
let rho_scalar: FpVar<CF1<C>> = FpVar::<CF1<C>>::new_constant(cs.clone(), rho_scalar_raw)?;
transcript.absorb(&rho_scalar)?;
let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(N_BITS_RO)?;
let rho_bits: Vec<Boolean<CF1<C>>> = transcript.get_challenge_nbits(NOVA_N_BITS_RO)?;
let rho = Boolean::le_bits_to_fp_var(&rho_bits)?;
// Self::fold will return the folded instance, together with the rho's powers vector so
@@ -342,7 +343,7 @@ where
let mut v_folded: Vec<FpVar<CF1<C>>> = vec![FpVar::zero(); sigmas[0].len()];
let mut rho_vec: Vec<Vec<Boolean<CF1<C>>>> =
vec![vec![Boolean::FALSE; N_BITS_RO]; lcccs.len() + cccs.len() - 1];
vec![vec![Boolean::FALSE; NOVA_N_BITS_RO]; lcccs.len() + cccs.len() - 1];
let mut rho_i = FpVar::one();
for i in 0..(lcccs.len() + cccs.len()) {
let u: FpVar<CF1<C>>;
@@ -381,12 +382,12 @@ where
// compute the next power of rho
rho_i *= rho.clone();
// crop the size of rho_i to N_BITS_RO
// crop the size of rho_i to NOVA_N_BITS_RO
let rho_i_bits = rho_i.to_bits_le()?;
rho_i = Boolean::le_bits_to_fp_var(&rho_i_bits[..N_BITS_RO])?;
rho_i = Boolean::le_bits_to_fp_var(&rho_i_bits[..NOVA_N_BITS_RO])?;
if i < lcccs.len() + cccs.len() - 1 {
// store the cropped rho_i into the rho_vec
rho_vec[i] = rho_i_bits[..N_BITS_RO].to_vec();
rho_vec[i] = rho_i_bits[..NOVA_N_BITS_RO].to_vec();
}
}
@@ -456,12 +457,32 @@ fn compute_c_gadget<F: PrimeField>(
Ok(c)
}
/// `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., the instances over `C1`).
/// In the paper, the primary instances are Nova's `CommittedInstance`, but we
/// extend this method to support using HyperNova's `LCCCS` and `CCCS` instances
/// as primary instances.
///
/// 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.
///
/// For multi-instance folding, one needs to specify the const generics below:
/// * `MU` - the number of LCCCS instances to be folded
/// * `NU` - the number of CCCS instances to be folded
#[derive(Debug, Clone)]
pub struct AugmentedFCircuit<
C1: CurveGroup,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>>,
FC: FCircuit<CF1<C1>>,
const MU: usize,
const NU: usize,
> where
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
@@ -470,8 +491,6 @@ pub struct AugmentedFCircuit<
pub poseidon_config: PoseidonConfig<CF1<C1>>,
pub ccs: CCS<C1::ScalarField>, // CCS of the AugmentedFCircuit
pub pp_hash: Option<CF1<C1>>,
pub mu: usize, // max number of LCCCS instances to be folded
pub nu: usize, // max number of CCCS instances to be folded
pub i: Option<CF1<C1>>,
pub i_usize: Option<usize>,
pub z_0: Option<Vec<C1::ScalarField>>,
@@ -487,13 +506,13 @@ pub struct AugmentedFCircuit<
pub nimfs_proof: Option<NIMFSProof<C1>>,
// cyclefold verifier on C1
pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
pub cf_U_i: Option<CommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
pub cf_u_i_cmW: Option<C2>, // input, cf_u_i.cmW
pub cf_U_i: Option<CycleFoldCommittedInstance<C2>>, // input, RelaxedR1CS CycleFold instance
pub cf_x: Option<CF1<C1>>, // public input (cf_u_{i+1}.x[1])
pub cf_cmT: Option<C2>,
}
impl<C1, C2, GC2, FC> AugmentedFCircuit<C1, C2, GC2, FC>
impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>
where
C1: CurveGroup,
C2: CurveGroup,
@@ -510,10 +529,8 @@ where
poseidon_config: &PoseidonConfig<CF1<C1>>,
F_circuit: FC,
ccs: CCS<C1::ScalarField>,
mu: usize,
nu: usize,
) -> Result<Self, Error> {
if mu < 1 || nu < 1 {
if MU < 1 || NU < 1 {
return Err(Error::CantBeZero("mu,nu".to_string()));
}
Ok(Self {
@@ -522,8 +539,6 @@ where
poseidon_config: poseidon_config.clone(),
ccs,
pp_hash: None,
mu,
nu,
i: None,
i_usize: None,
z_0: None,
@@ -548,8 +563,6 @@ where
poseidon_config: &PoseidonConfig<CF1<C1>>,
F: FC, // FCircuit
ccs: Option<CCS<C1::ScalarField>>,
mu: usize,
nu: usize,
) -> Result<Self, Error> {
let initial_ccs = CCS {
// m, n, s, s_prime and M will be overwritten by the `upper_bound_ccs' method
@@ -565,7 +578,7 @@ where
c: vec![C1::ScalarField::one(), C1::ScalarField::one().neg()],
M: vec![],
};
let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs, mu, nu)?;
let mut augmented_f_circuit = Self::default(poseidon_config, F, initial_ccs)?;
if ccs.is_some() {
augmented_f_circuit.ccs = ccs.unwrap();
} else {
@@ -590,10 +603,10 @@ where
let n_iters = 2;
for _ in 0..n_iters {
let Us = vec![U_i.clone(); self.mu - 1];
let Ws = vec![W_i.clone(); self.mu - 1];
let us = vec![u_i.clone(); self.nu - 1];
let ws = vec![w_i.clone(); self.nu - 1];
let Us = vec![U_i.clone(); MU - 1];
let Ws = vec![W_i.clone(); MU - 1];
let us = vec![u_i.clone(); NU - 1];
let ws = vec![w_i.clone(); NU - 1];
let all_Us = [vec![U_i.clone()], Us.clone()].concat();
let all_us = [vec![u_i.clone()], us.clone()].concat();
@@ -618,8 +631,6 @@ where
poseidon_config: self.poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(C1::ScalarField::zero()),
mu: self.mu,
nu: self.nu,
i: Some(C1::ScalarField::zero()),
i_usize: Some(0),
z_0: Some(z_0.clone()),
@@ -677,7 +688,8 @@ where
}
}
impl<C1, C2, GC2, FC> ConstraintSynthesizer<CF1<C1>> for AugmentedFCircuit<C1, C2, GC2, FC>
impl<C1, C2, GC2, FC, const MU: usize, const NU: usize> ConstraintSynthesizer<CF1<C1>>
for AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>
where
C1: CurveGroup,
C2: CurveGroup,
@@ -719,20 +731,21 @@ where
let U_i =
LCCCSVar::<C1>::new_witness(cs.clone(), || Ok(self.U_i.unwrap_or(U_dummy.clone())))?;
let Us = Vec::<LCCCSVar<C1>>::new_witness(cs.clone(), || {
Ok(self.Us.unwrap_or(vec![U_dummy.clone(); self.mu - 1]))
Ok(self.Us.unwrap_or(vec![U_dummy.clone(); MU - 1]))
})?;
let us = Vec::<CCCSVar<C1>>::new_witness(cs.clone(), || {
Ok(self.us.unwrap_or(vec![u_dummy.clone(); self.mu - 1]))
Ok(self.us.unwrap_or(vec![u_dummy.clone(); MU - 1]))
})?;
let U_i1_C = NonNativeAffineVar::new_witness(cs.clone(), || {
Ok(self.U_i1_C.unwrap_or_else(C1::zero))
})?;
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, self.mu, self.nu);
let nimfs_proof_dummy = NIMFSProof::<C1>::dummy(&self.ccs, MU, NU);
let nimfs_proof = ProofVar::<C1>::new_witness(cs.clone(), || {
Ok(self.nimfs_proof.unwrap_or(nimfs_proof_dummy))
})?;
let cf_u_dummy = CommittedInstance::dummy(cf_io_len(self.mu + self.nu));
let cf_u_dummy =
CycleFoldCommittedInstance::dummy(HyperNovaCycleFoldConfig::<C1, MU, NU>::IO_LEN);
let cf_U_i = CycleFoldCommittedInstanceVar::<C2, GC2>::new_witness(cs.clone(), || {
Ok(self.cf_U_i.unwrap_or(cf_u_dummy.clone()))
})?;
@@ -861,20 +874,9 @@ where
cf_u_i.clone(),
cf_cmT.clone(),
)?;
// Convert cf_r_bits to a `NonNativeFieldVar`
let cf_r_nonnat = {
let mut bits = cf_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 cf_U_i1 = NIFSFullGadget::<C2, GC2>::fold_committed_instance(
cf_r_bits,
cf_r_nonnat,
cf_cmT,
cf_U_i,
cf_u_i,
)?;
let cf_U_i1 =
NIFSFullGadget::<C2, GC2>::fold_committed_instance(cf_r_bits, cf_cmT, cf_U_i, cf_u_i)?;
// Back to Primary Part
// P.4.b compute and check the second output of F'
@@ -909,9 +911,12 @@ mod tests {
},
commitment::{pedersen::Pedersen, CommitmentScheme},
folding::{
circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit},
hypernova::utils::{compute_c, compute_sigmas_thetas},
nova::{traits::NovaR1CS, Witness as NovaWitness},
circuits::cyclefold::{fold_cyclefold_circuit, CycleFoldWitness},
hypernova::{
utils::{compute_c, compute_sigmas_thetas},
HyperNovaCycleFoldCircuit,
},
nova::traits::NovaR1CS,
},
frontend::tests::CubicFCircuit,
transcript::poseidon::poseidon_canonical_config,
@@ -1179,25 +1184,25 @@ mod tests {
let poseidon_config = poseidon_canonical_config::<Fr>();
let sponge = PoseidonSponge::<Fr>::new(&poseidon_config);
let mu = 3;
let nu = 3;
const MU: usize = 3;
const NU: usize = 3;
let start = Instant::now();
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
let mut augmented_f_circuit = AugmentedFCircuit::<
Projective,
Projective2,
GVar2,
CubicFCircuit<Fr>,
>::empty(&poseidon_config, F_circuit, None, mu, nu)
.unwrap();
let mut augmented_f_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>, MU, NU>::empty(
&poseidon_config,
F_circuit,
None,
)
.unwrap();
let ccs = augmented_f_circuit.ccs.clone();
println!("AugmentedFCircuit & CCS generation: {:?}", start.elapsed());
println!("CCS m x n: {} x {}", ccs.m, ccs.n);
// CycleFold circuit
let cs2 = ConstraintSystem::<Fq>::new_ref();
let cf_circuit = CycleFoldCircuit::<Projective, GVar>::empty(mu + nu);
let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU>::empty();
cf_circuit.generate_constraints(cs2.clone()).unwrap();
cs2.finalize();
let cs2 = cs2
@@ -1224,8 +1229,10 @@ mod tests {
let U_dummy = LCCCS::<Projective>::dummy(ccs.l, ccs.t, ccs.s);
let w_dummy = W_dummy.clone();
let u_dummy = CCCS::<Projective>::dummy(ccs.l);
let (cf_W_dummy, cf_U_dummy): (NovaWitness<Projective2>, CommittedInstance<Projective2>) =
cf_r1cs.dummy_instance();
let (cf_W_dummy, cf_U_dummy): (
CycleFoldWitness<Projective2>,
CycleFoldCommittedInstance<Projective2>,
) = cf_r1cs.dummy_instance();
// set the initial dummy instances
let mut W_i = W_dummy.clone();
@@ -1245,10 +1252,10 @@ mod tests {
let start = Instant::now();
// for this test, let Us & us be just an array of copies of the U_i & u_i respectively
let Us = vec![U_i.clone(); mu - 1];
let Ws = vec![W_i.clone(); mu - 1];
let us = vec![u_i.clone(); nu - 1];
let ws = vec![w_i.clone(); nu - 1];
let Us = vec![U_i.clone(); MU - 1];
let Ws = vec![W_i.clone(); MU - 1];
let us = vec![u_i.clone(); NU - 1];
let ws = vec![w_i.clone(); NU - 1];
let all_Us = [vec![U_i.clone()], Us.clone()].concat();
let all_us = [vec![u_i.clone()], us.clone()].concat();
let all_Ws = [vec![W_i.clone()], Ws].concat();
@@ -1268,35 +1275,39 @@ mod tests {
// input in the AugmentedFCircuit
let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, pp_hash);
augmented_f_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
mu,
nu,
i: Some(Fr::zero()),
i_usize: Some(0),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
external_inputs: Some(vec![]),
U_i: Some(U_i.clone()),
Us: Some(Us.clone()),
u_i_C: Some(u_i.C),
us: Some(us.clone()),
U_i1_C: Some(U_i1.C),
F: F_circuit,
x: Some(u_i1_x),
nimfs_proof: None,
augmented_f_circuit = AugmentedFCircuit::<
Projective,
Projective2,
GVar2,
CubicFCircuit<Fr>,
MU,
NU,
> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
i: Some(Fr::zero()),
i_usize: Some(0),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
external_inputs: Some(vec![]),
U_i: Some(U_i.clone()),
Us: Some(Us.clone()),
u_i_C: Some(u_i.C),
us: Some(us.clone()),
U_i1_C: Some(U_i1.C),
F: F_circuit,
x: Some(u_i1_x),
nimfs_proof: None,
// cyclefold values
cf_u_i_cmW: None,
cf_U_i: None,
cf_x: Some(cf_u_i1_x),
cf_cmT: None,
};
// cyclefold values
cf_u_i_cmW: None,
cf_U_i: None,
cf_x: Some(cf_u_i1_x),
cf_cmT: None,
};
} else {
let mut transcript_p: PoseidonSponge<Fr> =
PoseidonSponge::<Fr>::new(&poseidon_config.clone());
@@ -1328,7 +1339,7 @@ mod tests {
.collect();
let rho_powers_bits: Vec<Vec<bool>> = rho_powers
.iter()
.map(|rho_i| rho_i.into_bigint().to_bits_le()[..N_BITS_RO].to_vec())
.map(|rho_i| rho_i.into_bigint().to_bits_le()[..NOVA_N_BITS_RO].to_vec())
.collect();
// CycleFold part:
@@ -1348,9 +1359,8 @@ mod tests {
]
.concat();
let cf_circuit = CycleFoldCircuit::<Projective, GVar> {
let cf_circuit = HyperNovaCycleFoldCircuit::<Projective, GVar, MU, NU> {
_gc: PhantomData,
n_points: mu + nu,
r_bits: Some(rho_powers_bits.clone()),
points: Some(
[
@@ -1367,24 +1377,22 @@ mod tests {
// ensure that the CycleFoldCircuit is well defined
assert_eq!(
cf_circuit.r_bits.clone().unwrap().len(),
cf_circuit.n_points - 1
HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS - 1
);
assert_eq!(
cf_circuit.points.clone().unwrap().len(),
cf_circuit.n_points
HyperNovaCycleFoldConfig::<Projective, MU, NU>::N_INPUT_POINTS
);
let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::<
HyperNovaCycleFoldConfig<Projective, MU, NU>,
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
Pedersen<Projective>,
Pedersen<Projective2>,
false,
>(
mu + nu,
&mut transcript_p,
cf_r1cs.clone(),
cf_pedersen_params.clone(),
@@ -1401,35 +1409,39 @@ mod tests {
// AugmentedFCircuit
let cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, pp_hash);
augmented_f_circuit =
AugmentedFCircuit::<Projective, Projective2, GVar2, CubicFCircuit<Fr>> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
mu,
nu,
i: Some(iFr),
i_usize: Some(i),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
external_inputs: Some(vec![]),
U_i: Some(U_i.clone()),
Us: Some(Us.clone()),
u_i_C: Some(u_i.C),
us: Some(us.clone()),
U_i1_C: Some(U_i1.C),
F: F_circuit,
x: Some(u_i1_x),
nimfs_proof: Some(nimfs_proof),
augmented_f_circuit = AugmentedFCircuit::<
Projective,
Projective2,
GVar2,
CubicFCircuit<Fr>,
MU,
NU,
> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: poseidon_config.clone(),
ccs: ccs.clone(),
pp_hash: Some(pp_hash),
i: Some(iFr),
i_usize: Some(i),
z_0: Some(z_0.clone()),
z_i: Some(z_i.clone()),
external_inputs: Some(vec![]),
U_i: Some(U_i.clone()),
Us: Some(Us.clone()),
u_i_C: Some(u_i.C),
us: Some(us.clone()),
U_i1_C: Some(U_i1.C),
F: F_circuit,
x: Some(u_i1_x),
nimfs_proof: Some(nimfs_proof),
// cyclefold values
cf_u_i_cmW: Some(cf_u_i.cmW),
cf_U_i: Some(cf_U_i),
cf_x: Some(cf_u_i1_x),
cf_cmT: Some(cf_cmT),
};
// cyclefold values
cf_u_i_cmW: Some(cf_u_i.cmW),
cf_U_i: Some(cf_U_i),
cf_x: Some(cf_u_i1_x),
cf_cmT: Some(cf_cmT),
};
// assign the next round instances
cf_W_i = cf_W_i1;