Browse Source

Reduce the number of constraints in `DeciderEthCircuit` (#88)

* Add a dedicated variant of `mat_vec_mul_sparse` for `NonNativeFieldVar`

* Switch to a customized in-circuit nonnative implementation for efficiency

* Comments and tests for `NonNativeUintVar`

* Make `CycleFoldCircuit` a bit smaller

* Faster trusted setup and proof generation by avoiding some nested LCs

* Check the remaining limbs in a more safe way

* Format

* Disable the non-native checks in tests again

* Clarify the group operation in `enforce_equal_unaligned`

* Explain the rationale behind non-native mat-vec multiplication

* Explain the difference with some other impls of `enforce_equal_unaligned`

* Format
update-nifs-interface
winderica 7 months ago
committed by GitHub
parent
commit
b648ddb300
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
11 changed files with 1359 additions and 487 deletions
  1. +1
    -0
      folding-schemes/Cargo.toml
  2. +0
    -306
      folding-schemes/src/folding/circuits/nonnative.rs
  3. +161
    -0
      folding-schemes/src/folding/circuits/nonnative/affine.rs
  4. +2
    -0
      folding-schemes/src/folding/circuits/nonnative/mod.rs
  5. +1019
    -0
      folding-schemes/src/folding/circuits/nonnative/uint.rs
  6. +24
    -9
      folding-schemes/src/folding/nova/circuits.rs
  7. +29
    -39
      folding-schemes/src/folding/nova/cyclefold.rs
  8. +1
    -1
      folding-schemes/src/folding/nova/decider_eth.rs
  9. +70
    -77
      folding-schemes/src/folding/nova/decider_eth_circuit.rs
  10. +3
    -5
      folding-schemes/src/folding/nova/mod.rs
  11. +49
    -50
      folding-schemes/src/utils/gadgets.rs

+ 1
- 0
folding-schemes/Cargo.toml

@ -18,6 +18,7 @@ ark-circom = { git = "https://github.com/arnaucube/circom-compat.git" }
thiserror = "1.0"
rayon = "1.7.0"
num-bigint = "0.4"
num-integer = "0.1"
color-eyre = "=0.6.2"
# tmp imports for espresso's sumcheck

+ 0
- 306
folding-schemes/src/folding/circuits/nonnative.rs

@ -1,306 +0,0 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::{BigInteger, PrimeField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
fields::{
fp::FpVar,
nonnative::{
params::{get_params, OptimizationType},
AllocatedNonNativeFieldVar, NonNativeFieldVar,
},
FieldVar,
},
ToBitsGadget, ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSystemRef, Namespace, OptimizationGoal, SynthesisError};
use ark_std::Zero;
use core::borrow::Borrow;
use std::marker::PhantomData;
/// Compose a vector boolean into a `NonNativeFieldVar`
pub fn nonnative_field_var_from_le_bits<TargetField: PrimeField, BaseField: PrimeField>(
cs: ConstraintSystemRef<BaseField>,
bits: &[Boolean<BaseField>],
) -> Result<NonNativeFieldVar<TargetField, BaseField>, SynthesisError> {
let params = get_params(
TargetField::MODULUS_BIT_SIZE as usize,
BaseField::MODULUS_BIT_SIZE as usize,
match cs.optimization_goal() {
OptimizationGoal::None => OptimizationType::Constraints,
OptimizationGoal::Constraints => OptimizationType::Constraints,
OptimizationGoal::Weight => OptimizationType::Weight,
},
);
// push the lower limbs first
let mut limbs = bits
.chunks(params.bits_per_limb)
.map(Boolean::le_bits_to_fp_var)
.collect::<Result<Vec<_>, _>>()?;
limbs.resize(params.num_limbs, FpVar::zero());
limbs.reverse();
Ok(AllocatedNonNativeFieldVar {
cs,
limbs,
num_of_additions_over_normal_form: BaseField::one(),
is_in_the_normal_form: false,
target_phantom: PhantomData,
}
.into())
}
/// A more efficient version of `NonNativeFieldVar::to_constraint_field`
pub fn nonnative_field_var_to_constraint_field<TargetField: PrimeField, BaseField: PrimeField>(
f: &NonNativeFieldVar<TargetField, BaseField>,
) -> Result<Vec<FpVar<BaseField>>, SynthesisError> {
let bits = f.to_bits_le()?;
let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
let mut limbs = bits
.chunks(bits_per_limb)
.map(|chunk| {
let mut limb = FpVar::<BaseField>::zero();
let mut w = BaseField::one();
for b in chunk.iter() {
limb += FpVar::from(b.clone()) * w;
w.double_in_place();
}
limb
})
.collect::<Vec<FpVar<BaseField>>>();
limbs.resize(num_limbs, FpVar::zero());
limbs.reverse();
Ok(limbs)
}
/// The out-circuit counterpart of `nonnative_field_var_to_constraint_field`
pub fn nonnative_field_to_field_elements<TargetField: PrimeField, BaseField: PrimeField>(
f: &TargetField,
) -> Vec<BaseField> {
let bits = f.into_bigint().to_bits_le();
let bits_per_limb = BaseField::MODULUS_BIT_SIZE as usize - 1;
let num_limbs = (TargetField::MODULUS_BIT_SIZE as usize).div_ceil(bits_per_limb);
let mut limbs = bits
.chunks(bits_per_limb)
.map(|chunk| {
let mut limb = BaseField::zero();
let mut w = BaseField::one();
for &b in chunk.iter() {
limb += BaseField::from(b) * w;
w.double_in_place();
}
limb
})
.collect::<Vec<BaseField>>();
limbs.resize(num_limbs, BaseField::zero());
limbs.reverse();
limbs
}
/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native
/// field, over the constraint field. It is not intended to perform operations, but just to contain
/// the affine coordinates in order to perform hash operations of the point.
#[derive(Debug, Clone)]
pub struct NonNativeAffineVar<C: CurveGroup>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
pub x: NonNativeFieldVar<C::BaseField, C::ScalarField>,
pub y: NonNativeFieldVar<C::BaseField, C::ScalarField>,
}
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C>
where
C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn new_variable<T: Borrow<C>>(
cs: impl Into<Namespace<C::ScalarField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let affine = val.borrow().into_affine();
let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
let xy = affine.xy().unwrap_or(zero_point);
let x = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(xy.0),
mode,
)?;
let y = NonNativeFieldVar::<C::BaseField, C::ScalarField>::new_variable(
cs.clone(),
|| Ok(xy.1),
mode,
)?;
Ok(Self { x, y })
})
}
}
impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// A more efficient version of `point_to_nonnative_limbs_custom_opt`.
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
// the circuit.
fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
let x = nonnative_field_var_to_constraint_field(&self.x)?;
let y = nonnative_field_var_to_constraint_field(&self.y)?;
Ok([x, y].concat())
}
}
/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
#[allow(clippy::type_complexity)]
pub fn nonnative_affine_to_field_elements<C: CurveGroup>(
p: C,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
let affine = p.into_affine();
if affine.is_zero() {
let x = nonnative_field_to_field_elements(&C::BaseField::zero());
let y = nonnative_field_to_field_elements(&C::BaseField::zero());
return Ok((x, y));
}
let (x, y) = affine.xy().unwrap();
let x = nonnative_field_to_field_elements(x);
let y = nonnative_field_to_field_elements(y);
Ok((x, y))
}
impl<C: CurveGroup> NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization
// type (which is the default optimization type for arkworks' Groth16).
// Used for extracting a list of field elements of type `C::ScalarField` from the public input
// `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
// `FpVar` in-circuit.
#[allow(clippy::type_complexity)]
pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
point_to_nonnative_limbs_custom_opt(p, OptimizationType::Constraints)
}
}
// Used to compute (outside the circuit) the limbs representation of a point.
// For `OptimizationType::Constraints`, the result matches the one used in-circuit.
// For `OptimizationType::Weight`, the result vector is more dense and is suitable for hashing.
// It is possible to further optimize the length of the result vector (see
// `nonnative_affine_to_field_elements`)
#[allow(clippy::type_complexity)]
fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
p: C,
optimization_type: OptimizationType,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
let affine = p.into_affine();
if affine.is_zero() {
let x =
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
&C::BaseField::zero(),
optimization_type,
)?;
let y =
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
&C::BaseField::zero(),
optimization_type,
)?;
return Ok((x, y));
}
let (x, y) = affine.xy().unwrap();
let x = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
x,
optimization_type,
)?;
let y = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
y,
optimization_type,
)?;
Ok((x, y))
}
#[cfg(test)]
mod tests {
use super::*;
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
#[test]
fn test_alloc_zero() {
let cs = ConstraintSystem::<Fr>::new_ref();
// dealing with the 'zero' point should not panic when doing the unwrap
let p = Projective::zero();
assert!(NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
}
#[test]
fn test_arkworks_to_constraint_field() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = point_to_nonnative_limbs_custom_opt(p, OptimizationType::Weight).unwrap();
assert_eq!(pVar.x.to_constraint_field().unwrap().value().unwrap(), x);
assert_eq!(pVar.y.to_constraint_field().unwrap().value().unwrap(), y);
}
#[test]
fn test_improved_to_constraint_field() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = nonnative_affine_to_field_elements(p).unwrap();
assert_eq!(
pVar.to_constraint_field().unwrap().value().unwrap(),
[x, y].concat()
);
}
#[test]
fn test_inputize() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
match (pVar.x, pVar.y) {
(NonNativeFieldVar::Var(p_x), NonNativeFieldVar::Var(p_y)) => {
assert_eq!(p_x.limbs.value().unwrap(), x);
assert_eq!(p_y.limbs.value().unwrap(), y);
}
_ => unreachable!(),
}
}
}

+ 161
- 0
folding-schemes/src/folding/circuits/nonnative/affine.rs

@ -0,0 +1,161 @@
use ark_ec::{AffineRepr, CurveGroup};
use ark_ff::PrimeField;
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
fields::fp::FpVar,
ToConstraintFieldGadget,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use ark_std::Zero;
use core::borrow::Borrow;
use super::uint::{nonnative_field_to_field_elements, NonNativeUintVar};
/// NonNativeAffineVar represents an elliptic curve point in Affine representation in the non-native
/// field, over the constraint field. It is not intended to perform operations, but just to contain
/// the affine coordinates in order to perform hash operations of the point.
#[derive(Debug, Clone)]
pub struct NonNativeAffineVar<C: CurveGroup>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
pub x: NonNativeUintVar<C::ScalarField>,
pub y: NonNativeUintVar<C::ScalarField>,
}
impl<C> AllocVar<C, C::ScalarField> for NonNativeAffineVar<C>
where
C: CurveGroup,
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
fn new_variable<T: Borrow<C>>(
cs: impl Into<Namespace<C::ScalarField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let affine = val.borrow().into_affine();
let zero_point = (&C::BaseField::zero(), &C::BaseField::zero());
let xy = affine.xy().unwrap_or(zero_point);
let x = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.0), mode)?;
let y = NonNativeUintVar::new_variable(cs.clone(), || Ok(*xy.1), mode)?;
Ok(Self { x, y })
})
}
}
impl<C: CurveGroup> ToConstraintFieldGadget<C::ScalarField> for NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// Used for converting `NonNativeAffineVar` to a vector of `FpVar` with minimum length in
// the circuit.
fn to_constraint_field(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
let x = self.x.to_constraint_field()?;
let y = self.y.to_constraint_field()?;
Ok([x, y].concat())
}
}
/// The out-circuit counterpart of `NonNativeAffineVar::to_constraint_field`
#[allow(clippy::type_complexity)]
pub fn nonnative_affine_to_field_elements<C: CurveGroup>(
p: C,
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
let affine = p.into_affine();
if affine.is_zero() {
let x = nonnative_field_to_field_elements(&C::BaseField::zero());
let y = nonnative_field_to_field_elements(&C::BaseField::zero());
return Ok((x, y));
}
let (x, y) = affine.xy().unwrap();
let x = nonnative_field_to_field_elements(x);
let y = nonnative_field_to_field_elements(y);
Ok((x, y))
}
impl<C: CurveGroup> NonNativeAffineVar<C>
where
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
{
// A wrapper of `point_to_nonnative_limbs_custom_opt` with constraints-focused optimization
// type (which is the default optimization type for arkworks' Groth16).
// Used for extracting a list of field elements of type `C::ScalarField` from the public input
// `p`, in exactly the same way as how `NonNativeAffineVar` is represented as limbs of type
// `FpVar` in-circuit.
#[allow(clippy::type_complexity)]
pub fn inputize(p: C) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError> {
let affine = p.into_affine();
if affine.is_zero() {
let x = NonNativeUintVar::inputize(
&(C::ScalarField::zero()).into(),
C::ScalarField::MODULUS_BIT_SIZE as usize,
);
let y = NonNativeUintVar::inputize(
&(C::ScalarField::zero()).into(),
C::ScalarField::MODULUS_BIT_SIZE as usize,
);
return Ok((x, y));
}
let (x, y) = affine.xy().unwrap();
let x = NonNativeUintVar::inputize(&(*x).into(), C::ScalarField::MODULUS_BIT_SIZE as usize);
let y = NonNativeUintVar::inputize(&(*y).into(), C::ScalarField::MODULUS_BIT_SIZE as usize);
Ok((x, y))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_pallas::{Fr, Projective};
use ark_r1cs_std::R1CSVar;
use ark_relations::r1cs::ConstraintSystem;
use ark_std::UniformRand;
#[test]
fn test_alloc_zero() {
let cs = ConstraintSystem::<Fr>::new_ref();
// dealing with the 'zero' point should not panic when doing the unwrap
let p = Projective::zero();
assert!(NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).is_ok());
}
#[test]
fn test_improved_to_constraint_field() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = nonnative_affine_to_field_elements(p).unwrap();
assert_eq!(
pVar.to_constraint_field().unwrap().value().unwrap(),
[x, y].concat()
);
}
#[test]
fn test_inputize() {
let cs = ConstraintSystem::<Fr>::new_ref();
// check that point_to_nonnative_limbs returns the expected values
let mut rng = ark_std::test_rng();
let p = Projective::rand(&mut rng);
let pVar = NonNativeAffineVar::<Projective>::new_witness(cs.clone(), || Ok(p)).unwrap();
let (x, y) = NonNativeAffineVar::inputize(p).unwrap();
assert_eq!(pVar.x.0.value().unwrap(), x);
assert_eq!(pVar.y.0.value().unwrap(), y);
}
}

+ 2
- 0
folding-schemes/src/folding/circuits/nonnative/mod.rs

@ -0,0 +1,2 @@
pub mod affine;
pub mod uint;

+ 1019
- 0
folding-schemes/src/folding/circuits/nonnative/uint.rs
File diff suppressed because it is too large
View File


+ 24
- 9
folding-schemes/src/folding/nova/circuits.rs

@ -14,13 +14,13 @@ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
fields::{fp::FpVar, FieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
R1CSVar, ToConstraintFieldGadget,
};
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError};
use ark_std::{fmt::Debug, Zero};
use ark_std::{fmt::Debug, One, Zero};
use core::{borrow::Borrow, marker::PhantomData};
use super::{
@ -29,9 +29,12 @@ use super::{
},
CommittedInstance,
};
use crate::folding::circuits::nonnative::{nonnative_affine_to_field_elements, NonNativeAffineVar};
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::{
affine::{nonnative_affine_to_field_elements, NonNativeAffineVar},
uint::NonNativeUintVar,
};
use crate::frontend::FCircuit;
use crate::{constants::N_BITS_RO, folding::circuits::nonnative::nonnative_field_var_from_le_bits};
/// CF1 represents the ConstraintField used for the main Nova circuit which is over E1::Fr, where
/// E1 is the main curve where we do the folding.
@ -402,7 +405,11 @@ where
)?;
let r = Boolean::le_bits_to_fp_var(&r_bits)?;
// Also convert r_bits to a `NonNativeFieldVar`
let r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &r_bits)?;
let r_nonnat = {
let mut bits = r_bits;
bits.resize(C1::BaseField::MODULUS_BIT_SIZE as usize, Boolean::FALSE);
NonNativeUintVar::from(&bits)
};
// Notice that NIFSGadget::fold_committed_instance does not fold cmE & cmW.
// We set `U_i1.cmE` and `U_i1.cmW` to unconstrained witnesses `U_i1_cmE` and `U_i1_cmW`
@ -452,7 +459,7 @@ where
// cf1_u_i.cmE = 0
cmE: GC2::zero(),
// cf1_u_i.u = 1
u: NonNativeFieldVar::one(),
u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?,
// cf1_u_i.cmW is provided by the prover as witness
cmW: GC2::new_witness(cs.clone(), || Ok(self.cf1_u_i_cmW.unwrap_or(C2::zero())))?,
// cf1_u_i.x is computed in step 1
@ -462,7 +469,7 @@ where
// cf2_u_i.cmE = 0
cmE: GC2::zero(),
// cf2_u_i.u = 1
u: NonNativeFieldVar::one(),
u: NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::one())?,
// cf2_u_i.cmW is provided by the prover as witness
cmW: GC2::new_witness(cs.clone(), || Ok(self.cf2_u_i_cmW.unwrap_or(C2::zero())))?,
// cf2_u_i.x is computed in step 1
@ -482,7 +489,11 @@ where
cf1_cmT.clone(),
)?;
// Convert cf1_r_bits to a `NonNativeFieldVar`
let cf1_r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &cf1_r_bits)?;
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,
@ -500,7 +511,11 @@ where
cf2_u_i.clone(),
cf2_cmT.clone(),
)?;
let cf2_r_nonnat = nonnative_field_var_from_le_bits(cs.clone(), &cf2_r_bits)?;
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,

+ 29
- 39
folding-schemes/src/folding/nova/cyclefold.rs

@ -16,7 +16,7 @@ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
fields::{fp::FpVar, FieldVar},
groups::GroupOpsBounds,
prelude::CurveVar,
ToConstraintFieldGadget,
@ -29,7 +29,7 @@ use core::{borrow::Borrow, marker::PhantomData};
use super::circuits::CF2;
use super::CommittedInstance;
use crate::constants::N_BITS_RO;
use crate::folding::circuits::nonnative::nonnative_field_var_to_constraint_field;
use crate::folding::circuits::nonnative::uint::NonNativeUintVar;
use crate::Error;
// public inputs length for the CycleFoldCircuit: |[r, p1.x,y, p2.x,y, p3.x,y]|
@ -43,9 +43,9 @@ where
for<'a> &'a GC: GroupOpsBounds<'a, C, GC>,
{
pub cmE: GC,
pub u: NonNativeFieldVar<C::ScalarField, CF2<C>>,
pub u: NonNativeUintVar<CF2<C>>,
pub cmW: GC,
pub x: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
pub x: Vec<NonNativeUintVar<CF2<C>>>,
}
impl<C, GC> AllocVar<CommittedInstance<C>, CF2<C>> for CycleFoldCommittedInstanceVar<C, GC>
where
@ -64,16 +64,8 @@ where
let cmE = GC::new_variable(cs.clone(), || Ok(val.borrow().cmE), mode)?;
let cmW = GC::new_variable(cs.clone(), || Ok(val.borrow().cmW), mode)?;
let u = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
cs.clone(),
|| Ok(val.borrow().u),
mode,
)?;
let x = Vec::<NonNativeFieldVar<C::ScalarField, CF2<C>>>::new_variable(
cs.clone(),
|| Ok(val.borrow().x.clone()),
mode,
)?;
let u = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
let x = Vec::new_variable(cs.clone(), || Ok(val.borrow().x.clone()), mode)?;
Ok(Self { cmE, u, cmW, x })
})
@ -99,10 +91,10 @@ where
let is_inf = cmE_is_inf.double()? + cmW_is_inf;
Ok([
nonnative_field_var_to_constraint_field(&self.u)?,
self.u.to_constraint_field()?,
self.x
.iter()
.map(nonnative_field_var_to_constraint_field)
.map(|i| i.to_constraint_field())
.collect::<Result<Vec<_>, _>>()?
.concat(),
cmE_elems,
@ -193,7 +185,7 @@ where
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: NonNativeFieldVar<C::ScalarField, 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)
@ -202,20 +194,23 @@ where
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 + r_nonnat.clone(),
u: ci1.u.add_no_align(&r_nonnat).modulo::<C::ScalarField>()?,
x: ci1
.x
.iter()
.zip(ci2.x)
.map(|(a, b)| a + &r_nonnat * &b)
.collect::<Vec<_>>(),
.map(|(a, b)| {
a.add_no_align(&r_nonnat.mul_no_align(&b)?)
.modulo::<C::ScalarField>()
})
.collect::<Result<Vec<_>, _>>()?,
})
}
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: NonNativeFieldVar<C::ScalarField, 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)
@ -225,9 +220,11 @@ where
let ci = Self::fold_committed_instance(r_bits, r_nonnat, cmT, ci1, ci2)?;
ci.cmE.enforce_equal(&ci3.cmE)?;
ci.u.enforce_equal(&ci3.u)?;
ci.u.enforce_equal_unaligned(&ci3.u)?;
ci.cmW.enforce_equal(&ci3.cmW)?;
ci.x.enforce_equal(&ci3.x)?;
for (x, y) in ci.x.iter().zip(ci3.x.iter()) {
x.enforce_equal_unaligned(y)?;
}
Ok(())
}
@ -316,7 +313,6 @@ pub struct CycleFoldCircuit>> {
pub r_bits: Option<Vec<bool>>,
pub p1: Option<C>,
pub p2: Option<C>,
pub p3: Option<C>,
pub x: Option<Vec<CF2<C>>>, // public inputs (cf_u_{i+1}.x)
}
impl<C: CurveGroup, GC: CurveVar<C, CF2<C>>> CycleFoldCircuit<C, GC> {
@ -326,7 +322,6 @@ impl>> CycleFoldCircuit {
r_bits: None,
p1: None,
p2: None,
p3: None,
x: None,
}
}
@ -344,7 +339,11 @@ where
})?;
let p1 = GC::new_witness(cs.clone(), || Ok(self.p1.unwrap_or(C::zero())))?;
let p2 = GC::new_witness(cs.clone(), || Ok(self.p2.unwrap_or(C::zero())))?;
let p3 = GC::new_witness(cs.clone(), || Ok(self.p3.unwrap_or(C::zero())))?;
// Fold the original Nova instances natively in CycleFold
// For the cmW we're computing: U_i1.cmW = U_i.cmW + r * u_i.cmW
// For the cmE we're computing: U_i1.cmE = U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE
// is assumed to be 0, so, U_i1.cmE = U_i.cmE + r * cmT
let p3 = &p1 + p2.scalar_mul_le(r_bits.iter())?;
let x = Vec::<FpVar<CF2<C>>>::new_input(cs.clone(), || {
Ok(self.x.unwrap_or(vec![CF2::<C>::zero(); CF_IO_LEN]))
@ -356,19 +355,13 @@ where
let r: FpVar<CF2<C>> = Boolean::le_bits_to_fp_var(&r_bits)?;
let points_coords: Vec<FpVar<CF2<C>>> = [
vec![r],
p1.clone().to_constraint_field()?[..2].to_vec(),
p2.clone().to_constraint_field()?[..2].to_vec(),
p3.clone().to_constraint_field()?[..2].to_vec(),
p1.to_constraint_field()?[..2].to_vec(),
p2.to_constraint_field()?[..2].to_vec(),
p3.to_constraint_field()?[..2].to_vec(),
]
.concat();
points_coords.enforce_equal(&x)?;
// Fold the original Nova instances natively in CycleFold
// For the cmW we're checking: U_i1.cmW == U_i.cmW + r * u_i.cmW
// For the cmE we're checking: U_i1.cmE == U_i.cmE + r * cmT + r^2 * u_i.cmE, where u_i.cmE
// is assumed to be 0, so, U_i1.cmE == U_i.cmE + r * cmT
p3.enforce_equal(&(p1 + p2.scalar_mul_le(r_bits.iter())?))?;
Ok(())
}
}
@ -429,7 +422,6 @@ pub mod tests {
r_bits: Some(r_bits.clone()),
p1: Some(ci1.clone().cmW),
p2: Some(ci2.clone().cmW),
p3: Some(ci3.clone().cmW),
x: Some(cfW_u_i_x.clone()),
};
cfW_circuit.generate_constraints(cs.clone()).unwrap();
@ -449,7 +441,6 @@ pub mod tests {
r_bits: Some(r_bits.clone()),
p1: Some(ci1.clone().cmE),
p2: Some(cmT),
p3: Some(ci3.clone().cmE),
x: Some(cfE_u_i_x.clone()),
};
cfE_circuit.generate_constraints(cs.clone()).unwrap();
@ -462,8 +453,7 @@ pub mod tests {
let cs = ConstraintSystem::<Fq>::new_ref();
let r_nonnatVar =
NonNativeFieldVar::<Fr, Fq>::new_witness(cs.clone(), || Ok(r_Fr)).unwrap();
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 =

+ 1
- 1
folding-schemes/src/folding/nova/decider_eth.rs

@ -13,7 +13,7 @@ use super::{circuits::CF2, nifs::NIFS, CommittedInstance, Nova};
use crate::commitment::{
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
};
use crate::folding::circuits::nonnative::NonNativeAffineVar;
use crate::folding::circuits::nonnative::affine::NonNativeAffineVar;
use crate::frontend::FCircuit;
use crate::Error;
use crate::{Decider as DeciderTrait, FoldingScheme};

+ 70
- 77
folding-schemes/src/folding/nova/decider_eth_circuit.rs

@ -9,7 +9,7 @@ use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
boolean::Boolean,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar, FieldVar},
fields::{fp::FpVar, FieldVar},
groups::GroupOpsBounds,
poly::{domain::Radix2DomainVar, evaluations::univariate::EvaluationsVar},
prelude::CurveVar,
@ -22,7 +22,10 @@ use core::{borrow::Borrow, marker::PhantomData};
use super::{circuits::ChallengeGadget, nifs::NIFS};
use crate::ccs::r1cs::R1CS;
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
use crate::folding::circuits::nonnative::{nonnative_affine_to_field_elements, NonNativeAffineVar};
use crate::folding::circuits::nonnative::{
affine::{nonnative_affine_to_field_elements, NonNativeAffineVar},
uint::NonNativeUintVar,
};
use crate::folding::nova::{
circuits::{CommittedInstanceVar, CF1, CF2},
CommittedInstance, Nova, Witness,
@ -33,40 +36,54 @@ use crate::transcript::{
Transcript, TranscriptVar,
};
use crate::utils::{
gadgets::{hadamard, mat_vec_mul_sparse, vec_add, vec_scalar_mul, SparseMatrixVar},
gadgets::{MatrixGadget, SparseMatrixVar, VectorGadget},
vec::poly_from_vec,
};
use crate::Error;
#[derive(Debug, Clone)]
pub struct RelaxedR1CSGadget<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
}
impl<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> RelaxedR1CSGadget<F, CF, FV> {
/// performs the RelaxedR1CS check (Az∘Bz==uCz+E)
pub fn check(
r1cs: R1CSVar<F, CF, FV>,
E: Vec<FV>,
u: FV,
z: Vec<FV>,
pub struct RelaxedR1CSGadget {}
impl RelaxedR1CSGadget {
/// performs the RelaxedR1CS check for native variables (Az∘Bz==uCz+E)
pub fn check_native<F: PrimeField>(
r1cs: R1CSVar<F, F, FpVar<F>>,
E: Vec<FpVar<F>>,
u: FpVar<F>,
z: Vec<FpVar<F>>,
) -> Result<(), SynthesisError> {
let Az = mat_vec_mul_sparse(r1cs.A, z.clone());
let Bz = mat_vec_mul_sparse(r1cs.B, z.clone());
let Cz = mat_vec_mul_sparse(r1cs.C, z.clone());
let uCz = vec_scalar_mul(&Cz, &u);
let uCzE = vec_add(&uCz, &E)?;
let AzBz = hadamard(&Az, &Bz)?;
for i in 0..AzBz.len() {
AzBz[i].enforce_equal(&uCzE[i].clone())?;
}
let Az = r1cs.A.mul_vector(&z)?;
let Bz = r1cs.B.mul_vector(&z)?;
let Cz = r1cs.C.mul_vector(&z)?;
let uCzE = Cz.mul_scalar(&u)?.add(&E)?;
let AzBz = Az.hadamard(&Bz)?;
AzBz.enforce_equal(&uCzE)?;
Ok(())
}
/// performs the RelaxedR1CS check for non-native variables (Az∘Bz==uCz+E)
pub fn check_nonnative<F: PrimeField, CF: PrimeField>(
r1cs: R1CSVar<F, CF, NonNativeUintVar<CF>>,
E: Vec<NonNativeUintVar<CF>>,
u: NonNativeUintVar<CF>,
z: Vec<NonNativeUintVar<CF>>,
) -> Result<(), SynthesisError> {
// First we do addition and multiplication without mod F's order
let Az = r1cs.A.mul_vector(&z)?;
let Bz = r1cs.B.mul_vector(&z)?;
let Cz = r1cs.C.mul_vector(&z)?;
let uCzE = Cz.mul_scalar(&u)?.add(&E)?;
let AzBz = Az.hadamard(&Bz)?;
// Then we compare the results by checking if they are congruent
// modulo the field order
AzBz.into_iter()
.zip(uCzE)
.try_for_each(|(a, b)| a.enforce_congruent::<F>(&b))
}
}
#[derive(Debug, Clone)]
pub struct R1CSVar<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
pub struct R1CSVar<F: PrimeField, CF: PrimeField, FV: AllocVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
@ -79,7 +96,7 @@ impl AllocVar, CF> for R1CSVar
where
F: PrimeField,
CF: PrimeField,
FV: FieldVar<F, CF>,
FV: AllocVar<F, CF>,
{
fn new_variable<T: Borrow<R1CS<F>>>(
cs: impl Into<Namespace<CF>>,
@ -146,10 +163,10 @@ where
/// non-native representation, since it is used to represent the CycleFold witness.
#[derive(Debug, Clone)]
pub struct CycleFoldWitnessVar<C: CurveGroup> {
pub E: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
pub rE: NonNativeFieldVar<C::ScalarField, CF2<C>>,
pub W: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>>,
pub rW: NonNativeFieldVar<C::ScalarField, CF2<C>>,
pub E: Vec<NonNativeUintVar<CF2<C>>>,
pub rE: NonNativeUintVar<CF2<C>>,
pub W: Vec<NonNativeUintVar<CF2<C>>>,
pub rW: NonNativeUintVar<CF2<C>>,
}
impl<C> AllocVar<Witness<C>, CF2<C>> for CycleFoldWitnessVar<C>
@ -165,21 +182,11 @@ where
f().and_then(|val| {
let cs = cs.into();
let E: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> =
Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
let rE = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
cs.clone(),
|| Ok(val.borrow().rE),
mode,
)?;
let E = Vec::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
let rE = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rE), mode)?;
let W: Vec<NonNativeFieldVar<C::ScalarField, CF2<C>>> =
Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?;
let rW = NonNativeFieldVar::<C::ScalarField, CF2<C>>::new_variable(
cs.clone(),
|| Ok(val.borrow().rW),
mode,
)?;
let W = Vec::new_variable(cs.clone(), || Ok(val.borrow().W.clone()), mode)?;
let rW = NonNativeUintVar::new_variable(cs.clone(), || Ok(val.borrow().rW), mode)?;
Ok(Self { E, rE, W, rW })
})
@ -404,18 +411,13 @@ where
// 1. check RelaxedR1CS of U_{i+1}
let z_U1: Vec<FpVar<CF1<C1>>> =
[vec![U_i1.u.clone()], U_i1.x.to_vec(), W_i1.W.to_vec()].concat();
RelaxedR1CSGadget::<C1::ScalarField, CF1<C1>, FpVar<CF1<C1>>>::check(
r1cs,
W_i1.E.clone(),
U_i1.u.clone(),
z_U1,
)?;
RelaxedR1CSGadget::check_native(r1cs, W_i1.E.clone(), U_i1.u.clone(), z_U1)?;
// 2. u_i.cmE==cm(0), u_i.u==1
// Here zero is the x & y coordinates of the zero point affine representation.
let zero = NonNativeFieldVar::<C1::BaseField, C1::ScalarField>::zero();
u_i.cmE.x.enforce_equal(&zero)?;
u_i.cmE.y.enforce_equal(&zero)?;
let zero = NonNativeUintVar::new_constant(cs.clone(), C1::BaseField::zero())?;
u_i.cmE.x.enforce_equal_unaligned(&zero)?;
u_i.cmE.y.enforce_equal_unaligned(&zero)?;
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
@ -470,20 +472,15 @@ where
PedersenGadget::<C2, GC2>::commit(H, G, cf_W_i_W_bits?, cf_W_i.rW.to_bits_le()?)?;
cf_U_i.cmW.enforce_equal(&computed_cmW)?;
let cf_r1cs = R1CSVar::<
C1::BaseField,
CF1<C1>,
NonNativeFieldVar<C1::BaseField, CF1<C1>>,
>::new_witness(cs.clone(), || Ok(self.cf_r1cs.clone()))?;
let cf_r1cs =
R1CSVar::<C1::BaseField, CF1<C1>, NonNativeUintVar<CF1<C1>>>::new_witness(
cs.clone(),
|| Ok(self.cf_r1cs.clone()),
)?;
// 5. check RelaxedR1CS of cf_U_i
let cf_z_U: Vec<NonNativeFieldVar<C2::ScalarField, CF1<C1>>> =
[vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
RelaxedR1CSGadget::<
C2::ScalarField,
CF1<C1>,
NonNativeFieldVar<C2::ScalarField, CF1<C1>>,
>::check(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
let cf_z_U = [vec![cf_U_i.u.clone()], cf_U_i.x.to_vec(), cf_W_i.W.to_vec()].concat();
RelaxedR1CSGadget::check_nonnative(cf_r1cs, cf_W_i.E, cf_U_i.u.clone(), cf_z_U)?;
}
// 6. check KZG challenges
@ -632,7 +629,7 @@ pub mod tests {
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(r1csVar, EVar, uVar, zVar).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
assert!(cs.is_satisfied().unwrap());
}
@ -663,7 +660,7 @@ pub mod tests {
let uVar = FpVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let r1csVar = R1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(r1csVar, EVar, uVar, zVar).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
assert!(cs.is_satisfied().unwrap());
}
@ -752,20 +749,16 @@ pub mod tests {
let uVar = FpVar::<Fq>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let r1csVar =
R1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || Ok(r1cs.clone())).unwrap();
RelaxedR1CSGadget::<Fq, Fq, FpVar<Fq>>::check(r1csVar, EVar, uVar, zVar).unwrap();
RelaxedR1CSGadget::check_native(r1csVar, EVar, uVar, zVar).unwrap();
// non-natively
let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::<NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::<NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs.E))
.unwrap();
let uVar =
NonNativeFieldVar::<Fq, Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let zVar = Vec::new_witness(cs.clone(), || Ok(z)).unwrap();
let EVar = Vec::new_witness(cs.clone(), || Ok(relaxed_r1cs.E)).unwrap();
let uVar = NonNativeUintVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs.u)).unwrap();
let r1csVar =
R1CSVar::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(r1cs))
.unwrap();
RelaxedR1CSGadget::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::check(r1csVar, EVar, uVar, zVar)
.unwrap();
R1CSVar::<Fq, Fr, NonNativeUintVar<Fr>>::new_witness(cs.clone(), || Ok(r1cs)).unwrap();
RelaxedR1CSGadget::check_nonnative(r1csVar, EVar, uVar, zVar).unwrap();
}
#[test]

+ 3
- 5
folding-schemes/src/folding/nova/mod.rs

@ -16,7 +16,7 @@ use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
use crate::ccs::r1cs::{extract_r1cs, extract_w_x, R1CS};
use crate::commitment::CommitmentScheme;
use crate::folding::circuits::nonnative::{
nonnative_affine_to_field_elements, nonnative_field_to_field_elements,
affine::nonnative_affine_to_field_elements, uint::nonnative_field_to_field_elements,
};
use crate::frontend::FCircuit;
use crate::utils::vec::is_zero_vec;
@ -434,7 +434,6 @@ where
r_bits: Some(r_bits.clone()),
p1: Some(self.U_i.clone().cmW),
p2: Some(self.u_i.clone().cmW),
p3: Some(U_i1.clone().cmW),
x: Some(cfW_u_i_x.clone()),
};
let cfE_circuit = CycleFoldCircuit::<C1, GC1> {
@ -442,7 +441,6 @@ where
r_bits: Some(r_bits.clone()),
p1: Some(self.U_i.clone().cmE),
p2: Some(cmT),
p3: Some(U_i1.clone().cmE),
x: Some(cfE_u_i_x.clone()),
};
@ -482,8 +480,8 @@ where
cf_x: Some(cf_u_i1_x),
};
self.cf_W_i = cf_W_i1.clone();
self.cf_U_i = cf_U_i1.clone();
self.cf_W_i = cf_W_i1;
self.cf_U_i = cf_U_i1;
#[cfg(test)]
{

+ 49
- 50
folding-schemes/src/utils/gadgets.rs

@ -1,69 +1,48 @@
use ark_ff::PrimeField;
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
fields::FieldVar,
fields::{fp::FpVar, FieldVar},
R1CSVar,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
use crate::utils::vec::SparseMatrix;
pub fn mat_vec_mul_sparse<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
m: SparseMatrixVar<F, CF, FV>,
v: Vec<FV>,
) -> Vec<FV> {
let mut res = vec![FV::zero(); m.n_rows];
for (row_i, row) in m.coeffs.iter().enumerate() {
for (value, col_i) in row.iter() {
if value.value().unwrap() == F::one() {
// no need to multiply by 1
res[row_i] += v[*col_i].clone();
continue;
}
res[row_i] += value.clone().mul(&v[*col_i].clone());
}
}
res
pub trait MatrixGadget<FV> {
fn mul_vector(&self, v: &[FV]) -> Result<Vec<FV>, SynthesisError>;
}
pub fn vec_add<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
a: &Vec<FV>,
b: &Vec<FV>,
) -> Result<Vec<FV>, SynthesisError> {
if a.len() != b.len() {
return Err(SynthesisError::Unsatisfiable);
}
let mut r: Vec<FV> = vec![FV::zero(); a.len()];
for i in 0..a.len() {
r[i] = a[i].clone() + b[i].clone();
}
Ok(r)
pub trait VectorGadget<FV> {
fn add(&self, other: &Self) -> Result<Vec<FV>, SynthesisError>;
fn mul_scalar(&self, other: &FV) -> Result<Vec<FV>, SynthesisError>;
fn hadamard(&self, other: &Self) -> Result<Vec<FV>, SynthesisError>;
}
pub fn vec_scalar_mul<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
vec: &Vec<FV>,
c: &FV,
) -> Vec<FV> {
let mut result = vec![FV::zero(); vec.len()];
for (i, a) in vec.iter().enumerate() {
result[i] = a.clone() * c;
impl<F: PrimeField> VectorGadget<FpVar<F>> for [FpVar<F>] {
fn add(&self, other: &Self) -> Result<Vec<FpVar<F>>, SynthesisError> {
if self.len() != other.len() {
return Err(SynthesisError::Unsatisfiable);
}
Ok(self.iter().zip(other.iter()).map(|(a, b)| a + b).collect())
}
result
}
pub fn hadamard<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
a: &Vec<FV>,
b: &Vec<FV>,
) -> Result<Vec<FV>, SynthesisError> {
if a.len() != b.len() {
return Err(SynthesisError::Unsatisfiable);
fn mul_scalar(&self, c: &FpVar<F>) -> Result<Vec<FpVar<F>>, SynthesisError> {
Ok(self.iter().map(|a| a * c).collect())
}
let mut r: Vec<FV> = vec![FV::zero(); a.len()];
for i in 0..a.len() {
r[i] = a[i].clone() * b[i].clone();
fn hadamard(&self, other: &Self) -> Result<Vec<FpVar<F>>, SynthesisError> {
if self.len() != other.len() {
return Err(SynthesisError::Unsatisfiable);
}
Ok(self.iter().zip(other.iter()).map(|(a, b)| a * b).collect())
}
Ok(r)
}
#[derive(Debug, Clone)]
pub struct SparseMatrixVar<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
pub struct SparseMatrixVar<F: PrimeField, CF: PrimeField, FV: AllocVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
@ -77,7 +56,7 @@ impl AllocVar, CF> for SparseMatrixVar
where
F: PrimeField,
CF: PrimeField,
FV: FieldVar<F, CF>,
FV: AllocVar<F, CF>,
{
fn new_variable<T: Borrow<SparseMatrix<F>>>(
cs: impl Into<Namespace<CF>>,
@ -108,3 +87,23 @@ where
})
}
}
impl<F: PrimeField> MatrixGadget<FpVar<F>> for SparseMatrixVar<F, F, FpVar<F>> {
fn mul_vector(&self, v: &[FpVar<F>]) -> Result<Vec<FpVar<F>>, SynthesisError> {
Ok(self
.coeffs
.iter()
.map(|row| {
let products = row
.iter()
.map(|(value, col_i)| value * &v[*col_i])
.collect::<Vec<_>>();
if products.is_constant() {
FpVar::constant(products.value().unwrap_or_default().into_iter().sum())
} else {
products.iter().sum()
}
})
.collect())
}
}

Loading…
Cancel
Save