1use acir::brillig::{ForeignCallParam, ForeignCallResult};
7use acir::circuit::Program;
8use acir::native_types::{Witness, WitnessMap};
9use acir::FieldElement;
10use acvm::pwg::{ACVMStatus, OpcodeResolutionError, ResolvedAssertionPayload, ACVM};
11use bn254_blackbox_solver::Bn254BlackBoxSolver;
12
13use aztec_core::abi::ContractArtifact;
14use aztec_core::error::Error;
15use aztec_core::types::Fr;
16
17use super::field_conversion::{fe_to_fr, fr_to_fe, witness_map_to_frs};
18
19#[derive(Debug, Clone)]
25pub struct AcvmExecutionOutput {
26 pub return_values: Vec<Fr>,
28 pub witness: WitnessMap<FieldElement>,
30 pub acir_bytecode: Vec<u8>,
32 pub first_acir_call_return_values: Vec<Fr>,
35}
36
37#[derive(Debug, Clone)]
39pub struct UtilityResult {
40 pub return_values: Vec<Fr>,
42}
43
44#[async_trait::async_trait]
49pub trait OracleCallback: Send {
50 async fn handle_foreign_call(
51 &mut self,
52 function: &str,
53 inputs: Vec<Vec<Fr>>,
54 ) -> Result<Vec<Vec<Fr>>, Error>;
55}
56
57pub struct AcvmExecutor;
59
60impl AcvmExecutor {
61 fn error_types_contains_message(
62 error_types: Option<&serde_json::Value>,
63 expected: &str,
64 ) -> bool {
65 error_types
66 .and_then(|value| value.as_object())
67 .map(|entries| {
68 entries
69 .values()
70 .any(|entry| entry.get("string").and_then(|v| v.as_str()) == Some(expected))
71 })
72 .unwrap_or(false)
73 }
74
75 fn fallback_private_error_message(
76 last_oracle: &str,
77 resolved: &str,
78 error_types: Option<&serde_json::Value>,
79 ) -> Option<String> {
80 if resolved != "Cannot satisfy constraint" {
81 return None;
82 }
83
84 let invalid_nonce =
85 "Invalid authwit nonce. When 'from' and 'msg_sender' are the same, 'authwit_nonce' must be zero";
86 if last_oracle == "privateIsNullifierPending"
87 && Self::error_types_contains_message(error_types, invalid_nonce)
88 {
89 return Some(invalid_nonce.to_owned());
90 }
91
92 let balance_too_low = "Balance too low";
93 if (last_oracle == "privateNotifyNullifiedNote" || last_oracle == "utilityGetNotes")
94 && Self::error_types_contains_message(error_types, balance_too_low)
95 {
96 return Some(balance_too_low.to_owned());
97 }
98
99 None
100 }
101
102 fn decode_program(bytecode_b64: &str) -> Result<Program<FieldElement>, Error> {
109 let bytecode_bytes =
110 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, bytecode_b64)
111 .map_err(|e| Error::InvalidData(format!("base64 decode error: {e}")))?;
112
113 let is_gzip =
115 bytecode_bytes.len() >= 2 && bytecode_bytes[0] == 0x1f && bytecode_bytes[1] == 0x8b;
116
117 if is_gzip {
118 Program::deserialize_program(&bytecode_bytes)
119 .map_err(|e| Error::InvalidData(format!("ACIR deserialize error: {e}")))
120 } else {
121 use std::io::Write;
124 let mut enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
125 enc.write_all(&bytecode_bytes)
126 .map_err(|e| Error::InvalidData(format!("gzip compress error: {e}")))?;
127 let compressed = enc
128 .finish()
129 .map_err(|e| Error::InvalidData(format!("gzip finish error: {e}")))?;
130 Program::deserialize_program(&compressed)
131 .map_err(|e| Error::InvalidData(format!("ACIR deserialize error: {e}")))
132 }
133 }
134
135 fn build_initial_witness(args: &[Fr]) -> WitnessMap<FieldElement> {
137 let mut witness_map = WitnessMap::default();
138 for (i, arg) in args.iter().enumerate() {
139 witness_map.insert(Witness(i as u32), fr_to_fe(arg));
141 }
142 witness_map
143 }
144
145 fn convert_fc_inputs(inputs: &[ForeignCallParam<FieldElement>]) -> Vec<Vec<Fr>> {
147 inputs
148 .iter()
149 .map(|param| match param {
150 ForeignCallParam::Single(fe) => vec![fe_to_fr(fe)],
151 ForeignCallParam::Array(fes) => fes.iter().map(fe_to_fr).collect(),
152 })
153 .collect()
154 }
155
156 fn convert_fc_result(function: &str, result: Vec<Vec<Fr>>) -> ForeignCallResult<FieldElement> {
161 let force_array_indexes: &[usize] = match function {
162 "utilityLoadCapsule" | "loadCapsule" | "getCapsule" => &[1],
163 "utilityAes128Decrypt" | "aes128Decrypt" => &[0],
165 "utilityTryGetPublicKeysAndPartialAddress" | "tryGetPublicKeysAndPartialAddress" => {
167 &[1]
168 }
169 "privateLoadFromExecutionCache" | "loadFromExecutionCache" => &[0],
171 _ => &[],
172 };
173 let values: Vec<ForeignCallParam<FieldElement>> = result
174 .into_iter()
175 .enumerate()
176 .map(|(index, frs)| {
177 if frs.len() == 1 && !force_array_indexes.contains(&index) {
178 ForeignCallParam::Single(fr_to_fe(&frs[0]))
179 } else {
180 ForeignCallParam::Array(frs.iter().map(fr_to_fe).collect())
181 }
182 })
183 .collect();
184 ForeignCallResult { values }
185 }
186
187 fn resolve_error(
193 err: &OpcodeResolutionError<FieldElement>,
194 error_types: Option<&serde_json::Value>,
195 ) -> String {
196 let payload = match err {
197 OpcodeResolutionError::BrilligFunctionFailed { payload, .. } => payload.as_ref(),
198 OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => payload.as_ref(),
199 _ => None,
200 };
201
202 if let Some(ResolvedAssertionPayload::String(msg)) = payload {
203 return msg.clone();
204 }
205
206 if let Some(ResolvedAssertionPayload::Raw(raw)) = payload {
207 let selector_key = raw.selector.as_u64().to_string();
208 if let Some(et) = error_types {
209 if let Some(entry) = et.get(&selector_key) {
210 if entry.get("error_kind").and_then(|v| v.as_str()) == Some("string") {
211 if let Some(msg) = entry.get("string").and_then(|v| v.as_str()) {
212 return msg.to_owned();
213 }
214 }
215 if entry.get("error_kind").and_then(|v| v.as_str()) == Some("fmtstring") {
216 if let Some(tmpl) = entry.get("string").and_then(|v| v.as_str()) {
217 return tmpl.to_owned();
218 }
219 }
220 }
221 }
222 }
223
224 err.to_string()
225 }
226
227 pub async fn execute_private(
232 artifact: &ContractArtifact,
233 function_name: &str,
234 initial_witness_fields: &[Fr],
235 oracle: &mut dyn OracleCallback,
236 ) -> Result<AcvmExecutionOutput, Error> {
237 let function = artifact.find_function(function_name)?;
238
239 let bytecode_b64 = function.bytecode.as_ref().ok_or_else(|| {
240 Error::InvalidData(format!(
241 "function '{}' in '{}' has no bytecode",
242 function_name, artifact.name
243 ))
244 })?;
245
246 let program = Self::decode_program(bytecode_b64)?;
247 let initial_witness = Self::build_initial_witness(initial_witness_fields);
248
249 let main_circuit = program
250 .functions
251 .first()
252 .ok_or_else(|| Error::InvalidData("program has no circuits".to_string()))?;
253
254 let backend = Bn254BlackBoxSolver;
255 let empty_assertions = [];
256 let mut acvm = ACVM::new(
257 &backend,
258 &main_circuit.opcodes,
259 initial_witness,
260 &program.unconstrained_functions,
261 &empty_assertions,
262 );
263
264 let mut last_private_fc = String::new();
266 let mut first_acir_call_return_values: Vec<Fr> = Vec::new();
267 loop {
268 let status = acvm.solve();
269 match status {
270 ACVMStatus::Solved => break,
271 ACVMStatus::InProgress => continue,
272 ACVMStatus::RequiresForeignCall(foreign_call) => {
273 last_private_fc = foreign_call.function.clone();
274 let inputs = Self::convert_fc_inputs(&foreign_call.inputs);
275 tracing::trace!(
276 function = function_name,
277 oracle = foreign_call.function.as_str(),
278 "private oracle call"
279 );
280 let result = oracle
281 .handle_foreign_call(&foreign_call.function, inputs)
282 .await?;
283 acvm.resolve_pending_foreign_call(Self::convert_fc_result(
284 &foreign_call.function,
285 result,
286 ));
287 }
288 ACVMStatus::RequiresAcirCall(acir_call) => {
289 let called_circuit_idx = acir_call.id.0 as usize;
290 if called_circuit_idx >= program.functions.len() {
291 return Err(Error::InvalidData(format!(
292 "ACIR call references circuit {} but program only has {}",
293 called_circuit_idx,
294 program.functions.len()
295 )));
296 }
297 let called_circuit = &program.functions[called_circuit_idx];
298 let sub_witness = acir_call.initial_witness;
299 let mut sub_acvm = ACVM::new(
300 &backend,
301 &called_circuit.opcodes,
302 sub_witness,
303 &program.unconstrained_functions,
304 &empty_assertions,
305 );
306 loop {
307 let sub_status = sub_acvm.solve();
308 match sub_status {
309 ACVMStatus::Solved => break,
310 ACVMStatus::InProgress => continue,
311 ACVMStatus::RequiresForeignCall(fc) => {
312 let inputs = Self::convert_fc_inputs(&fc.inputs);
313 let result =
314 oracle.handle_foreign_call(&fc.function, inputs).await?;
315 sub_acvm.resolve_pending_foreign_call(Self::convert_fc_result(
316 &fc.function,
317 result,
318 ));
319 }
320 ACVMStatus::RequiresAcirCall(_) => {
321 return Err(Error::InvalidData(
322 "nested ACIR calls deeper than 2 levels not supported".into(),
323 ));
324 }
325 ACVMStatus::Failure(ref err) => {
326 let resolved =
327 Self::resolve_error(err, function.error_types.as_ref());
328 return Err(Error::InvalidData(format!(
329 "sub-circuit execution failed: {resolved}"
330 )));
331 }
332 }
333 }
334 let sub_witness_map = sub_acvm.finalize();
335 let return_values: Vec<FieldElement> = called_circuit
336 .return_values
337 .0
338 .iter()
339 .filter_map(|w| sub_witness_map.get(w).copied())
340 .collect();
341 if first_acir_call_return_values.is_empty() {
344 first_acir_call_return_values =
345 return_values.iter().map(fe_to_fr).collect();
346 }
347 acvm.resolve_pending_acir_call(return_values);
348 }
349 ACVMStatus::Failure(ref err) => {
350 let resolved = Self::resolve_error(err, function.error_types.as_ref());
351 let resolved = Self::fallback_private_error_message(
352 &last_private_fc,
353 &resolved,
354 function.error_types.as_ref(),
355 )
356 .unwrap_or(resolved);
357 return Err(Error::InvalidData(format!(
358 "private function '{}' execution failed (last oracle: {last_private_fc}): {resolved}",
359 function_name
360 )));
361 }
362 }
363 }
364
365 let witness = acvm.finalize();
366 let return_values = witness_map_to_frs(&witness, &main_circuit.return_values.0);
367
368 let acir_bytecode = base64::Engine::decode(
370 &base64::engine::general_purpose::STANDARD,
371 function.bytecode.as_deref().unwrap_or(""),
372 )
373 .unwrap_or_default();
374
375 Ok(AcvmExecutionOutput {
376 return_values,
377 witness,
378 acir_bytecode,
379 first_acir_call_return_values,
380 })
381 }
382
383 pub async fn execute_utility(
385 artifact: &ContractArtifact,
386 function_name: &str,
387 args: &[Fr],
388 oracle: &mut dyn OracleCallback,
389 ) -> Result<UtilityResult, Error> {
390 let function = artifact.find_function(function_name)?;
391
392 let bytecode_b64 = function.bytecode.as_ref().ok_or_else(|| {
393 Error::InvalidData(format!(
394 "function '{}' in '{}' has no bytecode",
395 function_name, artifact.name
396 ))
397 })?;
398
399 let program = Self::decode_program(bytecode_b64)?;
400 let initial_witness = Self::build_initial_witness(args);
401
402 let main_circuit = program
403 .functions
404 .first()
405 .ok_or_else(|| Error::InvalidData("program has no circuits".to_string()))?;
406
407 let backend = Bn254BlackBoxSolver;
408 let empty_assertions = [];
409 let mut acvm = ACVM::new(
410 &backend,
411 &main_circuit.opcodes,
412 initial_witness,
413 &program.unconstrained_functions,
414 &empty_assertions,
415 );
416
417 let mut last_fc = String::new();
418 loop {
419 let status = acvm.solve();
420 match status {
421 ACVMStatus::Solved => break,
422 ACVMStatus::InProgress => continue,
423 ACVMStatus::RequiresForeignCall(foreign_call) => {
424 last_fc = foreign_call.function.clone();
425 tracing::trace!(
426 function = function_name,
427 oracle = foreign_call.function.as_str(),
428 "utility oracle call"
429 );
430 let inputs = Self::convert_fc_inputs(&foreign_call.inputs);
431 let result = oracle
432 .handle_foreign_call(&foreign_call.function, inputs)
433 .await?;
434 acvm.resolve_pending_foreign_call(Self::convert_fc_result(
435 &foreign_call.function,
436 result,
437 ));
438 }
439 ACVMStatus::RequiresAcirCall(_) => {
440 return Err(Error::InvalidData(
441 "utility functions should not make ACIR calls".into(),
442 ));
443 }
444 ACVMStatus::Failure(ref err) => {
445 let resolved = Self::resolve_error(err, function.error_types.as_ref());
446 return Err(Error::InvalidData(format!(
447 "utility function '{}' execution failed (last oracle: {last_fc}): {resolved}",
448 function_name
449 )));
450 }
451 }
452 }
453
454 let witness = acvm.finalize();
455 let return_values = witness_map_to_frs(&witness, &main_circuit.return_values.0);
456
457 Ok(UtilityResult { return_values })
458 }
459}