Browse Source

update RelaxedR1CSGadget to work with FieldVar trait so we can plug in FpVar and NonNativeFieldVar indistinctly (#46)

This is to be able to instantiate the CycleFoldCircuit over Curve2
constraint field, and check it's RelaxedR1CS relation non-natively
inside the Curve1 constraint field, while reusing the same code that we
already have for checking the main circuit RelaxedR1CS over Curve1
constraint field natively.
main
arnaucube 8 months ago
committed by GitHub
parent
commit
4b929e8dc4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 55 deletions
  1. +134
    -55
      src/decider/circuit.rs
  2. +1
    -0
      src/folding/nova/cyclefold.rs

+ 134
- 55
src/decider/circuit.rs

@ -2,8 +2,7 @@ use ark_ec::CurveGroup;
use ark_ff::{Field, PrimeField};
use ark_r1cs_std::{
alloc::{AllocVar, AllocationMode},
eq::EqGadget,
fields::{fp::FpVar, FieldVar},
fields::FieldVar,
};
use ark_relations::r1cs::{Namespace, SynthesisError};
use core::{borrow::Borrow, marker::PhantomData};
@ -15,12 +14,14 @@ use crate::Error;
pub type ConstraintF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
#[derive(Debug, Clone)]
pub struct RelaxedR1CSGadget<F: PrimeField> {
pub struct RelaxedR1CSGadget<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
}
impl<F: PrimeField> RelaxedR1CSGadget<F> {
impl<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> RelaxedR1CSGadget<F, CF, FV> {
/// performs the RelaxedR1CS check (Az∘Bz==uCz+E)
pub fn check(rel_r1cs: RelaxedR1CSVar<F>, z: Vec<FpVar<F>>) -> Result<(), Error> {
pub fn check(rel_r1cs: RelaxedR1CSVar<F, CF, FV>, z: Vec<FV>) -> Result<(), Error> {
let Az = mat_vec_mul_sparse(rel_r1cs.A, z.clone());
let Bz = mat_vec_mul_sparse(rel_r1cs.B, z.clone());
let Cz = mat_vec_mul_sparse(rel_r1cs.C, z.clone());
@ -34,19 +35,22 @@ impl RelaxedR1CSGadget {
}
}
fn mat_vec_mul_sparse<F: PrimeField>(m: SparseMatrixVar<F>, v: Vec<FpVar<F>>) -> Vec<FpVar<F>> {
let mut res = vec![FpVar::<F>::zero(); m.n_rows];
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() {
res[row_i] += value * v[*col_i].clone();
res[row_i] += value.clone().mul(&v[*col_i].clone());
}
}
res
}
pub fn vec_add<F: PrimeField>(
a: &Vec<FpVar<F>>,
b: &Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, Error> {
pub fn vec_add<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
a: &Vec<FV>,
b: &Vec<FV>,
) -> Result<Vec<FV>, Error> {
if a.len() != b.len() {
return Err(Error::NotSameLength(
"a.len()".to_string(),
@ -55,23 +59,26 @@ pub fn vec_add(
b.len(),
));
}
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
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 fn vec_scalar_mul<F: PrimeField>(vec: &Vec<FpVar<F>>, c: &FpVar<F>) -> Vec<FpVar<F>> {
let mut result = vec![FpVar::<F>::zero(); vec.len()];
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;
}
result
}
pub fn hadamard<F: PrimeField>(
a: &Vec<FpVar<F>>,
b: &Vec<FpVar<F>>,
) -> Result<Vec<FpVar<F>>, Error> {
pub fn hadamard<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>>(
a: &Vec<FV>,
b: &Vec<FV>,
) -> Result<Vec<FV>, Error> {
if a.len() != b.len() {
return Err(Error::NotSameLength(
"a.len()".to_string(),
@ -80,7 +87,7 @@ pub fn hadamard(
b.len(),
));
}
let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
let mut r: Vec<FV> = vec![FV::zero(); a.len()];
for i in 0..a.len() {
r[i] = a[i].clone() * b[i].clone();
}
@ -88,36 +95,44 @@ pub fn hadamard(
}
#[derive(Debug, Clone)]
pub struct SparseMatrixVar<F: PrimeField> {
pub struct SparseMatrixVar<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
pub n_rows: usize,
pub n_cols: usize,
// same format as the native SparseMatrix (which follows ark_relations::r1cs::Matrix format
pub coeffs: Vec<Vec<(FpVar<F>, usize)>>,
pub coeffs: Vec<Vec<(FV, usize)>>,
}
impl<F> AllocVar<SparseMatrix<F>, F> for SparseMatrixVar<F>
impl<F, CF, FV> AllocVar<SparseMatrix<F>, CF> for SparseMatrixVar<F, CF, FV>
where
F: PrimeField,
CF: PrimeField,
FV: FieldVar<F, CF>,
{
fn new_variable<T: Borrow<SparseMatrix<F>>>(
cs: impl Into<Namespace<F>>,
cs: impl Into<Namespace<CF>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let mut coeffs: Vec<Vec<(FpVar<F>, usize)>> = Vec::new();
let mut coeffs: Vec<Vec<(FV, usize)>> = Vec::new();
for row in val.borrow().coeffs.iter() {
let mut rowVar: Vec<(FpVar<F>, usize)> = Vec::new();
let mut rowVar: Vec<(FV, usize)> = Vec::new();
for &(value, col_i) in row.iter() {
let coeffVar = FpVar::<F>::new_variable(cs.clone(), || Ok(value), mode)?;
let coeffVar = FV::new_variable(cs.clone(), || Ok(value), mode)?;
rowVar.push((coeffVar, col_i));
}
coeffs.push(rowVar);
}
Ok(Self {
_f: PhantomData,
_cf: PhantomData,
_fv: PhantomData,
n_rows: val.borrow().n_rows,
n_cols: val.borrow().n_cols,
coeffs,
@ -127,33 +142,47 @@ where
}
#[derive(Debug, Clone)]
pub struct RelaxedR1CSVar<F: PrimeField> {
pub A: SparseMatrixVar<F>,
pub B: SparseMatrixVar<F>,
pub C: SparseMatrixVar<F>,
pub u: FpVar<F>,
pub E: Vec<FpVar<F>>,
pub struct RelaxedR1CSVar<F: PrimeField, CF: PrimeField, FV: FieldVar<F, CF>> {
_f: PhantomData<F>,
_cf: PhantomData<CF>,
_fv: PhantomData<FV>,
pub A: SparseMatrixVar<F, CF, FV>,
pub B: SparseMatrixVar<F, CF, FV>,
pub C: SparseMatrixVar<F, CF, FV>,
pub u: FV,
pub E: Vec<FV>,
}
impl<F> AllocVar<RelaxedR1CS<F>, F> for RelaxedR1CSVar<F>
impl<F, CF, FV> AllocVar<RelaxedR1CS<F>, CF> for RelaxedR1CSVar<F, CF, FV>
where
F: PrimeField,
CF: PrimeField,
FV: FieldVar<F, CF>,
{
fn new_variable<T: Borrow<RelaxedR1CS<F>>>(
cs: impl Into<Namespace<F>>,
cs: impl Into<Namespace<CF>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|val| {
let cs = cs.into();
let A = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().A)?;
let B = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().B)?;
let C = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().C)?;
let E = Vec::<FpVar<F>>::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
let u = FpVar::<F>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
let A = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().A)?;
let B = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().B)?;
let C = SparseMatrixVar::<F, CF, FV>::new_constant(cs.clone(), &val.borrow().C)?;
let E = Vec::<FV>::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
let u = FV::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
Ok(Self { A, B, C, E, u })
Ok(Self {
_f: PhantomData,
_cf: PhantomData,
_fv: PhantomData,
A,
B,
C,
E,
u,
})
})
}
}
@ -169,8 +198,13 @@ mod tests {
CRHScheme, CRHSchemeGadget,
};
use ark_ff::BigInteger;
use ark_pallas::Fr;
use ark_r1cs_std::{alloc::AllocVar, bits::uint8::UInt8};
use ark_pallas::{Fq, Fr};
use ark_r1cs_std::{
alloc::AllocVar,
bits::uint8::UInt8,
eq::EqGadget,
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
};
use ark_relations::r1cs::{
ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
};
@ -191,11 +225,11 @@ mod tests {
let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let rel_r1csVar = RelaxedR1CSVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs)).unwrap();
let rel_r1csVar =
RelaxedR1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(rel_r1cs)).unwrap();
RelaxedR1CSGadget::<Fr>::check(rel_r1csVar, zVar).unwrap();
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(rel_r1csVar, zVar).unwrap();
assert!(cs.is_satisfied().unwrap());
dbg!(cs.num_constraints());
}
// gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
@ -207,9 +241,6 @@ mod tests {
cs.finalize();
assert!(cs.is_satisfied().unwrap());
// num constraints of the original circuit
dbg!(cs.num_constraints());
let cs = cs.into_inner().unwrap();
let (r1cs, z) = extract_r1cs_and_z::<Fr>(&cs);
@ -223,13 +254,11 @@ mod tests {
// prepare the inputs for our circuit
let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let rel_r1csVar =
RelaxedR1CSVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs)).unwrap();
RelaxedR1CSVar::<Fr, Fr, FpVar<Fr>>::new_witness(cs.clone(), || Ok(relaxed_r1cs))
.unwrap();
RelaxedR1CSGadget::<Fr>::check(rel_r1csVar, zVar).unwrap();
RelaxedR1CSGadget::<Fr, Fr, FpVar<Fr>>::check(rel_r1csVar, zVar).unwrap();
assert!(cs.is_satisfied().unwrap());
// num constraints of the circuit that checks the RelaxedR1CS of the original circuit
dbg!(cs.num_constraints());
}
#[test]
@ -269,7 +298,7 @@ mod tests {
test_relaxed_r1cs_gadget(circuit);
}
// circuit that has the numer of constraints specified in the `n_constraints` parameter. Note
// circuit that has the number of constraints specified in the `n_constraints` parameter. Note
// that the generated circuit will have very sparse matrices, so the resulting constraints
// number of the RelaxedR1CS gadget must take that into account.
struct CustomTestCircuit<F: PrimeField> {
@ -278,6 +307,21 @@ mod tests {
pub x: F,
pub y: F,
}
impl<F: PrimeField> CustomTestCircuit<F> {
fn new(n_constraints: usize) -> Self {
let x = F::from(5_u32);
let mut y = F::one();
for _ in 0..n_constraints - 1 {
y *= x;
}
Self {
_f: PhantomData,
n_constraints,
x,
y,
}
}
}
impl<F: PrimeField> ConstraintSynthesizer<F> for CustomTestCircuit<F> {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let x = FpVar::<F>::new_witness(cs.clone(), || Ok(self.x))?;
@ -292,6 +336,7 @@ mod tests {
Ok(())
}
}
#[test]
fn test_relaxed_r1cs_custom_circuit() {
let n_constraints = 10_000;
@ -309,4 +354,38 @@ mod tests {
};
test_relaxed_r1cs_gadget(circuit);
}
#[test]
fn test_relaxed_r1cs_nonnative_circuit() {
let cs = ConstraintSystem::<Fq>::new_ref();
// in practice we would use CycleFoldCircuit, but is a very big circuit (when computed
// non-natively inside the RelaxedR1CS circuit), so in order to have a short test we use a
// custom circuit.
let circuit = CustomTestCircuit::<Fq>::new(10);
circuit.generate_constraints(cs.clone()).unwrap();
cs.finalize();
let cs = cs.into_inner().unwrap();
let (r1cs, z) = extract_r1cs_and_z::<Fq>(&cs);
let relaxed_r1cs = r1cs.relax();
// natively
let cs = ConstraintSystem::<Fq>::new_ref();
let zVar = Vec::<FpVar<Fq>>::new_witness(cs.clone(), || Ok(z.clone())).unwrap();
let rel_r1csVar = RelaxedR1CSVar::<Fq, Fq, FpVar<Fq>>::new_witness(cs.clone(), || {
Ok(relaxed_r1cs.clone())
})
.unwrap();
RelaxedR1CSGadget::<Fq, Fq, FpVar<Fq>>::check(rel_r1csVar, zVar).unwrap();
// non-natively
let cs = ConstraintSystem::<Fr>::new_ref();
let zVar = Vec::<NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
let rel_r1csVar =
RelaxedR1CSVar::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::new_witness(cs.clone(), || {
Ok(relaxed_r1cs)
})
.unwrap();
RelaxedR1CSGadget::<Fq, Fr, NonNativeFieldVar<Fq, Fr>>::check(rel_r1csVar, zVar).unwrap();
}
}

+ 1
- 0
src/folding/nova/cyclefold.rs

@ -386,6 +386,7 @@ where
// the CycleFoldCircuit are the sames used in the public inputs 'x', which come from the
// AugmentedFCircuit.
// TODO: Issue to keep track of this: https://github.com/privacy-scaling-explorations/folding-schemes/issues/44
// and https://github.com/privacy-scaling-explorations/folding-schemes/issues/48
Ok(())
}

Loading…
Cancel
Save