arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/src/frontend/circom/mod.rs
src/frontend/circom/mod.rsRaw
  1use std::{error::Error, fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
  2
  3use color_eyre::Result;
  4use num_bigint::BigInt;
  5
  6use ark_circom::{circom::r1cs_reader, WitnessCalculator};
  7use ark_ec::pairing::Pairing;
  8use ark_ff::PrimeField;
  9
 10use crate::ccs::r1cs::R1CS;
 11use crate::utils::vec::SparseMatrix;
 12
 13// Define the sparse matrices on PrimeFiled.
 14pub type Constraints<F> = (ConstraintVec<F>, ConstraintVec<F>, ConstraintVec<F>);
 15pub type ConstraintVec<F> = Vec<(usize, F)>;
 16type ExtractedConstraints<F> = (Vec<Constraints<F>>, usize, usize);
 17pub type ExtractedConstraintsResult<F> = Result<ExtractedConstraints<F>, Box<dyn Error>>;
 18pub type R1CSandZ<F> = (R1CS<F>, Vec<F>);
 19
 20// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
 21// based on file paths to Circom's .r1cs and .wasm.
 22pub struct CircomWrapper<E: Pairing> {
 23    r1cs_filepath: PathBuf,
 24    wasm_filepath: PathBuf,
 25    _marker: PhantomData<E>,
 26}
 27
 28impl<E: Pairing> CircomWrapper<E> {
 29    // Creates a new instance of the CircomWrapper with the file paths.
 30    pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
 31        CircomWrapper {
 32            r1cs_filepath,
 33            wasm_filepath,
 34            _marker: PhantomData,
 35        }
 36    }
 37
 38    // Aggregates multiple functions to obtain R1CS and Z as defined in folding-schemes from Circom.
 39    pub fn extract_r1cs_and_z(
 40        &self,
 41        inputs: &[(String, Vec<BigInt>)],
 42    ) -> Result<R1CSandZ<E::ScalarField>, Box<dyn Error>> {
 43        let (constraints, pub_io_len, num_variables) = self.extract_constraints_from_r1cs()?;
 44        let witness = self.calculate_witness(inputs)?;
 45        self.circom_to_folding_schemes_r1cs_and_z(constraints, &witness, pub_io_len, num_variables)
 46    }
 47
 48    // Extracts constraints from the r1cs file.
 49    pub fn extract_constraints_from_r1cs(&self) -> ExtractedConstraintsResult<E::ScalarField>
 50    where
 51        E: Pairing,
 52    {
 53        // Opens the .r1cs file and create a reader.
 54        let file = File::open(&self.r1cs_filepath)?;
 55        let reader = BufReader::new(file);
 56
 57        // Reads the R1CS file and extract the constraints directly.
 58        let r1cs_file = r1cs_reader::R1CSFile::<E>::new(reader)?;
 59        let pub_io_len = (r1cs_file.header.n_pub_in + r1cs_file.header.n_pub_out) as usize;
 60        let r1cs = r1cs_reader::R1CS::<E>::from(r1cs_file);
 61        let num_variables = r1cs.num_variables;
 62        let constraints: Vec<Constraints<E::ScalarField>> = r1cs.constraints;
 63
 64        Ok((constraints, pub_io_len, num_variables))
 65    }
 66
 67    // Converts a set of constraints from ark-circom into R1CS format of folding-schemes.
 68    pub fn convert_to_folding_schemes_r1cs<F>(
 69        &self,
 70        constraints: Vec<Constraints<F>>,
 71        pub_io_len: usize,
 72        num_variables: usize,
 73    ) -> R1CS<F>
 74    where
 75        F: PrimeField,
 76    {
 77        let mut a_matrix: Vec<Vec<(F, usize)>> = Vec::new();
 78        let mut b_matrix: Vec<Vec<(F, usize)>> = Vec::new();
 79        let mut c_matrix: Vec<Vec<(F, usize)>> = Vec::new();
 80
 81        let n_rows = constraints.len();
 82
 83        for (ai, bi, ci) in constraints {
 84            a_matrix.push(
 85                ai.into_iter()
 86                    .map(|(index, scalar)| (scalar, index))
 87                    .collect(),
 88            );
 89            b_matrix.push(
 90                bi.into_iter()
 91                    .map(|(index, scalar)| (scalar, index))
 92                    .collect(),
 93            );
 94            c_matrix.push(
 95                ci.into_iter()
 96                    .map(|(index, scalar)| (scalar, index))
 97                    .collect(),
 98            );
 99        }
100
101        let l = pub_io_len;
102        let n_cols = num_variables;
103
104        let A = SparseMatrix {
105            n_rows,
106            n_cols,
107            coeffs: a_matrix,
108        };
109        let B = SparseMatrix {
110            n_rows,
111            n_cols,
112            coeffs: b_matrix,
113        };
114        let C = SparseMatrix {
115            n_rows,
116            n_cols,
117            coeffs: c_matrix,
118        };
119
120        R1CS::<F> { l, A, B, C }
121    }
122
123    // Calculates the witness given the Wasm filepath and inputs.
124    pub fn calculate_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<BigInt>> {
125        let mut calculator = WitnessCalculator::new(&self.wasm_filepath)?;
126        calculator.calculate_witness(inputs.iter().cloned(), true)
127    }
128
129    // Converts a num_bigint input to `PrimeField`'s BigInt.
130    pub fn num_bigint_to_ark_bigint<F: PrimeField>(
131        &self,
132        value: &BigInt,
133    ) -> Result<F::BigInt, Box<dyn Error>> {
134        let big_uint = value
135            .to_biguint()
136            .ok_or_else(|| "BigInt is negative".to_string())?;
137        F::BigInt::try_from(big_uint).map_err(|_| "BigInt conversion failed".to_string().into())
138    }
139
140    // Converts R1CS constraints and witness from ark-circom format
141    // into folding-schemes R1CS and z format.
142    pub fn circom_to_folding_schemes_r1cs_and_z<F>(
143        &self,
144        constraints: Vec<Constraints<F>>,
145        witness: &[BigInt],
146        pub_io_len: usize,
147        num_variables: usize,
148    ) -> Result<(R1CS<F>, Vec<F>), Box<dyn Error>>
149    where
150        F: PrimeField,
151    {
152        let folding_schemes_r1cs =
153            self.convert_to_folding_schemes_r1cs(constraints, pub_io_len, num_variables);
154
155        let z: Result<Vec<F>, _> = witness
156            .iter()
157            .map(|big_int| {
158                let ark_big_int = self.num_bigint_to_ark_bigint::<F>(big_int)?;
159                F::from_bigint(ark_big_int).ok_or_else(|| {
160                    "Failed to convert bigint to field element"
161                        .to_string()
162                        .into()
163                })
164            })
165            .collect();
166
167        z.map(|z| (folding_schemes_r1cs, z))
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use crate::frontend::circom::CircomWrapper;
174    use ark_bn254::Bn254;
175    use num_bigint::BigInt;
176    use std::env;
177
178    /*
179    test_circuit represents 35 = x^3 + x + 5 in test_folder/test_circuit.circom.
180    In the test_circom_conversion_success function, x is assigned the value 3, which satisfies the R1CS correctly.
181    In the test_circom_conversion_failure function, x is assigned the value 6, which doesn't satisfy the R1CS.
182    */
183
184    /*
185    To generate .r1cs and .wasm files, run the below command in the terminal.
186    bash ./src/frontend/circom/test_folder/compile.sh
187    */
188
189    fn test_circom_conversion_logic(expect_success: bool, inputs: Vec<(String, Vec<BigInt>)>) {
190        let current_dir = env::current_dir().unwrap();
191        let base_path = current_dir.join("src/frontend/circom/test_folder");
192
193        let r1cs_filepath = base_path.join("test_circuit.r1cs");
194        let wasm_filepath = base_path.join("test_circuit_js/test_circuit.wasm");
195
196        assert!(r1cs_filepath.exists());
197        assert!(wasm_filepath.exists());
198
199        let circom_wrapper = CircomWrapper::<Bn254>::new(r1cs_filepath, wasm_filepath);
200
201        let (r1cs, z) = circom_wrapper
202            .extract_r1cs_and_z(&inputs)
203            .expect("Error processing input");
204
205        // Checks the relationship of R1CS.
206        let check_result = std::panic::catch_unwind(|| {
207            r1cs.check_relation(&z).unwrap();
208        });
209
210        match (expect_success, check_result) {
211            (true, Ok(_)) => {}
212            (false, Err(_)) => {}
213            (true, Err(_)) => panic!("Expected success but got a failure."),
214            (false, Ok(_)) => panic!("Expected a failure but got success."),
215        }
216    }
217
218    #[test]
219    fn test_circom_conversion() {
220        // expect it to pass for correct inputs.
221        test_circom_conversion_logic(true, vec![("step_in".to_string(), vec![BigInt::from(3)])]);
222
223        // expect it to fail for incorrect inputs.
224        test_circom_conversion_logic(false, vec![("step_in".to_string(), vec![BigInt::from(7)])]);
225    }
226}