diff --git a/crates/acvm_backend_barretenberg/src/cli/mod.rs b/crates/acvm_backend_barretenberg/src/cli/mod.rs index 0d57c1692be..032bd6b14e3 100644 --- a/crates/acvm_backend_barretenberg/src/cli/mod.rs +++ b/crates/acvm_backend_barretenberg/src/cli/mod.rs @@ -3,7 +3,6 @@ mod contract; mod gates; mod prove; -mod prove_and_verify; mod verify; mod write_vk; diff --git a/crates/acvm_backend_barretenberg/src/cli/prove_and_verify.rs b/crates/acvm_backend_barretenberg/src/cli/prove_and_verify.rs deleted file mode 100644 index 90bfac7247e..00000000000 --- a/crates/acvm_backend_barretenberg/src/cli/prove_and_verify.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::path::{Path, PathBuf}; - -/// ProveAndVerifyCommand will call the barretenberg binary -/// to create a proof and then verify the proof once created. -/// -/// Note: Functions like this are useful for testing. In a real workflow, -/// ProveCommand and VerifyCommand will be used separately. -#[allow(dead_code)] -struct ProveAndVerifyCommand { - verbose: bool, - crs_path: PathBuf, - is_recursive: bool, - bytecode_path: PathBuf, - witness_path: PathBuf, -} - -#[allow(dead_code)] -impl ProveAndVerifyCommand { - fn run(self, binary_path: &Path) -> bool { - let mut command = std::process::Command::new(binary_path); - - command - .arg("prove_and_verify") - .arg("-c") - .arg(self.crs_path) - .arg("-b") - .arg(self.bytecode_path) - .arg("-w") - .arg(self.witness_path); - if self.verbose { - command.arg("-v"); - } - if self.is_recursive { - command.arg("-r"); - } - - let output = command.output().expect("Failed to execute command"); - - // We currently do not distinguish between an invalid proof and an error inside the backend. - output.status.success() - } -} - -#[test] -#[serial_test::serial] -fn prove_and_verify_command() { - use tempfile::tempdir; - - let bytecode_path = PathBuf::from("./src/1_mul.bytecode"); - let witness_path = PathBuf::from("./src/witness.tr"); - - let temp_directory = tempdir().expect("could not create a temporary directory"); - let temp_directory_path = temp_directory.path(); - let crs_path = temp_directory_path.join("crs"); - - let prove_and_verify_command = ProveAndVerifyCommand { - verbose: true, - crs_path, - is_recursive: false, - bytecode_path, - witness_path, - }; - - let backend = crate::get_bb(); - let verified = prove_and_verify_command.run(&backend.binary_path()); - assert!(verified); - drop(temp_directory); -} diff --git a/crates/acvm_backend_barretenberg/src/lib.rs b/crates/acvm_backend_barretenberg/src/lib.rs index 486513fb5f9..9c04746cfd5 100644 --- a/crates/acvm_backend_barretenberg/src/lib.rs +++ b/crates/acvm_backend_barretenberg/src/lib.rs @@ -3,26 +3,11 @@ use std::path::PathBuf; -// `acvm-backend-barretenberg` can either interact with the Barretenberg backend through a static library -// or through an embedded wasm binary. It does not make sense to include both of these backends at the same time. -// We then throw a compilation error if both flags are set. -#[cfg(all(feature = "native", feature = "wasm"))] -compile_error!("feature \"native\" and feature \"wasm\" cannot be enabled at the same time"); - -#[cfg(all(feature = "native", target_arch = "wasm32"))] -compile_error!("feature \"native\" cannot be enabled for a \"wasm32\" target"); - -#[cfg(all(feature = "wasm", target_arch = "wasm32"))] -compile_error!("feature \"wasm\" cannot be enabled for a \"wasm32\" target"); - mod bb; mod cli; mod proof_system; mod smart_contract; -/// The number of bytes necessary to store a `FieldElement`. -const FIELD_BYTES: usize = 32; - #[cfg(test)] fn get_bb() -> Backend { let bb = Backend::new(); diff --git a/crates/acvm_backend_barretenberg/src/proof_system.rs b/crates/acvm_backend_barretenberg/src/proof_system.rs index 28ae8b6541f..58d0d1551dd 100644 --- a/crates/acvm_backend_barretenberg/src/proof_system.rs +++ b/crates/acvm_backend_barretenberg/src/proof_system.rs @@ -9,7 +9,7 @@ use acvm::Language; use tempfile::tempdir; use crate::cli::{GatesCommand, ProveCommand, VerifyCommand, WriteVkCommand}; -use crate::{assert_binary_exists, Backend, BackendError, FIELD_BYTES}; +use crate::{assert_binary_exists, Backend, BackendError}; impl Backend { pub fn np_language(&self) -> Language { @@ -88,8 +88,7 @@ impl Backend { witness_path, proof_path: proof_path.clone(), } - .run(&binary_path) - .expect("prove command failed"); + .run(&binary_path)?; let proof_with_public_inputs = read_bytes_from_file(&proof_path).unwrap(); @@ -147,8 +146,7 @@ impl Backend { bytecode_path, vk_path_output: vk_path.clone(), } - .run(&binary_path) - .expect("write vk command failed"); + .run(&binary_path)?; // Verify the proof let valid_proof = VerifyCommand { @@ -195,7 +193,7 @@ pub(super) fn read_bytes_from_file(path: &Path) -> std::io::Result> { fn remove_public_inputs(num_pub_inputs: usize, proof: &[u8]) -> Vec { // Barretenberg prepends the public inputs onto the proof so we need to remove // the first `num_pub_inputs` field elements. - let num_bytes_to_remove = num_pub_inputs * FIELD_BYTES; + let num_bytes_to_remove = num_pub_inputs * (FieldElement::max_num_bytes() as usize); proof[num_bytes_to_remove..].to_vec() } diff --git a/crates/acvm_backend_barretenberg/src/smart_contract.rs b/crates/acvm_backend_barretenberg/src/smart_contract.rs index f5b2dc8db20..d17db746b89 100644 --- a/crates/acvm_backend_barretenberg/src/smart_contract.rs +++ b/crates/acvm_backend_barretenberg/src/smart_contract.rs @@ -32,8 +32,7 @@ impl Backend { bytecode_path, vk_path_output: vk_path.clone(), } - .run(&binary_path) - .expect("write vk command failed"); + .run(&binary_path)?; let contract_path = temp_directory_path.join("contract"); ContractCommand { @@ -42,8 +41,7 @@ impl Backend { vk_path, contract_path: contract_path.clone(), } - .run(&binary_path) - .expect("contract command failed"); + .run(&binary_path)?; let verification_key_library_bytes = read_bytes_from_file(&contract_path).unwrap(); let verification_key_library = String::from_utf8(verification_key_library_bytes).unwrap(); diff --git a/crates/nargo_cli/Cargo.toml b/crates/nargo_cli/Cargo.toml index da3eac653c5..e10fde13d7b 100644 --- a/crates/nargo_cli/Cargo.toml +++ b/crates/nargo_cli/Cargo.toml @@ -46,11 +46,13 @@ hex = "0.4.2" termcolor = "1.1.2" color-eyre = "0.6.2" tokio = { version = "1.0", features = ["io-std"] } -tokio-util = { version = "0.7.8", features = ["compat"] } # Backends acvm-backend-barretenberg = { path = "../acvm_backend_barretenberg" } +[target.'cfg(not(unix))'.dependencies] +tokio-util = { version = "0.7.8", features = ["compat"] } + [dev-dependencies] tempdir = "0.3.7" assert_cmd = "2.0.8" diff --git a/crates/nargo_cli/src/main.rs b/crates/nargo_cli/src/main.rs index 734dbdca2e7..f4d1e1862fc 100644 --- a/crates/nargo_cli/src/main.rs +++ b/crates/nargo_cli/src/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] -#![warn(unused_extern_crates)] #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] +#![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] //! Nargo is the package manager for Noir //! This name was used because it sounds like `cargo` and diff --git a/crates/noirc_frontend/src/hir/def_map/aztec_library.rs b/crates/noirc_frontend/src/hir/def_map/aztec_library.rs index 12aa60276ce..38a45bc16c4 100644 --- a/crates/noirc_frontend/src/hir/def_map/aztec_library.rs +++ b/crates/noirc_frontend/src/hir/def_map/aztec_library.rs @@ -5,8 +5,9 @@ use crate::graph::CrateId; use crate::{ hir::Context, token::Attribute, BlockExpression, CallExpression, CastExpression, Distinctness, Expression, ExpressionKind, ForExpression, FunctionReturnType, Ident, ImportStatement, - IndexExpression, LetStatement, Literal, MethodCallExpression, NoirFunction, ParsedModule, Path, - PathKind, Pattern, Statement, UnresolvedType, UnresolvedTypeData, Visibility, + IndexExpression, LetStatement, Literal, MemberAccessExpression, MethodCallExpression, + NoirFunction, ParsedModule, Path, PathKind, Pattern, Statement, UnresolvedType, + UnresolvedTypeData, Visibility, }; use noirc_errors::FileDiagnostic; @@ -33,6 +34,10 @@ fn variable(name: &str) -> Expression { expression(ExpressionKind::Variable(ident_path(name))) } +fn variable_ident(identifier: Ident) -> Expression { + expression(ExpressionKind::Variable(path(identifier))) +} + fn variable_path(path: Path) -> Expression { expression(ExpressionKind::Variable(path)) } @@ -61,6 +66,13 @@ fn mutable_assignment(name: &str, assigned_to: Expression) -> Statement { }) } +fn member_access(lhs: &str, rhs: &str) -> Expression { + expression(ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: variable(lhs), + rhs: ident(rhs), + }))) +} + macro_rules! chained_path { ( $base:expr $(, $tail:expr)* ) => { { @@ -101,6 +113,13 @@ fn index_array(array: Ident, index: &str) -> Expression { }))) } +fn index_array_variable(array: Expression, index: &str) -> Expression { + expression(ExpressionKind::Index(Box::new(IndexExpression { + collection: array, + index: variable(index), + }))) +} + fn import(path: Path) -> ImportStatement { ImportStatement { path, alias: None } } @@ -203,6 +222,11 @@ fn transform_function(ty: &str, func: &mut NoirFunction) { let input = create_inputs(&inputs_name); func.def.parameters.insert(0, input); + // Abstract return types such that they get added to the kernel's return_values + if let Some(return_values) = abstract_return_values(func) { + func.def.body.0.push(return_values); + } + // Push the finish method call to the end of the function let finish_def = create_context_finish(); func.def.body.0.push(finish_def); @@ -332,6 +356,124 @@ fn create_context(ty: &str, params: &[(Pattern, UnresolvedType, Visibility)]) -> injected_expressions } +/// Abstract Return Type +/// +/// This function intercepts the function's current return type and replaces it with pushes +/// To the kernel +/// +/// The replaced code: +/// ```noir +/// /// Before +/// #[aztec(private)] +/// fn foo() -> abi::PrivateCircuitPublicInputs { +/// // ... +/// let my_return_value: Field = 10; +/// context.return_values.push(my_return_value); +/// } +/// +/// /// After +/// #[aztec(private)] +/// fn foo() -> Field { +/// // ... +/// let my_return_value: Field = 10; +/// my_return_value +/// } +/// ``` +/// Similarly; Structs will be pushed to the context, after serialize() is called on them. +/// Arrays will be iterated over and each element will be pushed to the context. +/// Any primitive type that can be cast will be casted to a field and pushed to the context. +fn abstract_return_values(func: &mut NoirFunction) -> Option { + let current_return_type = func.return_type().typ; + let len = func.def.body.len(); + let last_statement = &func.def.body.0[len - 1]; + + // TODO: (length, type) => We can limit the size of the array returned to be limited by kernel size + // Doesnt need done until we have settled on a kernel size + // TODO: support tuples here and in inputs -> convert into an issue + + // Check if the return type is an expression, if it is, we can handle it + match last_statement { + Statement::Expression(expression) => match current_return_type { + // Call serialize on structs, push the whole array, calling push_array + UnresolvedTypeData::Named(..) => Some(make_struct_return_type(expression.clone())), + UnresolvedTypeData::Array(..) => Some(make_array_return_type(expression.clone())), + // Cast these types to a field before pushing + UnresolvedTypeData::Bool | UnresolvedTypeData::Integer(..) => { + Some(make_castable_return_type(expression.clone())) + } + UnresolvedTypeData::FieldElement => Some(make_return_push(expression.clone())), + _ => None, + }, + _ => None, + } +} + +/// Context Return Values +/// +/// Creates an instance to the context return values +/// ```noir +/// `context.return_values` +/// ``` +fn context_return_values() -> Expression { + member_access("context", "return_values") +} + +/// Make return Push +/// +/// Translates to: +/// `context.return_values.push({push_value})` +fn make_return_push(push_value: Expression) -> Statement { + Statement::Semi(method_call(context_return_values(), "push", vec![push_value])) +} + +/// Make Return push array +/// +/// Translates to: +/// `context.return_values.push_array({push_value})` +fn make_return_push_array(push_value: Expression) -> Statement { + Statement::Semi(method_call(context_return_values(), "push_array", vec![push_value])) +} + +/// Make struct return type +/// +/// Translates to: +/// ```noir +/// `context.return_values.push_array({push_value}.serialize())` +fn make_struct_return_type(expression: Expression) -> Statement { + let serialised_call = method_call( + expression.clone(), // variable + "serialize", // method name + vec![], // args + ); + make_return_push_array(serialised_call) +} + +/// Make array return type +/// +/// Translates to: +/// ```noir +/// for i in 0..{ident}.len() { +/// context.return_values.push({ident}[i] as Field) +/// } +/// ``` +fn make_array_return_type(expression: Expression) -> Statement { + let inner_cast_expression = + cast(index_array_variable(expression.clone(), "i"), UnresolvedTypeData::FieldElement); + create_loop_over(expression.clone(), vec![inner_cast_expression]) +} + +/// Castable return type +/// +/// Translates to: +/// ```noir +/// context.return_values.push({ident} as Field) +/// ``` +fn make_castable_return_type(expression: Expression) -> Statement { + // Cast these types to a field before pushing + let cast_expression = cast(expression.clone(), UnresolvedTypeData::FieldElement); + make_return_push(cast_expression) +} + /// Create Return Type /// /// Public functions return abi::PublicCircuitPublicInputs while @@ -407,30 +549,24 @@ fn add_struct_to_hasher(identifier: &Ident) -> Statement { )) } -fn add_array_to_hasher(identifier: &Ident) -> Statement { +fn create_loop_over(var: Expression, loop_body: Vec) -> Statement { // If this is an array of primitive types (integers / fields) we can add them each to the hasher // casted to a field // `array.len()` let end_range_expression = method_call( - variable_path(path(identifier.clone())), // variable - "len", // method name - vec![], // args + var.clone(), // variable + "len", // method name + vec![], // args ); - // Wrap in the semi thing - does that mean ended with semi colon? - // `hasher.add({ident}[i] as Field)` - let cast_expression = cast( - index_array(identifier.clone(), "i"), // lhs - `ident[i]` - UnresolvedTypeData::FieldElement, // cast to - `as Field` - ); // What will be looped over // - `hasher.add({ident}[i] as Field)` let for_loop_block = expression(ExpressionKind::Block(BlockExpression(vec![Statement::Semi(method_call( variable("hasher"), // variable "add", // method name - vec![cast_expression], + loop_body, ))]))); // `for i in 0..{ident}.len()` @@ -444,6 +580,19 @@ fn add_array_to_hasher(identifier: &Ident) -> Statement { })))) } +fn add_array_to_hasher(identifier: &Ident) -> Statement { + // If this is an array of primitive types (integers / fields) we can add them each to the hasher + // casted to a field + + // Wrap in the semi thing - does that mean ended with semi colon? + // `hasher.add({ident}[i] as Field)` + let cast_expression = cast( + index_array(identifier.clone(), "i"), // lhs - `ident[i]` + UnresolvedTypeData::FieldElement, // cast to - `as Field` + ); + create_loop_over(variable_ident(identifier.clone()), vec![cast_expression]) +} + fn add_field_to_hasher(identifier: &Ident) -> Statement { // `hasher.add({ident})` let iden = variable_path(path(identifier.clone()));