From 4b929e8dc41015ae1987e76c5e55a9e8b4976bb3 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 2 Jan 2024 11:28:21 +0100 Subject: [PATCH] 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. --- src/decider/circuit.rs | 189 ++++++++++++++++++++++++---------- src/folding/nova/cyclefold.rs | 1 + 2 files changed, 135 insertions(+), 55 deletions(-) diff --git a/src/decider/circuit.rs b/src/decider/circuit.rs index 3d82923..b008560 100644 --- a/src/decider/circuit.rs +++ b/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 = <::BaseField as Field>::BasePrimeField; #[derive(Debug, Clone)] -pub struct RelaxedR1CSGadget { +pub struct RelaxedR1CSGadget> { _f: PhantomData, + _cf: PhantomData, + _fv: PhantomData, } -impl RelaxedR1CSGadget { +impl> RelaxedR1CSGadget { /// performs the RelaxedR1CS check (Az∘Bz==uCz+E) - pub fn check(rel_r1cs: RelaxedR1CSVar, z: Vec>) -> Result<(), Error> { + pub fn check(rel_r1cs: RelaxedR1CSVar, z: Vec) -> 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(m: SparseMatrixVar, v: Vec>) -> Vec> { - let mut res = vec![FpVar::::zero(); m.n_rows]; +fn mat_vec_mul_sparse>( + m: SparseMatrixVar, + v: Vec, +) -> Vec { + 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( - a: &Vec>, - b: &Vec>, -) -> Result>, Error> { +pub fn vec_add>( + a: &Vec, + b: &Vec, +) -> Result, 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> = vec![FpVar::::zero(); a.len()]; + let mut r: Vec = 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(vec: &Vec>, c: &FpVar) -> Vec> { - let mut result = vec![FpVar::::zero(); vec.len()]; +pub fn vec_scalar_mul>( + vec: &Vec, + c: &FV, +) -> Vec { + let mut result = vec![FV::zero(); vec.len()]; for (i, a) in vec.iter().enumerate() { result[i] = a.clone() * c; } result } -pub fn hadamard( - a: &Vec>, - b: &Vec>, -) -> Result>, Error> { +pub fn hadamard>( + a: &Vec, + b: &Vec, +) -> Result, 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> = vec![FpVar::::zero(); a.len()]; + let mut r: Vec = 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 { +pub struct SparseMatrixVar> { + _f: PhantomData, + _cf: PhantomData, + _fv: PhantomData, pub n_rows: usize, pub n_cols: usize, // same format as the native SparseMatrix (which follows ark_relations::r1cs::Matrix format - pub coeffs: Vec, usize)>>, + pub coeffs: Vec>, } -impl AllocVar, F> for SparseMatrixVar +impl AllocVar, CF> for SparseMatrixVar where F: PrimeField, + CF: PrimeField, + FV: FieldVar, { fn new_variable>>( - cs: impl Into>, + cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); - let mut coeffs: Vec, usize)>> = Vec::new(); + let mut coeffs: Vec> = Vec::new(); for row in val.borrow().coeffs.iter() { - let mut rowVar: Vec<(FpVar, usize)> = Vec::new(); + let mut rowVar: Vec<(FV, usize)> = Vec::new(); for &(value, col_i) in row.iter() { - let coeffVar = FpVar::::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 { - pub A: SparseMatrixVar, - pub B: SparseMatrixVar, - pub C: SparseMatrixVar, - pub u: FpVar, - pub E: Vec>, +pub struct RelaxedR1CSVar> { + _f: PhantomData, + _cf: PhantomData, + _fv: PhantomData, + pub A: SparseMatrixVar, + pub B: SparseMatrixVar, + pub C: SparseMatrixVar, + pub u: FV, + pub E: Vec, } -impl AllocVar, F> for RelaxedR1CSVar +impl AllocVar, CF> for RelaxedR1CSVar where F: PrimeField, + CF: PrimeField, + FV: FieldVar, { fn new_variable>>( - cs: impl Into>, + cs: impl Into>, f: impl FnOnce() -> Result, mode: AllocationMode, ) -> Result { f().and_then(|val| { let cs = cs.into(); - let A = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?; - let B = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?; - let C = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?; - let E = Vec::>::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?; - let u = FpVar::::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?; + let A = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().A)?; + let B = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().B)?; + let C = SparseMatrixVar::::new_constant(cs.clone(), &val.borrow().C)?; + let E = Vec::::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::::new_ref(); let zVar = Vec::>::new_witness(cs.clone(), || Ok(z)).unwrap(); - let rel_r1csVar = RelaxedR1CSVar::::new_witness(cs.clone(), || Ok(rel_r1cs)).unwrap(); + let rel_r1csVar = + RelaxedR1CSVar::>::new_witness(cs.clone(), || Ok(rel_r1cs)).unwrap(); - RelaxedR1CSGadget::::check(rel_r1csVar, zVar).unwrap(); + RelaxedR1CSGadget::>::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::(&cs); @@ -223,13 +254,11 @@ mod tests { // prepare the inputs for our circuit let zVar = Vec::>::new_witness(cs.clone(), || Ok(z)).unwrap(); let rel_r1csVar = - RelaxedR1CSVar::::new_witness(cs.clone(), || Ok(relaxed_r1cs)).unwrap(); + RelaxedR1CSVar::>::new_witness(cs.clone(), || Ok(relaxed_r1cs)) + .unwrap(); - RelaxedR1CSGadget::::check(rel_r1csVar, zVar).unwrap(); + RelaxedR1CSGadget::>::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 { @@ -278,6 +307,21 @@ mod tests { pub x: F, pub y: F, } + impl CustomTestCircuit { + 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 ConstraintSynthesizer for CustomTestCircuit { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { let x = FpVar::::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::::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::::new(10); + circuit.generate_constraints(cs.clone()).unwrap(); + cs.finalize(); + let cs = cs.into_inner().unwrap(); + let (r1cs, z) = extract_r1cs_and_z::(&cs); + + let relaxed_r1cs = r1cs.relax(); + + // natively + let cs = ConstraintSystem::::new_ref(); + let zVar = Vec::>::new_witness(cs.clone(), || Ok(z.clone())).unwrap(); + let rel_r1csVar = RelaxedR1CSVar::>::new_witness(cs.clone(), || { + Ok(relaxed_r1cs.clone()) + }) + .unwrap(); + RelaxedR1CSGadget::>::check(rel_r1csVar, zVar).unwrap(); + + // non-natively + let cs = ConstraintSystem::::new_ref(); + let zVar = Vec::>::new_witness(cs.clone(), || Ok(z)).unwrap(); + let rel_r1csVar = + RelaxedR1CSVar::>::new_witness(cs.clone(), || { + Ok(relaxed_r1cs) + }) + .unwrap(); + RelaxedR1CSGadget::>::check(rel_r1csVar, zVar).unwrap(); + } } diff --git a/src/folding/nova/cyclefold.rs b/src/folding/nova/cyclefold.rs index 960f0b5..cb2d9c3 100644 --- a/src/folding/nova/cyclefold.rs +++ b/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(()) }