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 color_eyre::Result;
7use num_bigint::{BigInt, Sign};
8use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
9
10use crate::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 r1cs_filepath: PathBuf,
17 wasm_filepath: PathBuf,
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_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
24 CircomWrapper {
25 r1cs_filepath,
26 wasm_filepath,
27 _marker: PhantomData,
28 }
29 }
30
31 // Aggregated function to obtain R1CS and witness from Circom.
32 pub fn extract_r1cs_and_witness(
33 &self,
34 inputs: &[(String, Vec<BigInt>)],
35 ) -> Result<(R1CS<F>, Option<Vec<F>>), Error> {
36 // Extracts the R1CS
37 let file = File::open(&self.r1cs_filepath)?;
38 let reader = BufReader::new(file);
39 let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
40 let r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
41
42 // Extracts the witness vector
43 let witness_vec = self.extract_witness(inputs)?;
44
45 Ok((r1cs, Some(witness_vec)))
46 }
47
48 // Extracts the witness vector as a vector of PrimeField elements.
49 pub fn extract_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<F>, Error> {
50 let witness_bigint = self.calculate_witness(inputs)?;
51
52 witness_bigint
53 .iter()
54 .map(|big_int| {
55 self.num_bigint_to_ark_bigint(big_int)
56 .and_then(|ark_big_int| {
57 F::from_bigint(ark_big_int)
58 .ok_or_else(|| Error::Other("could not get F from bigint".to_string()))
59 })
60 })
61 .collect()
62 }
63
64 // Calculates the witness given the Wasm filepath and inputs.
65 pub fn calculate_witness(
66 &self,
67 inputs: &[(String, Vec<BigInt>)],
68 ) -> Result<Vec<BigInt>, Error> {
69 let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| {
70 Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
71 })?;
72 calculator
73 .calculate_witness(inputs.iter().cloned(), true)
74 .map_err(|e| {
75 Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
76 })
77 }
78
79 // Converts a num_bigint::BigInt to a PrimeField::BigInt.
80 pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result<F::BigInt, Error> {
81 let big_uint = value
82 .to_biguint()
83 .ok_or_else(|| Error::BigIntConversionError("BigInt is negative".to_string()))?;
84 F::BigInt::try_from(big_uint).map_err(|_| {
85 Error::BigIntConversionError("Failed to convert to PrimeField::BigInt".to_string())
86 })
87 }
88
89 // Converts a PrimeField element to a num_bigint::BigInt representation.
90 pub fn ark_primefield_to_num_bigint(&self, value: F) -> BigInt {
91 let primefield_bigint: F::BigInt = value.into_bigint();
92 let bytes = primefield_bigint.to_bytes_be();
93 BigInt::from_bytes_be(Sign::Plus, &bytes)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use ark_bn254::Fr;
101 use ark_circom::circom::{CircomBuilder, CircomConfig};
102 use ark_circom::CircomCircuit;
103 use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
104 use std::path::PathBuf;
105
106 //To generate .r1cs and .wasm files, run the below command in the terminal.
107 //bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh
108
109 // Test the satisfication by using the CircomBuilder of circom-compat
110 #[test]
111 fn test_circombuilder_satisfied() {
112 let cfg = CircomConfig::<Fr>::new(
113 "./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm",
114 "./src/frontend/circom/test_folder/cubic_circuit.r1cs",
115 )
116 .unwrap();
117 let mut builder = CircomBuilder::new(cfg);
118 builder.push_input("ivc_input", 3);
119
120 let circom = builder.build().unwrap();
121 let cs = ConstraintSystem::<Fr>::new_ref();
122 circom.generate_constraints(cs.clone()).unwrap();
123 assert!(cs.is_satisfied().unwrap());
124 }
125
126 // Test the satisfication by using the CircomWrapper
127 #[test]
128 fn test_extract_r1cs_and_witness() {
129 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
130 let wasm_path =
131 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
132
133 let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])];
134 let wrapper = CircomWrapper::<Fr>::new(r1cs_path, wasm_path);
135
136 let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();
137
138 let cs = ConstraintSystem::<Fr>::new_ref();
139
140 let circom_circuit = CircomCircuit {
141 r1cs,
142 witness,
143 inputs_already_allocated: false,
144 };
145
146 circom_circuit.generate_constraints(cs.clone()).unwrap();
147 assert!(cs.is_satisfied().unwrap());
148 }
149}