From 8452741fd755a6780d401d1f71dd14023813d1b0 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 13:26:51 +0000 Subject: [PATCH 01/17] working acvm js execute program --- .vscode/settings.json | 5 +- Cargo.lock | 1 + .../acir/src/native_types/witness_stack.rs | 6 +- acvm-repo/acvm_js/Cargo.toml | 1 + acvm-repo/acvm_js/src/compression.rs | 6 +- acvm-repo/acvm_js/src/execute.rs | 232 +++++++++++++----- acvm-repo/acvm_js/src/js_witness_stack.rs | 75 ++++++ acvm-repo/acvm_js/src/lib.rs | 2 + .../acvm_js/test/node/execute_circuit.test.ts | 98 ++++++++ 9 files changed, 364 insertions(+), 62 deletions(-) create mode 100644 acvm-repo/acvm_js/src/js_witness_stack.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 171d36f4e04..05e3cb054fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,5 +18,8 @@ }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "rust-analyzer.linkedProjects": [ + "acvm-repo/acvm_js/Cargo.toml", + ] } diff --git a/Cargo.lock b/Cargo.lock index 2f85b26f974..d1d078c4d14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,7 @@ dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", "const-str", + "futures 0.3.30", "gloo-utils", "js-sys", "pkg-config", 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/acvm_js/Cargo.toml b/acvm-repo/acvm_js/Cargo.toml index 65c072b1d96..636b1eb0af4 100644 --- a/acvm-repo/acvm_js/Cargo.toml +++ b/acvm-repo/acvm_js/Cargo.toml @@ -17,6 +17,7 @@ crate-type = ["cdylib"] [dependencies] cfg-if = "1.0.0" +futures = "0.3.30" [target.'cfg(target_arch = "wasm32")'.dependencies] acvm.workspace = true diff --git a/acvm-repo/acvm_js/src/compression.rs b/acvm-repo/acvm_js/src/compression.rs index 18e9216297e..cc46a2faea1 100644 --- a/acvm-repo/acvm_js/src/compression.rs +++ b/acvm-repo/acvm_js/src/compression.rs @@ -28,8 +28,10 @@ pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> pub fn decompress_witness(compressed_witness: Vec) -> Result { console_error_panic_hook::set_once(); - let witness_stack = + let mut witness_stack = WitnessStack::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; - Ok(witness_stack.stack[0].witness.clone().into()) + let witness = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness.into()) } diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 60d27a489e2..b1c42066c0e 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -1,5 +1,7 @@ +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,9 +11,10 @@ use wasm_bindgen::prelude::wasm_bindgen; use crate::{ foreign_call::{resolve_brillig, ForeignCallHandler}, - JsExecutionError, JsWitnessMap, + JsExecutionError, JsWitnessMap, JsWitnessStack, }; - +use core::pin::Pin; +use futures::future::Future; #[wasm_bindgen] pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); @@ -42,8 +45,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 +67,174 @@ 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 main = &program.functions[0]; + + let executor = ProgramExecutor::new(&program.functions, &solver.0, &foreign_call_executor); + + let mut witness_stack = WitnessStack::default(); + let main_witness = + executor.execute_circuit(main, initial_witness.into(), &mut witness_stack).await?; + witness_stack.push(0, main_witness); + + 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 } + } + + 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); + + // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. + let mut assert_message: Option = None; + 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.into(), 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.into(), 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..12665255752 --- /dev/null +++ b/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -0,0 +1,75 @@ +use acvm::{acir::native_types::WitnessStack, FieldElement}; +use js_sys::{Array, JsString, Map, Object}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +use crate::JsWitnessMap; + +// witness_map: Map; + +#[wasm_bindgen(typescript_custom_section)] +const WITNESS_MAP: &'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(index: JsValue, witness: JsWitnessMap) -> 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 witness_entry_name = JsString::from("witness"); + let index_entry_name = JsString::from("index"); + + 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); + } + js_witness_stack + } +} + +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 88afd1767c9..551e0cf61e5 100644 --- a/acvm-repo/acvm_js/src/lib.rs +++ b/acvm-repo/acvm_js/src/lib.rs @@ -14,6 +14,7 @@ cfg_if::cfg_if! { mod execute; mod foreign_call; mod js_witness_map; + mod js_witness_stack; mod logging; mod public_witness; mod js_execution_error; @@ -24,6 +25,7 @@ cfg_if::cfg_if! { pub use compression::{compress_witness, decompress_witness}; pub use execute::{execute_circuit, execute_circuit_with_black_box_solver, create_black_box_solver}; 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}; pub use js_execution_error::JsExecutionError; 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..9f08277c4eb 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,98 @@ 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'); + }); + console.log('witness stack: ', witnessStack); + const solvedStackItem: StackItem = witnessStack[0]; + console.log('solvedStackItem: ', solvedStackItem); + + expect(solvedStackItem.index).to.be.eq(0); + const solvedWitnessMap: WitnessMap = solvedStackItem.witness; + console.log('solvedWitnessMap: ', solvedWitnessMap); + + // 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 processes simple brillig foreign call opcodes', async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap, oracleResponse, oracleCallName, oracleCallInputs } = + await import('../shared/foreign_call'); + + let observedName = ''; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async (name: string, inputs: string[][]) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, foreignCallHandler); + + console.log('witness stack: ', witnessStack); + const solvedStackItem: StackItem = witnessStack[0]; + console.log('solvedStackItem: ', solvedStackItem); + + expect(solvedStackItem.index).to.be.eq(0); + const solvedWitnessMap: WitnessMap = solvedStackItem.witness; + console.log('solvedWitnessMap: ', solvedWitnessMap); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // assert-zero opcode. Nevertheless, check that returned value was inserted correctly. + expect(solvedWitnessMap).to.be.deep.eq(expectedWitnessMap); +}); + +it('executeProgram: successfully processes complex brillig foreign call opcodes', async () => { + const { bytecode, initialWitnessMap, expectedWitnessMap, oracleResponse, oracleCallName, oracleCallInputs } = + await import('../shared/complex_foreign_call'); + + let observedName = ''; + let observedInputs: string[][] = []; + const foreignCallHandler: ForeignCallHandler = async (name: string, inputs: string[][]) => { + // Throwing inside the oracle callback causes a timeout so we log the observed values + // and defer the check against expected values until after the execution is complete. + observedName = name; + observedInputs = inputs; + + return oracleResponse; + }; + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, foreignCallHandler); + + console.log('witness stack: ', witnessStack); + const solvedStackItem: StackItem = witnessStack[0]; + console.log('solvedStackItem: ', solvedStackItem); + + expect(solvedStackItem.index).to.be.eq(0); + const solvedWitnessMap: WitnessMap = solvedStackItem.witness; + console.log('solvedWitnessMap: ', solvedWitnessMap); + + // Check that expected values were passed to oracle callback. + expect(observedName).to.be.eq(oracleCallName); + expect(observedInputs).to.be.deep.eq(oracleCallInputs); + + // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in + // assert-zero opcode. Nevertheless, check that returned value was inserted correctly. + expect(solvedWitnessMap).to.be.deep.eq(expectedWitnessMap); +}); From 634951af84c5f9fe21c86dceab8eb16176c859b6 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:44:09 +0000 Subject: [PATCH 02/17] tests for nested acir calls and witness compression --- acvm-repo/acir/src/circuit/mod.rs | 36 ++++--- .../acir/tests/test_program_serialization.rs | 93 +++++++++++++++++++ acvm-repo/acvm_js/src/compression.rs | 36 ++++++- acvm-repo/acvm_js/src/js_witness_stack.rs | 10 +- .../acvm_js/test/node/execute_circuit.test.ts | 75 ++------------- .../test/node/witness_conversion.test.ts | 15 ++- .../acvm_js/test/shared/nested_acir_call.ts | 59 ++++++++++++ .../fold_basic_nested_call/Prover.toml | 2 +- .../fold_basic_nested_call/src/main.nr | 2 +- tooling/nargo_cli/src/cli/fs/witness.rs | 8 +- 10 files changed, 240 insertions(+), 96 deletions(-) create mode 100644 acvm-repo/acvm_js/test/shared/nested_acir_call.ts 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/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 cc46a2faea1..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. @@ -35,3 +36,32 @@ pub fn decompress_witness(compressed_witness: Vec) -> Result 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.into()) +} diff --git a/acvm-repo/acvm_js/src/js_witness_stack.rs b/acvm-repo/acvm_js/src/js_witness_stack.rs index 12665255752..7aa38dfc233 100644 --- a/acvm-repo/acvm_js/src/js_witness_stack.rs +++ b/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -1,11 +1,9 @@ -use acvm::{acir::native_types::WitnessStack, FieldElement}; +use acvm::acir::native_types::WitnessStack; use js_sys::{Array, JsString, Map, Object}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use crate::JsWitnessMap; -// witness_map: Map; - #[wasm_bindgen(typescript_custom_section)] const WITNESS_MAP: &'static str = r#" export type StackItem = { @@ -44,9 +42,6 @@ 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 witness_entry_name = JsString::from("witness"); - let index_entry_name = JsString::from("index"); - let js_map = JsWitnessMap::from(stack_item.witness); let js_index = JsValue::from_f64(stack_item.index.into()); @@ -57,7 +52,8 @@ impl From for JsWitnessStack { js_witness_stack.push(&stack_item); } - js_witness_stack + // `reverse()` returns an `Array` so we have to wrap it + JsWitnessStack { obj: js_witness_stack.reverse() } } } 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 9f08277c4eb..32487f8bbba 100644 --- a/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -171,13 +171,10 @@ it('executeProgram: successfully executes program and extracts return value', as const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { throw Error('unexpected oracle'); }); - console.log('witness stack: ', witnessStack); - const solvedStackItem: StackItem = witnessStack[0]; - console.log('solvedStackItem: ', solvedStackItem); + const solvedStackItem: StackItem = witnessStack[0]; expect(solvedStackItem.index).to.be.eq(0); const solvedWitnessMap: WitnessMap = solvedStackItem.witness; - console.log('solvedWitnessMap: ', solvedWitnessMap); // Witness stack should be consistent with initial witness initialWitnessMap.forEach((value, key) => { @@ -188,70 +185,12 @@ it('executeProgram: successfully executes program and extracts return value', as expect(solvedWitnessMap.get(resultWitness)).to.be.eq(expectedResult); }); -it('executeProgram: successfully processes simple brillig foreign call opcodes', async () => { - const { bytecode, initialWitnessMap, expectedWitnessMap, oracleResponse, oracleCallName, oracleCallInputs } = - await import('../shared/foreign_call'); - - let observedName = ''; - let observedInputs: string[][] = []; - const foreignCallHandler: ForeignCallHandler = async (name: string, inputs: string[][]) => { - // Throwing inside the oracle callback causes a timeout so we log the observed values - // and defer the check against expected values until after the execution is complete. - observedName = name; - observedInputs = inputs; - - return oracleResponse; - }; - - const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, foreignCallHandler); - - console.log('witness stack: ', witnessStack); - const solvedStackItem: StackItem = witnessStack[0]; - console.log('solvedStackItem: ', solvedStackItem); - - expect(solvedStackItem.index).to.be.eq(0); - const solvedWitnessMap: WitnessMap = solvedStackItem.witness; - console.log('solvedWitnessMap: ', solvedWitnessMap); - - // Check that expected values were passed to oracle callback. - expect(observedName).to.be.eq(oracleCallName); - expect(observedInputs).to.be.deep.eq(oracleCallInputs); - - // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in - // assert-zero opcode. Nevertheless, check that returned value was inserted correctly. - expect(solvedWitnessMap).to.be.deep.eq(expectedWitnessMap); -}); +it('executeProgram: successfully process a program of acir functions with a nested call', async () => { + const { bytecode, initialWitnessMap, expectedWitnessStack } = await import('../shared/nested_acir_call'); -it('executeProgram: successfully processes complex brillig foreign call opcodes', async () => { - const { bytecode, initialWitnessMap, expectedWitnessMap, oracleResponse, oracleCallName, oracleCallInputs } = - await import('../shared/complex_foreign_call'); - - let observedName = ''; - let observedInputs: string[][] = []; - const foreignCallHandler: ForeignCallHandler = async (name: string, inputs: string[][]) => { - // Throwing inside the oracle callback causes a timeout so we log the observed values - // and defer the check against expected values until after the execution is complete. - observedName = name; - observedInputs = inputs; - - return oracleResponse; - }; - - const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, foreignCallHandler); - - console.log('witness stack: ', witnessStack); - const solvedStackItem: StackItem = witnessStack[0]; - console.log('solvedStackItem: ', solvedStackItem); - - expect(solvedStackItem.index).to.be.eq(0); - const solvedWitnessMap: WitnessMap = solvedStackItem.witness; - console.log('solvedWitnessMap: ', solvedWitnessMap); - - // Check that expected values were passed to oracle callback. - expect(observedName).to.be.eq(oracleCallName); - expect(observedInputs).to.be.deep.eq(oracleCallInputs); + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); - // If incorrect value is written into circuit then execution should halt due to unsatisfied constraint in - // assert-zero opcode. Nevertheless, check that returned value was inserted correctly. - expect(solvedWitnessMap).to.be.deep.eq(expectedWitnessMap); + 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, +]); diff --git a/test_programs/execution_success/fold_basic_nested_call/Prover.toml b/test_programs/execution_success/fold_basic_nested_call/Prover.toml index f28f2f8cc48..5e7127334f7 100644 --- a/test_programs/execution_success/fold_basic_nested_call/Prover.toml +++ b/test_programs/execution_success/fold_basic_nested_call/Prover.toml @@ -1,2 +1,2 @@ -x = "5" +x = "8" y = "10" diff --git a/test_programs/execution_success/fold_basic_nested_call/src/main.nr b/test_programs/execution_success/fold_basic_nested_call/src/main.nr index 6d02b734727..3f57a23b5d4 100644 --- a/test_programs/execution_success/fold_basic_nested_call/src/main.nr +++ b/test_programs/execution_success/fold_basic_nested_call/src/main.nr @@ -11,6 +11,6 @@ fn func_with_nested_foo_call(x: Field, y: Field) -> Field { #[fold] fn foo(x: Field, y: Field) -> Field { - assert(x != y); + assert(x == y); x } diff --git a/tooling/nargo_cli/src/cli/fs/witness.rs b/tooling/nargo_cli/src/cli/fs/witness.rs index 613cdec28da..e36cdf666ea 100644 --- a/tooling/nargo_cli/src/cli/fs/witness.rs +++ b/tooling/nargo_cli/src/cli/fs/witness.rs @@ -15,7 +15,13 @@ pub(crate) fn save_witness_to_dir>( let witness_path = witness_dir.as_ref().join(witness_name).with_extension(WITNESS_EXT); let buf: Vec = witness_stack.try_into()?; - + println!("{:#?}", buf); + let bytes = vec![ + 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, + ]; write_to_file(buf.as_slice(), &witness_path); Ok(witness_path) From 1473451ed02cdd4d2a9d82a24114541c0e621573 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:52:21 +0000 Subject: [PATCH 03/17] cleanup and rename ts custom section for JsWitnessStack --- acvm-repo/acvm_js/src/js_witness_stack.rs | 4 ++-- tooling/nargo_cli/src/cli/fs/witness.rs | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/acvm-repo/acvm_js/src/js_witness_stack.rs b/acvm-repo/acvm_js/src/js_witness_stack.rs index 7aa38dfc233..dfd30b36f11 100644 --- a/acvm-repo/acvm_js/src/js_witness_stack.rs +++ b/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use crate::JsWitnessMap; #[wasm_bindgen(typescript_custom_section)] -const WITNESS_MAP: &'static str = r#" +const WITNESS_STACK: &'static str = r#" export type StackItem = { index: number; witness: WitnessMap; @@ -29,7 +29,7 @@ extern "C" { pub type JsStackItem; #[wasm_bindgen(constructor, js_class = "Object")] - pub fn new(index: JsValue, witness: JsWitnessMap) -> JsStackItem; + pub fn new() -> JsStackItem; } impl Default for JsWitnessStack { diff --git a/tooling/nargo_cli/src/cli/fs/witness.rs b/tooling/nargo_cli/src/cli/fs/witness.rs index e36cdf666ea..736955e4c69 100644 --- a/tooling/nargo_cli/src/cli/fs/witness.rs +++ b/tooling/nargo_cli/src/cli/fs/witness.rs @@ -15,13 +15,6 @@ pub(crate) fn save_witness_to_dir>( let witness_path = witness_dir.as_ref().join(witness_name).with_extension(WITNESS_EXT); let buf: Vec = witness_stack.try_into()?; - println!("{:#?}", buf); - let bytes = vec![ - 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, - ]; write_to_file(buf.as_slice(), &witness_path); Ok(witness_path) From ac7778744c403a65dfd47f111487a83883d09b18 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:53:26 +0000 Subject: [PATCH 04/17] change back to != in fold_basic_nested_call --- .../execution_success/fold_basic_nested_call/Prover.toml | 2 +- .../execution_success/fold_basic_nested_call/src/main.nr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test_programs/execution_success/fold_basic_nested_call/Prover.toml b/test_programs/execution_success/fold_basic_nested_call/Prover.toml index 5e7127334f7..f28f2f8cc48 100644 --- a/test_programs/execution_success/fold_basic_nested_call/Prover.toml +++ b/test_programs/execution_success/fold_basic_nested_call/Prover.toml @@ -1,2 +1,2 @@ -x = "8" +x = "5" y = "10" diff --git a/test_programs/execution_success/fold_basic_nested_call/src/main.nr b/test_programs/execution_success/fold_basic_nested_call/src/main.nr index 3f57a23b5d4..6d02b734727 100644 --- a/test_programs/execution_success/fold_basic_nested_call/src/main.nr +++ b/test_programs/execution_success/fold_basic_nested_call/src/main.nr @@ -11,6 +11,6 @@ fn func_with_nested_foo_call(x: Field, y: Field) -> Field { #[fold] fn foo(x: Field, y: Field) -> Field { - assert(x == y); + assert(x != y); x } From 5c078d771bc523504b891fa29cf94e225e4f369f Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:54:24 +0000 Subject: [PATCH 05/17] reduce diff --- tooling/nargo_cli/src/cli/fs/witness.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tooling/nargo_cli/src/cli/fs/witness.rs b/tooling/nargo_cli/src/cli/fs/witness.rs index 736955e4c69..d90ae01b8db 100644 --- a/tooling/nargo_cli/src/cli/fs/witness.rs +++ b/tooling/nargo_cli/src/cli/fs/witness.rs @@ -15,6 +15,7 @@ pub(crate) fn save_witness_to_dir>( let witness_path = witness_dir.as_ref().join(witness_name).with_extension(WITNESS_EXT); let buf: Vec = witness_stack.try_into()?; + write_to_file(buf.as_slice(), &witness_path); Ok(witness_path) From 563e6e9774049d92d3998d1f079cd022ff619d84 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:56:36 +0000 Subject: [PATCH 06/17] cargo fmt --- tooling/nargo_cli/src/cli/fs/witness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/nargo_cli/src/cli/fs/witness.rs b/tooling/nargo_cli/src/cli/fs/witness.rs index d90ae01b8db..613cdec28da 100644 --- a/tooling/nargo_cli/src/cli/fs/witness.rs +++ b/tooling/nargo_cli/src/cli/fs/witness.rs @@ -15,7 +15,7 @@ pub(crate) fn save_witness_to_dir>( let witness_path = witness_dir.as_ref().join(witness_name).with_extension(WITNESS_EXT); let buf: Vec = witness_stack.try_into()?; - + write_to_file(buf.as_slice(), &witness_path); Ok(witness_path) From 8dd2fe847bde13b5adb8a39fde502883cc161d4a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 17:59:33 +0000 Subject: [PATCH 07/17] remove future dep --- Cargo.lock | 1 - acvm-repo/acvm_js/Cargo.toml | 1 - .../acvm_js/outputs/out/acvm_js/README.md | 17 ++++++ .../acvm_js/outputs/out/acvm_js/package.json | 54 +++++++++++++++++++ acvm-repo/acvm_js/src/execute.rs | 2 +- 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 acvm-repo/acvm_js/outputs/out/acvm_js/README.md create mode 100644 acvm-repo/acvm_js/outputs/out/acvm_js/package.json diff --git a/Cargo.lock b/Cargo.lock index d1d078c4d14..2f85b26f974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,6 @@ dependencies = [ "cfg-if 1.0.0", "console_error_panic_hook", "const-str", - "futures 0.3.30", "gloo-utils", "js-sys", "pkg-config", diff --git a/acvm-repo/acvm_js/Cargo.toml b/acvm-repo/acvm_js/Cargo.toml index 636b1eb0af4..65c072b1d96 100644 --- a/acvm-repo/acvm_js/Cargo.toml +++ b/acvm-repo/acvm_js/Cargo.toml @@ -17,7 +17,6 @@ crate-type = ["cdylib"] [dependencies] cfg-if = "1.0.0" -futures = "0.3.30" [target.'cfg(target_arch = "wasm32")'.dependencies] acvm.workspace = true diff --git a/acvm-repo/acvm_js/outputs/out/acvm_js/README.md b/acvm-repo/acvm_js/outputs/out/acvm_js/README.md new file mode 100644 index 00000000000..2e59ca08cc9 --- /dev/null +++ b/acvm-repo/acvm_js/outputs/out/acvm_js/README.md @@ -0,0 +1,17 @@ +# acvm_js + +The `acvm_js` package enables users to execute an ACIR program, i.e. generating an initial witness from a set of inputs and calculating a partial witness. This partial witness can then be used to create a proof of execution using an ACVM backend. + +## Dependencies + +In order to build the wasm package, the following must be installed: + +- [jq](https://github.com/stedolan/jq) + +## Build + +The wasm package can be built using the command below: + +```bash +./build.sh +``` \ No newline at end of file diff --git a/acvm-repo/acvm_js/outputs/out/acvm_js/package.json b/acvm-repo/acvm_js/outputs/out/acvm_js/package.json new file mode 100644 index 00000000000..55345a2ddf6 --- /dev/null +++ b/acvm-repo/acvm_js/outputs/out/acvm_js/package.json @@ -0,0 +1,54 @@ +{ + "name": "@noir-lang/acvm_js", + "version": "0.42.0", + "publishConfig": { + "access": "public" + }, + "contributors": [ + "The Noir Team " + ], + "homepage": "https://noir-lang.org/", + "repository": { + "url": "https://github.com/noir-lang/noir.git", + "directory": "acvm_repo/acvm_js", + "type": "git" + }, + "bugs": { + "url": "https://github.com/noir-lang/noir/issues" + }, + "license": "MIT", + "main": "./nodejs/acvm_js.js", + "types": "./web/acvm_js.d.ts", + "module": "./web/acvm_js.js", + "files": [ + "nodejs", + "web", + "package.json" + ], + "sideEffects": false, + "packageManager": "yarn@3.5.1", + "scripts": { + "build": "bash ./build.sh", + "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", + "test:browser": "web-test-runner", + "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", + "publish": "echo 📡 publishing `$npm_package_name` && yarn npm publish", + "nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json", + "clean": "chmod u+w web nodejs || true && rm -rf web nodejs", + "build:nix": "nix build -L .#acvm_js", + "install:from:nix": "yarn clean && yarn build:nix && cp -rL ./result/acvm_js/nodejs ./ && cp -rL ./result/acvm_js/web ./" + }, + "devDependencies": { + "@esm-bundle/chai": "^4.3.4-fix.0", + "@web/dev-server-esbuild": "^0.3.6", + "@web/test-runner": "^0.18.1", + "@web/test-runner-playwright": "^0.10.0", + "chai": "^4.4.1", + "eslint": "^8.57.0", + "eslint-plugin-prettier": "^5.1.3", + "mocha": "^10.2.0", + "prettier": "3.2.5", + "ts-node": "^10.9.1", + "typescript": "^5.4.2" + } +} diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index b1c42066c0e..4d2ec199cf9 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -14,7 +14,7 @@ use crate::{ JsExecutionError, JsWitnessMap, JsWitnessStack, }; use core::pin::Pin; -use futures::future::Future; + #[wasm_bindgen] pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); From 74c871d8125057ef2eae624593ba0fbca846f1ed Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 18:00:05 +0000 Subject: [PATCH 08/17] use core Future --- acvm-repo/acvm_js/src/execute.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 4d2ec199cf9..d52b4b639a4 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -14,6 +14,7 @@ use crate::{ JsExecutionError, JsWitnessMap, JsWitnessStack, }; use core::pin::Pin; +use core::future::Future; #[wasm_bindgen] pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); From fc618726839f617748c9c2400d94d5454222955e Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 18:02:03 +0000 Subject: [PATCH 09/17] fmt on acvm js --- acvm-repo/acvm_js/src/execute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index d52b4b639a4..00f7f2fb0ee 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -13,8 +13,8 @@ use crate::{ foreign_call::{resolve_brillig, ForeignCallHandler}, JsExecutionError, JsWitnessMap, JsWitnessStack, }; -use core::pin::Pin; use core::future::Future; +use core::pin::Pin; #[wasm_bindgen] pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); From 775ddd81e37c4708f93b98a0691b2ba75cde7f57 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 18:03:35 +0000 Subject: [PATCH 10/17] remove accidentally committed outputs --- .../acvm_js/outputs/out/acvm_js/README.md | 17 ------ .../acvm_js/outputs/out/acvm_js/package.json | 54 ------------------- 2 files changed, 71 deletions(-) delete mode 100644 acvm-repo/acvm_js/outputs/out/acvm_js/README.md delete mode 100644 acvm-repo/acvm_js/outputs/out/acvm_js/package.json diff --git a/acvm-repo/acvm_js/outputs/out/acvm_js/README.md b/acvm-repo/acvm_js/outputs/out/acvm_js/README.md deleted file mode 100644 index 2e59ca08cc9..00000000000 --- a/acvm-repo/acvm_js/outputs/out/acvm_js/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# acvm_js - -The `acvm_js` package enables users to execute an ACIR program, i.e. generating an initial witness from a set of inputs and calculating a partial witness. This partial witness can then be used to create a proof of execution using an ACVM backend. - -## Dependencies - -In order to build the wasm package, the following must be installed: - -- [jq](https://github.com/stedolan/jq) - -## Build - -The wasm package can be built using the command below: - -```bash -./build.sh -``` \ No newline at end of file diff --git a/acvm-repo/acvm_js/outputs/out/acvm_js/package.json b/acvm-repo/acvm_js/outputs/out/acvm_js/package.json deleted file mode 100644 index 55345a2ddf6..00000000000 --- a/acvm-repo/acvm_js/outputs/out/acvm_js/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@noir-lang/acvm_js", - "version": "0.42.0", - "publishConfig": { - "access": "public" - }, - "contributors": [ - "The Noir Team " - ], - "homepage": "https://noir-lang.org/", - "repository": { - "url": "https://github.com/noir-lang/noir.git", - "directory": "acvm_repo/acvm_js", - "type": "git" - }, - "bugs": { - "url": "https://github.com/noir-lang/noir/issues" - }, - "license": "MIT", - "main": "./nodejs/acvm_js.js", - "types": "./web/acvm_js.d.ts", - "module": "./web/acvm_js.js", - "files": [ - "nodejs", - "web", - "package.json" - ], - "sideEffects": false, - "packageManager": "yarn@3.5.1", - "scripts": { - "build": "bash ./build.sh", - "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha", - "test:browser": "web-test-runner", - "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", - "publish": "echo 📡 publishing `$npm_package_name` && yarn npm publish", - "nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json", - "clean": "chmod u+w web nodejs || true && rm -rf web nodejs", - "build:nix": "nix build -L .#acvm_js", - "install:from:nix": "yarn clean && yarn build:nix && cp -rL ./result/acvm_js/nodejs ./ && cp -rL ./result/acvm_js/web ./" - }, - "devDependencies": { - "@esm-bundle/chai": "^4.3.4-fix.0", - "@web/dev-server-esbuild": "^0.3.6", - "@web/test-runner": "^0.18.1", - "@web/test-runner-playwright": "^0.10.0", - "chai": "^4.4.1", - "eslint": "^8.57.0", - "eslint-plugin-prettier": "^5.1.3", - "mocha": "^10.2.0", - "prettier": "3.2.5", - "ts-node": "^10.9.1", - "typescript": "^5.4.2" - } -} From 11a9785baaf790f535165afcd75b8fdecb6038a8 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 2 Apr 2024 18:05:29 +0000 Subject: [PATCH 11/17] remove unused JsString import --- acvm-repo/acvm_js/src/js_witness_stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acvm-repo/acvm_js/src/js_witness_stack.rs b/acvm-repo/acvm_js/src/js_witness_stack.rs index dfd30b36f11..59f2dbc051e 100644 --- a/acvm-repo/acvm_js/src/js_witness_stack.rs +++ b/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -1,5 +1,5 @@ use acvm::acir::native_types::WitnessStack; -use js_sys::{Array, JsString, Map, Object}; +use js_sys::{Array, Map, Object}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use crate::JsWitnessMap; From c89e0dac5f5db6e2baf69d6613f6c8eaa1ec5e62 Mon Sep 17 00:00:00 2001 From: Tom French Date: Wed, 3 Apr 2024 15:33:05 +0100 Subject: [PATCH 12/17] chore: improve `ProgramExecutor` interface --- acvm-repo/acvm_js/src/execute.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 00f7f2fb0ee..814f91ca418 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -1,3 +1,5 @@ +use std::{future::Future, pin::Pin}; + use acvm::BlackBoxFunctionSolver; use acvm::{ acir::circuit::{Circuit, Program}, @@ -13,8 +15,6 @@ use crate::{ foreign_call::{resolve_brillig, ForeignCallHandler}, JsExecutionError, JsWitnessMap, JsWitnessStack, }; -use core::future::Future; -use core::pin::Pin; #[wasm_bindgen] pub struct WasmBlackBoxFunctionSolver(Bn254BlackBoxSolver); @@ -127,14 +127,8 @@ async fn execute_program_with_native_type_return( 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 main = &program.functions[0]; - let executor = ProgramExecutor::new(&program.functions, &solver.0, &foreign_call_executor); - - let mut witness_stack = WitnessStack::default(); - let main_witness = - executor.execute_circuit(main, initial_witness.into(), &mut witness_stack).await?; - witness_stack.push(0, main_witness); + let witness_stack = executor.execute(initial_witness.into()).await?; Ok(witness_stack) } @@ -156,6 +150,16 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { 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.into(), &mut witness_stack).await?; + witness_stack.push(0, main_witness); + Ok(witness_stack) + } + fn execute_circuit( &'a self, circuit: &'a Circuit, From f856b2b248590de751118dc7690378341b7e04eb Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 Apr 2024 14:40:36 +0000 Subject: [PATCH 13/17] add new methods to lib.rs --- acvm-repo/acvm_js/src/execute.rs | 2 -- acvm-repo/acvm_js/src/lib.rs | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index f85119463d4..008c983ebd0 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -169,8 +169,6 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { Box::pin(async { let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); - // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. - let mut assert_message: Option = None; loop { let solver_status = acvm.solve(); diff --git a/acvm-repo/acvm_js/src/lib.rs b/acvm-repo/acvm_js/src/lib.rs index 89c3c64fa4a..b8819b20b59 100644 --- a/acvm-repo/acvm_js/src/lib.rs +++ b/acvm-repo/acvm_js/src/lib.rs @@ -17,9 +17,10 @@ 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, decompress_witness, compress_witness_stack, 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; From 851dc68311524ebff8c8488df457f86dd990c3c6 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 Apr 2024 14:41:21 +0000 Subject: [PATCH 14/17] fmt --- acvm-repo/acvm_js/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/acvm-repo/acvm_js/src/lib.rs b/acvm-repo/acvm_js/src/lib.rs index b8819b20b59..d7ecc0ae192 100644 --- a/acvm-repo/acvm_js/src/lib.rs +++ b/acvm-repo/acvm_js/src/lib.rs @@ -17,10 +17,12 @@ 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, compress_witness_stack, decompress_witness_stack}; +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 + execute_program, execute_program_with_black_box_solver, }; pub use js_execution_error::JsExecutionError; pub use js_witness_map::JsWitnessMap; From 8364502e0c326c32706fbfda177a96f9072acc24 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 Apr 2024 14:45:15 +0000 Subject: [PATCH 15/17] clippy --- acvm-repo/acvm_js/src/execute.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 008c983ebd0..815c38407db 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -75,7 +75,7 @@ pub async fn execute_circuit_with_black_box_solver( console_error_panic_hook::set_once(); let mut witness_stack = execute_program_with_native_type_return( - &solver, + solver, program, initial_witness, &foreign_call_handler, @@ -127,7 +127,7 @@ async fn execute_program_with_native_type_return( 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 executor = ProgramExecutor::new(&program.functions, &solver.0, &foreign_call_executor); + let executor = ProgramExecutor::new(&program.functions, &solver.0, foreign_call_executor); let witness_stack = executor.execute(initial_witness.into()).await?; Ok(witness_stack) @@ -155,7 +155,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { let mut witness_stack = WitnessStack::default(); let main_witness = - self.execute_circuit(main, initial_witness.into(), &mut witness_stack).await?; + self.execute_circuit(main, initial_witness, &mut witness_stack).await?; witness_stack.push(0, main_witness); Ok(witness_stack) } From ac9dd0fa37c609ae851dc880dd0882dcdc446143 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 Apr 2024 14:45:24 +0000 Subject: [PATCH 16/17] fmt --- acvm-repo/acvm_js/src/execute.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index 815c38407db..0e58ccf039c 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -154,8 +154,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { let main = &self.functions[0]; let mut witness_stack = WitnessStack::default(); - let main_witness = - self.execute_circuit(main, initial_witness, &mut witness_stack).await?; + let main_witness = self.execute_circuit(main, initial_witness, &mut witness_stack).await?; witness_stack.push(0, main_witness); Ok(witness_stack) } From 027ea53ea87444306fc1c55f04276d8f296ce242 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 Apr 2024 14:51:16 +0000 Subject: [PATCH 17/17] remove vscode setting for rustAnalyzer after no more conditional compilation for acvm js --- .vscode/settings.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f18db21696d..fb8ea527881 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,8 +7,5 @@ }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "rust-analyzer.linkedProjects": [ - "acvm-repo/acvm_js/Cargo.toml", - ] + } }