From 6191b75e582a0e8ea0f31c3d01c2e0f15b8e3df3 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 19 Dec 2023 10:35:10 +0100 Subject: [PATCH] update RelaxedR1CSGadget to work with FieldVar trait so we can plug in FpVar and NonNativeFieldVar indistinctly 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 | 185 +++++++++++++++++++++++++--------- src/folding/nova/cyclefold.rs | 1 + 2 files changed, 138 insertions(+), 48 deletions(-) diff --git a/src/decider/circuit.rs b/src/decider/circuit.rs index 3d829233..f801c160 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,9 +225,10 @@ 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()); } @@ -223,9 +258,10 @@ 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 @@ -269,7 +305,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 +314,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 +343,7 @@ mod tests { Ok(()) } } + #[test] fn test_relaxed_r1cs_custom_circuit() { let n_constraints = 10_000; @@ -309,4 +361,41 @@ 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(); + dbg!(cs.num_constraints()); + 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(); + dbg!(cs.num_constraints()); + + // 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(); + dbg!(cs.num_constraints()); + } } diff --git a/src/folding/nova/cyclefold.rs b/src/folding/nova/cyclefold.rs index 960f0b5c..64d94b10 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 + // TODO add also r_bits as public input, and check it in the main circuit Ok(()) }