Browse Source

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
update-nifs-interface
winderica 3 months ago
committed by GitHub
parent
commit
ecaecd483c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
13 changed files with 556 additions and 488 deletions
  1. +1
    -1
      folding-schemes/src/constants.rs
  2. +198
    -114
      folding-schemes/src/folding/circuits/cyclefold.rs
  3. +1
    -1
      folding-schemes/src/folding/hypernova/cccs.rs
  4. +146
    -134
      folding-schemes/src/folding/hypernova/circuits.rs
  5. +1
    -1
      folding-schemes/src/folding/hypernova/lcccs.rs
  6. +101
    -103
      folding-schemes/src/folding/hypernova/mod.rs
  7. +13
    -9
      folding-schemes/src/folding/hypernova/nimfs.rs
  8. +25
    -33
      folding-schemes/src/folding/nova/circuits.rs
  9. +21
    -12
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  10. +36
    -68
      folding-schemes/src/folding/nova/mod.rs
  11. +5
    -4
      folding-schemes/src/folding/nova/nifs.rs
  12. +7
    -7
      folding-schemes/src/folding/nova/serialize.rs
  13. +1
    -1
      folding-schemes/src/transcript/poseidon.rs

+ 1
- 1
folding-schemes/src/constants.rs

@ -2,4 +2,4 @@
// From [Srinath Setty](https://microsoft.com/en-us/research/people/srinath/): In Nova, soundness
// error ≤ 2/|S|, where S is the subset of the field F from which the challenges are drawn. In this
// case, we keep the size of S close to 2^128.
pub const N_BITS_RO: usize = 128;
pub const NOVA_N_BITS_RO: usize = 128;

+ 198
- 114
folding-schemes/src/folding/circuits/cyclefold.rs

@ -1,8 +1,8 @@
/// Contains [CycleFold](https://eprint.iacr.org/2023/1192.pdf) related circuits and functions that
/// are shared across the different folding schemes
use ark_crypto_primitives::sponge::{Absorb, CryptographicSponge};
use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_ec::{AffineRepr, CurveGroup, Group};
use ark_ff::{BigInteger, Field, PrimeField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
@ -20,25 +20,22 @@ use ark_std::rand::RngCore;
use ark_std::Zero;
use core::{borrow::Borrow, marker::PhantomData};
use super::{nonnative::uint::NonNativeUintVar, CF2};
use super::{nonnative::uint::NonNativeUintVar, CF1, CF2};
use crate::arith::r1cs::{extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::constants::N_BITS_RO;
use crate::folding::nova::{nifs::NIFS, CommittedInstance, Witness};
use crate::frontend::FCircuit;
use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar};
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::nova::nifs::NIFS;
use crate::transcript::{AbsorbNonNative, AbsorbNonNativeGadget, Transcript, TranscriptVar};
use crate::Error;
/// Public inputs length for the CycleFoldCircuit:
/// For Nova this is: |[r, p1.x,y, p2.x,y, p3.x,y]|
/// In general, |[r * (n_points-1), (p_i.x,y)*n_points, p_folded.x,y]|, thus, io len is:
/// (n_points-1) + 2*n_points + 2
pub fn cf_io_len(n_points: usize) -> usize {
(n_points - 1) + 2 * n_points + 2
}
/// Re-export the Nova committed instance as `CycleFoldCommittedInstance` and
/// witness as `CycleFoldWitness`, for clarity and consistency
pub use crate::folding::nova::{
CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness,
};
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance representation in the Nova
/// circuit.
/// CycleFoldCommittedInstanceVar is the CycleFold CommittedInstance represented
/// in folding verifier circuit
#[derive(Debug, Clone)]
pub struct CycleFoldCommittedInstanceVar<C: CurveGroup, GC: CurveVar<C, CF2<C>>>
where
@ -49,14 +46,14 @@ where
pub cmW: GC,
pub x: Vec<NonNativeUintVar<CF2<C>>>,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
@ -74,6 +71,29 @@ where
}
}
impl<C: CurveGroup> AbsorbNonNative<C::BaseField> for CycleFoldCommittedInstance<C>
where
C::BaseField: PrimeField + Absorb,
{
// Compatible with the in-circuit `CycleFoldCommittedInstanceVar::to_native_sponge_field_elements`
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, GC> AbsorbNonNativeGadget<C::BaseField> for CycleFoldCommittedInstanceVar<C, GC>
where
C: CurveGroup,
@ -107,6 +127,25 @@ where
}
}
impl<C: CurveGroup> CycleFoldCommittedInstance<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
{
/// hash_cyclefold implements the committed instance hash compatible with the
/// in-circuit implementation `CycleFoldCommittedInstanceVar::hash`.
/// Returns `H(U_i)`, where `U_i` is a `CycleFoldCommittedInstance`.
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]
}
}
impl<C, GC> CycleFoldCommittedInstanceVar<C, GC>
where
C: CurveGroup,
@ -114,11 +153,13 @@ where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField + Absorb,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
/// hash implements the committed instance hash compatible with the native implementation from
/// CommittedInstance.hash_cyclefold. Returns `H(U_i)`, where `U` is the `CommittedInstance`
/// for CycleFold. Additionally it returns the vector of the field elements from the self
/// parameters, so they can be reused in other gadgets avoiding recalculating (reconstraining)
/// them.
/// hash implements the committed instance hash compatible with the native
/// implementation `CycleFoldCommittedInstance::hash_cyclefold`.
/// Returns `H(U_i)`, where `U` is a `CycleFoldCommittedInstanceVar`.
///
/// Additionally it returns the vector of the field elements from the self
/// parameters, so they can be reused in other gadgets without recalculating
/// (reconstraining) them.
#[allow(clippy::type_complexity)]
pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF2<C>, S>>(
self,
@ -147,13 +188,14 @@ where
pub cmW: GC,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CommittedInstanceInCycleFoldVar<C, GC>
impl<C, GC> AllocVar<CycleFoldCommittedInstance<C>, CF2<C>>
for CommittedInstanceInCycleFoldVar<C, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>>,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
fn new_variable<T: Borrow<CommittedInstance<C>>>(
fn new_variable<T: Borrow<CycleFoldCommittedInstance<C>>>(
cs: impl Into<Namespace<CF2<C>>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
@ -189,25 +231,29 @@ where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub fn fold_committed_instance(
// assumes that r_bits is equal to r_nonnat just that in a different format
r_bits: Vec<Boolean<CF2<C>>>,
r_nonnat: NonNativeUintVar<CF2<C>>,
cmT: GC,
ci1: CycleFoldCommittedInstanceVar<C, GC>,
// ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci2: CycleFoldCommittedInstanceVar<C, GC>,
) -> Result<CycleFoldCommittedInstanceVar<C, GC>, SynthesisError> {
// r_nonnat is equal to r_bits just that in a different format
let r_nonnat = {
let mut bits = r_bits.clone();
bits.resize(CF1::<C>::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
};
Ok(CycleFoldCommittedInstanceVar {
cmE: cmT.scalar_mul_le(r_bits.iter())? + ci1.cmE,
cmW: ci1.cmW + ci2.cmW.scalar_mul_le(r_bits.iter())?,
u: ci1.u.add_no_align(&r_nonnat).modulo::<C::ScalarField>()?,
u: ci1.u.add_no_align(&r_nonnat).modulo::<CF1<C>>()?,
x: ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| {
a.add_no_align(&r_nonnat.mul_no_align(&b)?)
.modulo::<C::ScalarField>()
.modulo::<CF1<C>>()
})
.collect::<Result<Vec<_>, _>>()?,
})
@ -216,14 +262,13 @@ where
pub fn verify(
// assumes that r_bits is equal to r_nonnat just that in a different format
r_bits: Vec<Boolean<CF2<C>>>,
r_nonnat: NonNativeUintVar<CF2<C>>,
cmT: GC,
ci1: CycleFoldCommittedInstanceVar<C, GC>,
// ci2 is assumed to be always with cmE=0, u=1 (checks done previous to this method)
ci2: CycleFoldCommittedInstanceVar<C, GC>,
ci3: CycleFoldCommittedInstanceVar<C, GC>,
) -> Result<(), SynthesisError> {
let ci = Self::fold_committed_instance(r_bits, r_nonnat, cmT, ci1, ci2)?;
let ci = Self::fold_committed_instance(r_bits, cmT, ci1, ci2)?;
ci.cmE.enforce_equal(&ci3.cmE)?;
ci.u.enforce_equal_unaligned(&ci3.u)?;
@ -253,15 +298,15 @@ where
pub fn get_challenge_native<T: Transcript<C::BaseField>>(
transcript: &mut T,
pp_hash: C::BaseField, // public params hash
U_i: CommittedInstance<C>,
u_i: CommittedInstance<C>,
U_i: CycleFoldCommittedInstance<C>,
u_i: CycleFoldCommittedInstance<C>,
cmT: C,
) -> Vec<bool> {
transcript.absorb(&pp_hash);
transcript.absorb_nonnative(&U_i);
transcript.absorb_nonnative(&u_i);
transcript.absorb_point(&cmT);
transcript.squeeze_bits(N_BITS_RO)
transcript.squeeze_bits(NOVA_N_BITS_RO)
}
// compatible with the native get_challenge_native
@ -276,63 +321,101 @@ where
transcript.absorb(&U_i_vec)?;
transcript.absorb_nonnative(&u_i)?;
transcript.absorb_point(&cmT)?;
transcript.squeeze_bits(N_BITS_RO)
transcript.squeeze_bits(NOVA_N_BITS_RO)
}
}
pub trait CycleFoldConfig {
/// `N_INPUT_POINTS` specifies the number of input points that are folded in
/// [`CycleFoldCircuit`] via random linear combinations.
const N_INPUT_POINTS: usize;
/// `RANDOMNESS_BIT_LENGTH` is the (maximum) bit length of randomness `r`.
const RANDOMNESS_BIT_LENGTH: usize;
/// `FIELD_CAPACITY` is the maximum number of bits that can be stored in a
/// field element.
///
/// E.g., given a randomness `r` with `RANDOMNESS_BIT_LENGTH` bits, we need
/// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY` field elements to represent `r`
/// compactly in-circuit.
const FIELD_CAPACITY: usize = CF2::<Self::C>::MODULUS_BIT_SIZE as usize - 1;
/// Public inputs length for the CycleFoldCircuit.
/// * For Nova this is: `|[r, p1.x,y, p2.x,y, p3.x,y]|`
/// * In general, `|[r * (n_points-1), (p_i.x,y)*n_points, p_folded.x,y]|`.
///
/// Thus, `IO_LEN` is:
/// `RANDOMNESS_BIT_LENGTH / FIELD_CAPACITY * (N_INPUT_POINTS - 1) + 2 * N_INPUT_POINTS + 2`
const IO_LEN: usize = {
Self::RANDOMNESS_BIT_LENGTH.div_ceil(Self::FIELD_CAPACITY) * (Self::N_INPUT_POINTS - 1)
+ 2 * Self::N_INPUT_POINTS
+ 2
};
type F: Field;
type C: CurveGroup<BaseField = Self::F>;
}
/// CycleFoldCircuit contains the constraints that check the correct fold of the committed
/// instances from Curve1. Namely, it checks the random linear combinations of the elliptic curve
/// (Curve1) points of u_i, U_i leading to U_{i+1}
#[derive(Debug, Clone)]
pub struct CycleFoldCircuit<C: CurveGroup, GC: CurveVar<C, CF2<C>>> {
pub struct CycleFoldCircuit<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> {
pub _gc: PhantomData<GC>,
/// number of points being folded
pub n_points: usize,
/// r_bits is a vector containing the r_bits, one for each point except for the first one. They
/// are used for the scalar multiplication of the points. The r_bits are the bit
/// representation of each power of r (in Fr, while the CycleFoldCircuit is in Fq).
pub r_bits: Option<Vec<Vec<bool>>>,
/// points to be folded in the CycleFoldCircuit
pub points: Option<Vec<C>>,
pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
pub points: Option<Vec<CFG::C>>,
/// public inputs (cf_u_{i+1}.x)
pub x: Option<Vec<CFG::F>>,
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
impl<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> CycleFoldCircuit<CFG, GC> {
/// n_points indicates the number of points being folded in the CycleFoldCircuit
pub fn empty(n_points: usize) -> Self {
pub fn empty() -> Self {
Self {
_gc: PhantomData,
n_points,
r_bits: None,
points: None,
x: None,
}
}
}
impl<C, GC> ConstraintSynthesizer<CF2<C>> for CycleFoldCircuit<C, GC>
impl<CFG: CycleFoldConfig, GC: CurveVar<CFG::C, CFG::F>> ConstraintSynthesizer<CFG::F>
for CycleFoldCircuit<CFG, GC>
where
C: CurveGroup,
GC: CurveVar<C, CF2<C>> + ToConstraintFieldGadget<CF2<C>>,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
GC: ToConstraintFieldGadget<CFG::F>,
CFG::F: PrimeField,
for<'a> &'a GC: GroupOpsBounds<'a, CFG::C, GC>,
{
fn generate_constraints(self, cs: ConstraintSystemRef<CF2<C>>) -> Result<(), SynthesisError> {
let r_bits: Vec<Vec<Boolean<CF2<C>>>> = self
fn generate_constraints(self, cs: ConstraintSystemRef<CFG::F>) -> Result<(), SynthesisError> {
let r_bits: Vec<Vec<Boolean<CFG::F>>> = self
.r_bits
// n_points-1, bcs is one for each point except for the first one
.unwrap_or(vec![vec![false; N_BITS_RO]; self.n_points - 1])
.unwrap_or(vec![
vec![false; CFG::RANDOMNESS_BIT_LENGTH];
CFG::N_INPUT_POINTS - 1
])
.iter()
.map(|r_bits_i| {
Vec::<Boolean<CF2<C>>>::new_witness(cs.clone(), || Ok(r_bits_i.clone()))
Vec::<Boolean<CFG::F>>::new_witness(cs.clone(), || Ok(r_bits_i.clone()))
})
.collect::<Result<Vec<Vec<Boolean<CF2<C>>>>, SynthesisError>>()?;
.collect::<Result<_, _>>()?;
let points = Vec::<GC>::new_witness(cs.clone(), || {
Ok(self.points.unwrap_or(vec![C::zero(); self.n_points]))
Ok(self
.points
.unwrap_or(vec![CFG::C::zero(); CFG::N_INPUT_POINTS]))
})?;
#[cfg(test)]
{
assert_eq!(self.n_points, points.len());
assert_eq!(self.n_points - 1, r_bits.len());
assert_eq!(CFG::N_INPUT_POINTS, points.len());
assert_eq!(CFG::N_INPUT_POINTS - 1, r_bits.len());
for r_bits_i in &r_bits {
assert_eq!(r_bits_i.len(), CFG::RANDOMNESS_BIT_LENGTH);
}
}
// Fold the original points of the instances natively in CycleFold.
@ -343,36 +426,37 @@ where
let mut p_folded: GC = points[0].clone();
// iter over n_points-1 because the first point is not multiplied by r^i (it is multiplied
// by r^0=1)
for i in 0..self.n_points - 1 {
for i in 0..CFG::N_INPUT_POINTS - 1 {
p_folded += points[i + 1].scalar_mul_le(r_bits[i].iter())?;
}
let x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
Ok(self
.x
.unwrap_or(vec![CF2::<C>::zero(); cf_io_len(self.n_points)]))
let x = Vec::<FpVar<CFG::F>>::new_input(cs.clone(), || {
Ok(self.x.unwrap_or(vec![CFG::F::zero(); CFG::IO_LEN]))
})?;
#[cfg(test)]
assert_eq!(x.len(), cf_io_len(self.n_points)); // non-constrained sanity check
assert_eq!(x.len(), CFG::IO_LEN); // non-constrained sanity check
// Check that the points coordinates are placed as the public input x:
// In Nova, this is: x == [r, p1, p2, p3] (wheere p3 is the p_folded).
// In multifolding schemes such as HyperNova, this is:
// computed_x = [r_0, r_1, r_2, ..., r_n, p_0, p_1, p_2, ..., p_n, p_folded],
// where each p_i is in fact p_i.to_constraint_field()
let computed_x: Vec<FpVar<CF2<C>>> = [
r_bits
.iter()
.map(|r_bits_i| Boolean::le_bits_to_fp_var(r_bits_i))
.collect::<Result<Vec<FpVar<CF2<C>>>, SynthesisError>>()?,
points
.iter()
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec()))
.collect::<Result<Vec<Vec<FpVar<CF2<C>>>>, SynthesisError>>()?
.concat(),
p_folded.to_constraint_field()?[..2].to_vec(),
]
.concat();
let computed_x: Vec<FpVar<CFG::F>> = r_bits
.iter()
.map(|r_bits_i| {
r_bits_i
.chunks(CFG::FIELD_CAPACITY)
.map(Boolean::le_bits_to_fp_var)
.collect::<Result<Vec<_>, _>>()
})
.chain(
points
.iter()
.chain(&[p_folded])
.map(|p_i| Ok(p_i.to_constraint_field()?[..2].to_vec())),
)
.collect::<Result<Vec<_>, _>>()?
.concat();
computed_x.enforce_equal(&x)?;
Ok(())
@ -383,35 +467,33 @@ where
/// scheme struct because it is used both by Nova & HyperNova's CycleFold.
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_arguments)]
pub fn fold_cyclefold_circuit<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool>(
_n_points: usize,
pub fn fold_cyclefold_circuit<CFG, C1, GC1, C2, GC2, CS2, const H: bool>(
transcript: &mut impl Transcript<C1::ScalarField>,
cf_r1cs: R1CS<C2::ScalarField>,
cf_cs_params: CS2::ProverParams,
pp_hash: C1::ScalarField, // public params hash
cf_W_i: Witness<C2>, // witness of the running instance
cf_U_i: CommittedInstance<C2>, // running instance
pp_hash: C1::ScalarField, // public params hash
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: CycleFoldCircuit<CFG, GC1>,
mut rng: 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,
>
where
CFG: CycleFoldConfig<C = C1, F = CF2<C1>>,
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
FC: FCircuit<C1::ScalarField>,
CS1: CommitmentScheme<C1, H>,
CS2: CommitmentScheme<C2, H>,
<C1 as CurveGroup>::BaseField: PrimeField,
<C2 as CurveGroup>::BaseField: PrimeField,
@ -431,11 +513,12 @@ where
}
#[cfg(test)]
assert_eq!(cf_x_i.len(), cf_io_len(_n_points));
assert_eq!(cf_x_i.len(), CFG::IO_LEN);
// fold cyclefold instances
let cf_w_i = Witness::<C2>::new::<H>(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng);
let cf_u_i: CommittedInstance<C2> = cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?;
let cf_w_i = CycleFoldWitness::<C2>::new::<H>(cf_w_i.clone(), cf_r1cs.A.n_rows, &mut rng);
let cf_u_i: CycleFoldCommittedInstance<C2> =
cf_w_i.commit::<CS2, H>(&cf_cs_params, cf_x_i.clone())?;
// compute T* and cmT* for CycleFoldCircuit
let (cf_T, cf_cmT) = NIFS::<C2, CS2, H>::compute_cyclefold_cmT(
@ -478,11 +561,22 @@ pub mod tests {
use crate::transcript::poseidon::poseidon_canonical_config;
use crate::utils::get_cm_coordinates;
struct TestCycleFoldConfig<C: CurveGroup> {
_c: PhantomData<C>,
}
impl<C: CurveGroup> CycleFoldConfig for TestCycleFoldConfig<C> {
const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO;
const N_INPUT_POINTS: usize = 2;
type C = C;
type F = C::BaseField;
}
#[test]
fn test_committed_instance_cyclefold_var() {
let mut rng = ark_std::test_rng();
let ci = CommittedInstance::<Projective> {
let ci = CycleFoldCommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
@ -516,9 +610,8 @@ pub mod tests {
get_cm_coordinates(&ci3.cmW),
]
.concat();
let cfW_circuit = CycleFoldCircuit::<Projective, GVar> {
let cfW_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> {
_gc: PhantomData,
n_points: 2,
r_bits: Some(vec![r_bits.clone()]),
points: Some(vec![ci1.clone().cmW, ci2.clone().cmW]),
x: Some(cfW_u_i_x.clone()),
@ -535,9 +628,8 @@ pub mod tests {
get_cm_coordinates(&ci3.cmE),
]
.concat();
let cfE_circuit = CycleFoldCircuit::<Projective, GVar> {
let cfE_circuit = CycleFoldCircuit::<TestCycleFoldConfig<Projective>, GVar> {
_gc: PhantomData,
n_points: 2,
r_bits: Some(vec![r_bits.clone()]),
points: Some(vec![ci1.clone().cmE, cmT]),
x: Some(cfE_u_i_x.clone()),
@ -548,11 +640,10 @@ pub mod tests {
#[test]
fn test_nifs_full_gadget() {
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, r_Fr) = prepare_simple_fold_inputs();
let (_, _, _, _, ci1, _, ci2, _, ci3, _, cmT, r_bits, _) = prepare_simple_fold_inputs();
let cs = ConstraintSystem::<Fq>::new_ref();
let r_nonnatVar = NonNativeUintVar::<Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
let r_bitsVar = Vec::<Boolean<Fq>>::new_witness(cs.clone(), || Ok(r_bits)).unwrap();
let ci1Var =
@ -572,15 +663,8 @@ pub mod tests {
.unwrap();
let cmTVar = GVar::new_witness(cs.clone(), || Ok(cmT)).unwrap();
NIFSFullGadget::<Projective, GVar>::verify(
r_bitsVar,
r_nonnatVar,
cmTVar,
ci1Var,
ci2Var,
ci3Var,
)
.unwrap();
NIFSFullGadget::<Projective, GVar>::verify(r_bitsVar, cmTVar, ci1Var, ci2Var, ci3Var)
.unwrap();
assert!(cs.is_satisfied().unwrap());
}
@ -590,20 +674,20 @@ pub mod tests {
let poseidon_config = poseidon_canonical_config::<Fq>();
let mut transcript = PoseidonSponge::<Fq>::new(&poseidon_config);
let u_i = CommittedInstance::<Projective> {
let u_i = CycleFoldCommittedInstance::<Projective> {
cmE: Projective::zero(), // zero on purpose, so we test also the zero point case
u: Fr::zero(),
cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(7) // 7 = cf_io_len
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.collect(),
};
let U_i = CommittedInstance::<Projective> {
let U_i = CycleFoldCommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(7) // 7 = cf_io_len
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.collect(),
};
let cmT = Projective::rand(&mut rng);
@ -657,12 +741,12 @@ pub mod tests {
let poseidon_config = poseidon_canonical_config::<Fq>();
let sponge = PoseidonSponge::<Fq>::new(&poseidon_config);
let U_i = CommittedInstance::<Projective> {
let U_i = CycleFoldCommittedInstance::<Projective> {
cmE: Projective::rand(&mut rng),
u: Fr::rand(&mut rng),
cmW: Projective::rand(&mut rng),
x: std::iter::repeat_with(|| Fr::rand(&mut rng))
.take(7) // 7 = cf_io_len in Nova
.take(TestCycleFoldConfig::<Projective>::IO_LEN)
.collect(),
};
let pp_hash = Fq::from(42u32); // only for test

+ 1
- 1
folding-schemes/src/folding/hypernova/cccs.rs

@ -26,7 +26,7 @@ pub struct CCCS {
}
impl<F: PrimeField> CCS<F> {
pub fn to_cccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool>(
pub fn to_cccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>(
&self,
rng: &mut R,
cs_params: &CS::ProverParams,

+ 146
- 134
folding-schemes/src/folding/hypernova/circuits.rs

@ -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(
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,
// cyclefold values
cf_u_i_cmW: None,
cf_U_i: None,
cf_x: Some(cf_u_i1_x),
cf_cmT: 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,
};
} 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),
// 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),
};
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),
};
// assign the next round instances
cf_W_i = cf_W_i1;

+ 1
- 1
folding-schemes/src/folding/hypernova/lcccs.rs

@ -30,7 +30,7 @@ pub struct LCCCS {
}
impl<F: PrimeField> CCS<F> {
pub fn to_lcccs<R: Rng, C: CurveGroup, CS: CommitmentScheme<C, H>, const H: bool>(
pub fn to_lcccs<R: Rng, C, CS: CommitmentScheme<C, H>, const H: bool>(
&self,
rng: &mut R,
cs_params: &CS::ProverParams,

+ 101
- 103
folding-schemes/src/folding/hypernova/mod.rs

@ -6,31 +6,29 @@ use ark_crypto_primitives::sponge::{
use ark_ec::{CurveGroup, Group};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
use ark_std::rand::RngCore;
use ark_std::{One, Zero};
use core::marker::PhantomData;
use std::fmt::Debug;
use ark_std::{fmt::Debug, marker::PhantomData, rand::RngCore, One, Zero};
pub mod cccs;
pub mod circuits;
use circuits::AugmentedFCircuit;
pub mod lcccs;
pub mod nimfs;
pub mod utils;
use cccs::CCCS;
use circuits::AugmentedFCircuit;
use lcccs::LCCCS;
use nimfs::NIMFS;
use crate::commitment::CommitmentScheme;
use crate::constants::N_BITS_RO;
use crate::constants::NOVA_N_BITS_RO;
use crate::folding::circuits::{
cyclefold::{fold_cyclefold_circuit, CycleFoldCircuit},
cyclefold::{
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
CycleFoldWitness,
},
CF2,
};
use crate::folding::nova::{
get_r1cs_from_cs, traits::NovaR1CS, CommittedInstance, PreprocessorParam,
Witness as NovaWitness,
};
use crate::folding::nova::{get_r1cs_from_cs, traits::NovaR1CS, PreprocessorParam};
use crate::frontend::FCircuit;
use crate::utils::{get_cm_coordinates, pp_hash};
use crate::Error;
@ -42,6 +40,22 @@ use crate::{
FoldingScheme, MultiFolding,
};
struct HyperNovaCycleFoldConfig<C: CurveGroup, const MU: usize, const NU: usize> {
_c: PhantomData<C>,
}
impl<C: CurveGroup, const MU: usize, const NU: usize> CycleFoldConfig
for HyperNovaCycleFoldConfig<C, MU, NU>
{
const RANDOMNESS_BIT_LENGTH: usize = NOVA_N_BITS_RO;
const N_INPUT_POINTS: usize = MU + NU;
type C = C;
type F = C::BaseField;
}
type HyperNovaCycleFoldCircuit<C, GC, const MU: usize, const NU: usize> =
CycleFoldCircuit<HyperNovaCycleFoldConfig<C, MU, NU>, GC>;
/// Witness for the LCCCS & CCCS, containing the w vector, and the r_w used as randomness in the Pedersen commitment.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Witness<F: PrimeField> {
@ -73,8 +87,6 @@ where
pub cf_cs_params: CS2::ProverParams,
// if ccs is set, it will be used, if not, it will be computed at runtime
pub ccs: Option<CCS<C1::ScalarField>>,
pub mu: usize,
pub nu: usize,
}
#[derive(Debug, Clone)]
@ -114,9 +126,23 @@ where
/// Implements HyperNova+CycleFold's IVC, described in
/// [HyperNova](https://eprint.iacr.org/2023/573.pdf) and
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf), following the FoldingScheme trait
///
/// 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(Clone, Debug)]
pub struct HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool>
where
pub struct HyperNova<
C1,
GC1,
C2,
GC2,
FC,
CS1,
CS2,
const MU: usize,
const NU: usize,
const H: bool,
> where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
C2: CurveGroup,
@ -142,8 +168,6 @@ where
pub F: FC,
/// public params hash
pub pp_hash: C1::ScalarField,
pub mu: usize, // number of LCCCS instances to be folded
pub nu: usize, // number of CCCS instances to be folded
pub i: C1::ScalarField,
/// initial state
pub z_0: Vec<C1::ScalarField>,
@ -156,12 +180,12 @@ where
pub u_i: CCCS<C1>,
/// CycleFold running instance
pub cf_W_i: NovaWitness<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> MultiFolding<C1, C2, FC>
for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
MultiFolding<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
@ -227,7 +251,8 @@ where
}
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
@ -254,7 +279,8 @@ where
// prepare the initial dummy instances
let U_i = LCCCS::<C1>::dummy(self.ccs.l, self.ccs.t, self.ccs.s);
let mut u_i = CCCS::<C1>::dummy(self.ccs.l);
let (_, cf_U_i): (NovaWitness<C2>, CommittedInstance<C2>) = self.cf_r1cs.dummy_instance();
let (_, cf_U_i): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
self.cf_r1cs.dummy_instance();
let sponge = PoseidonSponge::<C1::ScalarField>::new(&self.poseidon_config);
@ -268,7 +294,7 @@ where
),
cf_U_i.hash_cyclefold(&sponge, self.pp_hash),
];
let us = vec![u_i.clone(); self.nu - 1];
let us = vec![u_i.clone(); NU - 1];
let z_i1 = self
.F
@ -285,14 +311,12 @@ where
);
let cf_u_i1_x = cf_U_i.hash_cyclefold(&sponge, self.pp_hash);
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: self.ccs.clone(),
pp_hash: Some(self.pp_hash),
mu: self.mu,
nu: self.nu,
i: Some(C1::ScalarField::zero()),
i_usize: Some(0),
z_0: Some(self.z_0.clone()),
@ -334,8 +358,8 @@ where
}
}
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const H: bool> FoldingScheme<C1, C2, FC>
for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, H>
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
FoldingScheme<C1, C2, FC> for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
where
C1: CurveGroup,
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
@ -352,37 +376,32 @@ where
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
{
/// Reuse Nova's PreprocessorParam, together with two usize values, which are mu & nu
/// respectively, which indicate the amount of LCCCS & CCCS instances to be folded at each
/// folding step.
type PreprocessorParam = (PreprocessorParam<C1, C2, FC, CS1, CS2, H>, usize, usize);
/// Reuse Nova's PreprocessorParam.
type PreprocessorParam = PreprocessorParam<C1, C2, FC, CS1, CS2, H>;
type ProverParam = ProverParams<C1, C2, CS1, CS2, H>;
type VerifierParam = VerifierParams<C1, C2, CS1, CS2, H>;
type RunningInstance = (LCCCS<C1>, Witness<C1::ScalarField>);
type IncomingInstance = (CCCS<C1>, Witness<C1::ScalarField>);
type MultiCommittedInstanceWithWitness =
(Vec<Self::RunningInstance>, Vec<Self::IncomingInstance>);
type CFInstance = (CommittedInstance<C2>, NovaWitness<C2>);
type CFInstance = (CycleFoldCommittedInstance<C2>, CycleFoldWitness<C2>);
fn preprocess(
mut rng: impl RngCore,
prep_param: &Self::PreprocessorParam,
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
let (prep_param, mu, nu) = prep_param;
if *mu < 1 || *nu < 1 {
if MU < 1 || NU < 1 {
return Err(Error::CantBeZero("mu,nu".to_string()));
}
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
&prep_param.poseidon_config,
prep_param.F.clone(),
None,
*mu,
*nu,
)?;
let ccs = augmented_f_circuit.ccs.clone();
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(mu + nu);
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
// if cs params exist, use them, if not, generate new ones
@ -409,8 +428,6 @@ where
cs_params: cs_pp.clone(),
cf_cs_params: cf_cs_pp.clone(),
ccs: Some(ccs.clone()),
mu: *mu,
nu: *nu,
};
let vp = VerifierParams::<C1, C2, CS1, CS2, H> {
poseidon_config: prep_param.poseidon_config.clone(),
@ -429,7 +446,7 @@ where
z_0: Vec<C1::ScalarField>,
) -> Result<Self, Error> {
let (pp, vp) = params;
if pp.mu < 1 || pp.nu < 1 {
if MU < 1 || NU < 1 {
return Err(Error::CantBeZero("mu,nu".to_string()));
}
@ -438,16 +455,14 @@ where
// prepare the HyperNova's AugmentedFCircuit and CycleFold's circuits and obtain its CCS
// and R1CS respectively
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC>::empty(
let augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU>::empty(
&pp.poseidon_config,
F.clone(),
pp.ccs.clone(),
pp.mu,
pp.nu,
)?;
let ccs = augmented_f_circuit.ccs.clone();
let cf_circuit = CycleFoldCircuit::<C1, GC1>::empty(pp.mu + pp.nu);
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU>::empty();
let cf_r1cs = get_r1cs_from_cs::<C2::ScalarField>(cf_circuit)?;
// compute the public params hash
@ -458,7 +473,7 @@ where
let U_dummy = LCCCS::<C1>::dummy(ccs.l, ccs.t, ccs.s);
let w_dummy = W_dummy.clone();
let mut u_dummy = CCCS::<C1>::dummy(ccs.l);
let (cf_W_dummy, cf_U_dummy): (NovaWitness<C2>, CommittedInstance<C2>) =
let (cf_W_dummy, cf_U_dummy): (CycleFoldWitness<C2>, CycleFoldCommittedInstance<C2>) =
cf_r1cs.dummy_instance();
u_dummy.x = vec![
U_dummy.hash(
@ -484,8 +499,6 @@ where
cf_cs_params: pp.cf_cs_params.clone(),
F,
pp_hash,
mu: pp.mu,
nu: pp.nu,
i: C1::ScalarField::zero(),
z_0: z_0.clone(),
z_i: z_0,
@ -536,27 +549,27 @@ where
// recall, mu & nu is the number of all the LCCCS & CCCS respectively, including the
// running and incoming instances that are not part of the 'other_instances', hence the +1
// in the couple of following checks.
if lcccs.len() + 1 != self.mu {
if lcccs.len() + 1 != MU {
return Err(Error::NotSameLength(
"other_instances.lcccs.len()".to_string(),
lcccs.len(),
"hypernova.mu".to_string(),
self.mu,
MU,
));
}
if cccs.len() + 1 != self.nu {
if cccs.len() + 1 != NU {
return Err(Error::NotSameLength(
"other_instances.cccs.len()".to_string(),
cccs.len(),
"hypernova.nu".to_string(),
self.nu,
NU,
));
}
let (Us, Ws): (Vec<LCCCS<C1>>, Vec<Witness<C1::ScalarField>>) = lcccs.into_iter().unzip();
let (us, ws): (Vec<CCCS<C1>>, Vec<Witness<C1::ScalarField>>) = cccs.into_iter().unzip();
let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC>;
let augmented_f_circuit: AugmentedFCircuit<C1, C2, GC2, FC, MU, NU>;
if self.z_i.len() != self.F.state_len() {
return Err(Error::NotSameLength(
@ -608,14 +621,12 @@ where
// input in the AugmentedFCircuit
cf_u_i1_x = self.cf_U_i.hash_cyclefold(&sponge, self.pp_hash);
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: self.ccs.clone(),
pp_hash: Some(self.pp_hash),
mu: self.mu,
nu: self.nu,
i: Some(C1::ScalarField::zero()),
i_usize: Some(0),
z_0: Some(self.z_0.clone()),
@ -674,7 +685,7 @@ where
.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:
@ -698,9 +709,8 @@ where
]
.concat();
let cf_circuit = CycleFoldCircuit::<C1, GC1> {
let cf_circuit = HyperNovaCycleFoldCircuit::<C1, GC1, MU, NU> {
_gc: PhantomData,
n_points: self.mu + self.nu,
r_bits: Some(rho_powers_bits.clone()),
points: Some(
[
@ -714,30 +724,34 @@ where
x: Some(cf_u_i_x.clone()),
};
let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) =
fold_cyclefold_circuit::<C1, GC1, C2, GC2, FC, CS1, CS2, H>(
self.mu + self.nu,
&mut transcript_p,
self.cf_r1cs.clone(),
self.cf_cs_params.clone(),
self.pp_hash,
self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance
cf_u_i_x,
cf_circuit,
&mut rng,
)?;
let (_cf_w_i, cf_u_i, cf_W_i1, cf_U_i1, cf_cmT, _) = fold_cyclefold_circuit::<
HyperNovaCycleFoldConfig<C1, MU, NU>,
C1,
GC1,
C2,
GC2,
CS2,
H,
>(
&mut transcript_p,
self.cf_r1cs.clone(),
self.cf_cs_params.clone(),
self.pp_hash,
self.cf_W_i.clone(), // CycleFold running instance witness
self.cf_U_i.clone(), // CycleFold running instance
cf_u_i_x,
cf_circuit,
&mut rng,
)?;
cf_u_i1_x = cf_U_i1.hash_cyclefold(&sponge, self.pp_hash);
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC> {
augmented_f_circuit = AugmentedFCircuit::<C1, C2, GC2, FC, MU, NU> {
_c2: PhantomData,
_gc2: PhantomData,
poseidon_config: self.poseidon_config.clone(),
ccs: self.ccs.clone(),
pp_hash: Some(self.pp_hash),
mu: self.mu,
nu: self.nu,
i: Some(self.i),
i_usize: Some(i_usize),
z_0: Some(self.z_0.clone()),
@ -920,37 +934,21 @@ mod tests {
) {
let mut rng = ark_std::test_rng();
let (mu, nu) = (2, 3);
const MU: usize = 2;
const NU: usize = 3;
type HN<CS1, CS2, const H: bool> =
HyperNova<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, MU, NU, H>;
let prep_param =
PreprocessorParam::<Projective, Projective2, CubicFCircuit<Fr>, CS1, CS2, H>::new(
poseidon_config.clone(),
F_circuit,
);
let hypernova_params = HyperNova::<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
CS1,
CS2,
H,
>::preprocess(&mut rng, &(prep_param, mu, nu))
.unwrap();
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
let z_0 = vec![Fr::from(3_u32)];
let mut hypernova = HyperNova::<
Projective,
GVar,
Projective2,
GVar2,
CubicFCircuit<Fr>,
CS1,
CS2,
H,
>::init(&hypernova_params, F_circuit, z_0.clone())
.unwrap();
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
let (w_i_blinding, W_i_blinding) = if H {
(Fr::rand(&mut rng), Fr::rand(&mut rng))
@ -964,7 +962,7 @@ mod tests {
for _ in 0..num_steps {
// prepare some new instances to fold in the multifolding step
let mut lcccs = vec![];
for j in 0..mu - 1 {
for j in 0..MU - 1 {
let instance_state = vec![Fr::from(j as u32 + 85_u32)];
let (U, W) = hypernova
.new_running_instance(&mut rng, instance_state, vec![])
@ -972,7 +970,7 @@ mod tests {
lcccs.push((U, W));
}
let mut cccs = vec![];
for j in 0..nu - 1 {
for j in 0..NU - 1 {
let instance_state = vec![Fr::from(j as u32 + 15_u32)];
let (u, w) = hypernova
.new_incoming_instance(&mut rng, instance_state, vec![])
@ -988,7 +986,7 @@ mod tests {
assert_eq!(Fr::from(num_steps as u32), hypernova.i);
let (running_instance, incoming_instance, cyclefold_instance) = hypernova.instances();
HyperNova::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>, CS1, CS2, H>::verify(
HN::verify(
hypernova_params.1, // verifier_params
z_0,
hypernova.z_i,

+ 13
- 9
folding-schemes/src/folding/hypernova/nimfs.rs

@ -12,7 +12,7 @@ use super::{
Witness,
};
use crate::arith::ccs::CCS;
use crate::constants::N_BITS_RO;
use crate::constants::NOVA_N_BITS_RO;
use crate::transcript::Transcript;
use crate::utils::sum_check::structs::{IOPProof as SumCheckProof, IOPProverMessage};
use crate::utils::sum_check::{IOPSumCheck, SumCheck};
@ -123,10 +123,12 @@ where
// compute the next power of rho
rho_i *= rho;
// 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.into_bigint().to_bits_le();
rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_i_bits[..N_BITS_RO]))
.unwrap();
rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(
&rho_i_bits[..NOVA_N_BITS_RO],
))
.unwrap();
if i < lcccs.len() + cccs.len() - 1 {
// store the cropped rho_i into the rho_powers vector
rho_powers[i] = rho_i;
@ -181,10 +183,12 @@ where
// compute the next power of rho
rho_i *= rho;
// 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.into_bigint().to_bits_le();
rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_i_bits[..N_BITS_RO]))
.unwrap();
rho_i = C::ScalarField::from_bigint(BigInteger::from_bits_le(
&rho_i_bits[..NOVA_N_BITS_RO],
))
.unwrap();
}
Witness {
w: w_folded,
@ -272,7 +276,7 @@ where
// Step 6: Get the folding challenge
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
transcript.absorb(&rho_scalar);
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(N_BITS_RO);
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
let rho: C::ScalarField =
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();
@ -391,7 +395,7 @@ where
// Step 6: Get the folding challenge
let rho_scalar = C::ScalarField::from_le_bytes_mod_order(b"rho");
transcript.absorb(&rho_scalar);
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(N_BITS_RO);
let rho_bits: Vec<bool> = transcript.get_challenge_nbits(NOVA_N_BITS_RO);
let rho: C::ScalarField =
C::ScalarField::from_bigint(BigInteger::from_bits_le(&rho_bits)).unwrap();

+ 25
- 33
folding-schemes/src/folding/nova/circuits.rs

@ -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]

+ 21
- 12
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -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 {
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()))
})?;

+ 36
- 68
folding-schemes/src/folding/nova/mod.rs

@ -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))

+ 5
- 4
folding-schemes/src/folding/nova/nifs.rs

@ -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,

+ 7
- 7
folding-schemes/src/folding/nova/serialize.rs

@ -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())

+ 1
- 1
folding-schemes/src/transcript/poseidon.rs

@ -226,7 +226,7 @@ pub mod tests {
#[test]
fn test_transcript_and_transcriptvar_nbits() {
let nbits = crate::constants::N_BITS_RO;
let nbits = crate::constants::NOVA_N_BITS_RO;
// use 'native' transcript
let config = poseidon_canonical_config::<Fq>();

Loading…
Cancel
Save