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