arnaucube
arnaucube/circom-compatPublic Self-hosted repository
circom-compat/src/witness/witness_calculator.rs
src/witness/witness_calculator.rsRaw
  1use super::{fnv, CircomBase, SafeMemory, Wasm};
  2use color_eyre::Result;
  3use num_bigint::BigInt;
  4use num_traits::Zero;
  5use std::cell::Cell;
  6use wasmer::{imports, Function, Instance, Memory, MemoryType, Module, RuntimeError, Store};
  7
  8#[cfg(feature = "circom-2")]
  9use super::Circom2;
 10
 11#[cfg(not(feature = "circom-2"))]
 12use super::Circom;
 13
 14#[derive(Clone, Debug)]
 15pub struct WitnessCalculator {
 16    pub instance: Wasm,
 17    pub memory: SafeMemory,
 18    pub n64: i32,
 19}
 20
 21// Error type to signal end of execution.
 22// From https://docs.wasmer.io/integrations/examples/exit-early
 23#[derive(thiserror::Error, Debug, Clone, Copy)]
 24#[error("{0}")]
 25struct ExitCode(u32);
 26
 27#[cfg(feature = "circom-2")]
 28fn from_array32(arr: Vec<i32>) -> BigInt {
 29    let mut res = BigInt::zero();
 30    let radix = BigInt::from(0x100000000u64);
 31    for &val in arr.iter() {
 32        res = res * &radix + BigInt::from(val);
 33    }
 34    res
 35}
 36
 37impl WitnessCalculator {
 38    pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self> {
 39        let store = Store::default();
 40        let module = Module::from_file(&store, path)?;
 41
 42        // Set up the memory
 43        let memory = Memory::new(&store, MemoryType::new(2000, None, false)).unwrap();
 44        let import_object = imports! {
 45            "env" => {
 46                "memory" => memory.clone(),
 47            },
 48            // Host function callbacks from the WASM
 49            "runtime" => {
 50                "error" => runtime::error(&store),
 51                "logSetSignal" => runtime::log_signal(&store),
 52                "logGetSignal" => runtime::log_signal(&store),
 53                "logFinishComponent" => runtime::log_component(&store),
 54                "logStartComponent" => runtime::log_component(&store),
 55                "log" => runtime::log_component(&store),
 56                "exceptionHandler" => runtime::exception_handler(&store),
 57                "showSharedRWMemory" => runtime::show_memory(&store),
 58            }
 59        };
 60        let instance = Wasm::new(Instance::new(&module, &import_object)?);
 61
 62        let n32;
 63        let prime: BigInt;
 64        let mut safe_memory: SafeMemory;
 65
 66        cfg_if::cfg_if! {
 67            if #[cfg(feature = "circom-2")] {
 68                //let version = instance.get_version()?;
 69                n32 = instance.get_field_num_len32()?;
 70                safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
 71                let _res = instance.get_raw_prime()?;
 72                let mut arr = vec![0; n32 as usize];
 73                for i in 0..n32 {
 74                    let res = instance.read_shared_rw_memory(i)?;
 75                    arr[(n32 as usize) - (i as usize) - 1] = res;
 76                }
 77                prime = from_array32(arr);
 78            } else {
 79                // Fallback to Circom 1 behavior
 80                //version = 1;
 81                n32 = (instance.get_fr_len()? >> 2) - 2;
 82                safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
 83                let ptr = instance.get_ptr_raw_prime()?;
 84                prime = safe_memory.read_big(ptr as usize, n32 as usize)?;
 85            }
 86        }
 87
 88        let n64 = ((prime.bits() - 1) / 64 + 1) as i32;
 89        safe_memory.prime = prime;
 90
 91        Ok(WitnessCalculator {
 92            instance,
 93            memory: safe_memory,
 94            n64,
 95        })
 96    }
 97
 98    pub fn calculate_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
 99        &mut self,
100        inputs: I,
101        sanity_check: bool,
102    ) -> Result<Vec<BigInt>> {
103        let old_mem_free_pos = self.memory.free_pos();
104
105        self.instance.init(sanity_check)?;
106
107        let p_sig_offset = self.memory.alloc_u32();
108        let p_fr = self.memory.alloc_fr();
109
110        // allocate the inputs
111        for (name, values) in inputs.into_iter() {
112            let (msb, lsb) = fnv(&name);
113            self.instance
114                .get_signal_offset32(p_sig_offset, 0, msb, lsb)?;
115
116            let sig_offset = self.memory.read_u32(p_sig_offset as usize) as usize;
117
118            for (i, value) in values.into_iter().enumerate() {
119                self.memory.write_fr(p_fr as usize, &value)?;
120                self.instance
121                    .set_signal(0, 0, (sig_offset + i) as i32, p_fr as i32)?;
122            }
123        }
124
125        let mut w = Vec::new();
126        let n_vars = self.instance.get_n_vars()?;
127        for i in 0..n_vars {
128            let ptr = self.instance.get_ptr_witness(i)? as usize;
129            let el = self.memory.read_fr(ptr)?;
130            w.push(el);
131        }
132
133        self.memory.set_free_pos(old_mem_free_pos);
134
135        Ok(w)
136    }
137
138    pub fn calculate_witness_element<
139        E: ark_ec::PairingEngine,
140        I: IntoIterator<Item = (String, Vec<BigInt>)>,
141    >(
142        &mut self,
143        inputs: I,
144        sanity_check: bool,
145    ) -> Result<Vec<E::Fr>> {
146        use ark_ff::{FpParameters, PrimeField};
147        let witness = self.calculate_witness(inputs, sanity_check)?;
148        let modulus = <<E::Fr as PrimeField>::Params as FpParameters>::MODULUS;
149
150        // convert it to field elements
151        use num_traits::Signed;
152        let witness = witness
153            .into_iter()
154            .map(|w| {
155                let w = if w.sign() == num_bigint::Sign::Minus {
156                    // Need to negate the witness element if negative
157                    modulus.into() - w.abs().to_biguint().unwrap()
158                } else {
159                    w.to_biguint().unwrap()
160                };
161                E::Fr::from(w)
162            })
163            .collect::<Vec<_>>();
164
165        Ok(witness)
166    }
167
168    pub fn get_witness_buffer(&self) -> Result<Vec<u8>> {
169        let ptr = self.instance.get_ptr_witness_buffer()? as usize;
170
171        let view = self.memory.memory.view::<u8>();
172
173        let len = self.instance.get_n_vars()? * self.n64 * 8;
174        let arr = view[ptr..ptr + len as usize]
175            .iter()
176            .map(Cell::get)
177            .collect::<Vec<_>>();
178
179        Ok(arr)
180    }
181}
182
183// callback hooks for debugging
184mod runtime {
185    use super::*;
186
187    pub fn error(store: &Store) -> Function {
188        #[allow(unused)]
189        #[allow(clippy::many_single_char_names)]
190        fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) {
191            // NOTE: We can also get more information why it is failing, see p2str etc here:
192            // https://github.com/iden3/circom_runtime/blob/master/js/witness_calculator.js#L52-L64
193            println!(
194                "runtime error, exiting early: {0} {1} {2} {3} {4} {5}",
195                a, b, c, d, e, f
196            );
197            RuntimeError::raise(Box::new(ExitCode(1)));
198        }
199        Function::new_native(store, func)
200    }
201
202    // Circom 2.0
203    pub fn exception_handler(store: &Store) -> Function {
204        #[allow(unused)]
205        fn func(a: i32) {}
206        Function::new_native(store, func)
207    }
208
209    // Circom 2.0
210    pub fn show_memory(store: &Store) -> Function {
211        #[allow(unused)]
212        fn func() {}
213        Function::new_native(store, func)
214    }
215
216    pub fn log_signal(store: &Store) -> Function {
217        #[allow(unused)]
218        fn func(a: i32, b: i32) {}
219        Function::new_native(store, func)
220    }
221
222    pub fn log_component(store: &Store) -> Function {
223        #[allow(unused)]
224        fn func(a: i32) {}
225        Function::new_native(store, func)
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use std::{collections::HashMap, path::PathBuf};
233
234    struct TestCase<'a> {
235        circuit_path: &'a str,
236        inputs_path: &'a str,
237        n_vars: u32,
238        n64: u32,
239        witness: &'a [&'a str],
240    }
241
242    pub fn root_path(p: &str) -> String {
243        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
244        path.push(p);
245        path.to_string_lossy().to_string()
246    }
247
248    #[test]
249    fn multiplier_1() {
250        run_test(TestCase {
251            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
252            inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
253            n_vars: 4,
254            n64: 4,
255            witness: &["1", "33", "3", "11"],
256        });
257    }
258
259    #[test]
260    fn multiplier_2() {
261        run_test(TestCase {
262            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
263            inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(),
264            n_vars: 4,
265            n64: 4,
266            witness: &[
267                "1",
268                "21888242871839275222246405745257275088548364400416034343698204186575672693159",
269                "21888242871839275222246405745257275088548364400416034343698204186575796149939",
270                "11",
271            ],
272        });
273    }
274
275    #[test]
276    fn multiplier_3() {
277        run_test(TestCase {
278            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
279            inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(),
280            n_vars: 4,
281            n64: 4,
282            witness: &[
283                "1",
284                "21888242871839275222246405745257275088548364400416034343698204186575808493616",
285                "10944121435919637611123202872628637544274182200208017171849102093287904246808",
286                "2",
287            ],
288        });
289    }
290
291    #[test]
292    fn safe_multipler() {
293        let witness =
294            std::fs::read_to_string(&root_path("test-vectors/safe-circuit-witness.json")).unwrap();
295        let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
296        let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
297        run_test(TestCase {
298            circuit_path: root_path("test-vectors/circuit2.wasm").as_str(),
299            inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
300            n_vars: 132, // 128 + 4
301            n64: 4,
302            witness,
303        });
304    }
305
306    #[test]
307    fn smt_verifier() {
308        let witness =
309            std::fs::read_to_string(&root_path("test-vectors/smtverifier10-witness.json")).unwrap();
310        let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
311        let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
312
313        run_test(TestCase {
314            circuit_path: root_path("test-vectors/smtverifier10.wasm").as_str(),
315            inputs_path: root_path("test-vectors/smtverifier10-input.json").as_str(),
316            n_vars: 4794,
317            n64: 4,
318            witness,
319        });
320    }
321
322    use serde_json::Value;
323    use std::str::FromStr;
324
325    fn value_to_bigint(v: Value) -> BigInt {
326        match v {
327            Value::String(inner) => BigInt::from_str(&inner).unwrap(),
328            Value::Number(inner) => BigInt::from(inner.as_u64().expect("not a u32")),
329            _ => panic!("unsupported type"),
330        }
331    }
332
333    fn run_test(case: TestCase) {
334        let mut wtns = WitnessCalculator::new(case.circuit_path).unwrap();
335        assert_eq!(
336            wtns.memory.prime.to_str_radix(16),
337            "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase()
338        );
339        assert_eq!(wtns.instance.get_n_vars().unwrap() as u32, case.n_vars);
340        assert_eq!(wtns.n64 as u32, case.n64);
341
342        let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap();
343        let inputs: std::collections::HashMap<String, serde_json::Value> =
344            serde_json::from_str(&inputs_str).unwrap();
345
346        let inputs = inputs
347            .iter()
348            .map(|(key, value)| {
349                let res = match value {
350                    Value::String(inner) => {
351                        vec![BigInt::from_str(inner).unwrap()]
352                    }
353                    Value::Number(inner) => {
354                        vec![BigInt::from(inner.as_u64().expect("not a u32"))]
355                    }
356                    Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(),
357                    _ => panic!(),
358                };
359
360                (key.clone(), res)
361            })
362            .collect::<HashMap<_, _>>();
363
364        let res = wtns.calculate_witness(inputs, false).unwrap();
365        for (r, w) in res.iter().zip(case.witness) {
366            assert_eq!(r, &BigInt::from_str(w).unwrap());
367        }
368    }
369}