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 num::ToPrimitive;
 10
 11#[cfg(feature = "circom-2")]
 12use super::Circom2;
 13
 14use super::Circom;
 15
 16#[derive(Clone, Debug)]
 17pub struct WitnessCalculator {
 18    pub instance: Wasm,
 19    pub memory: SafeMemory,
 20    pub n64: u32,
 21    pub circom_version: u32,
 22}
 23
 24// Error type to signal end of execution.
 25// From https://docs.wasmer.io/integrations/examples/exit-early
 26#[derive(thiserror::Error, Debug, Clone, Copy)]
 27#[error("{0}")]
 28struct ExitCode(u32);
 29
 30#[cfg(feature = "circom-2")]
 31fn from_array32(arr: Vec<u32>) -> BigInt {
 32    let mut res = BigInt::zero();
 33    let radix = BigInt::from(0x100000000u64);
 34    for &val in arr.iter() {
 35        res = res * &radix + BigInt::from(val);
 36    }
 37    res
 38}
 39
 40#[cfg(feature = "circom-2")]
 41fn to_array32(s: &BigInt, size: usize) -> Vec<u32> {
 42    let mut res = vec![0; size as usize];
 43    let mut rem = s.clone();
 44    let radix = BigInt::from(0x100000000u64);
 45    let mut c = size;
 46    while !rem.is_zero() {
 47        c -= 1;
 48        res[c] = (&rem % &radix).to_u32().unwrap();
 49        rem /= &radix;
 50    }
 51
 52    res
 53}
 54
 55impl WitnessCalculator {
 56    pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self> {
 57        Self::from_file(path)
 58    }
 59
 60    pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self> {
 61        let store = Store::default();
 62        let module = Module::from_file(&store, path)?;
 63        Self::from_module(module)
 64    }
 65
 66    pub fn from_module(module: Module) -> Result<Self> {
 67        let store = module.store();
 68
 69        // Set up the memory
 70        let memory = Memory::new(store, MemoryType::new(2000, None, false)).unwrap();
 71        let import_object = imports! {
 72            "env" => {
 73                "memory" => memory.clone(),
 74            },
 75            // Host function callbacks from the WASM
 76            "runtime" => {
 77                "error" => runtime::error(store),
 78                "logSetSignal" => runtime::log_signal(store),
 79                "logGetSignal" => runtime::log_signal(store),
 80                "logFinishComponent" => runtime::log_component(store),
 81                "logStartComponent" => runtime::log_component(store),
 82                "log" => runtime::log_component(store),
 83                "exceptionHandler" => runtime::exception_handler(store),
 84                "showSharedRWMemory" => runtime::show_memory(store),
 85                "printErrorMessage" => runtime::print_error_message(store),
 86                "writeBufferMessage" => runtime::write_buffer_message(store),
 87            }
 88        };
 89        let instance = Wasm::new(Instance::new(&module, &import_object)?);
 90
 91        let version = instance.get_version().unwrap_or(1);
 92
 93        // Circom 2 feature flag with version 2
 94        #[cfg(feature = "circom-2")]
 95        fn new_circom2(instance: Wasm, memory: Memory, version: u32) -> Result<WitnessCalculator> {
 96            let n32 = instance.get_field_num_len32()?;
 97            let mut safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
 98            instance.get_raw_prime()?;
 99            let mut arr = vec![0; n32 as usize];
100            for i in 0..n32 {
101                let res = instance.read_shared_rw_memory(i)?;
102                arr[(n32 as usize) - (i as usize) - 1] = res;
103            }
104            let prime = from_array32(arr);
105
106            let n64 = ((prime.bits() - 1) / 64 + 1) as u32;
107            safe_memory.prime = prime;
108
109            Ok(WitnessCalculator {
110                instance,
111                memory: safe_memory,
112                n64,
113                circom_version: version,
114            })
115        }
116
117        fn new_circom1(instance: Wasm, memory: Memory, version: u32) -> Result<WitnessCalculator> {
118            // Fallback to Circom 1 behavior
119            let n32 = (instance.get_fr_len()? >> 2) - 2;
120            let mut safe_memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
121            let ptr = instance.get_ptr_raw_prime()?;
122            let prime = safe_memory.read_big(ptr as usize, n32 as usize)?;
123
124            let n64 = ((prime.bits() - 1) / 64 + 1) as u32;
125            safe_memory.prime = prime;
126
127            Ok(WitnessCalculator {
128                instance,
129                memory: safe_memory,
130                n64,
131                circom_version: version,
132            })
133        }
134
135        // Three possibilities:
136        // a) Circom 2 feature flag enabled, WASM runtime version 2
137        // b) Circom 2 feature flag enabled, WASM runtime version 1
138        // c) Circom 1 default behavior
139        //
140        // Once Circom 2 support is more stable, feature flag can be removed
141
142        cfg_if::cfg_if! {
143            if #[cfg(feature = "circom-2")] {
144                match version {
145                    2 => new_circom2(instance, memory, version),
146                    1 => new_circom1(instance, memory, version),
147                    _ => panic!("Unknown Circom version")
148                }
149            } else {
150                new_circom1(instance, memory, version)
151            }
152        }
153    }
154
155    pub fn calculate_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
156        &mut self,
157        inputs: I,
158        sanity_check: bool,
159    ) -> Result<Vec<BigInt>> {
160        self.instance.init(sanity_check)?;
161
162        cfg_if::cfg_if! {
163            if #[cfg(feature = "circom-2")] {
164                match self.circom_version {
165                    2 => self.calculate_witness_circom2(inputs, sanity_check),
166                    1 => self.calculate_witness_circom1(inputs, sanity_check),
167                    _ => panic!("Unknown Circom version")
168                }
169            } else {
170                self.calculate_witness_circom1(inputs, sanity_check)
171            }
172        }
173    }
174
175    // Circom 1 default behavior
176    fn calculate_witness_circom1<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
177        &mut self,
178        inputs: I,
179        sanity_check: bool,
180    ) -> Result<Vec<BigInt>> {
181        self.instance.init(sanity_check)?;
182
183        let old_mem_free_pos = self.memory.free_pos();
184        let p_sig_offset = self.memory.alloc_u32();
185        let p_fr = self.memory.alloc_fr();
186
187        // allocate the inputs
188        for (name, values) in inputs.into_iter() {
189            let (msb, lsb) = fnv(&name);
190
191            self.instance
192                .get_signal_offset32(p_sig_offset, 0, msb, lsb)?;
193
194            let sig_offset = self.memory.read_u32(p_sig_offset as usize) as usize;
195
196            for (i, value) in values.into_iter().enumerate() {
197                self.memory.write_fr(p_fr as usize, &value)?;
198                self.instance
199                    .set_signal(0, 0, (sig_offset + i) as u32, p_fr)?;
200            }
201        }
202
203        let mut w = Vec::new();
204
205        let n_vars = self.instance.get_n_vars()?;
206        for i in 0..n_vars {
207            let ptr = self.instance.get_ptr_witness(i)? as usize;
208            let el = self.memory.read_fr(ptr)?;
209            w.push(el);
210        }
211
212        self.memory.set_free_pos(old_mem_free_pos);
213
214        Ok(w)
215    }
216
217    // Circom 2 feature flag with version 2
218    #[cfg(feature = "circom-2")]
219    fn calculate_witness_circom2<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
220        &mut self,
221        inputs: I,
222        sanity_check: bool,
223    ) -> Result<Vec<BigInt>> {
224        self.instance.init(sanity_check)?;
225
226        let n32 = self.instance.get_field_num_len32()?;
227
228        // allocate the inputs
229        for (name, values) in inputs.into_iter() {
230            let (msb, lsb) = fnv(&name);
231
232            for (i, value) in values.into_iter().enumerate() {
233                let f_arr = to_array32(&value, n32 as usize);
234                for j in 0..n32 {
235                    self.instance.write_shared_rw_memory(
236                        j as u32,
237                        f_arr[(n32 as usize) - 1 - (j as usize)],
238                    )?;
239                }
240                self.instance
241                    .set_input_signal(msb as u32, lsb as u32, i as u32)?;
242            }
243        }
244
245        let mut w = Vec::new();
246
247        let witness_size = self.instance.get_witness_size()?;
248        for i in 0..witness_size {
249            self.instance.get_witness(i)?;
250            let mut arr = vec![0; n32 as usize];
251            for j in 0..n32 {
252                arr[(n32 as usize) - 1 - (j as usize)] = self.instance.read_shared_rw_memory(j)?;
253            }
254            w.push(from_array32(arr));
255        }
256
257        Ok(w)
258    }
259
260    pub fn calculate_witness_element<
261        E: ark_ec::PairingEngine,
262        I: IntoIterator<Item = (String, Vec<BigInt>)>,
263    >(
264        &mut self,
265        inputs: I,
266        sanity_check: bool,
267    ) -> Result<Vec<E::Fr>> {
268        use ark_ff::{FpParameters, PrimeField};
269        let witness = self.calculate_witness(inputs, sanity_check)?;
270        let modulus = <<E::Fr as PrimeField>::Params as FpParameters>::MODULUS;
271
272        // convert it to field elements
273        use num_traits::Signed;
274        let witness = witness
275            .into_iter()
276            .map(|w| {
277                let w = if w.sign() == num_bigint::Sign::Minus {
278                    // Need to negate the witness element if negative
279                    modulus.into() - w.abs().to_biguint().unwrap()
280                } else {
281                    w.to_biguint().unwrap()
282                };
283                E::Fr::from(w)
284            })
285            .collect::<Vec<_>>();
286
287        Ok(witness)
288    }
289
290    pub fn get_witness_buffer(&self) -> Result<Vec<u8>> {
291        let ptr = self.instance.get_ptr_witness_buffer()? as usize;
292
293        let view = self.memory.memory.view::<u8>();
294
295        let len = self.instance.get_n_vars()? * self.n64 * 8;
296        let arr = view[ptr..ptr + len as usize]
297            .iter()
298            .map(Cell::get)
299            .collect::<Vec<_>>();
300
301        Ok(arr)
302    }
303}
304
305// callback hooks for debugging
306mod runtime {
307    use super::*;
308
309    pub fn error(store: &Store) -> Function {
310        #[allow(unused)]
311        #[allow(clippy::many_single_char_names)]
312        fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> Result<(), RuntimeError> {
313            // NOTE: We can also get more information why it is failing, see p2str etc here:
314            // https://github.com/iden3/circom_runtime/blob/master/js/witness_calculator.js#L52-L64
315            println!(
316                "runtime error, exiting early: {a} {b} {c} {d} {e} {f}",
317            );
318            Err(RuntimeError::user(Box::new(ExitCode(1))))
319        }
320        Function::new_native(store, func)
321    }
322
323    // Circom 2.0
324    pub fn exception_handler(store: &Store) -> Function {
325        #[allow(unused)]
326        fn func(a: i32) {}
327        Function::new_native(store, func)
328    }
329
330    // Circom 2.0
331    pub fn show_memory(store: &Store) -> Function {
332        #[allow(unused)]
333        fn func() {}
334        Function::new_native(store, func)
335    }
336
337    // Circom 2.0
338    pub fn print_error_message(store: &Store) -> Function {
339        #[allow(unused)]
340        fn func() {}
341        Function::new_native(store, func)
342    }
343
344    // Circom 2.0
345    pub fn write_buffer_message(store: &Store) -> Function {
346        #[allow(unused)]
347        fn func() {}
348        Function::new_native(store, func)
349    }
350
351    pub fn log_signal(store: &Store) -> Function {
352        #[allow(unused)]
353        fn func(a: i32, b: i32) {}
354        Function::new_native(store, func)
355    }
356
357    pub fn log_component(store: &Store) -> Function {
358        #[allow(unused)]
359        fn func(a: i32) {}
360        Function::new_native(store, func)
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use std::{collections::HashMap, path::PathBuf};
368
369    struct TestCase<'a> {
370        circuit_path: &'a str,
371        inputs_path: &'a str,
372        n_vars: u32,
373        n64: u32,
374        witness: &'a [&'a str],
375    }
376
377    pub fn root_path(p: &str) -> String {
378        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
379        path.push(p);
380        path.to_string_lossy().to_string()
381    }
382
383    #[test]
384    fn multiplier_1() {
385        run_test(TestCase {
386            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
387            inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
388            n_vars: 4,
389            n64: 4,
390            witness: &["1", "33", "3", "11"],
391        });
392    }
393
394    #[test]
395    fn multiplier_2() {
396        run_test(TestCase {
397            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
398            inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(),
399            n_vars: 4,
400            n64: 4,
401            witness: &[
402                "1",
403                "21888242871839275222246405745257275088548364400416034343698204186575672693159",
404                "21888242871839275222246405745257275088548364400416034343698204186575796149939",
405                "11",
406            ],
407        });
408    }
409
410    #[test]
411    fn multiplier_3() {
412        run_test(TestCase {
413            circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
414            inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(),
415            n_vars: 4,
416            n64: 4,
417            witness: &[
418                "1",
419                "21888242871839275222246405745257275088548364400416034343698204186575808493616",
420                "10944121435919637611123202872628637544274182200208017171849102093287904246808",
421                "2",
422            ],
423        });
424    }
425
426    #[test]
427    fn safe_multipler() {
428        let witness =
429            std::fs::read_to_string(&root_path("test-vectors/safe-circuit-witness.json")).unwrap();
430        let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
431        let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
432        run_test(TestCase {
433            circuit_path: root_path("test-vectors/circuit2.wasm").as_str(),
434            inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
435            n_vars: 132, // 128 + 4
436            n64: 4,
437            witness,
438        });
439    }
440
441    #[test]
442    fn smt_verifier() {
443        let witness =
444            std::fs::read_to_string(&root_path("test-vectors/smtverifier10-witness.json")).unwrap();
445        let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
446        let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
447
448        run_test(TestCase {
449            circuit_path: root_path("test-vectors/smtverifier10.wasm").as_str(),
450            inputs_path: root_path("test-vectors/smtverifier10-input.json").as_str(),
451            n_vars: 4794,
452            n64: 4,
453            witness,
454        });
455    }
456
457    use serde_json::Value;
458    use std::str::FromStr;
459
460    fn value_to_bigint(v: Value) -> BigInt {
461        match v {
462            Value::String(inner) => BigInt::from_str(&inner).unwrap(),
463            Value::Number(inner) => BigInt::from(inner.as_u64().expect("not a u32")),
464            _ => panic!("unsupported type"),
465        }
466    }
467
468    fn run_test(case: TestCase) {
469        let mut wtns = WitnessCalculator::new(case.circuit_path).unwrap();
470        assert_eq!(
471            wtns.memory.prime.to_str_radix(16),
472            "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase()
473        );
474        assert_eq!(wtns.instance.get_n_vars().unwrap() as u32, case.n_vars);
475        assert_eq!(wtns.n64 as u32, case.n64);
476
477        let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap();
478        let inputs: std::collections::HashMap<String, serde_json::Value> =
479            serde_json::from_str(&inputs_str).unwrap();
480
481        let inputs = inputs
482            .iter()
483            .map(|(key, value)| {
484                let res = match value {
485                    Value::String(inner) => {
486                        vec![BigInt::from_str(inner).unwrap()]
487                    }
488                    Value::Number(inner) => {
489                        vec![BigInt::from(inner.as_u64().expect("not a u32"))]
490                    }
491                    Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(),
492                    _ => panic!(),
493                };
494
495                (key.clone(), res)
496            })
497            .collect::<HashMap<_, _>>();
498
499        let res = wtns.calculate_witness(inputs, false).unwrap();
500        for (r, w) in res.iter().zip(case.witness) {
501            assert_eq!(r, &BigInt::from_str(w).unwrap());
502        }
503    }
504}