arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/frontends/src/circom/utils.rs
frontends/src/circom/utils.rsRaw
  1use ark_circom::{
  2    circom::{r1cs_reader, R1CS},
  3    WitnessCalculator,
  4};
  5use ark_ff::{BigInteger, PrimeField};
  6use ark_serialize::Read;
  7use num_bigint::{BigInt, Sign};
  8use std::{fs::File, io::Cursor, marker::PhantomData, path::PathBuf};
  9
 10use folding_schemes::{utils::PathOrBin, Error};
 11
 12// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
 13// based on file paths to Circom's .r1cs and .wasm.
 14#[derive(Clone, Debug)]
 15pub struct CircomWrapper<F: PrimeField> {
 16    r1csfile_bytes: Vec<u8>,
 17    wasmfile_bytes: Vec<u8>,
 18    _marker: PhantomData<F>,
 19}
 20
 21impl<F: PrimeField> CircomWrapper<F> {
 22    // Creates a new instance of the CircomWrapper with the file paths.
 23    pub fn new(r1cs: PathOrBin, wasm: PathOrBin) -> Result<Self, Error> {
 24        match (r1cs, wasm) {
 25            (PathOrBin::Path(r1cs_path), PathOrBin::Path(wasm_path)) => {
 26                Self::new_from_path(r1cs_path, wasm_path)
 27            }
 28            (PathOrBin::Bin(r1cs_bin), PathOrBin::Bin(wasm_bin)) => Ok(Self {
 29                r1csfile_bytes: r1cs_bin,
 30                wasmfile_bytes: wasm_bin,
 31                _marker: PhantomData,
 32            }),
 33            _ => unreachable!("You should pass the same enum branch for both inputs"),
 34        }
 35    }
 36
 37    // Creates a new instance of the CircomWrapper with the file paths.
 38    fn new_from_path(r1cs_file_path: PathBuf, wasm_file_path: PathBuf) -> Result<Self, Error> {
 39        let mut file = File::open(r1cs_file_path)?;
 40        let metadata = File::metadata(&file)?;
 41        let mut r1csfile_bytes = vec![0; metadata.len() as usize];
 42        file.read_exact(&mut r1csfile_bytes)?;
 43
 44        let mut file = File::open(wasm_file_path)?;
 45        let metadata = File::metadata(&file)?;
 46        let mut wasmfile_bytes = vec![0; metadata.len() as usize];
 47        file.read_exact(&mut wasmfile_bytes)?;
 48
 49        Ok(CircomWrapper {
 50            r1csfile_bytes,
 51            wasmfile_bytes,
 52            _marker: PhantomData,
 53        })
 54    }
 55
 56    // Aggregated function to obtain R1CS and witness from Circom.
 57    pub fn extract_r1cs_and_witness(
 58        &self,
 59        inputs: &[(String, Vec<BigInt>)],
 60    ) -> Result<(R1CS<F>, Option<Vec<F>>), Error> {
 61        // Extracts the R1CS
 62        let r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
 63        let r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
 64
 65        // Extracts the witness vector
 66        let witness_vec = self.extract_witness(inputs)?;
 67
 68        Ok((r1cs, Some(witness_vec)))
 69    }
 70
 71    pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> {
 72        let r1cs_file = r1cs_reader::R1CSFile::<F>::new(Cursor::new(&self.r1csfile_bytes))?;
 73        let mut r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
 74        r1cs.wire_mapping = None;
 75        Ok(r1cs)
 76    }
 77
 78    // Extracts the witness vector as a vector of PrimeField elements.
 79    pub fn extract_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<F>, Error> {
 80        let witness_bigint = self.calculate_witness(inputs)?;
 81
 82        witness_bigint
 83            .iter()
 84            .map(|big_int| {
 85                self.num_bigint_to_ark_bigint(big_int)
 86                    .and_then(|ark_big_int| {
 87                        F::from_bigint(ark_big_int)
 88                            .ok_or_else(|| Error::Other("could not get F from bigint".to_string()))
 89                    })
 90            })
 91            .collect()
 92    }
 93
 94    // Calculates the witness given the Wasm filepath and inputs.
 95    pub fn calculate_witness(
 96        &self,
 97        inputs: &[(String, Vec<BigInt>)],
 98    ) -> Result<Vec<BigInt>, Error> {
 99        let mut calculator = WitnessCalculator::from_binary(&self.wasmfile_bytes).map_err(|e| {
100            Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
101        })?;
102        calculator
103            .calculate_witness(inputs.iter().cloned(), true)
104            .map_err(|e| {
105                Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
106            })
107    }
108
109    // Converts a num_bigint::BigInt to a PrimeField::BigInt.
110    pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result<F::BigInt, Error> {
111        let big_uint = value.to_biguint().ok_or_else(|| {
112            Error::ConversionError(
113                "BigInt".into(),
114                "BigUint".into(),
115                "BigInt is negative".into(),
116            )
117        })?;
118        F::BigInt::try_from(big_uint).map_err(|_| {
119            Error::ConversionError(
120                "BigUint".into(),
121                "PrimeField::BigInt".into(),
122                "BigUint is too large to fit into PrimeField::BigInt".into(),
123            )
124        })
125    }
126
127    // Converts a PrimeField element to a num_bigint::BigInt representation.
128    pub fn ark_primefield_to_num_bigint(&self, value: F) -> BigInt {
129        let primefield_bigint: F::BigInt = value.into_bigint();
130        let bytes = primefield_bigint.to_bytes_be();
131        BigInt::from_bytes_be(Sign::Plus, &bytes)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use ark_bn254::Fr;
139    use ark_circom::circom::{CircomBuilder, CircomConfig};
140    use ark_circom::CircomCircuit;
141    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
142
143    //To generate .r1cs and .wasm files, run the below command in the terminal.
144    //bash ./frontends/src/circom/test_folder/compile.sh
145
146    // Test the satisfication by using the CircomBuilder of circom-compat
147    #[test]
148    fn test_circombuilder_satisfied() {
149        let cfg = CircomConfig::<Fr>::new(
150            "./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm",
151            "./src/circom/test_folder/cubic_circuit.r1cs",
152        )
153        .unwrap();
154        let mut builder = CircomBuilder::new(cfg);
155        builder.push_input("ivc_input", 3);
156
157        let circom = builder.build().unwrap();
158        let cs = ConstraintSystem::<Fr>::new_ref();
159        circom.generate_constraints(cs.clone()).unwrap();
160        assert!(cs.is_satisfied().unwrap());
161    }
162
163    // Test the satisfication by using the CircomWrapper
164    #[test]
165    fn test_extract_r1cs_and_witness() {
166        let r1cs_path = PathBuf::from("./src/circom/test_folder/cubic_circuit.r1cs");
167        let wasm_path =
168            PathBuf::from("./src/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
169
170        let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])];
171        let wrapper = CircomWrapper::<Fr>::new(r1cs_path.into(), wasm_path.into()).unwrap();
172
173        let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();
174
175        let cs = ConstraintSystem::<Fr>::new_ref();
176
177        let circom_circuit = CircomCircuit {
178            r1cs,
179            witness,
180            public_inputs_indexes: vec![],
181            allocate_inputs_as_witnesses: false,
182        };
183
184        circom_circuit.generate_constraints(cs.clone()).unwrap();
185        assert!(cs.is_satisfied().unwrap());
186    }
187}