arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/src/decider/circuit.rs
src/decider/circuit.rsRaw
  1use ark_ec::CurveGroup;
  2use ark_ff::{Field, PrimeField};
  3use ark_r1cs_std::{
  4    alloc::{AllocVar, AllocationMode},
  5    eq::EqGadget,
  6    fields::{fp::FpVar, FieldVar},
  7};
  8use ark_relations::r1cs::{Namespace, SynthesisError};
  9use core::{borrow::Borrow, marker::PhantomData};
 10
 11use crate::ccs::r1cs::RelaxedR1CS;
 12use crate::utils::vec::SparseMatrix;
 13
 14pub type ConstraintF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;
 15
 16#[derive(Debug, Clone)]
 17pub struct RelaxedR1CSGadget<F: PrimeField> {
 18    _f: PhantomData<F>,
 19}
 20impl<F: PrimeField> RelaxedR1CSGadget<F> {
 21    /// performs the RelaxedR1CS check (Az∘Bz==uCz+E)
 22    pub fn check(rel_r1cs: RelaxedR1CSVar<F>, z: Vec<FpVar<F>>) -> Result<(), SynthesisError> {
 23        let Az = mat_vec_mul_sparse(rel_r1cs.A, z.clone());
 24        let Bz = mat_vec_mul_sparse(rel_r1cs.B, z.clone());
 25        let Cz = mat_vec_mul_sparse(rel_r1cs.C, z.clone());
 26        let uCz = vec_scalar_mul(&Cz, &rel_r1cs.u);
 27        let uCzE = vec_add(&uCz, &rel_r1cs.E);
 28        let AzBz = hadamard(&Az, &Bz);
 29        for i in 0..AzBz.len() {
 30            AzBz[i].enforce_equal(&uCzE[i].clone())?;
 31        }
 32        Ok(())
 33    }
 34}
 35
 36fn mat_vec_mul_sparse<F: PrimeField>(m: SparseMatrixVar<F>, v: Vec<FpVar<F>>) -> Vec<FpVar<F>> {
 37    let mut res = vec![FpVar::<F>::zero(); m.n_rows];
 38    for (row_i, row) in m.coeffs.iter().enumerate() {
 39        for (value, col_i) in row.iter() {
 40            res[row_i] += value * v[*col_i].clone();
 41        }
 42    }
 43    res
 44}
 45pub fn vec_add<F: PrimeField>(a: &Vec<FpVar<F>>, b: &Vec<FpVar<F>>) -> Vec<FpVar<F>> {
 46    assert_eq!(a.len(), b.len());
 47    let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
 48    for i in 0..a.len() {
 49        r[i] = a[i].clone() + b[i].clone();
 50    }
 51    r
 52}
 53pub fn vec_scalar_mul<F: PrimeField>(vec: &Vec<FpVar<F>>, c: &FpVar<F>) -> Vec<FpVar<F>> {
 54    let mut result = vec![FpVar::<F>::zero(); vec.len()];
 55    for (i, a) in vec.iter().enumerate() {
 56        result[i] = a.clone() * c;
 57    }
 58    result
 59}
 60pub fn hadamard<F: PrimeField>(a: &Vec<FpVar<F>>, b: &Vec<FpVar<F>>) -> Vec<FpVar<F>> {
 61    assert_eq!(a.len(), b.len());
 62    let mut r: Vec<FpVar<F>> = vec![FpVar::<F>::zero(); a.len()];
 63    for i in 0..a.len() {
 64        r[i] = a[i].clone() * b[i].clone();
 65    }
 66    r
 67}
 68
 69#[derive(Debug, Clone)]
 70pub struct SparseMatrixVar<F: PrimeField> {
 71    pub n_rows: usize,
 72    pub n_cols: usize,
 73    // same format as the native SparseMatrix (which follows ark_relations::r1cs::Matrix format
 74    pub coeffs: Vec<Vec<(FpVar<F>, usize)>>,
 75}
 76
 77impl<F> AllocVar<SparseMatrix<F>, F> for SparseMatrixVar<F>
 78where
 79    F: PrimeField,
 80{
 81    fn new_variable<T: Borrow<SparseMatrix<F>>>(
 82        cs: impl Into<Namespace<F>>,
 83        f: impl FnOnce() -> Result<T, SynthesisError>,
 84        mode: AllocationMode,
 85    ) -> Result<Self, SynthesisError> {
 86        f().and_then(|val| {
 87            let cs = cs.into();
 88
 89            let mut coeffs: Vec<Vec<(FpVar<F>, usize)>> = Vec::new();
 90            for row in val.borrow().coeffs.iter() {
 91                let mut rowVar: Vec<(FpVar<F>, usize)> = Vec::new();
 92                for &(value, col_i) in row.iter() {
 93                    let coeffVar = FpVar::<F>::new_variable(cs.clone(), || Ok(value), mode)?;
 94                    rowVar.push((coeffVar, col_i));
 95                }
 96                coeffs.push(rowVar);
 97            }
 98
 99            Ok(Self {
100                n_rows: val.borrow().n_rows,
101                n_cols: val.borrow().n_cols,
102                coeffs,
103            })
104        })
105    }
106}
107
108#[derive(Debug, Clone)]
109pub struct RelaxedR1CSVar<F: PrimeField> {
110    pub A: SparseMatrixVar<F>,
111    pub B: SparseMatrixVar<F>,
112    pub C: SparseMatrixVar<F>,
113    pub u: FpVar<F>,
114    pub E: Vec<FpVar<F>>,
115}
116
117impl<F> AllocVar<RelaxedR1CS<F>, F> for RelaxedR1CSVar<F>
118where
119    F: PrimeField,
120{
121    fn new_variable<T: Borrow<RelaxedR1CS<F>>>(
122        cs: impl Into<Namespace<F>>,
123        f: impl FnOnce() -> Result<T, SynthesisError>,
124        mode: AllocationMode,
125    ) -> Result<Self, SynthesisError> {
126        f().and_then(|val| {
127            let cs = cs.into();
128
129            let A = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().A)?;
130            let B = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().B)?;
131            let C = SparseMatrixVar::<F>::new_constant(cs.clone(), &val.borrow().C)?;
132            let E = Vec::<FpVar<F>>::new_variable(cs.clone(), || Ok(val.borrow().E.clone()), mode)?;
133            let u = FpVar::<F>::new_variable(cs.clone(), || Ok(val.borrow().u), mode)?;
134
135            Ok(Self { A, B, C, E, u })
136        })
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use ark_crypto_primitives::crh::{
144        sha256::{
145            constraints::{Sha256Gadget, UnitVar},
146            Sha256,
147        },
148        CRHScheme, CRHSchemeGadget,
149    };
150    use ark_ff::BigInteger;
151    use ark_pallas::Fr;
152    use ark_r1cs_std::{alloc::AllocVar, bits::uint8::UInt8};
153    use ark_relations::r1cs::{
154        ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, SynthesisError,
155    };
156    use ark_std::One;
157
158    use crate::ccs::r1cs::{
159        tests::{get_test_r1cs, get_test_z},
160        R1CS,
161    };
162    use crate::frontend::arkworks::{extract_r1cs_and_z, tests::TestCircuit};
163
164    #[test]
165    fn test_relaxed_r1cs_small_gadget_handcrafted() {
166        let r1cs: R1CS<Fr> = get_test_r1cs();
167        let rel_r1cs = r1cs.relax();
168        let z = get_test_z(3);
169
170        let cs = ConstraintSystem::<Fr>::new_ref();
171
172        let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
173        let rel_r1csVar = RelaxedR1CSVar::<Fr>::new_witness(cs.clone(), || Ok(rel_r1cs)).unwrap();
174
175        RelaxedR1CSGadget::<Fr>::check(rel_r1csVar, zVar).unwrap();
176        assert!(cs.is_satisfied().unwrap());
177        dbg!(cs.num_constraints());
178    }
179
180    // gets as input a circuit that implements the ConstraintSynthesizer trait, and that has been
181    // initialized.
182    fn test_relaxed_r1cs_gadget<CS: ConstraintSynthesizer<Fr>>(circuit: CS) {
183        let cs = ConstraintSystem::<Fr>::new_ref();
184
185        circuit.generate_constraints(cs.clone()).unwrap();
186        cs.finalize();
187        assert!(cs.is_satisfied().unwrap());
188
189        // num constraints of the original circuit
190        dbg!(cs.num_constraints());
191
192        let cs = cs.into_inner().unwrap();
193
194        let (r1cs, z) = extract_r1cs_and_z::<Fr>(&cs);
195        r1cs.check_relation(&z).unwrap();
196
197        let relaxed_r1cs = r1cs.relax();
198        relaxed_r1cs.check_relation(&z).unwrap();
199
200        // set new CS for the circuit that checks the RelaxedR1CS of our original circuit
201        let cs = ConstraintSystem::<Fr>::new_ref();
202        // prepare the inputs for our circuit
203        let zVar = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z)).unwrap();
204        let rel_r1csVar =
205            RelaxedR1CSVar::<Fr>::new_witness(cs.clone(), || Ok(relaxed_r1cs)).unwrap();
206
207        RelaxedR1CSGadget::<Fr>::check(rel_r1csVar, zVar).unwrap();
208        assert!(cs.is_satisfied().unwrap());
209
210        // num constraints of the circuit that checks the RelaxedR1CS of the original circuit
211        dbg!(cs.num_constraints());
212    }
213
214    #[test]
215    fn test_relaxed_r1cs_small_gadget_arkworks() {
216        let x = Fr::from(5_u32);
217        let y = x * x * x + x + Fr::from(5_u32);
218        let circuit = TestCircuit::<Fr> { x, y };
219        test_relaxed_r1cs_gadget(circuit);
220    }
221
222    struct Sha256TestCircuit<F: PrimeField> {
223        _f: PhantomData<F>,
224        pub x: Vec<u8>,
225        pub y: Vec<u8>,
226    }
227    impl<F: PrimeField> ConstraintSynthesizer<F> for Sha256TestCircuit<F> {
228        fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
229            let x = Vec::<UInt8<F>>::new_witness(cs.clone(), || Ok(self.x))?;
230            let y = Vec::<UInt8<F>>::new_input(cs.clone(), || Ok(self.y))?;
231
232            let unitVar = UnitVar::default();
233            let comp_y = <Sha256Gadget<F> as CRHSchemeGadget<Sha256, F>>::evaluate(&unitVar, &x)?;
234            comp_y.0.enforce_equal(&y)?;
235            Ok(())
236        }
237    }
238    #[test]
239    fn test_relaxed_r1cs_medium_gadget_arkworks() {
240        let x = Fr::from(5_u32).into_bigint().to_bytes_le();
241        let y = <Sha256 as CRHScheme>::evaluate(&(), x.clone()).unwrap();
242
243        let circuit = Sha256TestCircuit::<Fr> {
244            _f: PhantomData,
245            x,
246            y,
247        };
248        test_relaxed_r1cs_gadget(circuit);
249    }
250
251    // circuit that has the numer of constraints specified in the `n_constraints` parameter. Note
252    // that the generated circuit will have very sparse matrices, so the resulting constraints
253    // number of the RelaxedR1CS gadget must take that into account.
254    struct CustomTestCircuit<F: PrimeField> {
255        _f: PhantomData<F>,
256        pub n_constraints: usize,
257        pub x: F,
258        pub y: F,
259    }
260    impl<F: PrimeField> ConstraintSynthesizer<F> for CustomTestCircuit<F> {
261        fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
262            let x = FpVar::<F>::new_witness(cs.clone(), || Ok(self.x))?;
263            let y = FpVar::<F>::new_input(cs.clone(), || Ok(self.y))?;
264
265            let mut comp_y = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
266            for _ in 0..self.n_constraints - 1 {
267                comp_y *= x.clone();
268            }
269
270            comp_y.enforce_equal(&y)?;
271            Ok(())
272        }
273    }
274    #[test]
275    fn test_relaxed_r1cs_custom_circuit() {
276        let n_constraints = 10_000;
277        let x = Fr::from(5_u32);
278        let mut y = Fr::one();
279        for _ in 0..n_constraints - 1 {
280            y *= x;
281        }
282
283        let circuit = CustomTestCircuit::<Fr> {
284            _f: PhantomData,
285            n_constraints,
286            x,
287            y,
288        };
289        test_relaxed_r1cs_gadget(circuit);
290    }
291}