diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index b5d6348d34f..cb846bdaffa 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -216,25 +216,33 @@ impl std::fmt::Display for Circuit { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "current witness index : {}", self.current_witness_index)?; - let write_public_inputs = |f: &mut std::fmt::Formatter<'_>, - public_inputs: &PublicInputs| - -> Result<(), std::fmt::Error> { - write!(f, "[")?; - let public_input_indices = public_inputs.indices(); - for (index, public_input) in public_input_indices.iter().enumerate() { - write!(f, "{public_input}")?; - if index != public_input_indices.len() - 1 { - write!(f, ", ")?; + let write_witness_indices = + |f: &mut std::fmt::Formatter<'_>, indices: &[u32]| -> Result<(), std::fmt::Error> { + write!(f, "[")?; + for (index, witness_index) in indices.iter().enumerate() { + write!(f, "{witness_index}")?; + if index != indices.len() - 1 { + write!(f, ", ")?; + } } - } - writeln!(f, "]") - }; + writeln!(f, "]") + }; + + write!(f, "private parameters indices : ")?; + write_witness_indices( + f, + &self + .private_parameters + .iter() + .map(|witness| witness.witness_index()) + .collect::>(), + )?; write!(f, "public parameters indices : ")?; - write_public_inputs(f, &self.public_parameters)?; + write_witness_indices(f, &self.public_parameters.indices())?; write!(f, "return value indices : ")?; - write_public_inputs(f, &self.return_values)?; + write_witness_indices(f, &self.return_values.indices())?; for opcode in &self.opcodes { writeln!(f, "{opcode}")?; diff --git a/acvm-repo/acir/src/native_types/witness_stack.rs b/acvm-repo/acir/src/native_types/witness_stack.rs index a9e8f219b3e..7c79e3db431 100644 --- a/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/acvm-repo/acir/src/native_types/witness_stack.rs @@ -21,7 +21,7 @@ pub struct WitnessStackError(#[from] SerializationError); /// An ordered set of witness maps for separate circuits #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] pub struct WitnessStack { - pub stack: Vec, + stack: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] @@ -37,6 +37,10 @@ impl WitnessStack { self.stack.push(StackItem { index, witness }); } + pub fn pop(&mut self) -> Option { + self.stack.pop() + } + pub fn peek(&self) -> Option<&StackItem> { self.stack.last() } diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index 8b04292dfaa..a5b683c15e1 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -362,3 +362,96 @@ fn memory_op_circuit() { assert_eq!(bytes, expected_serialization) } + +#[test] +fn nested_acir_call_circuit() { + // Circuit for the following program: + // fn main(x: Field, y: pub Field) { + // let z = nested_call(x, y); + // let z2 = nested_call(x, y); + // assert(z == z2); + // } + // #[fold] + // fn nested_call(x: Field, y: Field) -> Field { + // inner_call(x + 2, y) + // } + // #[fold] + // fn inner_call(x: Field, y: Field) -> Field { + // assert(x == y); + // x + // } + let nested_call = + Opcode::Call { id: 1, inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(2)] }; + let nested_call_two = + Opcode::Call { id: 1, inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(3)] }; + + let assert_nested_call_results = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(2)), + (-FieldElement::one(), Witness(3)), + ], + q_c: FieldElement::zero(), + }); + + let main = Circuit { + current_witness_index: 3, + private_parameters: BTreeSet::from([Witness(0)]), + public_parameters: PublicInputs([Witness(1)].into()), + opcodes: vec![nested_call, nested_call_two, assert_nested_call_results], + ..Circuit::default() + }; + + let call_parameter_addition = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(0)), + (-FieldElement::one(), Witness(2)), + ], + q_c: FieldElement::one() + FieldElement::one(), + }); + let call = + Opcode::Call { id: 2, inputs: vec![Witness(2), Witness(1)], outputs: vec![Witness(3)] }; + + let nested_call = Circuit { + current_witness_index: 3, + private_parameters: BTreeSet::from([Witness(0), Witness(1)]), + return_values: PublicInputs([Witness(3)].into()), + opcodes: vec![call_parameter_addition, call], + ..Circuit::default() + }; + + let assert_param_equality = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(0)), + (-FieldElement::one(), Witness(1)), + ], + q_c: FieldElement::zero(), + }); + + let inner_call = Circuit { + current_witness_index: 1, + private_parameters: BTreeSet::from([Witness(0), Witness(1)]), + return_values: PublicInputs([Witness(0)].into()), + opcodes: vec![assert_param_equality], + ..Circuit::default() + }; + + let program = Program { functions: vec![main, nested_call, inner_call] }; + + let bytes = Program::serialize_program(&program); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, + 24, 109, 227, 191, 93, 101, 50, 123, 255, 35, 172, 99, 25, 83, 17, 250, 99, 14, 250, 224, + 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 57, 108, 14, 91, 248, 202, 168, 65, + 255, 207, 122, 28, 180, 250, 244, 221, 244, 197, 223, 68, 182, 154, 197, 184, 134, 80, 54, + 95, 136, 233, 142, 62, 101, 137, 24, 98, 94, 133, 132, 162, 196, 135, 23, 230, 34, 65, 182, + 148, 211, 134, 137, 2, 23, 218, 99, 226, 93, 135, 185, 121, 123, 33, 84, 12, 234, 218, 192, + 64, 174, 3, 248, 47, 88, 48, 17, 150, 157, 183, 151, 95, 244, 86, 91, 221, 61, 10, 81, 31, + 178, 190, 110, 194, 102, 96, 76, 251, 202, 80, 13, 204, 77, 224, 25, 176, 70, 79, 197, 128, + 18, 64, 3, 4, 0, 0, + ]; + assert_eq!(bytes, expected_serialization); +} diff --git a/acvm-repo/acvm_js/src/compression.rs b/acvm-repo/acvm_js/src/compression.rs index 18e9216297e..8114e0d57d2 100644 --- a/acvm-repo/acvm_js/src/compression.rs +++ b/acvm-repo/acvm_js/src/compression.rs @@ -2,12 +2,12 @@ use acvm::acir::native_types::{WitnessMap, WitnessStack}; use js_sys::JsString; use wasm_bindgen::prelude::wasm_bindgen; -use crate::JsWitnessMap; +use crate::{JsWitnessMap, JsWitnessStack}; /// Compresses a `WitnessMap` into the binary format outputted by Nargo. /// -/// @param {Uint8Array} compressed_witness - A witness map. -/// @returns {WitnessMap} A compressed witness map +/// @param {WitnessMap} witness_map - A witness map. +/// @returns {Uint8Array} A compressed witness map #[wasm_bindgen(js_name = compressWitness, skip_jsdoc)] pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> { console_error_panic_hook::set_once(); @@ -21,6 +21,7 @@ pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> } /// Decompresses a compressed witness as outputted by Nargo into a `WitnessMap`. +/// This should be used to only fetch the witness map for the main function. /// /// @param {Uint8Array} compressed_witness - A compressed witness. /// @returns {WitnessMap} The decompressed witness map. @@ -28,8 +29,39 @@ pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> pub fn decompress_witness(compressed_witness: Vec) -> Result { console_error_panic_hook::set_once(); + let mut witness_stack = + WitnessStack::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; + + let witness = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness.into()) +} + +/// Compresses a `WitnessStack` into the binary format outputted by Nargo. +/// +/// @param {WitnessStack} witness_stack - A witness stack. +/// @returns {Uint8Array} A compressed witness stack +#[wasm_bindgen(js_name = compressWitnessStack, skip_jsdoc)] +pub fn compress_witness_stack(witness_stack: JsWitnessStack) -> Result, JsString> { + console_error_panic_hook::set_once(); + + let witness_stack = WitnessStack::from(witness_stack); + let compressed_witness_stack: Vec = + Vec::::try_from(witness_stack).map_err(|err| err.to_string())?; + + Ok(compressed_witness_stack) +} + +/// Decompresses a compressed witness stack as outputted by Nargo into a `WitnessStack`. +/// +/// @param {Uint8Array} compressed_witness - A compressed witness. +/// @returns {WitnessStack} The decompressed witness stack. +#[wasm_bindgen(js_name = decompressWitnessStack, skip_jsdoc)] +pub fn decompress_witness_stack(compressed_witness: Vec) -> Result { + console_error_panic_hook::set_once(); + let witness_stack = WitnessStack::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; - Ok(witness_stack.stack[0].witness.clone().into()) + Ok(witness_stack.into()) } diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 4c23acea5db..0e58ccf039c 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -1,5 +1,9 @@ +use std::{future::Future, pin::Pin}; + +use acvm::BlackBoxFunctionSolver; use acvm::{ - acir::circuit::Program, + acir::circuit::{Circuit, Program}, + acir::native_types::{WitnessMap, WitnessStack}, pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}, }; use bn254_blackbox_solver::Bn254BlackBoxSolver; @@ -9,7 +13,7 @@ use wasm_bindgen::prelude::wasm_bindgen; use crate::{ foreign_call::{resolve_brillig, ForeignCallHandler}, - JsExecutionError, JsWitnessMap, + JsExecutionError, JsWitnessMap, JsWitnessStack, }; #[wasm_bindgen] @@ -42,8 +46,16 @@ pub async fn execute_circuit( let solver = WasmBlackBoxFunctionSolver::initialize().await; - execute_circuit_with_black_box_solver(&solver, program, initial_witness, foreign_call_handler) - .await + let mut witness_stack = execute_program_with_native_type_return( + &solver, + program, + initial_witness, + &foreign_call_handler, + ) + .await?; + let witness_map = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness_map.into()) } /// Executes an ACIR circuit to generate the solved witness from the initial witness. @@ -56,69 +68,175 @@ pub async fn execute_circuit( #[wasm_bindgen(js_name = executeCircuitWithBlackBoxSolver, skip_jsdoc)] pub async fn execute_circuit_with_black_box_solver( solver: &WasmBlackBoxFunctionSolver, - // TODO(https://github.com/noir-lang/noir/issues/4428): These need to be updated to match the same interfaces - // as the native ACVM executor. Right now native execution still only handles one circuit so I do not feel the need - // to break the JS interface just yet. program: Vec, initial_witness: JsWitnessMap, foreign_call_handler: ForeignCallHandler, ) -> Result { console_error_panic_hook::set_once(); + + let mut witness_stack = execute_program_with_native_type_return( + solver, + program, + initial_witness, + &foreign_call_handler, + ) + .await?; + let witness_map = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness_map.into()) +} + +#[wasm_bindgen(js_name = executeProgram, skip_jsdoc)] +pub async fn execute_program( + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_handler: ForeignCallHandler, +) -> Result { + console_error_panic_hook::set_once(); + + let solver = WasmBlackBoxFunctionSolver::initialize().await; + + execute_program_with_black_box_solver(&solver, program, initial_witness, &foreign_call_handler) + .await +} + +#[wasm_bindgen(js_name = executeProgramWithBlackBoxSolver, skip_jsdoc)] +pub async fn execute_program_with_black_box_solver( + solver: &WasmBlackBoxFunctionSolver, + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_executor: &ForeignCallHandler, +) -> Result { + let witness_stack = execute_program_with_native_type_return( + solver, + program, + initial_witness, + foreign_call_executor, + ) + .await?; + + Ok(witness_stack.into()) +} + +async fn execute_program_with_native_type_return( + solver: &WasmBlackBoxFunctionSolver, + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_executor: &ForeignCallHandler, +) -> Result { let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; - let circuit = match program.functions.len() { - 0 => return Ok(initial_witness), - 1 => &program.functions[0], - _ => return Err(JsExecutionError::new("Program contains multiple circuits however ACVM currently only supports programs containing a single circuit".to_string(), None).into()) - }; - - let mut acvm = ACVM::new(&solver.0, &circuit.opcodes, initial_witness.into()); - - loop { - let solver_status = acvm.solve(); - - match solver_status { - ACVMStatus::Solved => break, - ACVMStatus::InProgress => { - unreachable!("Execution should not stop while in `InProgress` state.") - } - ACVMStatus::Failure(error) => { - let (assert_message, call_stack) = match &error { - OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: ErrorLocation::Resolved(opcode_location), - } - | OpcodeResolutionError::IndexOutOfBounds { - opcode_location: ErrorLocation::Resolved(opcode_location), - .. - } => { - (circuit.get_assert_message(*opcode_location), Some(vec![*opcode_location])) + .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + + let executor = ProgramExecutor::new(&program.functions, &solver.0, foreign_call_executor); + let witness_stack = executor.execute(initial_witness.into()).await?; + + Ok(witness_stack) +} + +struct ProgramExecutor<'a, B: BlackBoxFunctionSolver> { + functions: &'a [Circuit], + + blackbox_solver: &'a B, + + foreign_call_handler: &'a ForeignCallHandler, +} + +impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { + fn new( + functions: &'a [Circuit], + blackbox_solver: &'a B, + foreign_call_handler: &'a ForeignCallHandler, + ) -> Self { + ProgramExecutor { functions, blackbox_solver, foreign_call_handler } + } + + async fn execute(&self, initial_witness: WitnessMap) -> Result { + let main = &self.functions[0]; + + let mut witness_stack = WitnessStack::default(); + let main_witness = self.execute_circuit(main, initial_witness, &mut witness_stack).await?; + witness_stack.push(0, main_witness); + Ok(witness_stack) + } + + fn execute_circuit( + &'a self, + circuit: &'a Circuit, + initial_witness: WitnessMap, + witness_stack: &'a mut WitnessStack, + ) -> Pin> + 'a>> { + Box::pin(async { + let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); + + loop { + let solver_status = acvm.solve(); + + match solver_status { + ACVMStatus::Solved => break, + ACVMStatus::InProgress => { + unreachable!("Execution should not stop while in `InProgress` state.") } - OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { - let failing_opcode = - call_stack.last().expect("Brillig error call stacks cannot be empty"); - (circuit.get_assert_message(*failing_opcode), Some(call_stack.clone())) + ACVMStatus::Failure(error) => { + let (assert_message, call_stack) = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(opcode_location), + } + | OpcodeResolutionError::IndexOutOfBounds { + opcode_location: ErrorLocation::Resolved(opcode_location), + .. + } => ( + circuit.get_assert_message(*opcode_location), + Some(vec![*opcode_location]), + ), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + let failing_opcode = call_stack + .last() + .expect("Brillig error call stacks cannot be empty"); + ( + circuit.get_assert_message(*failing_opcode), + Some(call_stack.clone()), + ) + } + _ => (None, None), + }; + + let error_string = match &assert_message { + Some(assert_message) => format!("Assertion failed: {}", assert_message), + None => error.to_string(), + }; + + return Err(JsExecutionError::new(error_string, call_stack).into()); } - _ => (None, None), - }; - - let error_string = match &assert_message { - Some(assert_message) => format!("Assertion failed: {}", assert_message), - None => error.to_string(), - }; + ACVMStatus::RequiresForeignCall(foreign_call) => { + let result = + resolve_brillig(self.foreign_call_handler, &foreign_call).await?; - return Err(JsExecutionError::new(error_string, call_stack).into()); + acvm.resolve_pending_foreign_call(result); + } + ACVMStatus::RequiresAcirCall(call_info) => { + let acir_to_call = &self.functions[call_info.id as usize]; + let initial_witness = call_info.initial_witness; + let call_solved_witness = self + .execute_circuit(acir_to_call, initial_witness, witness_stack) + .await?; + let mut call_resolved_outputs = Vec::new(); + for return_witness_index in acir_to_call.return_values.indices() { + if let Some(return_value) = + call_solved_witness.get_index(return_witness_index) + { + call_resolved_outputs.push(*return_value); + } else { + // TODO: look at changing this call stack from None + return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None).into()); + } + } + acvm.resolve_pending_acir_call(call_resolved_outputs); + witness_stack.push(call_info.id, call_solved_witness.clone()); + } + } } - ACVMStatus::RequiresForeignCall(foreign_call) => { - let result = resolve_brillig(&foreign_call_handler, &foreign_call).await?; - acvm.resolve_pending_foreign_call(result); - } - ACVMStatus::RequiresAcirCall(_) => { - todo!("Handle acir calls in acvm JS"); - } - } + Ok(acvm.finalize()) + }) } - - let witness_map = acvm.finalize(); - Ok(witness_map.into()) } diff --git a/acvm-repo/acvm_js/src/js_witness_stack.rs b/acvm-repo/acvm_js/src/js_witness_stack.rs new file mode 100644 index 00000000000..59f2dbc051e --- /dev/null +++ b/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -0,0 +1,71 @@ +use acvm::acir::native_types::WitnessStack; +use js_sys::{Array, Map, Object}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +use crate::JsWitnessMap; + +#[wasm_bindgen(typescript_custom_section)] +const WITNESS_STACK: &'static str = r#" +export type StackItem = { + index: number; + witness: WitnessMap; +} + +export type WitnessStack = Array; +"#; + +// WitnessStack +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Array, js_name = "WitnessStack", typescript_type = "WitnessStack")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsWitnessStack; + + #[wasm_bindgen(constructor, js_class = "Array")] + pub fn new() -> JsWitnessStack; + + #[wasm_bindgen(extends = Object, js_name = "StackItem", typescript_type = "StackItem")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsStackItem; + + #[wasm_bindgen(constructor, js_class = "Object")] + pub fn new() -> JsStackItem; +} + +impl Default for JsWitnessStack { + fn default() -> Self { + Self::new() + } +} + +impl From for JsWitnessStack { + fn from(mut witness_stack: WitnessStack) -> Self { + let js_witness_stack = JsWitnessStack::new(); + while let Some(stack_item) = witness_stack.pop() { + let js_map = JsWitnessMap::from(stack_item.witness); + let js_index = JsValue::from_f64(stack_item.index.into()); + + let entry_map = Map::new(); + entry_map.set(&JsValue::from_str("index"), &js_index); + entry_map.set(&JsValue::from_str("witness"), &js_map); + let stack_item = Object::from_entries(&entry_map).unwrap(); + + js_witness_stack.push(&stack_item); + } + // `reverse()` returns an `Array` so we have to wrap it + JsWitnessStack { obj: js_witness_stack.reverse() } + } +} + +impl From for WitnessStack { + fn from(js_witness_stack: JsWitnessStack) -> Self { + let mut witness_stack = WitnessStack::default(); + js_witness_stack.for_each(&mut |stack_item, _, _| { + let values_array = Object::values(&Object::from(stack_item)); + let index = values_array.get(0).as_f64().unwrap() as u32; + let js_witness_map: JsWitnessMap = values_array.get(1).into(); + witness_stack.push(index, js_witness_map.into()); + }); + witness_stack + } +} diff --git a/acvm-repo/acvm_js/src/lib.rs b/acvm-repo/acvm_js/src/lib.rs index 8ded77a4e22..d7ecc0ae192 100644 --- a/acvm-repo/acvm_js/src/lib.rs +++ b/acvm-repo/acvm_js/src/lib.rs @@ -9,6 +9,7 @@ mod execute; mod foreign_call; mod js_execution_error; mod js_witness_map; +mod js_witness_stack; mod logging; mod public_witness; @@ -16,11 +17,15 @@ pub use black_box_solvers::{ and, blake2s256, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, keccak256, sha256, xor, }; pub use build_info::build_info; -pub use compression::{compress_witness, decompress_witness}; +pub use compression::{ + compress_witness, compress_witness_stack, decompress_witness, decompress_witness_stack, +}; pub use execute::{ create_black_box_solver, execute_circuit, execute_circuit_with_black_box_solver, + execute_program, execute_program_with_black_box_solver, }; pub use js_execution_error::JsExecutionError; pub use js_witness_map::JsWitnessMap; +pub use js_witness_stack::JsWitnessStack; pub use logging::init_log_level; pub use public_witness::{get_public_parameters_witness, get_public_witness, get_return_witness}; diff --git a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts index adee3c15312..32487f8bbba 100644 --- a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -6,6 +6,9 @@ import { WasmBlackBoxFunctionSolver, WitnessMap, ForeignCallHandler, + executeProgram, + WitnessStack, + StackItem, } from '@noir-lang/acvm_js'; it('successfully executes circuit and extracts return value', async () => { @@ -157,3 +160,37 @@ it('successfully executes 500 circuits with same backend', async function () { expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); } }); + +/** + * Below are all the same tests as above but using `executeProgram` + * TODO: also add a couple tests for executing multiple circuits + */ +it('executeProgram: successfully executes program and extracts return value', async () => { + const { bytecode, initialWitnessMap, resultWitness, expectedResult } = await import('../shared/addition'); + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + const solvedStackItem: StackItem = witnessStack[0]; + expect(solvedStackItem.index).to.be.eq(0); + const solvedWitnessMap: WitnessMap = solvedStackItem.witness; + + // Witness stack should be consistent with initial witness + initialWitnessMap.forEach((value, key) => { + expect(solvedWitnessMap.get(key) as string).to.be.eq(value); + }); + + // Solved witness should contain expected return value + expect(solvedWitnessMap.get(resultWitness)).to.be.eq(expectedResult); +}); + +it('executeProgram: successfully process a program of acir functions with a nested call', async () => { + const { bytecode, initialWitnessMap, expectedWitnessStack } = await import('../shared/nested_acir_call'); + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + expect(witnessStack).to.be.deep.eq(expectedWitnessStack); +}); diff --git a/acvm-repo/acvm_js/test/node/witness_conversion.test.ts b/acvm-repo/acvm_js/test/node/witness_conversion.test.ts index 41291c894ea..c6dccb4c83d 100644 --- a/acvm-repo/acvm_js/test/node/witness_conversion.test.ts +++ b/acvm-repo/acvm_js/test/node/witness_conversion.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { compressWitness, decompressWitness } from '@noir-lang/acvm_js'; +import { compressWitness, decompressWitness, compressWitnessStack, decompressWitnessStack } from '@noir-lang/acvm_js'; import { expectedCompressedWitnessMap, expectedWitnessMap } from '../shared/witness_compression'; +import { expectedCompressedWitnessStack, expectedWitnessStack } from '../shared/nested_acir_call'; it('successfully compresses the witness', () => { const compressedWitnessMap = compressWitness(expectedWitnessMap); @@ -13,3 +14,15 @@ it('successfully decompresses the witness', () => { expect(witnessMap).to.be.deep.eq(expectedWitnessMap); }); + +it('successfully compresses the witness stack', () => { + const compressedWitnessStack = compressWitnessStack(expectedWitnessStack); + + expect(compressedWitnessStack).to.be.deep.eq(expectedCompressedWitnessStack); +}); + +it('successfully decompresses the witness stack', () => { + const witnessStack = decompressWitnessStack(expectedCompressedWitnessStack); + + expect(witnessStack).to.be.deep.eq(expectedWitnessStack); +}); diff --git a/acvm-repo/acvm_js/test/shared/nested_acir_call.ts b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts new file mode 100644 index 00000000000..ce91282a681 --- /dev/null +++ b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts @@ -0,0 +1,59 @@ +import { WitnessMap, StackItem, WitnessStack } from '@noir-lang/acvm_js'; + +// See `nested_acir_call_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, 24, 109, 227, 191, 93, 101, + 50, 123, 255, 35, 172, 99, 25, 83, 17, 250, 99, 14, 250, 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 57, + 108, 14, 91, 248, 202, 168, 65, 255, 207, 122, 28, 180, 250, 244, 221, 244, 197, 223, 68, 182, 154, 197, 184, 134, 80, + 54, 95, 136, 233, 142, 62, 101, 137, 24, 98, 94, 133, 132, 162, 196, 135, 23, 230, 34, 65, 182, 148, 211, 134, 137, 2, + 23, 218, 99, 226, 93, 135, 185, 121, 123, 33, 84, 12, 234, 218, 192, 64, 174, 3, 248, 47, 88, 48, 17, 150, 157, 183, + 151, 95, 244, 86, 91, 221, 61, 10, 81, 31, 178, 190, 110, 194, 102, 96, 76, 251, 202, 80, 13, 204, 77, 224, 25, 176, + 70, 79, 197, 128, 18, 64, 3, 4, 0, 0, +]); + +export const initialWitnessMap: WitnessMap = new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], +]); + +const inner_call_witness: StackItem = { + index: 2, + witness: new Map([ + [0, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +const nested_call_witness: StackItem = { + index: 1, + witness: new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [2, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [3, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +const main_witness: StackItem = { + index: 0, + witness: new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [2, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [3, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +export const expectedWitnessStack: WitnessStack = [ + inner_call_witness, + nested_call_witness, + inner_call_witness, + nested_call_witness, + main_witness, +]; + +export const expectedCompressedWitnessStack = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 2, 255, 237, 145, 177, 13, 0, 32, 8, 4, 17, 117, 31, 75, 75, 87, 113, 255, 37, 44, 196, 5, + 228, 42, 194, 39, 132, 238, 114, 249, 239, 114, 163, 118, 47, 203, 254, 240, 101, 23, 152, 213, 120, 199, 73, 58, 42, + 200, 170, 176, 87, 238, 27, 119, 95, 201, 238, 190, 89, 7, 37, 195, 196, 176, 4, 5, 0, 0, +]);