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, 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
16impl WitnessCalculator {
17 pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self> {
18 let store = Store::default();
19 let module = Module::from_file(&store, path)?;
20
21 // Set up the memory
22 let memory = Memory::new(&store, MemoryType::new(2000, None, false)).unwrap();
23 let import_object = imports! {
24 "env" => {
25 "memory" => memory.clone(),
26 },
27 // Host function callbacks from the WASM
28 "runtime" => {
29 "error" => runtime::error(&store),
30 "logSetSignal" => runtime::log_signal(&store),
31 "logGetSignal" => runtime::log_signal(&store),
32 "logFinishComponent" => runtime::log_component(&store),
33 "logStartComponent" => runtime::log_component(&store),
34 "log" => runtime::log_component(&store),
35 }
36 };
37 let instance = Wasm::new(Instance::new(&module, &import_object)?);
38
39 let n32 = (instance.get_fr_len()? >> 2) - 2;
40
41 let mut memory = SafeMemory::new(memory, n32 as usize, BigInt::zero());
42
43 let ptr = instance.get_ptr_raw_prime()?;
44 let prime = memory.read_big(ptr as usize, n32 as usize)?;
45 let n64 = ((prime.bits() - 1) / 64 + 1) as i32;
46 memory.prime = prime;
47
48 Ok(WitnessCalculator {
49 instance,
50 memory,
51 n64,
52 })
53 }
54
55 pub fn calculate_witness<I: IntoIterator<Item = (String, Vec<BigInt>)>>(
56 &mut self,
57 inputs: I,
58 sanity_check: bool,
59 ) -> Result<Vec<BigInt>> {
60 let old_mem_free_pos = self.memory.free_pos();
61
62 self.instance.init(sanity_check)?;
63
64 let p_sig_offset = self.memory.alloc_u32();
65 let p_fr = self.memory.alloc_fr();
66
67 // allocate the inputs
68 for (name, values) in inputs.into_iter() {
69 let (msb, lsb) = fnv(&name);
70 self.instance
71 .get_signal_offset32(p_sig_offset, 0, msb, lsb)?;
72
73 let sig_offset = self.memory.read_u32(p_sig_offset as usize) as usize;
74
75 for (i, value) in values.into_iter().enumerate() {
76 self.memory.write_fr(p_fr as usize, &value)?;
77 self.instance
78 .set_signal(0, 0, (sig_offset + i) as i32, p_fr as i32)?;
79 }
80 }
81
82 let mut w = Vec::new();
83 let n_vars = self.instance.get_n_vars()?;
84 for i in 0..n_vars {
85 let ptr = self.instance.get_ptr_witness(i)? as usize;
86 let el = self.memory.read_fr(ptr)?;
87 w.push(el);
88 }
89
90 self.memory.set_free_pos(old_mem_free_pos);
91
92 Ok(w)
93 }
94
95 pub fn get_witness_buffer(&self) -> Result<Vec<u8>> {
96 let ptr = self.instance.get_ptr_witness_buffer()? as usize;
97
98 let view = self.memory.memory.view::<u8>();
99
100 let len = self.instance.get_n_vars()? * self.n64 * 8;
101 let arr = view[ptr..ptr + len as usize]
102 .iter()
103 .map(Cell::get)
104 .collect::<Vec<_>>();
105
106 Ok(arr)
107 }
108}
109
110// callback hooks for debugging
111mod runtime {
112 use super::*;
113
114 pub fn error(store: &Store) -> Function {
115 #[allow(unused)]
116 #[allow(clippy::many_single_char_names)]
117 fn func(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) {}
118 Function::new_native(&store, func)
119 }
120
121 pub fn log_signal(store: &Store) -> Function {
122 #[allow(unused)]
123 fn func(a: i32, b: i32) {}
124 Function::new_native(&store, func)
125 }
126
127 pub fn log_component(store: &Store) -> Function {
128 #[allow(unused)]
129 fn func(a: i32) {}
130 Function::new_native(&store, func)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use std::{collections::HashMap, path::PathBuf};
138
139 struct TestCase<'a> {
140 circuit_path: &'a str,
141 inputs_path: &'a str,
142 n_vars: u32,
143 n64: u32,
144 witness: &'a [&'a str],
145 }
146
147 pub fn root_path(p: &str) -> String {
148 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
149 path.push(p);
150 path.to_string_lossy().to_string()
151 }
152
153 #[test]
154 fn multiplier_1() {
155 run_test(TestCase {
156 circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
157 inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
158 n_vars: 4,
159 n64: 4,
160 witness: &["1", "33", "3", "11"],
161 });
162 }
163
164 #[test]
165 fn multiplier_2() {
166 run_test(TestCase {
167 circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
168 inputs_path: root_path("test-vectors/mycircuit-input2.json").as_str(),
169 n_vars: 4,
170 n64: 4,
171 witness: &[
172 "1",
173 "21888242871839275222246405745257275088548364400416034343698204186575672693159",
174 "21888242871839275222246405745257275088548364400416034343698204186575796149939",
175 "11",
176 ],
177 });
178 }
179
180 #[test]
181 fn multiplier_3() {
182 run_test(TestCase {
183 circuit_path: root_path("test-vectors/mycircuit.wasm").as_str(),
184 inputs_path: root_path("test-vectors/mycircuit-input3.json").as_str(),
185 n_vars: 4,
186 n64: 4,
187 witness: &[
188 "1",
189 "21888242871839275222246405745257275088548364400416034343698204186575808493616",
190 "10944121435919637611123202872628637544274182200208017171849102093287904246808",
191 "2",
192 ],
193 });
194 }
195
196 #[test]
197 fn safe_multipler() {
198 let witness =
199 std::fs::read_to_string(&root_path("test-vectors/safe-circuit-witness.json")).unwrap();
200 let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
201 let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
202 run_test(TestCase {
203 circuit_path: root_path("test-vectors/circuit2.wasm").as_str(),
204 inputs_path: root_path("test-vectors/mycircuit-input1.json").as_str(),
205 n_vars: 132, // 128 + 4
206 n64: 4,
207 witness,
208 });
209 }
210
211 #[test]
212 fn smt_verifier() {
213 let witness =
214 std::fs::read_to_string(&root_path("test-vectors/smtverifier10-witness.json")).unwrap();
215 let witness: Vec<String> = serde_json::from_str(&witness).unwrap();
216 let witness = &witness.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
217
218 run_test(TestCase {
219 circuit_path: root_path("test-vectors/smtverifier10.wasm").as_str(),
220 inputs_path: root_path("test-vectors/smtverifier10-input.json").as_str(),
221 n_vars: 4794,
222 n64: 4,
223 witness,
224 });
225 }
226
227 use serde_json::Value;
228 use std::str::FromStr;
229
230 fn value_to_bigint(v: Value) -> BigInt {
231 match v {
232 Value::String(inner) => BigInt::from_str(&inner).unwrap(),
233 Value::Number(inner) => BigInt::from(inner.as_u64().expect("not a u32")),
234 _ => panic!("unsupported type"),
235 }
236 }
237
238 fn run_test(case: TestCase) {
239 let mut wtns = WitnessCalculator::new(case.circuit_path).unwrap();
240 assert_eq!(
241 wtns.memory.prime.to_str_radix(16),
242 "30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0000001".to_lowercase()
243 );
244 assert_eq!(wtns.instance.get_n_vars().unwrap() as u32, case.n_vars);
245 assert_eq!(wtns.n64 as u32, case.n64);
246
247 let inputs_str = std::fs::read_to_string(case.inputs_path).unwrap();
248 let inputs: std::collections::HashMap<String, serde_json::Value> =
249 serde_json::from_str(&inputs_str).unwrap();
250
251 let inputs = inputs
252 .iter()
253 .map(|(key, value)| {
254 let res = match value {
255 Value::String(inner) => {
256 vec![BigInt::from_str(inner).unwrap()]
257 }
258 Value::Number(inner) => {
259 vec![BigInt::from(inner.as_u64().expect("not a u32"))]
260 }
261 Value::Array(inner) => inner.iter().cloned().map(value_to_bigint).collect(),
262 _ => panic!(),
263 };
264
265 (key.clone(), res)
266 })
267 .collect::<HashMap<_, _>>();
268
269 let res = wtns.calculate_witness(inputs, false).unwrap();
270 for (r, w) in res.iter().zip(case.witness) {
271 assert_eq!(r, &BigInt::from_str(w).unwrap());
272 }
273 }
274}