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