diff --git a/.noir-sync-commit b/.noir-sync-commit index 10716ce49501..34ef64dcc096 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -9c99a97ca8f42bee23cf97ebd724fdc51e647c60 +2e543b40eb83ef2080e4d8f870f525fadd631099 diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index 8dddef373add..b08fa7d74380 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -37,6 +37,7 @@ dependencies = [ "brillig_vm", "indexmap 1.9.3", "num-bigint", + "serde", "thiserror", "tracing", ] diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr index a8d92e002f6f..2f1b93d9aad1 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs.nr @@ -2,4 +2,4 @@ mod header; mod incoming_body; mod outgoing_body; mod payload; -mod encrypted_note_emission; \ No newline at end of file +mod encrypted_note_emission; diff --git a/noir-projects/aztec-nr/aztec/src/note.nr b/noir-projects/aztec-nr/aztec/src/note.nr index 644450efc4f8..670b340735ee 100644 --- a/noir-projects/aztec-nr/aztec/src/note.nr +++ b/noir-projects/aztec-nr/aztec/src/note.nr @@ -6,4 +6,4 @@ mod note_header; mod note_interface; mod note_viewer_options; mod utils; -mod note_emission; \ No newline at end of file +mod note_emission; diff --git a/noir-projects/aztec-nr/aztec/src/oracle/key_validation_request.nr b/noir-projects/aztec-nr/aztec/src/oracle/key_validation_request.nr index 8f9e0d428f62..77ab29f9e41f 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/key_validation_request.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/key_validation_request.nr @@ -4,7 +4,10 @@ use dep::protocol_types::{ }; #[oracle(getKeyValidationRequest)] -unconstrained fn get_key_validation_request_oracle(_pk_m_hash: Field, _key_index: Field) -> [Field; KEY_VALIDATION_REQUEST_LENGTH] {} +unconstrained fn get_key_validation_request_oracle( + _pk_m_hash: Field, + _key_index: Field +) -> [Field; KEY_VALIDATION_REQUEST_LENGTH] {} unconstrained fn get_key_validation_request_internal(npk_m_hash: Field, key_index: Field) -> KeyValidationRequest { let result = get_key_validation_request_oracle(npk_m_hash, key_index); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/logs.nr b/noir-projects/aztec-nr/aztec/src/oracle/logs.nr index 34aea8558aca..02bbd75de4af 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/logs.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/logs.nr @@ -2,11 +2,7 @@ use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; // = 480 + 32 * N bytes #[oracle(emitEncryptedNoteLog)] -unconstrained fn emit_encrypted_note_log_oracle( - _note_hash_counter: u32, - _encrypted_note: [u8; M], - _counter: u32 -) {} +unconstrained fn emit_encrypted_note_log_oracle(_note_hash_counter: u32, _encrypted_note: [u8; M], _counter: u32) {} unconstrained pub fn emit_encrypted_note_log( note_hash_counter: u32, @@ -17,12 +13,7 @@ unconstrained pub fn emit_encrypted_note_log( } #[oracle(emitEncryptedEventLog)] -unconstrained fn emit_encrypted_event_log_oracle( - _contract_address: AztecAddress, - _randomness: Field, - _encrypted_event: [u8; M], - _counter: u32 -) {} +unconstrained fn emit_encrypted_event_log_oracle(_contract_address: AztecAddress, _randomness: Field, _encrypted_event: [u8; M], _counter: u32) {} unconstrained pub fn emit_encrypted_event_log( contract_address: AztecAddress, @@ -98,12 +89,7 @@ unconstrained pub fn compute_encrypted_event_log( } #[oracle(emitUnencryptedLog)] -unconstrained fn emit_unencrypted_log_oracle_private( - _contract_address: AztecAddress, - _event_selector: Field, - _message: T, - _counter: u32 -) -> Field {} +unconstrained fn emit_unencrypted_log_oracle_private(_contract_address: AztecAddress, _event_selector: Field, _message: T, _counter: u32) -> Field {} unconstrained pub fn emit_unencrypted_log_private_internal( contract_address: AztecAddress, @@ -115,12 +101,7 @@ unconstrained pub fn emit_unencrypted_log_private_internal( } #[oracle(emitContractClassUnencryptedLog)] -unconstrained fn emit_contract_class_unencrypted_log_private( - contract_address: AztecAddress, - event_selector: Field, - message: [Field; N], - counter: u32 -) -> Field {} +unconstrained fn emit_contract_class_unencrypted_log_private(contract_address: AztecAddress, event_selector: Field, message: [Field; N], counter: u32) -> Field {} unconstrained pub fn emit_contract_class_unencrypted_log_private_internal( contract_address: AztecAddress, diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 6a2a311f3f9e..42c6bcdb7ee3 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -28,11 +28,7 @@ unconstrained pub fn notify_created_note( } #[oracle(notifyNullifiedNote)] -unconstrained fn notify_nullified_note_oracle( - _nullifier: Field, - _inner_note_hash: Field, - _counter: u32 -) -> Field {} +unconstrained fn notify_nullified_note_oracle(_nullifier: Field, _inner_note_hash: Field, _counter: u32) -> Field {} unconstrained pub fn notify_nullified_note( nullifier: Field, diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr index e7d89b68f74c..01d06077fb7e 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr @@ -6,8 +6,8 @@ use dep::aztec::{ traits::{ToField, Serialize, FromField}, grumpkin_point::GrumpkinPoint, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL }, - encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys, note::note_getter::view_notes, - state_vars::PrivateSet, note::constants::MAX_NOTES_PER_PAGE + encrypted_logs::encrypted_note_emission::encode_and_encrypt_with_keys, + note::note_getter::view_notes, state_vars::PrivateSet, note::constants::MAX_NOTES_PER_PAGE }; use dep::std; use dep::std::{option::Option}; diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index db162e212694..9f46e6f98e8b 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -221,32 +221,6 @@ jobs: - name: Run browser tests run: yarn workspace @noir-lang/noirc_abi test:browser - test-noir-js-backend-barretenberg: - needs: [build-noirc-abi] - name: noir-js-backend-barretenberg - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Download wasm package artifact - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: ./tooling/noirc_abi_wasm - - - name: Install Yarn dependencies - uses: ./.github/actions/setup - - - name: Build noir_js_types - run: yarn workspace @noir-lang/types build - - - name: Run barretenberg wrapper tests - run: | - yarn workspace @noir-lang/backend_barretenberg test - test-noir-js: needs: [build-nargo, build-acvm-js, build-noirc-abi] name: Noir JS @@ -546,7 +520,6 @@ jobs: - test-acvm_js-node - test-acvm_js-browser - test-noirc-abi - - test-noir-js-backend-barretenberg - test-noir-js - test-noir-wasm - test-noir-codegen diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 61bfdd08c0e0..bd575d0dca26 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -34,6 +34,7 @@ dependencies = [ "cfg-if 1.0.0", "hex", "num-bigint", + "proptest", "serde", ] @@ -47,9 +48,7 @@ dependencies = [ "brillig_vm", "indexmap 1.9.3", "num-bigint", - "paste", - "proptest", - "rand 0.8.5", + "serde", "thiserror", "tracing", ] @@ -66,6 +65,7 @@ dependencies = [ "libaes", "num-bigint", "p256", + "proptest", "sha2", "sha3", "thiserror", @@ -574,6 +574,7 @@ version = "0.46.0" dependencies = [ "acir", "acvm_blackbox_solver", + "ark-bn254", "ark-ec", "ark-ff", "ark-std", @@ -2485,6 +2486,7 @@ dependencies = [ "noirc_errors", "noirc_frontend", "noirc_printable_type", + "rand 0.8.5", "rayon", "serde", "tempfile", diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 182580f8d679..c08e9f9f38b2 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -77,6 +77,14 @@ noirc_abi = { path = "tooling/noirc_abi" } bb_abstraction_leaks = { path = "tooling/bb_abstraction_leaks" } acvm_cli = { path = "tooling/acvm_cli" } +# Arkworks +ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } +ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } +grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } +ark-ec = { version = "^0.4.0", default-features = false } +ark-ff = { version = "^0.4.0", default-features = false } +ark-std = { version = "^0.4.0", default-features = false } + # Misc utils crates iter-extended = { path = "utils/iter-extended" } @@ -131,6 +139,8 @@ similar-asserts = "1.5.0" tempfile = "3.6.0" jsonrpc = { version = "0.16.0", features = ["minreq_http"] } flate2 = "1.0.24" +rand = "0.8.5" +proptest = "1.2.0" im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" diff --git a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml index f182c6c4aba2..801935c90f95 100644 --- a/noir/noir-repo/acvm-repo/acir_field/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir_field/Cargo.toml @@ -17,12 +17,15 @@ hex.workspace = true num-bigint.workspace = true serde.workspace = true -ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } -ark-bls12-381 = { version = "^0.4.0", optional = true, default-features = false, features = ["curve"] } -ark-ff = { version = "^0.4.0", default-features = false } +ark-bn254.workspace = true +ark-bls12-381 = { workspace = true, optional = true } +ark-ff.workspace = true cfg-if = "1.0.0" +[dev-dependencies] +proptest.workspace = true + [features] bn254 = [] bls12_381 = ["dep:ark-bls12-381"] diff --git a/noir/noir-repo/acvm-repo/acir_field/proptest-regressions/field_element.txt b/noir/noir-repo/acvm-repo/acir_field/proptest-regressions/field_element.txt new file mode 100644 index 000000000000..79b3209d4f21 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir_field/proptest-regressions/field_element.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc c1b2755b427c874de3ee55c2969e8adc1b4b0331a28173e9e1e492b3fee6b7b7 # shrinks to hex = "3a0aaaa0aa0aaa00000aaaaaaaa0000a000aaa0a00a0000aa00a00aa0a000a0a" diff --git a/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs b/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs index 7810578ff736..3c16276adc9d 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/field_element.rs @@ -174,10 +174,6 @@ impl FieldElement { self.0 } - fn is_negative(&self) -> bool { - self.neg().num_bits() < self.num_bits() - } - fn fits_in_u128(&self) -> bool { self.num_bits() <= 128 } @@ -195,15 +191,6 @@ impl FieldElement { Some(FieldElement(fr)) } - // mask_to methods will not remove any bytes from the field - // they are simply zeroed out - // Whereas truncate_to will remove those bits and make the byte array smaller - fn mask_to_be_bytes(&self, num_bits: u32) -> Vec { - let mut bytes = self.to_be_bytes(); - mask_vector_le(&mut bytes, num_bits as usize); - bytes - } - fn bits(&self) -> Vec { fn byte_to_bit(byte: u8) -> Vec { let mut bits = Vec::with_capacity(8); @@ -220,29 +207,6 @@ impl FieldElement { } bits } - - fn and_xor(&self, rhs: &FieldElement, num_bits: u32, is_xor: bool) -> FieldElement { - // XXX: Gadgets like SHA256 need to have their input be a multiple of 8 - // This is not a restriction caused by SHA256, as it works on bits - // but most backends assume bytes. - // We could implicitly pad, however this may not be intuitive for users. - // assert!( - // num_bits % 8 == 0, - // "num_bits is not a multiple of 8, it is {}", - // num_bits - // ); - - let lhs_bytes = self.mask_to_be_bytes(num_bits); - let rhs_bytes = rhs.mask_to_be_bytes(num_bits); - - let and_byte_arr: Vec<_> = lhs_bytes - .into_iter() - .zip(rhs_bytes) - .map(|(lhs, rhs)| if is_xor { lhs ^ rhs } else { lhs & rhs }) - .collect(); - - FieldElement::from_be_bytes_reduce(&and_byte_arr) - } } impl AcirField for FieldElement { @@ -310,7 +274,10 @@ impl AcirField for FieldElement { } fn to_i128(self) -> i128 { - let is_negative = self.is_negative(); + // Negative integers are represented by the range [p + i128::MIN, p) whilst + // positive integers are represented by the range [0, i128::MAX). + // We can then differentiate positive from negative values by their MSB. + let is_negative = self.neg().num_bits() < self.num_bits(); let bytes = if is_negative { self.neg() } else { self }.to_be_bytes(); i128::from_be_bytes(bytes[16..32].try_into().unwrap()) * if is_negative { -1 } else { 1 } } @@ -376,13 +343,6 @@ impl AcirField for FieldElement { bytes[0..num_elements].to_vec() } - - fn and(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { - self.and_xor(rhs, num_bits, false) - } - fn xor(&self, rhs: &FieldElement, num_bits: u32) -> FieldElement { - self.and_xor(rhs, num_bits, true) - } } impl Neg for FieldElement { @@ -433,35 +393,6 @@ impl SubAssign for FieldElement { } } -fn mask_vector_le(bytes: &mut [u8], num_bits: usize) { - // reverse to big endian format - bytes.reverse(); - - let mask_power = num_bits % 8; - let array_mask_index = num_bits / 8; - - for (index, byte) in bytes.iter_mut().enumerate() { - match index.cmp(&array_mask_index) { - std::cmp::Ordering::Less => { - // do nothing if the current index is less than - // the array index. - } - std::cmp::Ordering::Equal => { - let mask = 2u8.pow(mask_power as u32) - 1; - // mask the byte - *byte &= mask; - } - std::cmp::Ordering::Greater => { - // Anything greater than the array index - // will be set to zero - *byte = 0; - } - } - } - // reverse back to little endian - bytes.reverse(); -} - // For pretty printing powers fn superscript(n: u64) -> String { if n == 0 { @@ -494,19 +425,7 @@ fn superscript(n: u64) -> String { #[cfg(test)] mod tests { use super::{AcirField, FieldElement}; - - #[test] - fn and() { - let max = 10_000u32; - - let num_bits = (std::mem::size_of::() * 8) as u32 - max.leading_zeros(); - - for x in 0..max { - let x = FieldElement::::from(x as i128); - let res = x.and(&x, num_bits); - assert_eq!(res.to_be_bytes(), x.to_be_bytes()); - } - } + use proptest::prelude::*; #[test] fn serialize_fixed_test_vectors() { @@ -524,24 +443,36 @@ mod tests { } } - #[test] - fn deserialize_even_and_odd_length_hex() { - // Test cases of (odd, even) length hex strings - let hex_strings = - vec![("0x0", "0x00"), ("0x1", "0x01"), ("0x002", "0x0002"), ("0x00003", "0x000003")]; - for (i, case) in hex_strings.into_iter().enumerate() { - let i_field_element = FieldElement::::from(i as i128); - let odd_field_element = FieldElement::::from_hex(case.0).unwrap(); - let even_field_element = FieldElement::::from_hex(case.1).unwrap(); - - assert_eq!(i_field_element, odd_field_element); - assert_eq!(odd_field_element, even_field_element); - } - } - #[test] fn max_num_bits_smoke() { let max_num_bits_bn254 = FieldElement::::max_num_bits(); assert_eq!(max_num_bits_bn254, 254); } + + proptest! { + // This currently panics due to the fact that we allow inputs which are greater than the field modulus, + // automatically reducing them to fit within the canonical range. + #[test] + #[should_panic(expected = "serialized field element is not equal to input")] + fn recovers_original_hex_string(hex in "[0-9a-f]{64}") { + let fe: FieldElement:: = FieldElement::from_hex(&hex).expect("should accept any 32 byte hex string"); + let output_hex = fe.to_hex(); + + prop_assert_eq!(hex, output_hex, "serialized field element is not equal to input"); + } + + #[test] + fn accepts_odd_length_hex_strings(hex in "(?:0x)[0-9a-fA-F]+") { + // Here we inject a "0" immediately after the "0x" (if it exists) to construct an equivalent + // hex string with the opposite parity length. + let insert_index = if hex.starts_with("0x") { 2 } else { 0 }; + let mut opposite_parity_string = hex.to_string(); + opposite_parity_string.insert(insert_index, '0'); + + let fe_1: FieldElement:: = FieldElement::from_hex(&hex).unwrap(); + let fe_2: FieldElement:: = FieldElement::from_hex(&opposite_parity_string).unwrap(); + + prop_assert_eq!(fe_1, fe_2, "equivalent hex strings with opposite parity deserialized to different values"); + } + } } diff --git a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs index 3d93de5a9921..f7228e663147 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/generic_ark.rs @@ -78,7 +78,4 @@ pub trait AcirField: /// Returns the closest number of bytes to the bits specified /// This method truncates fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec; - - fn and(&self, rhs: &Self, num_bits: u32) -> Self; - fn xor(&self, rhs: &Self, num_bits: u32) -> Self; } diff --git a/noir/noir-repo/acvm-repo/acir_field/src/lib.rs b/noir/noir-repo/acvm-repo/acir_field/src/lib.rs index db97134cb1d3..1ad9a5b00370 100644 --- a/noir/noir-repo/acvm-repo/acir_field/src/lib.rs +++ b/noir/noir-repo/acvm-repo/acir_field/src/lib.rs @@ -2,9 +2,9 @@ #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] -mod generic_ark; mod field_element; +mod generic_ark; pub use generic_ark::AcirField; diff --git a/noir/noir-repo/acvm-repo/acvm/Cargo.toml b/noir/noir-repo/acvm-repo/acvm/Cargo.toml index 577978939b0c..59305ec49f04 100644 --- a/noir/noir-repo/acvm-repo/acvm/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm/Cargo.toml @@ -16,6 +16,7 @@ repository.workspace = true num-bigint.workspace = true thiserror.workspace = true tracing.workspace = true +serde.workspace = true acir.workspace = true brillig_vm.workspace = true @@ -36,7 +37,4 @@ bls12_381 = [ ] [dev-dependencies] -rand = "0.8.5" -proptest = "1.2.0" -paste = "1.0.14" -ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } \ No newline at end of file +ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/logic.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/logic.rs index 6e2ade3c49ba..5be888c8ac64 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/logic.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/logic.rs @@ -5,6 +5,7 @@ use acir::{ native_types::{Witness, WitnessMap}, AcirField, }; +use acvm_blackbox_solver::{bit_and, bit_xor}; /// Solves a [`BlackBoxFunc::And`][acir::circuit::black_box_functions::BlackBoxFunc::AND] opcode and inserts /// the result into the supplied witness map @@ -19,7 +20,7 @@ pub(super) fn and( "number of bits specified for each input must be the same" ); solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { - left.and(right, lhs.num_bits) + bit_and(left, right, lhs.num_bits) }) } @@ -36,7 +37,7 @@ pub(super) fn xor( "number of bits specified for each input must be the same" ); solve_logic_opcode(initial_witness, &lhs.witness, &rhs.witness, *output, |left, right| { - left.xor(right, lhs.num_bits) + bit_xor(left, right, lhs.num_bits) }) } @@ -46,11 +47,11 @@ fn solve_logic_opcode( a: &Witness, b: &Witness, result: Witness, - logic_op: impl Fn(&F, &F) -> F, + logic_op: impl Fn(F, F) -> F, ) -> Result<(), OpcodeResolutionError> { let w_l_value = witness_to_value(initial_witness, *a)?; let w_r_value = witness_to_value(initial_witness, *b)?; - let assignment = logic_op(w_l_value, w_r_value); + let assignment = logic_op(*w_l_value, *w_r_value); insert_value(&result, assignment, initial_witness) } diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs index 7e6c207b69af..3a639df044ae 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/brillig.rs @@ -13,6 +13,7 @@ use acir::{ }; use acvm_blackbox_solver::BlackBoxFunctionSolver; use brillig_vm::{FailureReason, MemoryValue, VMStatus, VM}; +use serde::{Deserialize, Serialize}; use crate::{pwg::OpcodeNotSolvable, OpcodeResolutionError}; @@ -286,7 +287,7 @@ impl<'b, B: BlackBoxFunctionSolver, F: AcirField> BrilligSolver<'b, F, B> { /// where the result of the foreign call has not yet been provided. /// /// The caller must resolve this opcode externally based upon the information in the request. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct ForeignCallWaitInfo { /// An identifier interpreted by the caller process pub function: String, diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs b/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs index 4f2676f8d2fa..4cd85a5ed87e 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs @@ -9,7 +9,7 @@ use acvm::{acir::AcirField, FieldElement}; pub fn and(lhs: JsString, rhs: JsString) -> JsString { let lhs = js_value_to_field_element(lhs.into()).unwrap(); let rhs = js_value_to_field_element(rhs.into()).unwrap(); - let result = lhs.and(&rhs, FieldElement::max_num_bits()); + let result = acvm::blackbox_solver::bit_and(lhs, rhs, FieldElement::max_num_bits()); field_element_to_js_string(&result) } @@ -18,7 +18,7 @@ pub fn and(lhs: JsString, rhs: JsString) -> JsString { pub fn xor(lhs: JsString, rhs: JsString) -> JsString { let lhs = js_value_to_field_element(lhs.into()).unwrap(); let rhs = js_value_to_field_element(rhs.into()).unwrap(); - let result = lhs.xor(&rhs, FieldElement::max_num_bits()); + let result = acvm::blackbox_solver::bit_xor(lhs, rhs, FieldElement::max_num_bits()); field_element_to_js_string(&result) } diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml index 00c87bbca74d..dd7f84e63e46 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml @@ -39,6 +39,9 @@ p256 = { version = "0.11.0", features = [ libaes = "0.7.0" +[dev-dependencies] +proptest.workspace = true + [features] bn254 = ["acir/bn254"] bls12_381 = ["acir/bls12_381"] diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/lib.rs index a68b52a2a620..c39deb641382 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/lib.rs @@ -15,12 +15,14 @@ mod bigint; mod curve_specific_solver; mod ecdsa; mod hash; +mod logic; pub use aes128::aes128_encrypt; pub use bigint::BigIntSolver; pub use curve_specific_solver::{BlackBoxFunctionSolver, StubbedBlackBoxSolver}; pub use ecdsa::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; pub use hash::{blake2s, blake3, keccak256, keccakf1600, sha256, sha256compression}; +pub use logic::{bit_and, bit_xor}; #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum BlackBoxResolutionError { diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/logic.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/logic.rs new file mode 100644 index 000000000000..6cccb521c20a --- /dev/null +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/logic.rs @@ -0,0 +1,99 @@ +use acir::AcirField; + +pub fn bit_and(lhs: F, rhs: F, num_bits: u32) -> F { + bitwise_op(lhs, rhs, num_bits, |lhs_byte, rhs_byte| lhs_byte & rhs_byte) +} + +pub fn bit_xor(lhs: F, rhs: F, num_bits: u32) -> F { + bitwise_op(lhs, rhs, num_bits, |lhs_byte, rhs_byte| lhs_byte ^ rhs_byte) +} + +fn bitwise_op(lhs: F, rhs: F, num_bits: u32, op: fn(u8, u8) -> u8) -> F { + // XXX: Gadgets like SHA256 need to have their input be a multiple of 8 + // This is not a restriction caused by SHA256, as it works on bits + // but most backends assume bytes. + // We could implicitly pad, however this may not be intuitive for users. + // assert!( + // num_bits % 8 == 0, + // "num_bits is not a multiple of 8, it is {}", + // num_bits + // ); + + let lhs_bytes = mask_to_be_bytes(lhs, num_bits); + let rhs_bytes = mask_to_be_bytes(rhs, num_bits); + + let and_byte_arr: Vec<_> = + lhs_bytes.into_iter().zip(rhs_bytes).map(|(left, right)| op(left, right)).collect(); + + F::from_be_bytes_reduce(&and_byte_arr) +} + +// mask_to methods will not remove any bytes from the field +// they are simply zeroed out +// Whereas truncate_to will remove those bits and make the byte array smaller +fn mask_to_be_bytes(field: F, num_bits: u32) -> Vec { + let mut bytes = field.to_be_bytes(); + mask_vector_le(&mut bytes, num_bits as usize); + bytes +} + +fn mask_vector_le(bytes: &mut [u8], num_bits: usize) { + // reverse to big endian format + bytes.reverse(); + + let mask_power = num_bits % 8; + let array_mask_index = num_bits / 8; + + for (index, byte) in bytes.iter_mut().enumerate() { + match index.cmp(&array_mask_index) { + std::cmp::Ordering::Less => { + // do nothing if the current index is less than + // the array index. + } + std::cmp::Ordering::Equal => { + let mask = 2u8.pow(mask_power as u32) - 1; + // mask the byte + *byte &= mask; + } + std::cmp::Ordering::Greater => { + // Anything greater than the array index + // will be set to zero + *byte = 0; + } + } + } + // reverse back to little endian + bytes.reverse(); +} + +#[cfg(test)] +mod tests { + use acir::FieldElement; + use proptest::prelude::*; + + use crate::{bit_and, bit_xor}; + + proptest! { + #[test] + fn matches_bitwise_and_on_u128s(x in 0..=u128::MAX, y in 0..=u128::MAX, bit_size in 128u32..) { + let x_as_field = FieldElement::from(x); + let y_as_field = FieldElement::from(y); + + let x_and_y = x & y; + let x_and_y_as_field = bit_and(x_as_field, y_as_field, bit_size); + + prop_assert_eq!(x_and_y_as_field, FieldElement::from(x_and_y), "AND on fields should match that on integers"); + } + + #[test] + fn matches_bitwise_xor_on_u128s(x in 0..=u128::MAX, y in 0..=u128::MAX, bit_size in 128u32..) { + let x_as_field = FieldElement::from(x); + let y_as_field = FieldElement::from(y); + + let x_xor_y = x ^ y; + let x_xor_y_as_field = bit_xor(x_as_field, y_as_field, bit_size); + + prop_assert_eq!(x_xor_y_as_field, FieldElement::from(x_xor_y), "XOR on fields should match that on integers"); + } + } +} diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml index 132dddd50e56..37d40de71a9e 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -18,14 +18,14 @@ acvm_blackbox_solver.workspace = true hex.workspace = true lazy_static = "1.4" -# BN254 fixed base scalar multiplication solver -grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } -ark-ec = { version = "^0.4.0", default-features = false } -ark-ff = { version = "^0.4.0", default-features = false } +ark-bn254.workspace = true +grumpkin.workspace = true +ark-ec.workspace = true +ark-ff.workspace = true num-bigint.workspace = true [dev-dependencies] -ark-std = { version = "^0.4.0", default-features = false } +ark-std.workspace = true criterion = "0.5.0" pprof = { version = "0.12", features = [ "flamegraph", @@ -36,7 +36,3 @@ pprof = { version = "0.12", features = [ [[bench]] name = "criterion" harness = false - -[features] -default = ["bn254"] -bn254 = ["acir/bn254"] diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/embedded_curve_ops.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/embedded_curve_ops.rs index 148accd8b23a..35cc68051d71 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/embedded_curve_ops.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/embedded_curve_ops.rs @@ -3,8 +3,9 @@ use ark_ec::AffineRepr; use ark_ff::MontConfig; use num_bigint::BigUint; +use crate::FieldElement; +use acir::AcirField; use acir::BlackBoxFunc; -use acir::{AcirField, FieldElement}; use crate::BlackBoxResolutionError; diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs index e3cea1153be1..08e0fb66a6d2 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -2,7 +2,6 @@ #![warn(clippy::semicolon_if_nothing_returned)] #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] -use acir::FieldElement; use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; mod embedded_curve_ops; @@ -15,6 +14,10 @@ use ark_ec::AffineRepr; pub use embedded_curve_ops::{embedded_curve_add, multi_scalar_mul}; pub use poseidon2::poseidon2_permutation; +// Temporary hack, this ensure that we always use a bn254 field here +// without polluting the feature flags of the `acir_field` crate. +type FieldElement = acir::acir_field::GenericFieldElement; + #[derive(Default)] pub struct Bn254BlackBoxSolver; diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/commitment.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/commitment.rs index 5ff2c269d8cb..03f03fcf5ab6 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/commitment.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/commitment.rs @@ -27,12 +27,13 @@ pub(crate) fn commit_native_with_index( #[cfg(test)] mod test { - use acir::{AcirField, FieldElement}; + use acir::AcirField; use ark_ec::short_weierstrass::Affine; use ark_std::{One, Zero}; use grumpkin::Fq; use crate::pedersen::commitment::commit_native_with_index; + use crate::FieldElement; #[test] fn commitment() { diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/hash.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/hash.rs index 5c637207491f..152526a9943c 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/hash.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/pedersen/hash.rs @@ -30,8 +30,9 @@ fn length_generator() -> &'static Affine { pub(crate) mod test { use super::*; + use crate::FieldElement; - use acir::{AcirField, FieldElement}; + use acir::AcirField; use ark_std::One; use grumpkin::Fq; diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs index 95c620aab0d9..18ed0b1d8ab7 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/poseidon2.rs @@ -1,7 +1,9 @@ -use acir::{AcirField, FieldElement}; +use acir::AcirField; use acvm_blackbox_solver::BlackBoxResolutionError; use lazy_static::lazy_static; +use crate::FieldElement; + pub fn poseidon2_permutation( inputs: &[FieldElement], len: u32, @@ -543,9 +545,9 @@ impl<'a> Poseidon2<'a> { #[cfg(test)] mod test { - use acir::{AcirField, FieldElement}; + use acir::AcirField; - use super::{field_from_hex, poseidon2_permutation}; + use super::{field_from_hex, poseidon2_permutation, FieldElement}; #[test] fn smoke_test() { diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/schnorr/mod.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/schnorr/mod.rs index 62e515a0792b..8e3a40803f81 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/schnorr/mod.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/schnorr/mod.rs @@ -65,9 +65,10 @@ fn schnorr_generate_challenge( #[cfg(test)] mod schnorr_tests { - use acir::{AcirField, FieldElement}; + use acir::AcirField; use super::verify_signature; + use crate::FieldElement; #[test] fn verifies_valid_signature() { diff --git a/noir/noir-repo/acvm-repo/brillig/src/foreign_call.rs b/noir/noir-repo/acvm-repo/brillig/src/foreign_call.rs index a439d5c32029..9a45a4d2f20b 100644 --- a/noir/noir-repo/acvm-repo/brillig/src/foreign_call.rs +++ b/noir/noir-repo/acvm-repo/brillig/src/foreign_call.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; /// Single output of a [foreign call][crate::Opcode::ForeignCall]. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[serde(untagged)] pub enum ForeignCallParam { Single(F), Array(Vec), diff --git a/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts b/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts index 86550c6687b3..699dcf5e9184 100644 --- a/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts +++ b/noir/noir-repo/compiler/integration-tests/test/node/prove_and_verify.test.ts @@ -24,7 +24,6 @@ it('end-to-end proof creation and verification (outer)', async () => { // bb.js part // // Proof creation - // const prover = new Backend(assert_lt_program); const proof = await backend.generateProof(witness); // Proof verification @@ -44,7 +43,6 @@ it('end-to-end proof creation and verification (outer) -- Verifier API', async ( const { witness } = await program.execute(inputs); // Generate proof - // const backend = new Backend(assert_lt_program); const proof = await backend.generateProof(witness); const verificationKey = await backend.getVerificationKey(); @@ -70,7 +68,6 @@ it('end-to-end proof creation and verification (inner)', async () => { // bb.js part // // Proof creation - // const prover = new Backend(assert_lt_program); const proof = await backend.generateProof(witness); // Proof verification @@ -90,8 +87,6 @@ it('end-to-end proving and verification with different instances', async () => { const { witness } = await program.execute(inputs); // bb.js part - // const prover = new Backend(assert_lt_program); - const proof = await backend.generateProof(witness); const verifier = new Backend(assert_lt_program); @@ -121,7 +116,6 @@ it('[BUG] -- bb.js null function or function signature mismatch (outer-inner) ', // // Proof creation // - // const prover = new Backend(assert_lt_program); // Create a proof using both proving systems, the majority of the time // one would only use outer proofs. const proofOuter = await backend.generateProof(witness); diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 609c88b92c20..71dd1b187610 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -1,8 +1,7 @@ use std::collections::BTreeMap; use acvm::acir::circuit::ErrorSelector; -use acvm::acir::native_types::Witness; -use iter_extended::{btree_map, vecmap}; +use iter_extended::vecmap; use noirc_abi::{Abi, AbiErrorType, AbiParameter, AbiReturnType, AbiType, AbiValue}; use noirc_frontend::ast::Visibility; use noirc_frontend::{ @@ -11,27 +10,23 @@ use noirc_frontend::{ macros_api::{HirExpression, HirLiteral}, node_interner::{FuncId, NodeInterner}, }; -use std::ops::Range; /// Arranges a function signature and a generated circuit's return witnesses into a /// `noirc_abi::Abi`. pub(super) fn gen_abi( context: &Context, func_id: &FuncId, - input_witnesses: Vec, - return_witnesses: Vec, return_visibility: Visibility, error_types: BTreeMap, ) -> Abi { let (parameters, return_type) = compute_function_abi(context, func_id); - let param_witnesses = param_witnesses_from_abi_param(¶meters, input_witnesses); let return_type = return_type .map(|typ| AbiReturnType { abi_type: typ, visibility: return_visibility.into() }); let error_types = error_types .into_iter() .map(|(selector, typ)| (selector, AbiErrorType::from_type(context, &typ))) .collect(); - Abi { parameters, return_type, param_witnesses, return_witnesses, error_types } + Abi { parameters, return_type, error_types } } pub(super) fn compute_function_abi( @@ -67,55 +62,6 @@ fn into_abi_params(context: &Context, params: Vec) -> Vec { }) } -// Takes each abi parameter and shallowly maps to the expected witness range in which the -// parameter's constituent values live. -fn param_witnesses_from_abi_param( - abi_params: &[AbiParameter], - input_witnesses: Vec, -) -> BTreeMap>> { - let mut idx = 0_usize; - if input_witnesses.is_empty() { - return BTreeMap::new(); - } - - btree_map(abi_params, |param| { - let num_field_elements_needed = param.typ.field_count() as usize; - let param_witnesses = &input_witnesses[idx..idx + num_field_elements_needed]; - - // It's likely that `param_witnesses` will consist of mostly incrementing witness indices. - // We then want to collapse these into `Range`s to save space. - let param_witnesses = collapse_ranges(param_witnesses); - idx += num_field_elements_needed; - (param.name.clone(), param_witnesses) - }) -} - -/// Takes a vector of [`Witnesses`][`Witness`] and collapses it into a vector of [`Range`]s of [`Witnesses`][`Witness`]. -fn collapse_ranges(witnesses: &[Witness]) -> Vec> { - if witnesses.is_empty() { - return Vec::new(); - } - let mut wit = Vec::new(); - let mut last_wit: Witness = witnesses[0]; - - for (i, witness) in witnesses.iter().enumerate() { - if i == 0 { - continue; - }; - let witness_index = witness.witness_index(); - let prev_witness_index = witnesses[i - 1].witness_index(); - if witness_index != prev_witness_index + 1 { - wit.push(last_wit..Witness(prev_witness_index + 1)); - last_wit = *witness; - }; - } - - let last_witness = witnesses.last().unwrap().witness_index(); - wit.push(last_wit..Witness(last_witness + 1)); - - wit -} - pub(super) fn value_from_hir_expression(context: &Context, expression: HirExpression) -> AbiValue { match expression { HirExpression::Tuple(expr_ids) => { @@ -169,38 +115,3 @@ pub(super) fn value_from_hir_expression(context: &Context, expression: HirExpres _ => unreachable!("Type cannot be used in the abi {:?}", expression), } } - -#[cfg(test)] -mod test { - use std::ops::Range; - - use acvm::acir::native_types::Witness; - - use super::collapse_ranges; - - #[test] - fn collapses_single_range() { - let witnesses: Vec<_> = vec![1, 2, 3].into_iter().map(Witness::from).collect(); - - let collapsed_witnesses = collapse_ranges(&witnesses); - - assert_eq!(collapsed_witnesses, vec![Range { start: Witness(1), end: Witness(4) },]); - } - - #[test] - fn collapse_ranges_correctly() { - let witnesses: Vec<_> = - vec![1, 2, 3, 5, 6, 2, 3, 4].into_iter().map(Witness::from).collect(); - - let collapsed_witnesses = collapse_ranges(&witnesses); - - assert_eq!( - collapsed_witnesses, - vec![ - Range { start: Witness(1), end: Witness(4) }, - Range { start: Witness(5), end: Witness(7) }, - Range { start: Witness(2), end: Witness(5) } - ] - ); - } -} diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index ca2fbce3204e..f8043a60f8cf 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -543,17 +543,9 @@ pub fn compile_no_check( info!("Program matches existing artifact, returning early"); return Ok(cached_program.expect("cache must exist for hashes to match")); } - let visibility = program.return_visibility; + let return_visibility = program.return_visibility; - let SsaProgramArtifact { - program, - debug, - warnings, - main_input_witnesses, - main_return_witnesses, - names, - error_types, - } = create_program( + let SsaProgramArtifact { program, debug, warnings, names, error_types, .. } = create_program( program, options.show_ssa, options.show_brillig, @@ -561,14 +553,7 @@ pub fn compile_no_check( options.benchmark_codegen, )?; - let abi = abi_gen::gen_abi( - context, - &main_function, - main_input_witnesses, - main_return_witnesses, - visibility, - error_types, - ); + let abi = abi_gen::gen_abi(context, &main_function, return_visibility, error_types); let file_map = filter_relevant_files(&debug, &context.file_manager); Ok(CompiledProgram { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs index 1e9220601009..dd63a254a18b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs @@ -43,6 +43,8 @@ pub enum RuntimeError { UnconstrainedSliceReturnToConstrained { call_stack: CallStack }, #[error("All `oracle` methods should be wrapped in an unconstrained fn")] UnconstrainedOracleReturnToConstrained { call_stack: CallStack }, + #[error("Could not resolve some references to the array. All references must be resolved at compile time")] + UnknownReference { call_stack: CallStack }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -123,7 +125,8 @@ impl RuntimeError { | RuntimeError::NestedSlice { call_stack, .. } | RuntimeError::BigIntModulus { call_stack, .. } | RuntimeError::UnconstrainedSliceReturnToConstrained { call_stack } - | RuntimeError::UnconstrainedOracleReturnToConstrained { call_stack } => call_stack, + | RuntimeError::UnconstrainedOracleReturnToConstrained { call_stack } + | RuntimeError::UnknownReference { call_stack } => call_stack, } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 6d7c5e570c12..40170e58a306 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -682,7 +682,9 @@ impl<'a> Context<'a> { self.handle_array_operation(instruction_id, dfg)?; } Instruction::Allocate => { - unreachable!("Expected all allocate instructions to be removed before acir_gen") + return Err(RuntimeError::UnknownReference { + call_stack: self.acir_context.get_call_stack().clone(), + }); } Instruction::Store { .. } => { unreachable!("Expected all store instructions to be removed before acir_gen") @@ -839,9 +841,10 @@ impl<'a> Context<'a> { self.handle_ssa_call_outputs(result_ids, outputs, dfg)?; } Value::ForeignFunction(_) => { + // TODO: Remove this once elaborator is default frontend. This is now caught by a lint inside the frontend. return Err(RuntimeError::UnconstrainedOracleReturnToConstrained { call_stack: self.acir_context.get_call_stack(), - }) + }); } _ => unreachable!("expected calling a function but got {function_value:?}"), } @@ -1306,6 +1309,9 @@ impl<'a> Context<'a> { } Ok(AcirValue::Array(values)) } + Type::Reference(reference_type) => { + self.array_get_value(reference_type.as_ref(), block_id, var_index) + } _ => unreachable!("ICE: Expected an array or numeric but got {ssa_type:?}"), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 749e41d9c1cf..50836add8de4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -34,7 +34,7 @@ pub enum ExpressionKind { Lambda(Box), Parenthesized(Box), Quote(BlockExpression), - Comptime(BlockExpression), + Comptime(BlockExpression, Span), // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while @@ -536,7 +536,7 @@ impl Display for ExpressionKind { Lambda(lambda) => lambda.fmt(f), Parenthesized(sub_expr) => write!(f, "({sub_expr})"), Quote(block) => write!(f, "quote {block}"), - Comptime(block) => write!(f, "comptime {block}"), + Comptime(block, _) => write!(f, "comptime {block}"), Error => write!(f, "Error"), Resolved(_) => write!(f, "?Resolved"), } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs index 39b232289670..bd2b45d9c487 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/mod.rs @@ -390,26 +390,3 @@ impl std::fmt::Display for Visibility { } } } - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -/// Represents whether the return value should compromise of unique witness indices such that no -/// index occurs within the program's abi more than once. -/// -/// This is useful for application stacks that require an uniform abi across across multiple -/// circuits. When index duplication is allowed, the compiler may identify that a public input -/// reaches the output unaltered and is thus referenced directly, causing the input and output -/// witness indices to overlap. Similarly, repetitions of copied values in the output may be -/// optimized away. -pub enum Distinctness { - Distinct, - DuplicationAllowed, -} - -impl std::fmt::Display for Distinctness { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Distinct => write!(f, "distinct"), - Self::DuplicationAllowed => write!(f, "duplication-allowed"), - } - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index 5eee7ee6c7cc..a922f552c4b1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -59,7 +59,7 @@ impl<'context> Elaborator<'context> { ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), ExpressionKind::Quote(quote) => self.elaborate_quote(quote), - ExpressionKind::Comptime(comptime) => { + ExpressionKind::Comptime(comptime, _) => { return self.elaborate_comptime_block(comptime, expr.span) } ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs index d228588d367d..4859ac5f97ca 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/lints.rs @@ -10,7 +10,7 @@ use crate::{ HirExpression, HirLiteral, NodeInterner, NoirFunction, UnaryOp, UnresolvedTypeData, Visibility, }, - node_interner::{DefinitionKind, ExprId}, + node_interner::{DefinitionKind, ExprId, FuncId}, Type, }; use acvm::AcirField; @@ -72,6 +72,40 @@ pub(super) fn low_level_function_outside_stdlib( } } +/// Oracle definitions (functions with the `#[oracle]` attribute) must be marked as unconstrained. +pub(super) fn oracle_not_marked_unconstrained(func: &NoirFunction) -> Option { + let is_oracle_function = + func.attributes().function.as_ref().map_or(false, |func| func.is_oracle()); + if is_oracle_function && !func.def.is_unconstrained { + Some(ResolverError::OracleMarkedAsConstrained { ident: func.name_ident().clone() }) + } else { + None + } +} + +/// Oracle functions may not be called by constrained functions directly. +/// +/// In order for a constrained function to call an oracle it must first call through an unconstrained function. +pub(super) fn oracle_called_from_constrained_function( + interner: &NodeInterner, + called_func: &FuncId, + calling_from_constrained_runtime: bool, + span: Span, +) -> Option { + if !calling_from_constrained_runtime { + return None; + } + + let function_attributes = interner.function_attributes(called_func); + let is_oracle_call = + function_attributes.function.as_ref().map_or(false, |func| func.is_oracle()); + if is_oracle_call { + Some(ResolverError::UnconstrainedOracleReturnToConstrained { span }) + } else { + None + } +} + /// `pub` is required on return types for entry point functions pub(super) fn missing_pub(func: &NoirFunction, is_entry_point: bool) -> Option { if is_entry_point diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index 217119cd2e7a..37ad74b78b0a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -565,6 +565,7 @@ impl<'context> Elaborator<'context> { self.run_lint(|elaborator| { lints::unnecessary_pub_return(func, elaborator.pub_allowed(func)).map(Into::into) }); + self.run_lint(|_| lints::oracle_not_marked_unconstrained(func).map(Into::into)); self.run_lint(|elaborator| { lints::low_level_function_outside_stdlib(func, elaborator.crate_id).map(Into::into) }); @@ -801,7 +802,6 @@ impl<'context> Elaborator<'context> { self.push_err(DefCollectorErrorKind::MutableReferenceInTraitImpl { span }); } - assert!(trait_impl.trait_id.is_some()); if let Some(trait_id) = trait_impl.trait_id { self.generics = trait_impl.resolved_generics.clone(); self.collect_trait_impl_methods(trait_id, trait_impl); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 1b611d8f71f5..3baa7054fc58 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -17,7 +17,7 @@ use crate::{ }, hir_def::{ expr::{ - HirBinaryOp, HirCallExpression, HirIdent, HirMemberAccess, HirMethodReference, + HirBinaryOp, HirCallExpression, HirMemberAccess, HirMethodReference, HirPrefixExpression, }, function::{FuncMeta, Parameters}, @@ -1155,6 +1155,19 @@ impl<'context> Elaborator<'context> { let is_unconstrained_call = self.is_unconstrained_call(call.func); let crossing_runtime_boundary = is_current_func_constrained && is_unconstrained_call; if crossing_runtime_boundary { + let called_func_id = self + .interner + .lookup_function_from_expr(&call.func) + .expect("Called function should exist"); + self.run_lint(|elaborator| { + lints::oracle_called_from_constrained_function( + elaborator.interner, + &called_func_id, + is_current_func_constrained, + span, + ) + .map(Into::into) + }); let errors = lints::unconstrained_function_args(&args); for error in errors { self.push_err(error); @@ -1173,15 +1186,12 @@ impl<'context> Elaborator<'context> { } fn is_unconstrained_call(&self, expr: ExprId) -> bool { - if let HirExpression::Ident(HirIdent { id, .. }, _) = self.interner.expression(&expr) { - if let Some(DefinitionKind::Function(func_id)) = - self.interner.try_definition(id).map(|def| &def.kind) - { - let modifiers = self.interner.function_modifiers(func_id); - return modifiers.is_unconstrained; - } + if let Some(func_id) = self.interner.lookup_function_from_expr(&expr) { + let modifiers = self.interner.function_modifiers(&func_id); + modifiers.is_unconstrained + } else { + false } - false } /// Check if the given method type requires a mutable reference to the object type, and check diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index df4bc941f66e..05962420f8a6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -36,7 +36,7 @@ pub enum InterpreterError { CannotInlineMacro { value: Value, location: Location }, UnquoteFoundDuringEvaluation { location: Location }, - Unimplemented { item: &'static str, location: Location }, + Unimplemented { item: String, location: Location }, // Perhaps this should be unreachable! due to type checking also preventing this error? // Currently it and the Continue variant are the only interpreter errors without a Location field diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index e1e19ad653c8..186eaa58c146 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -59,11 +59,6 @@ impl<'a> Interpreter<'a> { let previous_state = self.enter_function(); let meta = self.interner.function_meta(&function); - if meta.kind != FunctionKind::Normal { - let item = "Evaluation for builtin functions"; - return Err(InterpreterError::Unimplemented { item, location }); - } - if meta.parameters.len() != arguments.len() { return Err(InterpreterError::ArgumentCountMismatch { expected: meta.parameters.len(), @@ -72,6 +67,10 @@ impl<'a> Interpreter<'a> { }); } + if meta.kind != FunctionKind::Normal { + return self.call_builtin(function, arguments, location); + } + let parameters = meta.parameters.0.clone(); for ((parameter, typ, _), (argument, arg_location)) in parameters.iter().zip(arguments) { self.define_pattern(parameter, typ, argument, arg_location)?; @@ -84,6 +83,35 @@ impl<'a> Interpreter<'a> { Ok(result) } + fn call_builtin( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + location: Location, + ) -> IResult { + let attributes = self.interner.function_attributes(&function); + let func_attrs = attributes.function.as_ref() + .expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); + + if let Some(builtin) = func_attrs.builtin() { + let item = format!("Evaluation for builtin functions like {builtin}"); + Err(InterpreterError::Unimplemented { item, location }) + } else if let Some(foreign) = func_attrs.foreign() { + let item = format!("Evaluation for foreign functions like {foreign}"); + Err(InterpreterError::Unimplemented { item, location }) + } else if let Some(oracle) = func_attrs.oracle() { + if oracle == "print" { + self.print_oracle(arguments) + } else { + let item = format!("Evaluation for oracle functions like {oracle}"); + Err(InterpreterError::Unimplemented { item, location }) + } + } else { + let name = self.interner.function_name(&function); + unreachable!("Non-builtin, lowlevel or oracle builtin fn '{name}'") + } + } + fn call_closure( &mut self, closure: HirLambda, @@ -121,8 +149,8 @@ impl<'a> Interpreter<'a> { let mut scope = Vec::new(); if self.scopes.len() > 1 { scope = self.scopes.drain(1..).collect(); - self.push_scope(); } + self.push_scope(); (std::mem::take(&mut self.in_loop), scope) } @@ -177,10 +205,11 @@ impl<'a> Interpreter<'a> { } }, HirPattern::Struct(struct_type, pattern_fields, _) => { + self.push_scope(); self.type_check(typ, &argument, location)?; self.type_check(struct_type, &argument, location)?; - match argument { + let res = match argument { Value::Struct(fields, struct_type) if fields.len() == pattern_fields.len() => { for (field_name, field_pattern) in pattern_fields { let field = fields.get(&field_name.0.contents).ok_or_else(|| { @@ -206,7 +235,9 @@ impl<'a> Interpreter<'a> { value, location, }), - } + }; + self.pop_scope(); + res } } } @@ -219,7 +250,8 @@ impl<'a> Interpreter<'a> { argument: Value, location: Location, ) -> IResult<()> { - self.type_check(typ, &argument, location)?; + // Temporarily disabled since this fails on generic types + // self.type_check(typ, &argument, location)?; self.current_scope_mut().insert(id, argument); Ok(()) } @@ -287,7 +319,10 @@ impl<'a> Interpreter<'a> { match self.interner.expression(&id) { HirExpression::Ident(ident, _) => self.evaluate_ident(ident, id), HirExpression::Literal(literal) => self.evaluate_literal(literal, id), - HirExpression::Block(block) => self.evaluate_block(block), + HirExpression::Block(block) => { + dbg!("going to evaluate a block"); + self.evaluate_block(block) + } HirExpression::Prefix(prefix) => self.evaluate_prefix(prefix, id), HirExpression::Infix(infix) => self.evaluate_infix(infix, id), HirExpression::Index(index) => self.evaluate_index(index, id), @@ -324,13 +359,14 @@ impl<'a> Interpreter<'a> { } DefinitionKind::Local(_) => self.lookup(&ident), DefinitionKind::Global(global_id) => { - // Don't need to check let_.comptime, we can evaluate non-comptime globals too. // Avoid resetting the value if it is already known if let Ok(value) = self.lookup(&ident) { Ok(value) } else { let let_ = self.interner.get_global_let_statement(*global_id).unwrap(); - self.evaluate_let(let_)?; + if let_.comptime { + self.evaluate_let(let_.clone())?; + } self.lookup(&ident) } } @@ -360,7 +396,11 @@ impl<'a> Interpreter<'a> { self.evaluate_integer(value, is_negative, id) } HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), - HirLiteral::FmtStr(_, _) => todo!("Evaluate format strings"), + HirLiteral::FmtStr(_, _) => { + let item = "format strings in a comptime context".into(); + let location = self.interner.expr_location(&id); + Err(InterpreterError::Unimplemented { item, location }) + } HirLiteral::Array(array) => self.evaluate_array(array, id), HirLiteral::Slice(array) => self.evaluate_slice(array, id), } @@ -454,6 +494,14 @@ impl<'a> Interpreter<'a> { Ok(Value::I64(value)) } } + } else if let Type::TypeVariable(variable, TypeVariableKind::IntegerOrField) = &typ { + Ok(Value::Field(value)) + } else if let Type::TypeVariable(variable, TypeVariableKind::Integer) = &typ { + let value: u64 = value + .try_to_u64() + .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; + let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; + Ok(Value::U64(value)) } else { Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) } @@ -1241,6 +1289,7 @@ impl<'a> Interpreter<'a> { Err(InterpreterError::Continue) => continue, Err(other) => return Err(other), } + self.pop_scope(); } @@ -1269,4 +1318,17 @@ impl<'a> Interpreter<'a> { pub(super) fn evaluate_comptime(&mut self, statement: StmtId) -> IResult { self.evaluate_statement(statement) } + + fn print_oracle(&self, arguments: Vec<(Value, Location)>) -> Result { + assert_eq!(arguments.len(), 2); + + let print_newline = arguments[0].0 == Value::Bool(true); + if print_newline { + println!("{}", arguments[1].0); + } else { + print!("{}", arguments[1].0); + } + + Ok(Value::Unit) + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs index 02010b6886d1..9e3b03ef525c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/scan.rs @@ -53,13 +53,14 @@ impl<'interner> Interpreter<'interner> { /// Otherwise, scan through its expression for any comptime blocks to evaluate. pub fn scan_global(&mut self, global: GlobalId) -> IResult<()> { if let Some(let_) = self.interner.get_global_let_statement(global) { + // dbg!(let_.clone()); if let_.comptime { + dbg!("got here"); self.evaluate_let(let_)?; } else { self.scan_expression(let_.expression)?; } } - Ok(()) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 43d461ad0433..11bbbc7484da 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, fmt::Display, rc::Rc}; use acvm::{AcirField, FieldElement}; use im::Vector; @@ -135,7 +135,7 @@ impl Value { } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn"; + let item = "Returning closures from a comptime fn".into(); return Err(InterpreterError::Unimplemented { item, location }); } Value::Tuple(fields) => { @@ -235,7 +235,7 @@ impl Value { } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn"; + let item = "Returning closures from a comptime fn".into(); return Err(InterpreterError::Unimplemented { item, location }); } Value::Tuple(fields) => { @@ -306,3 +306,49 @@ impl Value { fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Unit => write!(f, "()"), + Value::Bool(value) => { + let msg = if *value { "true" } else { "false" }; + write!(f, "{msg}") + } + Value::Field(value) => write!(f, "{value}"), + Value::I8(value) => write!(f, "{value}"), + Value::I16(value) => write!(f, "{value}"), + Value::I32(value) => write!(f, "{value}"), + Value::I64(value) => write!(f, "{value}"), + Value::U8(value) => write!(f, "{value}"), + Value::U16(value) => write!(f, "{value}"), + Value::U32(value) => write!(f, "{value}"), + Value::U64(value) => write!(f, "{value}"), + Value::String(value) => write!(f, "{value}"), + Value::Function(_, _) => write!(f, "(function)"), + Value::Closure(_, _, _) => write!(f, "(closure)"), + Value::Tuple(fields) => { + let fields = vecmap(fields, ToString::to_string); + write!(f, "({})", fields.join(", ")) + } + Value::Struct(fields, typ) => { + let typename = match typ.follow_bindings() { + Type::Struct(def, _) => def.borrow().name.to_string(), + other => other.to_string(), + }; + let fields = vecmap(fields, |(name, value)| format!("{}: {}", name, value)); + write!(f, "{typename} {{ {} }}", fields.join(", ")) + } + Value::Pointer(value) => write!(f, "&mut {}", value.borrow()), + Value::Array(values, _) => { + let values = vecmap(values, ToString::to_string); + write!(f, "[{}]", values.join(", ")) + } + Value::Slice(values, _) => { + let values = vecmap(values, ToString::to_string); + write!(f, "&[{}]", values.join(", ")) + } + Value::Code(_) => todo!(), + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index e8985deda113..06f6dda71427 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -40,8 +40,6 @@ pub enum ResolverError { UnnecessaryPub { ident: Ident, position: PubPosition }, #[error("Required 'pub', main function must return public value")] NecessaryPub { ident: Ident }, - #[error("'distinct' keyword can only be used with main method")] - DistinctNotAllowed { ident: Ident }, #[error("Missing expression for declared constant")] MissingRhsExpr { name: String, span: Span }, #[error("Expression invalid in an array length context")] @@ -80,6 +78,12 @@ pub enum ResolverError { AbiAttributeOutsideContract { span: Span }, #[error("Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library")] LowLevelFunctionOutsideOfStdlib { ident: Ident }, + #[error( + "Usage of the `#[oracle]` function attribute is only valid on unconstrained functions" + )] + OracleMarkedAsConstrained { ident: Ident }, + #[error("Oracle functions cannot be called directly from constrained functions")] + UnconstrainedOracleReturnToConstrained { span: Span }, #[error("Dependency cycle found, '{item}' recursively depends on itself: {cycle} ")] DependencyCycle { span: Span, item: String, cycle: String }, #[error("break/continue are only allowed in unconstrained functions")] @@ -214,18 +218,6 @@ impl<'a> From<&'a ResolverError> for Diagnostic { diag.add_note("The `pub` keyword is mandatory for the entry-point function return type because the verifier cannot retrieve private witness and thus the function will not be able to return a 'priv' value".to_owned()); diag } - ResolverError::DistinctNotAllowed { ident } => { - let name = &ident.0.contents; - - let mut diag = Diagnostic::simple_error( - format!("Invalid `distinct` keyword on return type of function {name}"), - "Invalid distinct on return type".to_string(), - ident.0.span(), - ); - - diag.add_note("The `distinct` keyword is only valid when used on the main function of a program, as its only purpose is to ensure that all witness indices that occur in the abi are unique".to_owned()); - diag - } ResolverError::MissingRhsExpr { name, span } => Diagnostic::simple_error( format!( "no expression specifying the value stored by the constant variable {name}" @@ -327,6 +319,16 @@ impl<'a> From<&'a ResolverError> for Diagnostic { "Usage of the `#[foreign]` or `#[builtin]` function attributes are not allowed outside of the Noir standard library".into(), ident.span(), ), + ResolverError::OracleMarkedAsConstrained { ident } => Diagnostic::simple_error( + error.to_string(), + "Oracle functions must have the `unconstrained` keyword applied".into(), + ident.span(), + ), + ResolverError::UnconstrainedOracleReturnToConstrained { span } => Diagnostic::simple_error( + error.to_string(), + "This oracle call must be wrapped in a call to another unconstrained function before being returned to a constrained runtime".into(), + *span, + ), ResolverError::DependencyCycle { span, item, cycle } => { Diagnostic::simple_error( "Dependency cycle found".into(), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 4988628db832..01f58ba4c275 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1642,7 +1642,9 @@ impl<'a> Resolver<'a> { // The quoted expression isn't resolved since we don't want errors if variables aren't defined ExpressionKind::Quote(block) => HirExpression::Quote(block), - ExpressionKind::Comptime(block) => HirExpression::Comptime(self.resolve_block(block)), + ExpressionKind::Comptime(block, _) => { + HirExpression::Comptime(self.resolve_block(block)) + } ExpressionKind::Resolved(_) => unreachable!( "ExpressionKind::Resolved should only be emitted by the comptime interpreter" ), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index fdda271e79ca..d8555b4fbf75 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -702,20 +702,27 @@ pub enum FunctionAttribute { } impl FunctionAttribute { - pub fn builtin(self) -> Option { + pub fn builtin(&self) -> Option<&String> { match self { FunctionAttribute::Builtin(name) => Some(name), _ => None, } } - pub fn foreign(self) -> Option { + pub fn foreign(&self) -> Option<&String> { match self { FunctionAttribute::Foreign(name) => Some(name), _ => None, } } + pub fn oracle(&self) -> Option<&String> { + match self { + FunctionAttribute::Oracle(name) => Some(name), + _ => None, + } + } + pub fn is_foreign(&self) -> bool { matches!(self, FunctionAttribute::Foreign(_)) } @@ -832,7 +839,6 @@ pub enum Keyword { Contract, Crate, Dep, - Distinct, Else, Field, Fn, @@ -877,7 +883,6 @@ impl fmt::Display for Keyword { Keyword::Contract => write!(f, "contract"), Keyword::Crate => write!(f, "crate"), Keyword::Dep => write!(f, "dep"), - Keyword::Distinct => write!(f, "distinct"), Keyword::Else => write!(f, "else"), Keyword::Field => write!(f, "Field"), Keyword::Fn => write!(f, "fn"), @@ -925,7 +930,6 @@ impl Keyword { "contract" => Keyword::Contract, "crate" => Keyword::Crate, "dep" => Keyword::Dep, - "distinct" => Keyword::Distinct, "else" => Keyword::Else, "Field" => Keyword::Field, "fn" => Keyword::Fn, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index a25d6488c831..7ecea5c9eac5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -18,7 +18,6 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, - token::FunctionAttribute, Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, }; use acvm::{acir::AcirField, FieldElement}; @@ -216,18 +215,18 @@ impl<'interner> Monomorphizer<'interner> { let attributes = self.interner.function_attributes(&id); match self.interner.function_meta(&id).kind { FunctionKind::LowLevel => { - let attribute = attributes.function.clone().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function.as_ref().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.foreign().expect( "ice: function marked as foreign, but attribute kind does not match this", ); - Definition::LowLevel(opcode) + Definition::LowLevel(opcode.to_string()) } FunctionKind::Builtin => { - let attribute = attributes.function.clone().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function.as_ref().expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.builtin().expect( "ice: function marked as builtin, but attribute kind does not match this", ); - Definition::Builtin(opcode) + Definition::Builtin(opcode.to_string()) } FunctionKind::Normal => { let id = @@ -235,15 +234,11 @@ impl<'interner> Monomorphizer<'interner> { Definition::Function(id) } FunctionKind::Oracle => { - let attr = attributes - .function - .clone() - .expect("Oracle function must have an oracle attribute"); - - match attr { - FunctionAttribute::Oracle(name) => Definition::Oracle(name), - _ => unreachable!("Oracle function must have an oracle attribute"), - } + let attribute = attributes.function.as_ref().expect("all oracle functions must contain a function attribute which contains the opcode which it links to"); + let opcode = attribute.oracle().expect( + "ice: function marked as builtin, but attribute kind does not match this", + ); + Definition::Oracle(opcode.to_string()) } FunctionKind::Recursive => { unreachable!("Only main can be specified as recursive, which should already be checked"); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 327900af641f..cef49332b001 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -20,6 +20,7 @@ use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::ast::{BinaryOpKind, FunctionDefinition, ItemVisibility}; use crate::hir::resolution::errors::ResolverError; +use crate::hir_def::expr::HirIdent; use crate::hir_def::stmt::HirLetStatement; use crate::hir_def::traits::TraitImpl; use crate::hir_def::traits::{Trait, TraitConstraint}; @@ -824,6 +825,21 @@ impl NodeInterner { self.function_modules[&func] } + /// Returns the [`FuncId`] corresponding to the function referred to by `expr_id` + pub fn lookup_function_from_expr(&self, expr: &ExprId) -> Option { + if let HirExpression::Ident(HirIdent { id, .. }, _) = self.expression(expr) { + if let Some(DefinitionKind::Function(func_id)) = + self.try_definition(id).map(|def| &def.kind) + { + Some(*func_id) + } else { + None + } + } else { + None + } + } + /// Returns the interned HIR function corresponding to `func_id` // // Cloning HIR structures is cheap, so we return owned structures diff --git a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop index 9acb5ef8b581..c6cb788a5a4b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop +++ b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop @@ -67,7 +67,6 @@ extern { "contract" => BorrowedToken::Keyword(noir_token::Keyword::Contract), "crate" => BorrowedToken::Keyword(noir_token::Keyword::Crate), "dep" => BorrowedToken::Keyword(noir_token::Keyword::Dep), - "distinct" => BorrowedToken::Keyword(noir_token::Keyword::Distinct), "else" => BorrowedToken::Keyword(noir_token::Keyword::Else), "Field" => BorrowedToken::Keyword(noir_token::Keyword::Field), "fn" => BorrowedToken::Keyword(noir_token::Keyword::Fn), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index 9f9a82009541..af3d4caa1452 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -30,8 +30,6 @@ pub enum ParserErrorReason { TraitImplFunctionModifiers, #[error("comptime keyword is deprecated")] ComptimeDeprecated, - #[error("distinct keyword is deprecated. The `distinct` behavior is now the default.")] - DistinctDeprecated, #[error("{0} are experimental and aren't fully supported yet")] ExperimentalFeature(&'static str), #[error( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index 702ea79af9d7..cabc788e07dc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -545,7 +545,9 @@ fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a where S: NoirParser + 'a, { - keyword(Keyword::Comptime).ignore_then(block(statement)).map(ExpressionKind::Comptime) + keyword(Keyword::Comptime) + .ignore_then(spanned(block(statement))) + .map(|(block, span)| ExpressionKind::Comptime(block, span)) } fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index 40180a9f9ac5..4db5637f6a71 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -4,16 +4,12 @@ use super::{ parameter_name_recovery, parameter_recovery, parenthesized, parse_type, pattern, self_parameter, where_clause, NoirParser, }; +use crate::ast::{ + FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, Visibility, +}; use crate::parser::labels::ParsingRuleLabel; use crate::parser::spanned; use crate::token::{Keyword, Token}; -use crate::{ - ast::{ - FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, - Visibility, - }, - parser::{ParserError, ParserErrorReason}, -}; use chumsky::prelude::*; @@ -95,20 +91,9 @@ pub(super) fn generics() -> impl NoirParser> { .map(|opt| opt.unwrap_or_default()) } -#[deprecated = "Distinct keyword is now deprecated. Remove this function after the 0.30.0 release"] -fn optional_distinctness() -> impl NoirParser { - keyword(Keyword::Distinct).or_not().validate(|opt, span, emit| { - if opt.is_some() { - emit(ParserError::with_reason(ParserErrorReason::DistinctDeprecated, span)); - } - opt.is_some() - }) -} - pub(super) fn function_return_type() -> impl NoirParser<(Visibility, FunctionReturnType)> { #[allow(deprecated)] just(Token::Arrow) - .ignore_then(optional_distinctness()) .ignore_then(optional_visibility()) .then(spanned(parse_type())) .or_not() @@ -224,8 +209,6 @@ mod test { // A leading plus is not allowed. "fn func_name(f: Field, y : T) where T: + SomeTrait {}", "fn func_name(f: Field, y : T) where T: TraitX + {}", - // `distinct` is deprecated - "fn main(x: pub u8, y: pub u8) -> distinct pub [u8; 2] { [x, y] }", ], ); } diff --git a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs index d9c9c7f97317..dfecd301b351 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, str}; -use acvm::{acir::AcirField, brillig_vm::brillig::ForeignCallParam, FieldElement}; +use acvm::{acir::AcirField, brillig_vm::brillig::ForeignCallParam}; use iter_extended::vecmap; use regex::{Captures, Regex}; use serde::{Deserialize, Serialize}; @@ -51,19 +51,19 @@ pub enum PrintableType { /// For example, a toml file will parse into TomlTypes /// and those TomlTypes will be mapped to Value #[derive(Debug, Clone, Serialize, PartialEq)] -pub enum PrintableValue { - Field(FieldElement), +pub enum PrintableValue { + Field(F), String(String), - Vec { array_elements: Vec, is_slice: bool }, - Struct(BTreeMap), + Vec { array_elements: Vec>, is_slice: bool }, + Struct(BTreeMap>), Other, } /// In order to display a `PrintableValue` we need a `PrintableType` to accurately /// convert the value into a human-readable format. -pub enum PrintableValueDisplay { - Plain(PrintableValue, PrintableType), - FmtString(String, Vec<(PrintableValue, PrintableType)>), +pub enum PrintableValueDisplay { + Plain(PrintableValue, PrintableType), + FmtString(String, Vec<(PrintableValue, PrintableType)>), } #[derive(Debug, Error)] @@ -81,12 +81,10 @@ pub enum ForeignCallError { ResolvedAssertMessage(String), } -impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { +impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { type Error = ForeignCallError; - fn try_from( - foreign_call_inputs: &[ForeignCallParam], - ) -> Result { + fn try_from(foreign_call_inputs: &[ForeignCallParam]) -> Result { let (is_fmt_str, foreign_call_inputs) = foreign_call_inputs.split_last().ok_or(ForeignCallError::MissingForeignCallInputs)?; @@ -98,9 +96,9 @@ impl TryFrom<&[ForeignCallParam]> for PrintableValueDisplay { } } -fn convert_string_inputs( - foreign_call_inputs: &[ForeignCallParam], -) -> Result { +fn convert_string_inputs( + foreign_call_inputs: &[ForeignCallParam], +) -> Result, ForeignCallError> { // Fetch the PrintableType from the foreign call input // The remaining input values should hold what is to be printed let (printable_type_as_values, input_values) = @@ -115,9 +113,9 @@ fn convert_string_inputs( Ok(PrintableValueDisplay::Plain(value, printable_type)) } -fn convert_fmt_string_inputs( - foreign_call_inputs: &[ForeignCallParam], -) -> Result { +fn convert_fmt_string_inputs( + foreign_call_inputs: &[ForeignCallParam], +) -> Result, ForeignCallError> { let (message, input_and_printable_types) = foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?; @@ -144,8 +142,8 @@ fn convert_fmt_string_inputs( Ok(PrintableValueDisplay::FmtString(message_as_string, output)) } -fn fetch_printable_type( - printable_type: &ForeignCallParam, +fn fetch_printable_type( + printable_type: &ForeignCallParam, ) -> Result { let printable_type_as_fields = printable_type.fields(); let printable_type_as_string = decode_string_value(&printable_type_as_fields); @@ -154,7 +152,7 @@ fn fetch_printable_type( Ok(printable_type) } -fn to_string(value: &PrintableValue, typ: &PrintableType) -> Option { +fn to_string(value: &PrintableValue, typ: &PrintableType) -> Option { let mut output = String::new(); match (value, typ) { (PrintableValue::Field(f), PrintableType::Field) => { @@ -269,7 +267,7 @@ fn replace_all( Ok(new) } -impl std::fmt::Display for PrintableValueDisplay { +impl std::fmt::Display for PrintableValueDisplay { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Plain(value, typ) => { @@ -295,7 +293,7 @@ impl std::fmt::Display for PrintableValueDisplay { /// A singular '0' will be prepended as well if the trimmed string has an odd length. /// A hex string's length needs to be even to decode into bytes, as two digits correspond to /// one byte. -fn format_field_string(field: FieldElement) -> String { +fn format_field_string(field: F) -> String { if field.is_zero() { return "0x00".to_owned(); } @@ -306,11 +304,11 @@ fn format_field_string(field: FieldElement) -> String { "0x".to_owned() + &trimmed_field } -/// Assumes that `field_iterator` contains enough [FieldElement] in order to decode the [PrintableType] -pub fn decode_value( - field_iterator: &mut impl Iterator, +/// Assumes that `field_iterator` contains enough field elements in order to decode the [PrintableType] +pub fn decode_value( + field_iterator: &mut impl Iterator, typ: &PrintableType, -) -> PrintableValue { +) -> PrintableValue { match typ { PrintableType::Field | PrintableType::SignedInteger { .. } @@ -346,7 +344,7 @@ pub fn decode_value( is_slice: false, }, PrintableType::String { length } => { - let field_elements: Vec = field_iterator.take(*length as usize).collect(); + let field_elements: Vec = field_iterator.take(*length as usize).collect(); PrintableValue::String(decode_string_value(&field_elements)) } @@ -372,11 +370,11 @@ pub fn decode_value( // we decode the reference, but it's not really used for printing decode_value(field_iterator, typ) } - PrintableType::Unit => PrintableValue::Field(FieldElement::zero()), + PrintableType::Unit => PrintableValue::Field(F::zero()), } } -pub fn decode_string_value(field_elements: &[FieldElement]) -> String { +pub fn decode_string_value(field_elements: &[F]) -> String { // TODO: Replace with `into` when Char is supported let string_as_slice = vecmap(field_elements, |e| { let mut field_as_bytes = e.to_be_bytes(); diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index cea0dd7ed385..9eb6e6f92390 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -157,6 +157,7 @@ "preprocess", "prettytable", "printstd", + "proptest", "pseudocode", "pubkey", "quantile", diff --git a/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md b/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md index 1c0405853401..f7505bef7abe 100644 --- a/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md +++ b/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md @@ -100,7 +100,8 @@ After the export and codegen steps, you should have an `index.ts` like: export type Field = string; -export const is_equal_circuit: CompiledCircuit = {"abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"param_witnesses":{"x":[{"start":0,"end":1}],"y":[{"start":1,"end":2}]},"return_type":{"abi_type":{"kind":"boolean"},"visibility":"private"},"return_witnesses":[4]},"bytecode":"H4sIAAAAAAAA/7WUMQ7DIAxFQ0Krrr2JjSGYLVcpKrn/CaqqDQN12WK+hPBgmWd/wEyHbF1SS923uhOs3pfoChI+wKXMAXzIKyNj4PB0TFTYc0w5RUjoqeAeEu1wqK0F54RGkWvW44LPzExnlkbMEs4JNZmN8PxS42uHv82T8a3Jeyn2Ks+VLPcO558HmyLMCDOXAXXtpPt4R/Rt9T36ss6dS9HGPx/eG17nGegKBQAA"}; +export const is_equal_circuit: CompiledCircuit = +{"abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"return_type":{"abi_type":{"kind":"boolean"},"visibility":"private"}},"bytecode":"H4sIAAAAAAAA/7WUMQ7DIAxFQ0Krrr2JjSGYLVcpKrn/CaqqDQN12WK+hPBgmWd/wEyHbF1SS923uhOs3pfoChI+wKXMAXzIKyNj4PB0TFTYc0w5RUjoqeAeEu1wqK0F54RGkWvW44LPzExnlkbMEs4JNZmN8PxS42uHv82T8a3Jeyn2Ks+VLPcO558HmyLMCDOXAXXtpPt4R/Rt9T36ss6dS9HGPx/eG17nGegKBQAA"}; export async function is_equal(x: Field, y: Field, foreignCallHandler?: ForeignCallHandler): Promise { const program = new Noir(is_equal_circuit); diff --git a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md index 2811968c6341..62ead1f534f3 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-oracles.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-oracles.md @@ -137,24 +137,17 @@ app.listen(5555); Now, we will add our `getSqrt` method, as expected by the `#[oracle(getSqrt)]` decorator in our Noir code. It maps through the params array and returns their square roots: ```js -server.addMethod("getSqrt", async (params) => { - const values = params[0].Array.map((field) => { +server.addMethod("resolve_function_call", async (params) => { + if params.function !== "getSqrt" { + throw Error("Unexpected foreign call") + }; + const values = params.inputs[0].Array.map((field) => { return `${Math.sqrt(parseInt(field, 16))}`; }); return { values: [{ Array: values }] }; }); ``` -:::tip - -Brillig expects an object with an array of values. Each value is an object declaring to be `Single` or `Array` and returning a field element *as a string*. For example: - -```json -{ "values": [{ "Array": ["1", "2"] }]} -{ "values": [{ "Single": "1" }]} -{ "values": [{ "Single": "1" }, { "Array": ["1", "2"] }]} -``` - If you're using Typescript, the following types may be helpful in understanding the expected return value and making sure they're easy to follow: ```js diff --git a/noir/noir-repo/docs/docs/index.mdx b/noir/noir-repo/docs/docs/index.mdx index 75086ddcdded..a6bd306f91da 100644 --- a/noir/noir-repo/docs/docs/index.mdx +++ b/noir/noir-repo/docs/docs/index.mdx @@ -24,9 +24,9 @@ import TabItem from '@theme/TabItem'; Noir Logo -Noir is a Domain-Specific Language for SNARK proving systems developed by [Aztec Labs](https://aztec.network/). It allows you to generate complex Zero-Knowledge Programs (ZKP) by using simple and flexible syntax, requiring no previous knowledge on the underlying mathematics or cryptography. +Noir is an open-source Domain-Specific Language for safe and seamless construction of privacy-preserving Zero-Knowledge programs, requiring no previous knowledge on the underlying mathematics or cryptography. -ZK programs are programs that can generate short proofs of a certain statement without revealing some details about it. You can read more about ZKPs [here](https://dev.to/spalladino/a-beginners-intro-to-coding-zero-knowledge-proofs-c56). +ZK programs are programs that can generate short proofs of statements without revealing all inputs to the statements. You can read more about Zero-Knowledge Proofs [here](https://dev.to/spalladino/a-beginners-intro-to-coding-zero-knowledge-proofs-c56). ## What's new about Noir? diff --git a/noir/noir-repo/docs/docs/noir/concepts/functions.md b/noir/noir-repo/docs/docs/noir/concepts/functions.md index b63c3ecd55b6..f656cdfd97a1 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/functions.md +++ b/noir/noir-repo/docs/docs/noir/concepts/functions.md @@ -184,7 +184,7 @@ See [Lambdas](./lambdas.md) for more details. Attributes are metadata that can be applied to a function, using the following syntax: `#[attribute(value)]`. -A few supported attributes include: +Supported attributes include: - **builtin**: the function is implemented by the compiler, for efficiency purposes. - **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function` @@ -192,8 +192,6 @@ A few supported attributes include: - **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details. - **test**: mark the function as unit tests. See [Tests](../../tooling/testing.md) for more details -See the Noir compiler for the full list of supported attributes [here](https://github.com/noir-lang/noir/blob/master/compiler/noirc_frontend/src/lexer/token.rs) (inside `let attribute = match &word_segments[..]` at the time of writing). - ### Field Attribute The field attribute defines which field the function is compatible for. The function is conditionally compiled, under the condition that the field attribute matches the Noir native field. diff --git a/noir/noir-repo/docs/docs/tooling/debugger.md b/noir/noir-repo/docs/docs/tooling/debugger.md index 184c436068fc..7c158d949d19 100644 --- a/noir/noir-repo/docs/docs/tooling/debugger.md +++ b/noir/noir-repo/docs/docs/tooling/debugger.md @@ -14,9 +14,8 @@ There are currently two ways of debugging Noir programs: In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation) and vscode-noir: -- Noir 0.xx -- Nargo 0.xx -- vscode-noir 0.xx +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 :::info At the moment, the debugger supports debugging binary projects, but not contracts. diff --git a/noir/noir-repo/docs/docusaurus.config.ts b/noir/noir-repo/docs/docusaurus.config.ts index 2d5b8941f559..29f612b01097 100644 --- a/noir/noir-repo/docs/docusaurus.config.ts +++ b/noir/noir-repo/docs/docusaurus.config.ts @@ -109,7 +109,7 @@ export default { }, { label: 'Docs GitHub', - href: 'https://github.com/noir-lang/docs', + href: 'https://github.com/noir-lang/noir/tree/master/docs', }, ], }, diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.28.0/tooling/debugger.md b/noir/noir-repo/docs/versioned_docs/version-v0.28.0/tooling/debugger.md index 184c436068fc..7c158d949d19 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.28.0/tooling/debugger.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.28.0/tooling/debugger.md @@ -14,9 +14,8 @@ There are currently two ways of debugging Noir programs: In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation) and vscode-noir: -- Noir 0.xx -- Nargo 0.xx -- vscode-noir 0.xx +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 :::info At the moment, the debugger supports debugging binary projects, but not contracts. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.29.0/tooling/debugger.md b/noir/noir-repo/docs/versioned_docs/version-v0.29.0/tooling/debugger.md index 184c436068fc..7c158d949d19 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.29.0/tooling/debugger.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.29.0/tooling/debugger.md @@ -14,9 +14,8 @@ There are currently two ways of debugging Noir programs: In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation) and vscode-noir: -- Noir 0.xx -- Nargo 0.xx -- vscode-noir 0.xx +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 :::info At the moment, the debugger supports debugging binary projects, but not contracts. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.30.0/tooling/debugger.md b/noir/noir-repo/docs/versioned_docs/version-v0.30.0/tooling/debugger.md index 184c436068fc..7c158d949d19 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.30.0/tooling/debugger.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.30.0/tooling/debugger.md @@ -14,9 +14,8 @@ There are currently two ways of debugging Noir programs: In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation) and vscode-noir: -- Noir 0.xx -- Nargo 0.xx -- vscode-noir 0.xx +- Noir & Nargo ≥0.28.0 +- Noir's VS Code extension ≥0.0.11 :::info At the moment, the debugger supports debugging binary projects, but not contracts. diff --git a/noir/noir-repo/noir_stdlib/src/hash.nr b/noir/noir-repo/noir_stdlib/src/hash.nr index ef1cb0889d74..6c295d127ab0 100644 --- a/noir/noir-repo/noir_stdlib/src/hash.nr +++ b/noir/noir-repo/noir_stdlib/src/hash.nr @@ -5,6 +5,7 @@ mod poseidon2; use crate::default::Default; use crate::uint128::U128; use crate::sha256::{digest, sha256_var}; +use crate::embedded_curve_ops::EmbeddedCurvePoint; #[foreign(sha256)] // docs:start:sha256 @@ -25,12 +26,7 @@ pub fn blake3(input: [u8; N]) -> [u8; 32] {} // docs:start:pedersen_commitment -struct PedersenPoint { - x : Field, - y : Field, -} - -pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint { +pub fn pedersen_commitment(input: [Field; N]) -> EmbeddedCurvePoint { // docs:end:pedersen_commitment pedersen_commitment_with_separator(input, 0) } @@ -38,9 +34,9 @@ pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint { #[foreign(pedersen_commitment)] pub fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} -pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> PedersenPoint { +pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> EmbeddedCurvePoint { let values = __pedersen_commitment_with_separator(input, separator); - PedersenPoint { x: values[0], y: values[1] } + EmbeddedCurvePoint { x: values[0], y: values[1], is_infinite: false } } // docs:start:pedersen_hash diff --git a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml b/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml deleted file mode 100644 index 488b94ca8586..000000000000 --- a/noir/noir-repo/test_programs/benchmarks/bench_sha256/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "bench_sha256" -version = "0.1.0" -type = "bin" -authors = [""] - -[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/comptime_println/Nargo.toml b/noir/noir-repo/test_programs/execution_success/comptime_println/Nargo.toml new file mode 100644 index 000000000000..7f8ae3a9cb98 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/comptime_println/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_println" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/comptime_println/src/main.nr b/noir/noir-repo/test_programs/execution_success/comptime_println/src/main.nr new file mode 100644 index 000000000000..f9770066c047 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/comptime_println/src/main.nr @@ -0,0 +1,7 @@ +fn main() { + let x = comptime { + println("hello from compile-time!"); + 1 + 2 + }; + println(x); +} diff --git a/noir/noir-repo/test_programs/execution_success/pedersen_commitment/Prover.toml b/noir/noir-repo/test_programs/execution_success/pedersen_commitment/Prover.toml index 6ad8c6bca574..0279f7d20963 100644 --- a/noir/noir-repo/test_programs/execution_success/pedersen_commitment/Prover.toml +++ b/noir/noir-repo/test_programs/execution_success/pedersen_commitment/Prover.toml @@ -4,3 +4,4 @@ y = "1" [expected_commitment] x = "0x054aa86a73cb8a34525e5bbed6e43ba1198e860f5f3950268f71df4591bde402" y = "0x209dcfbf2cfb57f9f6046f44d71ac6faf87254afc7407c04eb621a6287cac126" +is_infinite = false diff --git a/noir/noir-repo/test_programs/execution_success/pedersen_commitment/src/main.nr b/noir/noir-repo/test_programs/execution_success/pedersen_commitment/src/main.nr index 83cbe20851dc..a3a9aea1cf06 100644 --- a/noir/noir-repo/test_programs/execution_success/pedersen_commitment/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/pedersen_commitment/src/main.nr @@ -1,7 +1,7 @@ // docs:start:pedersen-commitment use dep::std; -fn main(x: Field, y: Field, expected_commitment: std::hash::PedersenPoint) { +fn main(x: Field, y: Field, expected_commitment: std::embedded_curve_ops::EmbeddedCurvePoint) { let commitment = std::hash::pedersen_commitment([x, y]); assert_eq(commitment.x, expected_commitment.x); assert_eq(commitment.y, expected_commitment.y); diff --git a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml index 1cb4e38f47fd..d298dabb5601 100644 --- a/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml +++ b/noir/noir-repo/test_programs/execution_success/verify_honk_proof/Nargo.toml @@ -2,6 +2,6 @@ name = "verify_honk_proof" type = "bin" authors = [""] -compiler_version = ">=0.29.0" +compiler_version = ">=0.30.0" [dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr index 56ee5cfa586c..1f5c29e58a2c 100644 --- a/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/should_fail_with_matches/src/main.nr @@ -19,10 +19,10 @@ fn test_should_fail_without_runtime_match() { } struct InvalidPointError { - point: dep::std::hash::PedersenPoint, + point: dep::std::embedded_curve_ops::EmbeddedCurvePoint, } -#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +#[test(should_fail_with = "InvalidPointError { point: EmbeddedCurvePoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8, is_infinite: false } }")] fn test_should_fail_with_struct() { let hash = dep::std::hash::pedersen_commitment([27]); assert_eq(hash.x, 0, InvalidPointError { point: hash }); @@ -35,7 +35,7 @@ fn test_should_fail_with_basic_type_fmt_string() { assert_eq(a, b, f"A: {a} is not 1!"); } -#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +#[test(should_fail_with = "Invalid hash: EmbeddedCurvePoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8, is_infinite: false }")] fn test_should_fail_with_struct_fmt_string() { let hash = dep::std::hash::pedersen_commitment([27]); assert_eq(hash.x, 0, f"Invalid hash: {hash}"); @@ -63,7 +63,7 @@ unconstrained fn unconstrained_test_should_fail_without_runtime_match() { assert_eq(dep::std::hash::pedersen_commitment([27]).x, 0); } -#[test(should_fail_with = "InvalidPointError { point: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 } }")] +#[test(should_fail_with = "InvalidPointError { point: EmbeddedCurvePoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8, is_infinite: false } }")] unconstrained fn unconstrained_test_should_fail_with_struct() { let hash = dep::std::hash::pedersen_commitment([27]); assert_eq(hash.x, 0, InvalidPointError { point: hash }); @@ -76,7 +76,7 @@ unconstrained fn unconstrained_test_should_fail_with_basic_type_fmt_string() { assert_eq(a, b, f"A: {a} is not 1!"); } -#[test(should_fail_with = "Invalid hash: PedersenPoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8 }")] +#[test(should_fail_with = "Invalid hash: EmbeddedCurvePoint { x: 0x1cea3a116d01eb94d568ef04c3dfbc39f96f015ed801ab8958e360d406503ce0, y: 0x2721b237df87234acc36a238b8f231a3d31d18fe3845fff4cc59f0bd873818f8, is_infinite: false }")] unconstrained fn unconstrained_test_should_fail_with_struct_fmt_string() { let hash = dep::std::hash::pedersen_commitment([27]); assert_eq(hash.x, 0, f"Invalid hash: {hash}"); diff --git a/noir/noir-repo/tooling/acvm_cli/Cargo.toml b/noir/noir-repo/tooling/acvm_cli/Cargo.toml index 72424405d367..1cfd1f3b2705 100644 --- a/noir/noir-repo/tooling/acvm_cli/Cargo.toml +++ b/noir/noir-repo/tooling/acvm_cli/Cargo.toml @@ -33,6 +33,6 @@ tracing-subscriber.workspace = true tracing-appender = "0.2.3" [dev-dependencies] -rand = "0.8.5" +rand.workspace = true proptest = "1.2.0" paste = "1.0.14" diff --git a/noir/noir-repo/tooling/acvm_cli/src/errors.rs b/noir/noir-repo/tooling/acvm_cli/src/errors.rs index 8bc79347159a..886c1bf80f29 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/errors.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/errors.rs @@ -1,3 +1,4 @@ +use acir::FieldElement; use nargo::NargoError; use std::path::PathBuf; use thiserror::Error; @@ -34,7 +35,7 @@ pub(crate) enum CliError { /// Error related to circuit execution #[error(transparent)] - CircuitExecutionError(#[from] NargoError), + CircuitExecutionError(#[from] NargoError), /// Input Witness Value Error #[error("Error: failed to parse witness value {0}")] diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 2f0f1c5bcfba..4436ce0ec3de 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -23,7 +23,7 @@ pub(super) enum DebugCommandResult { Done, Ok, BreakpointReached(OpcodeLocation), - Error(NargoError), + Error(NargoError), } pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> { @@ -482,11 +482,11 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - pub(super) fn get_variables(&self) -> Vec { + pub(super) fn get_variables(&self) -> Vec> { return self.foreign_call_executor.get_variables(); } - pub(super) fn current_stack_frame(&self) -> Option { + pub(super) fn current_stack_frame(&self) -> Option> { return self.foreign_call_executor.current_stack_frame(); } diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index 6989936ae939..03b1a35dfa55 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -38,14 +38,14 @@ impl DebugForeignCall { } } -pub trait DebugForeignCallExecutor: ForeignCallExecutor { - fn get_variables(&self) -> Vec; - fn current_stack_frame(&self) -> Option; +pub trait DebugForeignCallExecutor: ForeignCallExecutor { + fn get_variables(&self) -> Vec>; + fn current_stack_frame(&self) -> Option>; } pub struct DefaultDebugForeignCallExecutor { - executor: DefaultForeignCallExecutor, - pub debug_vars: DebugVars, + executor: DefaultForeignCallExecutor, + pub debug_vars: DebugVars, } impl DefaultDebugForeignCallExecutor { @@ -73,11 +73,11 @@ impl DefaultDebugForeignCallExecutor { } impl DebugForeignCallExecutor for DefaultDebugForeignCallExecutor { - fn get_variables(&self) -> Vec { + fn get_variables(&self) -> Vec> { self.debug_vars.get_variables() } - fn current_stack_frame(&self) -> Option { + fn current_stack_frame(&self) -> Option> { self.debug_vars.current_stack_frame() } } @@ -90,7 +90,7 @@ fn debug_fn_id(value: &FieldElement) -> DebugFnId { DebugFnId(value.to_u128() as u32) } -impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { +impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { fn execute( &mut self, foreign_call: &ForeignCallWaitInfo, diff --git a/noir/noir-repo/tooling/debugger/src/lib.rs b/noir/noir-repo/tooling/debugger/src/lib.rs index d7a1337c82fa..9168a6228f08 100644 --- a/noir/noir-repo/tooling/debugger/src/lib.rs +++ b/noir/noir-repo/tooling/debugger/src/lib.rs @@ -24,7 +24,7 @@ pub fn debug_circuit>( debug_artifact: DebugArtifact, initial_witness: WitnessMap, unconstrained_functions: &[BrilligBytecode], -) -> Result>, NargoError> { +) -> Result>, NargoError> { repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness, unconstrained_functions) } diff --git a/noir/noir-repo/tooling/debugger/src/repl.rs b/noir/noir-repo/tooling/debugger/src/repl.rs index 5aef12ad8d4d..07f9333d51cf 100644 --- a/noir/noir-repo/tooling/debugger/src/repl.rs +++ b/noir/noir-repo/tooling/debugger/src/repl.rs @@ -382,7 +382,7 @@ pub fn run>( debug_artifact: &DebugArtifact, initial_witness: WitnessMap, unconstrained_functions: &[BrilligBytecode], -) -> Result>, NargoError> { +) -> Result>, NargoError> { let context = RefCell::new(ReplDebugger::new( blackbox_solver, circuit, diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index 48047d10ea6e..8abec267d20f 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -24,6 +24,7 @@ codespan-reporting.workspace = true tracing.workspace = true rayon = "1.8.0" jsonrpc.workspace = true +rand.workspace = true [dev-dependencies] # TODO: This dependency is used to generate unit tests for `get_all_paths_in_dir` diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs b/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs index d2c0bb3ea00a..aa9328432b8e 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs +++ b/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs @@ -1,4 +1,4 @@ -use acvm::FieldElement; +use acvm::AcirField; use noirc_errors::debug_info::{ DebugFnId, DebugFunction, DebugInfo, DebugTypeId, DebugVarId, DebugVariable, }; @@ -6,31 +6,31 @@ use noirc_printable_type::{decode_value, PrintableType, PrintableValue}; use std::collections::HashMap; #[derive(Debug, Default, Clone)] -pub struct DebugVars { +pub struct DebugVars { variables: HashMap, functions: HashMap, types: HashMap, - frames: Vec<(DebugFnId, HashMap)>, + frames: Vec<(DebugFnId, HashMap>)>, } -pub struct StackFrame<'a> { +pub struct StackFrame<'a, F> { pub function_name: &'a str, pub function_params: Vec<&'a str>, - pub variables: Vec<(&'a str, &'a PrintableValue, &'a PrintableType)>, + pub variables: Vec<(&'a str, &'a PrintableValue, &'a PrintableType)>, } -impl DebugVars { +impl DebugVars { pub fn insert_debug_info(&mut self, info: &DebugInfo) { self.variables.extend(info.variables.clone()); self.types.extend(info.types.clone()); self.functions.extend(info.functions.clone()); } - pub fn get_variables(&self) -> Vec { + pub fn get_variables(&self) -> Vec> { self.frames.iter().map(|(fn_id, frame)| self.build_stack_frame(fn_id, frame)).collect() } - pub fn current_stack_frame(&self) -> Option { + pub fn current_stack_frame(&self) -> Option> { self.frames.last().map(|(fn_id, frame)| self.build_stack_frame(fn_id, frame)) } @@ -44,13 +44,13 @@ impl DebugVars { fn build_stack_frame<'a>( &'a self, fn_id: &DebugFnId, - frame: &'a HashMap, - ) -> StackFrame { + frame: &'a HashMap>, + ) -> StackFrame { let debug_fn = &self.functions.get(fn_id).expect("failed to find function metadata"); let params: Vec<&str> = debug_fn.arg_names.iter().map(|arg_name| arg_name.as_str()).collect(); - let vars: Vec<(&str, &PrintableValue, &PrintableType)> = frame + let vars: Vec<(&str, &PrintableValue, &PrintableType)> = frame .iter() .filter_map(|(var_id, var_value)| { self.lookup_var(*var_id).map(|(name, typ)| (name, var_value, typ)) @@ -64,7 +64,7 @@ impl DebugVars { } } - pub fn assign_var(&mut self, var_id: DebugVarId, values: &[FieldElement]) { + pub fn assign_var(&mut self, var_id: DebugVarId, values: &[F]) { let type_id = &self.variables.get(&var_id).unwrap().debug_type_id; let ptype = self.types.get(type_id).unwrap(); @@ -75,9 +75,9 @@ impl DebugVars { .insert(var_id, decode_value(&mut values.iter().copied(), ptype)); } - pub fn assign_field(&mut self, var_id: DebugVarId, indexes: Vec, values: &[FieldElement]) { + pub fn assign_field(&mut self, var_id: DebugVarId, indexes: Vec, values: &[F]) { let current_frame = &mut self.frames.last_mut().expect("unexpected empty stack frames").1; - let mut cursor: &mut PrintableValue = current_frame + let mut cursor: &mut PrintableValue = current_frame .get_mut(&var_id) .unwrap_or_else(|| panic!("value unavailable for var_id {var_id:?}")); let cursor_type_id = &self @@ -146,7 +146,7 @@ impl DebugVars { *cursor = decode_value(&mut values.iter().copied(), cursor_type); } - pub fn assign_deref(&mut self, _var_id: DebugVarId, _values: &[FieldElement]) { + pub fn assign_deref(&mut self, _var_id: DebugVarId, _values: &[F]) { unimplemented![] } diff --git a/noir/noir-repo/tooling/nargo/src/errors.rs b/noir/noir-repo/tooling/nargo/src/errors.rs index 200420e5ce52..b2248605cb53 100644 --- a/noir/noir-repo/tooling/nargo/src/errors.rs +++ b/noir/noir-repo/tooling/nargo/src/errors.rs @@ -6,7 +6,7 @@ use acvm::{ ResolvedOpcodeLocation, }, pwg::{ErrorLocation, OpcodeResolutionError}, - FieldElement, + AcirField, FieldElement, }; use noirc_abi::{display_abi_error, Abi, AbiErrorType}; use noirc_errors::{ @@ -40,21 +40,21 @@ impl From for CompileError { } #[derive(Debug, Error)] -pub enum NargoError { +pub enum NargoError { /// Error while compiling Noir into ACIR. #[error("Failed to compile circuit")] CompilationError, /// ACIR circuit execution error #[error(transparent)] - ExecutionError(#[from] ExecutionError), + ExecutionError(#[from] ExecutionError), /// Oracle handling error #[error(transparent)] ForeignCallError(#[from] ForeignCallError), } -impl NargoError { +impl NargoError { /// Extracts the user defined failure message from the ExecutionError /// If one exists. /// @@ -94,17 +94,17 @@ impl NargoError { } #[derive(Debug, Error)] -pub enum ExecutionError { +pub enum ExecutionError { #[error("Failed assertion")] - AssertionFailed(ResolvedAssertionPayload, Vec), + AssertionFailed(ResolvedAssertionPayload, Vec), #[error("Failed to solve program: '{}'", .0)] - SolvingError(OpcodeResolutionError, Option>), + SolvingError(OpcodeResolutionError, Option>), } /// Extracts the opcode locations from a nargo error. -fn extract_locations_from_error( - error: &ExecutionError, +fn extract_locations_from_error( + error: &ExecutionError, debug: &[DebugInfo], ) -> Option> { let mut opcode_locations = match error { @@ -163,7 +163,7 @@ fn extract_locations_from_error( fn extract_message_from_error( error_types: &BTreeMap, - nargo_err: &NargoError, + nargo_err: &NargoError, ) -> String { match nargo_err { NargoError::ExecutionError(ExecutionError::AssertionFailed( @@ -198,7 +198,7 @@ fn extract_message_from_error( /// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. pub fn try_to_diagnose_runtime_error( - nargo_err: &NargoError, + nargo_err: &NargoError, abi: &Abi, debug: &[DebugInfo], ) -> Option { diff --git a/noir/noir-repo/tooling/nargo/src/ops/execute.rs b/noir/noir-repo/tooling/nargo/src/ops/execute.rs index 42e93e0e3cf9..c9cc60d03d93 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/execute.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/execute.rs @@ -5,24 +5,24 @@ use acvm::acir::circuit::{ use acvm::acir::native_types::WitnessStack; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM}; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; -use acvm::{BlackBoxFunctionSolver, FieldElement}; +use acvm::{AcirField, BlackBoxFunctionSolver}; use crate::errors::ExecutionError; use crate::NargoError; use super::foreign_calls::ForeignCallExecutor; -struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> { - functions: &'a [Circuit], +struct ProgramExecutor<'a, F, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> { + functions: &'a [Circuit], - unconstrained_functions: &'a [BrilligBytecode], + unconstrained_functions: &'a [BrilligBytecode], // This gets built as we run through the program looking at each function call - witness_stack: WitnessStack, + witness_stack: WitnessStack, blackbox_solver: &'a B, - foreign_call_executor: &'a mut F, + foreign_call_executor: &'a mut E, // The Noir compiler codegens per function and call stacks are not shared across ACIR function calls. // We must rebuild a call stack when executing a program of many circuits. @@ -34,14 +34,14 @@ struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCa current_function_index: usize, } -impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> - ProgramExecutor<'a, B, F> +impl<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> + ProgramExecutor<'a, F, B, E> { fn new( - functions: &'a [Circuit], - unconstrained_functions: &'a [BrilligBytecode], + functions: &'a [Circuit], + unconstrained_functions: &'a [BrilligBytecode], blackbox_solver: &'a B, - foreign_call_executor: &'a mut F, + foreign_call_executor: &'a mut E, ) -> Self { ProgramExecutor { functions, @@ -54,15 +54,15 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> } } - fn finalize(self) -> WitnessStack { + fn finalize(self) -> WitnessStack { self.witness_stack } #[tracing::instrument(level = "trace", skip_all)] fn execute_circuit( &mut self, - initial_witness: WitnessMap, - ) -> Result, NargoError> { + initial_witness: WitnessMap, + ) -> Result, NargoError> { let circuit = &self.functions[self.current_function_index]; let mut acvm = ACVM::new( self.blackbox_solver, @@ -109,14 +109,13 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> _ => None, }; - let assertion_payload: Option> = - match &error { - OpcodeResolutionError::BrilligFunctionFailed { payload, .. } - | OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => { - payload.clone() - } - _ => None, - }; + let assertion_payload: Option> = match &error { + OpcodeResolutionError::BrilligFunctionFailed { payload, .. } + | OpcodeResolutionError::UnsatisfiedConstrain { payload, .. } => { + payload.clone() + } + _ => None, + }; return Err(NargoError::ExecutionError(match assertion_payload { Some(payload) => ExecutionError::AssertionFailed( @@ -174,12 +173,12 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> } #[tracing::instrument(level = "trace", skip_all)] -pub fn execute_program, F: ForeignCallExecutor>( - program: &Program, - initial_witness: WitnessMap, +pub fn execute_program, E: ForeignCallExecutor>( + program: &Program, + initial_witness: WitnessMap, blackbox_solver: &B, - foreign_call_executor: &mut F, -) -> Result, NargoError> { + foreign_call_executor: &mut E, +) -> Result, NargoError> { let mut executor = ProgramExecutor::new( &program.functions, &program.unconstrained_functions, diff --git a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs index c6b284beb13b..987c7dd9cb9d 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/foreign_calls.rs @@ -1,16 +1,18 @@ use acvm::{ acir::brillig::{ForeignCallParam, ForeignCallResult}, pwg::ForeignCallWaitInfo, - AcirField, FieldElement, + AcirField, }; use jsonrpc::{arg as build_json_rpc_arg, minreq_http::Builder, Client}; use noirc_printable_type::{decode_string_value, ForeignCallError, PrintableValueDisplay}; +use rand::Rng; +use serde::{Deserialize, Serialize}; -pub trait ForeignCallExecutor { +pub trait ForeignCallExecutor { fn execute( &mut self, - foreign_call: &ForeignCallWaitInfo, - ) -> Result, ForeignCallError>; + foreign_call: &ForeignCallWaitInfo, + ) -> Result, ForeignCallError>; } /// This enumeration represents the Brillig foreign calls that are natively supported by nargo. @@ -60,22 +62,22 @@ impl ForeignCall { /// This struct represents an oracle mock. It can be used for testing programs that use oracles. #[derive(Debug, PartialEq, Eq, Clone)] -struct MockedCall { +struct MockedCall { /// The id of the mock, used to update or remove it id: usize, /// The oracle it's mocking name: String, /// Optionally match the parameters - params: Option>>, + params: Option>>, /// The parameters with which the mock was last called - last_called_params: Option>>, + last_called_params: Option>>, /// The result to return when this mock is called - result: ForeignCallResult, + result: ForeignCallResult, /// How many times should this mock be called before it is removed times_left: Option, } -impl MockedCall { +impl MockedCall { fn new(id: usize, name: String) -> Self { Self { id, @@ -88,25 +90,45 @@ impl MockedCall { } } -impl MockedCall { - fn matches(&self, name: &str, params: &[ForeignCallParam]) -> bool { +impl MockedCall { + fn matches(&self, name: &str, params: &[ForeignCallParam]) -> bool { self.name == name && (self.params.is_none() || self.params.as_deref() == Some(params)) } } #[derive(Debug, Default)] -pub struct DefaultForeignCallExecutor { +pub struct DefaultForeignCallExecutor { + /// A randomly generated id for this `DefaultForeignCallExecutor`. + /// + /// This is used so that a single `external_resolver` can distinguish between requests from multiple + /// instantiations of `DefaultForeignCallExecutor`. + id: u64, + /// Mocks have unique ids used to identify them in Noir, allowing to update or remove them. last_mock_id: usize, /// The registered mocks - mocked_responses: Vec, + mocked_responses: Vec>, /// Whether to print [`ForeignCall::Print`] output. show_output: bool, /// JSON RPC client to resolve foreign calls external_resolver: Option, } -impl DefaultForeignCallExecutor { +#[derive(Debug, Serialize, Deserialize)] +struct ResolveForeignCallRequest { + /// A session ID which allows the external RPC server to link this foreign call request to other foreign calls + /// for the same program execution. + /// + /// This is intended to allow a single RPC server to maintain state related to multiple program executions being + /// performed in parallel. + session_id: u64, + + #[serde(flatten)] + /// The foreign call which the external RPC server is to provide a response for. + function_call: ForeignCallWaitInfo, +} + +impl DefaultForeignCallExecutor { pub fn new(show_output: bool, resolver_url: Option<&str>) -> Self { let oracle_resolver = resolver_url.map(|resolver_url| { let mut transport_builder = @@ -123,15 +145,17 @@ impl DefaultForeignCallExecutor { DefaultForeignCallExecutor { show_output, external_resolver: oracle_resolver, - ..DefaultForeignCallExecutor::default() + id: rand::thread_rng().gen(), + mocked_responses: Vec::new(), + last_mock_id: 0, } } } -impl DefaultForeignCallExecutor { +impl DefaultForeignCallExecutor { fn extract_mock_id( - foreign_call_inputs: &[ForeignCallParam], - ) -> Result<(usize, &[ForeignCallParam]), ForeignCallError> { + foreign_call_inputs: &[ForeignCallParam], + ) -> Result<(usize, &[ForeignCallParam]), ForeignCallError> { let (id, params) = foreign_call_inputs.split_first().ok_or(ForeignCallError::MissingForeignCallInputs)?; let id = @@ -140,22 +164,20 @@ impl DefaultForeignCallExecutor { Ok((id, params)) } - fn find_mock_by_id(&self, id: usize) -> Option<&MockedCall> { + fn find_mock_by_id(&self, id: usize) -> Option<&MockedCall> { self.mocked_responses.iter().find(|response| response.id == id) } - fn find_mock_by_id_mut(&mut self, id: usize) -> Option<&mut MockedCall> { + fn find_mock_by_id_mut(&mut self, id: usize) -> Option<&mut MockedCall> { self.mocked_responses.iter_mut().find(|response| response.id == id) } - fn parse_string(param: &ForeignCallParam) -> String { + fn parse_string(param: &ForeignCallParam) -> String { let fields: Vec<_> = param.fields().to_vec(); decode_string_value(&fields) } - fn execute_print( - foreign_call_inputs: &[ForeignCallParam], - ) -> Result<(), ForeignCallError> { + fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> { let skip_newline = foreign_call_inputs[0].unwrap_field().is_zero(); let foreign_call_inputs = @@ -168,10 +190,10 @@ impl DefaultForeignCallExecutor { } fn format_printable_value( - foreign_call_inputs: &[ForeignCallParam], + foreign_call_inputs: &[ForeignCallParam], skip_newline: bool, ) -> Result { - let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; + let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?; let result = format!("{display_values}{}", if skip_newline { "" } else { "\n" }); @@ -179,11 +201,13 @@ impl DefaultForeignCallExecutor { } } -impl ForeignCallExecutor for DefaultForeignCallExecutor { +impl Deserialize<'a>> ForeignCallExecutor + for DefaultForeignCallExecutor +{ fn execute( &mut self, - foreign_call: &ForeignCallWaitInfo, - ) -> Result, ForeignCallError> { + foreign_call: &ForeignCallWaitInfo, + ) -> Result, ForeignCallError> { let foreign_call_name = foreign_call.function.as_str(); match ForeignCall::lookup(foreign_call_name) { Some(ForeignCall::Print) => { @@ -199,7 +223,7 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { self.mocked_responses.push(MockedCall::new(id, mock_oracle_name)); self.last_mock_id += 1; - Ok(FieldElement::from(id).into()) + Ok(F::from(id).into()) } Some(ForeignCall::SetMockParams) => { let (id, params) = Self::extract_mock_id(&foreign_call.inputs)?; @@ -275,14 +299,17 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor { } else if let Some(external_resolver) = &self.external_resolver { // If the user has registered an external resolver then we forward any remaining oracle calls there. - let encoded_params: Vec<_> = - foreign_call.inputs.iter().map(build_json_rpc_arg).collect(); + let encoded_params = vec![build_json_rpc_arg(ResolveForeignCallRequest { + session_id: self.id, + function_call: foreign_call.clone(), + })]; - let req = external_resolver.build_request(foreign_call_name, &encoded_params); + let req = + external_resolver.build_request("resolve_foreign_call", &encoded_params); let response = external_resolver.send_request(req)?; - let parsed_response: ForeignCallResult = response.result()?; + let parsed_response: ForeignCallResult = response.result()?; Ok(parsed_response) } else { @@ -312,43 +339,49 @@ mod tests { use crate::ops::{DefaultForeignCallExecutor, ForeignCallExecutor}; + use super::ResolveForeignCallRequest; + #[allow(unreachable_pub)] #[rpc] pub trait OracleResolver { - #[rpc(name = "echo")] - fn echo( - &self, - param: ForeignCallParam, - ) -> RpcResult>; - - #[rpc(name = "sum")] - fn sum( + #[rpc(name = "resolve_foreign_call")] + fn resolve_foreign_call( &self, - array: ForeignCallParam, + req: ResolveForeignCallRequest, ) -> RpcResult>; } struct OracleResolverImpl; - impl OracleResolver for OracleResolverImpl { - fn echo( - &self, - param: ForeignCallParam, - ) -> RpcResult> { - Ok(vec![param].into()) + impl OracleResolverImpl { + fn echo(&self, param: ForeignCallParam) -> ForeignCallResult { + vec![param].into() } - fn sum( - &self, - array: ForeignCallParam, - ) -> RpcResult> { + fn sum(&self, array: ForeignCallParam) -> ForeignCallResult { let mut res: FieldElement = 0_usize.into(); for value in array.fields() { res += value; } - Ok(res.into()) + res.into() + } + } + + impl OracleResolver for OracleResolverImpl { + fn resolve_foreign_call( + &self, + req: ResolveForeignCallRequest, + ) -> RpcResult> { + let response = match req.function_call.function.as_str() { + "sum" => self.sum(req.function_call.inputs[0].clone()), + "echo" => self.echo(req.function_call.inputs[0].clone()), + "id" => FieldElement::from(req.session_id as u128).into(), + + _ => panic!("unexpected foreign call"), + }; + Ok(response) } } @@ -369,7 +402,7 @@ mod tests { fn test_oracle_resolver_echo() { let (server, url) = build_oracle_server(); - let mut executor = DefaultForeignCallExecutor::new(false, Some(&url)); + let mut executor = DefaultForeignCallExecutor::::new(false, Some(&url)); let foreign_call = ForeignCallWaitInfo { function: "echo".to_string(), @@ -398,4 +431,35 @@ mod tests { server.close(); } + + #[test] + fn foreign_call_executor_id_is_persistent() { + let (server, url) = build_oracle_server(); + + let mut executor = DefaultForeignCallExecutor::::new(false, Some(&url)); + + let foreign_call = ForeignCallWaitInfo { function: "id".to_string(), inputs: Vec::new() }; + + let result_1 = executor.execute(&foreign_call).unwrap(); + let result_2 = executor.execute(&foreign_call).unwrap(); + assert_eq!(result_1, result_2); + + server.close(); + } + + #[test] + fn oracle_resolver_rpc_can_distinguish_executors() { + let (server, url) = build_oracle_server(); + + let mut executor_1 = DefaultForeignCallExecutor::::new(false, Some(&url)); + let mut executor_2 = DefaultForeignCallExecutor::::new(false, Some(&url)); + + let foreign_call = ForeignCallWaitInfo { function: "id".to_string(), inputs: Vec::new() }; + + let result_1 = executor_1.execute(&foreign_call).unwrap(); + let result_2 = executor_2.execute(&foreign_call).unwrap(); + assert_ne!(result_1, result_2); + + server.close(); + } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index ed45251ac8ab..ace2e9f0d0cb 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -76,7 +76,7 @@ fn test_status_program_compile_pass( test_function: &TestFunction, abi: Abi, debug: Vec, - circuit_execution: Result, NargoError>, + circuit_execution: Result, NargoError>, ) -> TestStatus { let circuit_execution_err = match circuit_execution { // Circuit execution was successful; ie no errors or unsatisfied constraints diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 25a69489788e..fcc09653c7d9 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -287,9 +287,6 @@ fn generate_compile_success_empty_tests(test_file: &mut File, test_data_dir: &Pa #[test] fn compile_success_empty_{test_name}() {{ - // We use a mocked backend for this test as we do not rely on the returned circuit size - // but we must call a backend as part of querying the number of opcodes in the circuit. - let test_program_dir = PathBuf::from("{test_dir}"); let mut cmd = Command::cargo_bin("nargo").unwrap(); cmd.arg("--program-dir").arg(test_program_dir); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs index ebcd9ff65699..086dddc27e5f 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -205,11 +205,10 @@ fn debug_program_and_decode( let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; let solved_witness = debug_program(&program, &inputs_map)?; - let public_abi = program.abi.public_abi(); match solved_witness { Some(witness) => { - let (_, return_value) = public_abi.decode(&witness)?; + let (_, return_value) = program.abi.decode(&witness)?; Ok((return_value, Some(witness))) } None => Ok((None, None)), diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index 5d6754b29f75..b548336275bd 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -97,11 +97,10 @@ fn execute_program_and_decode( let (inputs_map, _) = read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &program.abi)?; let witness_stack = execute_program(&program, &inputs_map, foreign_call_resolver_url)?; - let public_abi = program.abi.public_abi(); // Get the entry point witness for the ABI let main_witness = &witness_stack.peek().expect("Should have at least one witness on the stack").witness; - let (_, return_value) = public_abi.decode(main_witness)?; + let (_, return_value) = program.abi.decode(main_witness)?; Ok((return_value, witness_stack)) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/errors.rs b/noir/noir-repo/tooling/nargo_cli/src/errors.rs index 3e0b13a9cbcb..b28012ae7aad 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/errors.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/errors.rs @@ -1,4 +1,4 @@ -use acvm::acir::native_types::WitnessStackError; +use acvm::{acir::native_types::WitnessStackError, FieldElement}; use nargo::{errors::CompileError, NargoError}; use nargo_toml::ManifestError; use noir_debugger::errors::DapError; @@ -54,7 +54,7 @@ pub(crate) enum CliError { /// Error from Nargo #[error(transparent)] - NargoError(#[from] NargoError), + NargoError(#[from] NargoError), /// Error from Manifest #[error(transparent)] diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 9a704717aded..7ff943aea62e 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -167,8 +167,8 @@ pub(crate) fn rewrite( } ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), ExpressionKind::Quote(block) => format!("quote {}", rewrite_block(visitor, block, span)), - ExpressionKind::Comptime(block) => { - format!("comptime {}", rewrite_block(visitor, block, span)) + ExpressionKind::Comptime(block, block_span) => { + format!("comptime {}", rewrite_block(visitor, block, block_span)) } ExpressionKind::Error => unreachable!(), ExpressionKind::Resolved(_) => { diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 1a26367e3c04..35fbd6f438bf 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -34,7 +34,6 @@ "generate:package": "bash ./fixup.sh", "build": "yarn clean && tsc && tsc -p ./tsconfig.cjs.json && yarn generate:package", "clean": "rm -rf ./lib", - "test": "mocha --timeout 25000 --exit --config ./.mocharc.json", "prettier": "prettier 'src/**/*.ts'", "prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", "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", @@ -49,10 +48,8 @@ "devDependencies": { "@types/node": "^20.6.2", "@types/prettier": "^3", - "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/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts index f28abb9a6584..cefef07520f0 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/index.ts @@ -1,6 +1,5 @@ export { BarretenbergBackend } from './backend.js'; export { BarretenbergVerifier } from './verifier.js'; -export { publicInputsToWitnessMap } from './public_inputs.js'; // typedoc exports export { Backend, CompiledCircuit, ProofData } from '@noir-lang/types'; diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts index 75ee0de68007..ed771ab0d348 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/src/public_inputs.ts @@ -1,4 +1,4 @@ -import { Abi, WitnessMap } from '@noir-lang/types'; +import { WitnessMap } from '@noir-lang/types'; export function flattenPublicInputsAsArray(publicInputs: string[]): Uint8Array { const flattenedPublicInputs = publicInputs.map(hexToUint8Array); @@ -23,30 +23,6 @@ export function witnessMapToPublicInputs(publicInputs: WitnessMap): string[] { return flattenedPublicInputs; } -export function publicInputsToWitnessMap(publicInputs: string[], abi: Abi): WitnessMap { - const return_value_witnesses = abi.return_witnesses; - const public_parameters = abi.parameters.filter((param) => param.visibility === 'public'); - const public_parameter_witnesses: number[] = public_parameters.flatMap((param) => - abi.param_witnesses[param.name].flatMap((witness_range) => - Array.from({ length: witness_range.end - witness_range.start }, (_, i) => witness_range.start + i), - ), - ); - - // We now have an array of witness indices which have been deduplicated and sorted in ascending order. - // The elements of this array should correspond to the elements of `flattenedPublicInputs` so that we can build up a `WitnessMap`. - const public_input_witnesses = [...new Set(public_parameter_witnesses.concat(return_value_witnesses))].sort( - (a, b) => a - b, - ); - - const witnessMap: WitnessMap = new Map(); - public_input_witnesses.forEach((witness_index, index) => { - const witness_value = publicInputs[index]; - witnessMap.set(witness_index, witness_value); - }); - - return witnessMap; -} - function flattenUint8Arrays(arrays: Uint8Array[]): Uint8Array { const totalLength = arrays.reduce((acc, val) => acc + val.length, 0); const result = new Uint8Array(totalLength); diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts b/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts deleted file mode 100644 index cfd43eff2506..000000000000 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/test/public_input_deflattening.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Abi } from '@noir-lang/types'; -import { expect } from 'chai'; -import { witnessMapToPublicInputs, publicInputsToWitnessMap } from '../src/public_inputs.js'; - -const abi: Abi = { - parameters: [ - { - name: 'array_with_returned_element', - type: { - kind: 'array', - type: { - kind: 'field', - }, - length: 10, - }, - visibility: 'private', - }, - { - name: 'pub_field', - type: { - kind: 'field', - }, - visibility: 'public', - }, - ], - param_witnesses: { - array_with_returned_element: [ - { - start: 1, - end: 11, - }, - ], - pub_field: [ - { - start: 11, - end: 12, - }, - ], - }, - return_type: { - abi_type: { - kind: 'tuple', - fields: [ - { - kind: 'field', - }, - { - kind: 'field', - }, - { - kind: 'field', - }, - ], - }, - visibility: 'public', - }, - return_witnesses: [2, 13, 13], - error_types: {}, -}; - -it('flattens a witness map in order of its witness indices', async () => { - // Note that these are not in ascending order. This means that if we read from `witness_map` in insertion order - // then the witness values will be sorted incorrectly. - const public_input_indices = [2, 13, 11]; - - const witness_map = new Map( - public_input_indices.map((witness_index) => [ - witness_index, - '0x' + BigInt(witness_index).toString(16).padStart(64, '0'), - ]), - ); - - const flattened_public_inputs = witnessMapToPublicInputs(witness_map); - expect(flattened_public_inputs).to.be.deep.eq([ - '0x0000000000000000000000000000000000000000000000000000000000000002', - '0x000000000000000000000000000000000000000000000000000000000000000b', - '0x000000000000000000000000000000000000000000000000000000000000000d', - ]); -}); - -it('recovers the original witness map when deflattening a public input array', async () => { - // Note that these are not in ascending order. This means that if we read from `witness_map` in insertion order - // then the witness values will be sorted incorrectly. - const public_input_indices = [2, 13, 11]; - - const witness_map = new Map( - public_input_indices.map((witness_index) => [ - witness_index, - '0x' + BigInt(witness_index).toString(16).padStart(64, '0'), - ]), - ); - - const flattened_public_inputs = witnessMapToPublicInputs(witness_map); - const deflattened_public_inputs = publicInputsToWitnessMap(flattened_public_inputs, abi); - - expect(deflattened_public_inputs).to.be.deep.eq(witness_map); -}); diff --git a/noir/noir-repo/tooling/noir_js_types/src/types.ts b/noir/noir-repo/tooling/noir_js_types/src/types.ts index 0258f2f90c9f..dd6548ddb6c7 100644 --- a/noir/noir-repo/tooling/noir_js_types/src/types.ts +++ b/noir/noir-repo/tooling/noir_js_types/src/types.ts @@ -38,9 +38,7 @@ export type WitnessMap = Map; export type Abi = { parameters: AbiParameter[]; - param_witnesses: Record; return_type: { abi_type: AbiType; visibility: Visibility } | null; - return_witnesses: number[]; error_types: Record; }; diff --git a/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs b/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs index 14d92bc71b32..d7bbb0adfe3a 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/input_parser/mod.rs @@ -267,9 +267,6 @@ mod serialization_tests { abi_type: AbiType::String { length: 5 }, visibility: AbiVisibility::Public, }), - // These two fields are unused when serializing/deserializing to file. - param_witnesses: BTreeMap::new(), - return_witnesses: Vec::new(), error_types: Default::default(), }; diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index b1ddba26f15b..514fac2e73d6 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -20,7 +20,7 @@ use noirc_printable_type::{ PrintableValueDisplay, }; use serde::{Deserialize, Serialize}; -use std::{borrow::Borrow, ops::Range}; +use std::borrow::Borrow; use std::{collections::BTreeMap, str}; // This is the ABI used to bridge the different TOML formats for the initial // witness, the partial witness generator and the interpreter. @@ -107,21 +107,6 @@ impl From<&Visibility> for AbiVisibility { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -/// Represents whether the return value should compromise of unique witness indices such that no -/// index occurs within the program's abi more than once. -/// -/// This is useful for application stacks that require an uniform abi across across multiple -/// circuits. When index duplication is allowed, the compiler may identify that a public input -/// reaches the output unaltered and is thus referenced directly, causing the input and output -/// witness indices to overlap. Similarly, repetitions of copied values in the output may be -/// optimized away. -pub enum AbiDistinctness { - Distinct, - DuplicationAllowed, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Sign { @@ -267,11 +252,7 @@ pub struct AbiReturnType { pub struct Abi { /// An ordered list of the arguments to the program's `main` function, specifying their types and visibility. pub parameters: Vec, - /// A map from the ABI's parameters to the indices they are written to in the [`WitnessMap`]. - /// This defines how to convert between the [`InputMap`] and [`WitnessMap`]. - pub param_witnesses: BTreeMap>>, pub return_type: Option, - pub return_witnesses: Vec, pub error_types: BTreeMap, } @@ -307,25 +288,6 @@ impl Abi { map } - /// ABI with only the public parameters - #[must_use] - pub fn public_abi(self) -> Abi { - let parameters: Vec<_> = - self.parameters.into_iter().filter(|param| param.is_public()).collect(); - let param_witnesses = self - .param_witnesses - .into_iter() - .filter(|(param_name, _)| parameters.iter().any(|param| ¶m.name == param_name)) - .collect(); - Abi { - parameters, - param_witnesses, - return_type: self.return_type, - return_witnesses: self.return_witnesses, - error_types: self.error_types, - } - } - /// Encode a set of inputs as described in the ABI into a `WitnessMap`. pub fn encode( &self, @@ -341,34 +303,21 @@ impl Abi { } // First encode each input separately, performing any input validation. - let encoded_input_map: BTreeMap> = self - .to_btree_map() - .into_iter() - .map(|(param_name, expected_type)| { + let mut encoded_inputs: Vec> = self + .parameters + .iter() + .map(|param| { let value = input_map - .get(¶m_name) - .ok_or_else(|| AbiError::MissingParam(param_name.clone()))? + .get(¶m.name) + .ok_or_else(|| AbiError::MissingParam(param.name.clone()))? .clone(); - value.find_type_mismatch(&expected_type, param_name.clone())?; + value.find_type_mismatch(¶m.typ, param.name.clone())?; - Self::encode_value(value, &expected_type).map(|v| (param_name, v)) + Self::encode_value(value, ¶m.typ) }) .collect::>()?; - // Write input field elements into witness indices specified in `self.param_witnesses`. - let mut witness_map: BTreeMap = encoded_input_map - .iter() - .flat_map(|(param_name, encoded_param_fields)| { - let param_witness_indices = range_to_vec(&self.param_witnesses[param_name]); - param_witness_indices - .iter() - .zip(encoded_param_fields.iter()) - .map(|(&witness, &field_element)| (witness, field_element)) - .collect::>() - }) - .collect::>(); - // When encoding public inputs to be passed to the verifier, the user can must provide a return value // to be inserted into the witness map. This is not needed when generating a witness when proving the circuit. match (&self.return_type, return_value) { @@ -380,18 +329,7 @@ impl Abi { }); } let encoded_return_fields = Self::encode_value(return_value, return_type)?; - - // We need to be more careful when writing the return value's witness values. - // This is as it may share witness indices with other public inputs so we must check that when - // this occurs the witness values are consistent with each other. - self.return_witnesses.iter().zip(encoded_return_fields.iter()).try_for_each( - |(&witness, &field_element)| match witness_map.insert(witness, field_element) { - Some(existing_value) if existing_value != field_element => { - Err(AbiError::InconsistentWitnessAssignment(witness)) - } - _ => Ok(()), - }, - )?; + encoded_inputs.push(encoded_return_fields); } (None, Some(return_value)) => { return Err(AbiError::UnexpectedReturnValue(return_value)) @@ -401,6 +339,14 @@ impl Abi { (_, None) => {} } + // Write input field elements into witness map. + let witness_map: BTreeMap = encoded_inputs + .into_iter() + .flatten() + .enumerate() + .map(|(index, field_element)| (Witness(index as u32), field_element)) + .collect::>(); + Ok(witness_map.into()) } @@ -441,18 +387,21 @@ impl Abi { &self, witness_map: &WitnessMap, ) -> Result<(InputMap, Option), AbiError> { + let mut pointer: u32 = 0; let public_inputs_map = try_btree_map(self.parameters.clone(), |AbiParameter { name, typ, .. }| { - let param_witness_values = - try_vecmap(range_to_vec(&self.param_witnesses[&name]), |witness_index| { - witness_map - .get(&witness_index) - .ok_or_else(|| AbiError::MissingParamWitnessValue { - name: name.clone(), - witness_index, - }) - .copied() - })?; + let num_fields = typ.field_count(); + let param_witness_values = try_vecmap(0..num_fields, |index| { + let witness_index = Witness(pointer + index); + witness_map + .get(&witness_index) + .ok_or_else(|| AbiError::MissingParamWitnessValue { + name: name.clone(), + witness_index, + }) + .copied() + })?; + pointer += num_fields; decode_value(&mut param_witness_values.into_iter(), &typ) .map(|input_value| (name.clone(), input_value)) @@ -461,7 +410,8 @@ impl Abi { // We also attempt to decode the circuit's return value from `witness_map`. let return_value = if let Some(return_type) = &self.return_type { if let Ok(return_witness_values) = - try_vecmap(self.return_witnesses.clone(), |witness_index| { + try_vecmap(0..return_type.abi_type.field_count(), |index| { + let witness_index = Witness(pointer + index); witness_map .get(&witness_index) .ok_or_else(|| AbiError::MissingParamWitnessValue { @@ -587,16 +537,6 @@ pub enum AbiValue { }, } -fn range_to_vec(ranges: &[Range]) -> Vec { - let mut result = Vec::new(); - for range in ranges { - for witness in range.start.witness_index()..range.end.witness_index() { - result.push(witness.into()); - } - } - result -} - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "error_kind", rename_all = "lowercase")] pub enum AbiErrorType { @@ -620,10 +560,10 @@ impl AbiErrorType { } } -pub fn display_abi_error( - fields: &[FieldElement], +pub fn display_abi_error( + fields: &[F], error_type: AbiErrorType, -) -> PrintableValueDisplay { +) -> PrintableValueDisplay { match error_type { AbiErrorType::FmtString { length, item_types } => { let mut fields_iter = fields.iter().copied(); @@ -652,7 +592,7 @@ pub fn display_abi_error( mod test { use std::collections::BTreeMap; - use acvm::{acir::native_types::Witness, AcirField, FieldElement}; + use acvm::{AcirField, FieldElement}; use crate::{ input_parser::InputValue, Abi, AbiParameter, AbiReturnType, AbiType, AbiVisibility, @@ -674,16 +614,10 @@ mod test { visibility: AbiVisibility::Public, }, ], - // Note that the return value shares a witness with `thing2` - param_witnesses: BTreeMap::from([ - ("thing1".to_string(), vec![(Witness(1)..Witness(3))]), - ("thing2".to_string(), vec![(Witness(3)..Witness(4))]), - ]), return_type: Some(AbiReturnType { abi_type: AbiType::Field, visibility: AbiVisibility::Public, }), - return_witnesses: vec![Witness(3)], error_types: BTreeMap::default(), }; @@ -706,7 +640,6 @@ mod test { assert_eq!(reconstructed_inputs[&key], expected_value); } - // We also decode the return value (we can do this immediately as we know it shares a witness with an input). - assert_eq!(return_value.unwrap(), reconstructed_inputs["thing2"]); + assert!(return_value.is_none()); } } diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts index f4ab8175700c..62eb7658f436 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/abi_encode.ts @@ -9,9 +9,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { foo: [{ start: 1, end: 2 }], bar: [{ start: 2, end: 4 }] }, return_type: null, - return_witnesses: [], error_types: {}, }; diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts index 3698b913c667..8fecacf79f88 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/array_as_field.ts @@ -8,9 +8,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, - return_witnesses: [], error_types: {}, }; diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/decode_error.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/decode_error.ts index 36eb18b52108..662e0acb4169 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/decode_error.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/decode_error.ts @@ -15,9 +15,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, - return_witnesses: [], error_types: { [FAKE_FIELD_SELECTOR]: { error_kind: 'custom', diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts index 4e3e2fd12a8c..3610c51e6db8 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/field_as_array.ts @@ -8,9 +8,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { foo: [{ start: 1, end: 3 }] }, return_type: null, - return_witnesses: [], error_types: {}, }; diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts index ee666e40e87c..6d098832b3a6 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/structs.ts @@ -47,13 +47,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { - struct_arg: [{ start: 1, end: 2 }], - struct_array_arg: [{ start: 2, end: 5 }], - nested_struct_arg: [{ start: 5, end: 6 }], - }, return_type: null, - return_witnesses: [], error_types: {}, }; diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts index 82a3e3998cae..b7f1f221b484 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts +++ b/noir/noir-repo/tooling/noirc_abi_wasm/test/shared/uint_overflow.ts @@ -8,9 +8,7 @@ export const abi: Abi = { visibility: 'private', }, ], - param_witnesses: { foo: [{ start: 1, end: 2 }] }, return_type: null, - return_witnesses: [], error_types: {}, }; diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index b45678f5d8bd..181b6b3b206f 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -4399,11 +4399,9 @@ __metadata: "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3 - chai: ^4.4.1 eslint: ^8.57.0 eslint-plugin-prettier: ^5.1.3 fflate: ^0.8.0 - mocha: ^10.2.0 prettier: 3.2.5 ts-node: ^10.9.1 typescript: 5.4.2 diff --git a/yarn-project/types/src/noir/index.ts b/yarn-project/types/src/noir/index.ts index f268071eae0a..7361d6687dbe 100644 --- a/yarn-project/types/src/noir/index.ts +++ b/yarn-project/types/src/noir/index.ts @@ -14,15 +14,10 @@ export const AZTEC_INTERNAL_ATTRIBUTE = 'aztec(internal)'; export const AZTEC_INITIALIZER_ATTRIBUTE = 'aztec(initializer)'; export const AZTEC_VIEW_ATTRIBUTE = 'aztec(view)'; -/** The witness indices of the parameters. */ -type ParamWitnessIndices = { /** Start */ start: number; /** End */ end: number }; - /** The ABI of an Aztec.nr function. */ export interface NoirFunctionAbi { /** The parameters of the function. */ parameters: ABIParameter[]; - /** The witness indices of the parameters. Indexed by parameter name. */ - param_witnesses: { [key: string]: undefined | ParamWitnessIndices[] }; /** The return type of the function. */ return_type: { /** @@ -34,8 +29,6 @@ export interface NoirFunctionAbi { */ visibility: ABIParameterVisibility; }; - /** The witness indices of the return type. */ - return_witnesses: number[]; } /**