From 7cbabd6840ee8127c12a51a2342ef634f1d58954 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Tue, 24 Sep 2024 11:00:31 +0200 Subject: [PATCH] feat!: `aztec_macros` are dead, long live `aztec::macros` (#8438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requires noir sync and https://github.com/noir-lang/noir/issues/6078 to be merged so `compute_note_hash_and_optionally_a_nullifier` can be autogenerated. Also, fixed a lot of explicit numeric generics, used arithmetics on them (yay!!) and introduced a macro for partial notes --------- Co-authored-by: TomAFrench Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: benesjan Co-authored-by: Nicolás Venturo Co-authored-by: Ary Borenszweig --- avm-transpiler/src/transpile_contract.rs | 2 +- boxes/boxes/react/src/contracts/src/main.nr | 20 +- boxes/boxes/vanilla/src/contracts/src/main.nr | 18 +- boxes/init/src/main.nr | 7 +- .../aztec/concepts/storage/partial_notes.md | 4 +- docs/docs/migration_notes.md | 81 ++++ .../aztec-nr/address-note/src/address_note.nr | 18 +- .../aztec/src/context/private_context.nr | 2 +- .../encrypted_event_emission.nr | 44 +- .../encrypted_logs/encrypted_note_emission.nr | 24 +- .../aztec/src/encrypted_logs/incoming_body.nr | 69 ++-- .../aztec/src/encrypted_logs/payload.nr | 8 +- .../aztec/src/event/event_interface.nr | 6 +- .../aztec-nr/aztec/src/generators.nr | 8 +- .../aztec/src/history/note_inclusion.nr | 8 +- .../aztec/src/history/note_validity.nr | 8 +- .../aztec/src/history/nullifier_inclusion.nr | 8 +- .../src/history/nullifier_non_inclusion.nr | 9 +- noir-projects/aztec-nr/aztec/src/lib.nr | 1 + .../aztec-nr/aztec/src/macros/events/mod.nr | 76 ++++ .../aztec/src/macros/functions/interfaces.nr | 186 +++++++++ .../aztec/src/macros/functions/mod.nr | 328 +++++++++++++++ .../aztec-nr/aztec/src/macros/mod.nr | 164 ++++++++ .../aztec-nr/aztec/src/macros/notes/mod.nr | 388 ++++++++++++++++++ .../aztec-nr/aztec/src/macros/storage/mod.nr | 162 ++++++++ .../aztec-nr/aztec/src/macros/utils.nr | 257 ++++++++++++ .../aztec-nr/aztec/src/note/lifecycle.nr | 24 +- .../aztec/src/note/note_getter/mod.nr | 36 +- .../aztec/src/note/note_getter_options.nr | 10 +- .../aztec-nr/aztec/src/note/note_header.nr | 2 +- .../aztec-nr/aztec/src/note/note_interface.nr | 39 +- .../aztec/src/note/note_viewer_options.nr | 6 +- .../aztec-nr/aztec/src/note/utils.nr | 40 +- .../aztec-nr/aztec/src/oracle/notes.nr | 4 +- noir-projects/aztec-nr/aztec/src/prelude.nr | 4 +- .../aztec/src/state_vars/private_immutable.nr | 13 +- .../aztec/src/state_vars/private_mutable.nr | 7 +- .../aztec/src/state_vars/private_set.nr | 14 +- .../aztec/src/state_vars/public_immutable.nr | 4 +- .../scheduled_delay_change/test.nr | 2 +- .../aztec-nr/aztec/src/state_vars/storage.nr | 6 +- .../src/test/helpers/test_environment.nr | 11 +- .../aztec/src/test/mocks/mock_note.nr | 65 +-- .../unencrypted_event_emission.nr | 6 +- .../aztec-nr/uint-note/src/uint_note.nr | 78 +--- .../aztec-nr/value-note/src/utils.nr | 4 +- .../aztec-nr/value-note/src/value_note.nr | 92 +---- .../app_subscription_contract/src/main.nr | 33 +- .../src/subscription_note.nr | 12 +- .../contracts/auth_contract/src/main.nr | 43 +- .../auth_registry_contract/src/main.nr | 50 ++- .../auth_wit_test_contract/src/main.nr | 10 +- .../avm_initializer_test_contract/src/main.nr | 18 +- .../contracts/avm_test_contract/src/main.nr | 156 +++---- .../benchmarking_contract/src/main.nr | 25 +- .../contracts/card_game_contract/src/game.nr | 14 +- .../contracts/card_game_contract/src/main.nr | 46 ++- .../contracts/child_contract/src/main.nr | 45 +- .../contracts/claim_contract/src/main.nr | 25 +- .../src/events/class_registered.nr | 2 +- .../events/private_function_broadcasted.nr | 10 +- .../unconstrained_function_broadcasted.nr | 6 +- .../src/main.nr | 23 +- .../src/main.nr | 17 +- .../contracts/counter_contract/src/main.nr | 37 +- .../crowdfunding_contract/src/main.nr | 44 +- .../delegated_on_contract/src/main.nr | 27 +- .../contracts/delegator_contract/src/main.nr | 32 +- .../docs_example_contract/src/main.nr | 129 +++--- .../docs_example_contract/src/options.nr | 10 +- .../src/types/card_note.nr | 15 +- .../easy_private_token_contract/src/main.nr | 18 +- .../easy_private_voting_contract/src/main.nr | 32 +- .../ecdsa_k_account_contract/src/main.nr | 24 +- .../ecdsa_public_key_note/src/lib.nr | 94 ++++- .../ecdsa_r_account_contract/src/main.nr | 24 +- .../contracts/escrow_contract/src/main.nr | 17 +- .../contracts/fee_juice_contract/src/main.nr | 32 +- .../contracts/fpc_contract/src/main.nr | 32 +- .../import_test_contract/src/main.nr | 13 +- .../inclusion_proofs_contract/src/main.nr | 48 ++- .../contracts/lending_contract/src/main.nr | 73 ++-- .../src/main.nr | 6 +- .../contracts/nft_contract/src/main.nr | 112 ++--- .../src/test/transfer_to_private.nr | 2 +- .../contracts/nft_contract/src/test/utils.nr | 4 +- .../nft_contract/src/types/nft_note.nr | 83 +--- .../contracts/parent_contract/src/main.nr | 61 +-- .../pending_note_hashes_contract/src/main.nr | 42 +- .../contracts/price_feed_contract/src/main.nr | 19 +- .../private_fpc_contract/src/main.nr | 23 +- .../contracts/router_contract/src/main.nr | 21 +- .../schnorr_account_contract/src/main.nr | 24 +- .../src/public_key_note.nr | 13 +- .../src/main.nr | 11 +- .../src/main.nr | 11 +- .../contracts/spam_contract/src/main.nr | 20 +- .../spam_contract/src/types/balance_set.nr | 24 +- .../spam_contract/src/types/token_note.nr | 81 +--- .../stateful_test_contract/src/main.nr | 54 +-- .../static_child_contract/src/main.nr | 41 +- .../static_parent_contract/src/main.nr | 35 +- .../contracts/test_contract/src/main.nr | 107 ++--- .../contracts/test_contract/src/test_note.nr | 13 +- .../contracts/test_log_contract/src/main.nr | 27 +- .../contracts/test_log_contract/src/test.nr | 0 .../token_blacklist_contract/src/main.nr | 66 +-- .../src/types/balances_map.nr | 23 +- .../src/types/token_note.nr | 12 +- .../src/types/transparent_note.nr | 37 +- .../token_bridge_contract/src/main.nr | 41 +- .../contracts/token_contract/src/main.nr | 279 +++++-------- .../token_contract/src/test/minting.nr | 10 +- .../token_contract/src/test/refunds.nr | 4 +- .../token_contract/src/test/shielding.nr | 4 +- .../token_contract/src/test/utils.nr | 4 +- .../token_contract/src/types/balance_set.nr | 28 +- .../token_contract/src/types/token_note.nr | 83 +--- .../src/types/transparent_note.nr | 41 +- .../contracts/uniswap_contract/src/main.nr | 34 +- .../crates/types/src/abis/event_selector.nr | 2 +- .../src/abis/private_circuit_public_inputs.nr | 2 +- .../crates/types/src/address/aztec_address.nr | 2 +- .../crates/types/src/constants.nr | 12 +- .../crates/types/src/lib.nr | 1 + .../crates/types/src/meta/mod.nr | 227 ++++++++++ .../crates/types/src/traits.nr | 28 +- .../aztec_macros/src/utils/checks.rs | 5 +- .../compiler/noirc_driver/src/lib.rs | 1 + .../aztec.js/src/contract/batch_call.ts | 4 +- .../aztec.js/src/contract/contract.test.ts | 4 +- .../contract/contract_function_interaction.ts | 4 +- yarn-project/aztec.js/src/index.ts | 2 +- .../src/contract-interface-gen/typescript.ts | 70 ++-- .../circuit-types/src/interfaces/pxe.ts | 9 +- .../end-to-end/src/e2e_event_logs.test.ts | 26 +- .../src/e2e_fees/private_refunds.test.ts | 6 +- .../private_transfer_recursion.test.ts | 6 +- .../transfer_private.test.ts | 2 +- yarn-project/foundation/src/abi/decoder.ts | 49 ++- yarn-project/prover-client/package.json | 2 +- .../pxe/src/pxe_service/pxe_service.ts | 24 +- .../simulator/src/client/simulator.test.ts | 4 +- .../simulator/src/client/test_utils.ts | 45 +- .../src/client/unconstrained_execution.ts | 11 +- .../types/src/abi/contract_artifact.ts | 6 +- yarn-project/types/src/noir/index.ts | 11 +- 147 files changed, 3648 insertions(+), 2026 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/macros/events/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/functions/interfaces.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr create mode 100644 noir-projects/aztec-nr/aztec/src/macros/utils.nr create mode 100644 noir-projects/noir-contracts/contracts/test_log_contract/src/test.nr create mode 100644 noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr diff --git a/avm-transpiler/src/transpile_contract.rs b/avm-transpiler/src/transpile_contract.rs index 9cb9d5fbd46..542baef9cc7 100644 --- a/avm-transpiler/src/transpile_contract.rs +++ b/avm-transpiler/src/transpile_contract.rs @@ -92,7 +92,7 @@ impl From for TranspiledContractArtifact { for function in contract.functions { // TODO(4269): once functions are tagged for transpilation to AVM, check tag - if function.custom_attributes.contains(&"aztec(public)".to_string()) { + if function.custom_attributes.contains(&"public".to_string()) { info!("Transpiling AVM function {} on contract {}", function.name, contract.name); // Extract Brillig Opcodes from acir let acir_program = function.bytecode; diff --git a/boxes/boxes/react/src/contracts/src/main.nr b/boxes/boxes/react/src/contracts/src/main.nr index d0fafea3250..6d5d4ecc830 100644 --- a/boxes/boxes/react/src/contracts/src/main.nr +++ b/boxes/boxes/react/src/contracts/src/main.nr @@ -1,18 +1,22 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract BoxReact { use dep::aztec::{ keys::public_keys::{IvpkM, OvpkM}, prelude::{AztecAddress, PrivateMutable, Map, NoteInterface, NoteHeader, Point}, - encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys + encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, + macros::{storage::storage, functions::{private, public, initializer}} }; - use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN}; + use dep::value_note::value_note::ValueNote; - #[aztec(storage)] - struct Storage { - numbers: Map>, + #[storage] + struct Storage { + numbers: Map, Context>, } - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor( number: Field, owner: AztecAddress, @@ -25,7 +29,7 @@ contract BoxReact { numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); } - #[aztec(private)] + #[private] fn setNumber( number: Field, owner: AztecAddress, diff --git a/boxes/boxes/vanilla/src/contracts/src/main.nr b/boxes/boxes/vanilla/src/contracts/src/main.nr index 401a2c86989..f336c4ad841 100644 --- a/boxes/boxes/vanilla/src/contracts/src/main.nr +++ b/boxes/boxes/vanilla/src/contracts/src/main.nr @@ -1,18 +1,22 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract Vanilla { use dep::aztec::{ keys::public_keys::{IvpkM, OvpkM}, prelude::{AztecAddress, PrivateMutable, Map, NoteInterface, NoteHeader, Point}, - encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys + encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, + macros::{storage::storage, functions::{private, public, initializer}} }; use dep::value_note::value_note::{ValueNote, VALUE_NOTE_LEN}; - #[aztec(storage)] - struct Storage { - numbers: Map>, + #[storage] + struct Storage { + numbers: Map, Context>, } - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor( number: Field, owner: AztecAddress, @@ -25,7 +29,7 @@ contract Vanilla { numbers.at(owner).initialize(&mut new_number).emit(encode_and_encrypt_note_with_keys(&mut context, owner_ovpk_m, owner_ivpk_m, owner)); } - #[aztec(private)] + #[private] fn setNumber( number: Field, owner: AztecAddress, diff --git a/boxes/init/src/main.nr b/boxes/init/src/main.nr index 8e06a99f11c..7924ff03505 100644 --- a/boxes/init/src/main.nr +++ b/boxes/init/src/main.nr @@ -1,6 +1,9 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract Main { - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor() { } } diff --git a/docs/docs/aztec/concepts/storage/partial_notes.md b/docs/docs/aztec/concepts/storage/partial_notes.md index 7a72ef21814..1d21c0b227f 100644 --- a/docs/docs/aztec/concepts/storage/partial_notes.md +++ b/docs/docs/aztec/concepts/storage/partial_notes.md @@ -134,9 +134,9 @@ Then we just emit `P_a.x` and `P_b.x` as a note hashes, and we're done! [`NoteInterface.nr`](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/note/note_interface.nr) implements `compute_note_hiding_point`, which takes a note and computes the point "hides" it. -This is implemented in the example token contract: +This is implemented by applying the `partial_note` attribute: -#include_code compute_note_hiding_point noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr rust +#include_code TokenNote noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr rust Those `G_x` are generators that generated [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-projects/aztec-nr/aztec/src/generators.nr). Anyone can use them for separating different fields in a "partial note". diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 85270d9718d..be2603f0c13 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -26,6 +26,87 @@ The `select` function in both `NoteGetterOptions` and `NoteViewerOptions` no lon + options.select(ValueNote::properties().value, Comparator.EQ, amount) ``` +### [Aztec.nr] Changes to contract definition + +We've migrated the Aztec macros to use the newly introduce meta programming Noir feature. Due to being Noir-based, the new macros are less obscure and can be more easily modified. + +As part of this transition, some changes need to be applied to Aztec contracts: + +- The top level `contract` block needs to have the `#[aztec]` macro applied to it. +- All `#[aztec(name)]` macros are renamed to `#[name]`. +- The storage struct (the one that gets the `#[storage]` macro applied) but be generic over a `Context` type, and all state variables receive this type as their last generic type parameter. + +```diff ++ use dep::aztec::macros::aztec; + +#[aztec] +contract Token { ++ use dep::aztec::macros::{storage::storage, events::event, functions::{initializer, private, view, public}}; + +- #[aztec(storage)] +- struct Storage { ++ #[storage] ++ struct Storage { +- admin: PublicMutable, ++ admin: PublicMutable, +- minters: Map>, ++ minters: Map, Context>, + } + +- #[aztec(public)] +- #[aztec(initializer)] ++ #[public] ++ #[initializer] + fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { + ... + } + +- #[aztec(public)] +- #[aztec(view)] +- fn public_get_name() -> FieldCompressedString { ++ #[public] ++ #[view] + fn public_get_name() -> FieldCompressedString { + ... + } +``` + +### [Aztec.nr] Changes to `NoteInterface` + +The new macro model prevents partial trait auto-implementation: they either implement the entire trait or none of it. This means users can no longer implement part of `NoteInterface` and have the rest be auto-implemented. + +For this reason we've separated the methods which are auto-implemented and those which needs to be implemented manually into two separate traits: the auto-implemented ones stay in the `NoteInterface` trace and the manually implemented ones were moved to `NullifiableNote` (name likely to change): + +```diff +-#[aztec(note)] ++#[note] +struct AddressNote { + ... +} + +-impl NoteInterface for AddressNote { ++impl NullifiableNote for AddressNote { + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + ... + } + + fn compute_nullifier_without_context(self) -> Field { + ... + } +} +``` + +### [Aztec.nr] Changes to contract interface + +The `Contract::storage()` static method has been renamed to `Contract::storage_layout()`. + +```diff +- let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, fee_payer); +- let user_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, user); ++ let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, fee_payer); ++ let user_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, user); +``` + ## 0.53.0 ### [Aztec.nr] Remove `OwnedNote` and create `UintNote` diff --git a/noir-projects/aztec-nr/address-note/src/address_note.nr b/noir-projects/aztec-nr/address-note/src/address_note.nr index cec5e576629..e8724d50e14 100644 --- a/noir-projects/aztec-nr/address-note/src/address_note.nr +++ b/noir-projects/aztec-nr/address-note/src/address_note.nr @@ -1,20 +1,14 @@ use dep::aztec::{ - protocol_types::{ - address::AztecAddress, traits::Empty, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator -}, - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_nullify}, - oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app, context::PrivateContext + protocol_types::{address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + note::{note_header::NoteHeader, note_interface::NullifiableNote, utils::compute_note_hash_for_nullify}, + oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app, context::PrivateContext, + macros::notes::note }; -global ADDRESS_NOTE_LEN: u32 = 3; -// ADDRESS_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global ADDRESS_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - // docs:start:address_note_def // docs:start:address_note_struct // Stores an address -#[aztec(note)] +#[note] struct AddressNote { address: AztecAddress, // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. @@ -23,7 +17,7 @@ struct AddressNote { } // docs:end:address_note_struct -impl NoteInterface for AddressNote { +impl NullifiableNote for AddressNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index bb415cacd0d..18840c511a0 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -34,7 +34,7 @@ use dep::protocol_types::{ }; // When finished, one can call .finish() to convert back to the abi -struct PrivateContext { +pub struct PrivateContext { // docs:start:private-context inputs: PrivateContextInputs, side_effect_counter: u32, diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr index 4030a7a85ef..70953b5fe66 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_event_emission.nr @@ -6,7 +6,7 @@ use crate::{ }; use dep::protocol_types::{address::AztecAddress, hash::sha256_to_field}; -unconstrained fn compute_unconstrained( +unconstrained fn compute_unconstrained( contract_address: AztecAddress, randomness: Field, ovsk_app: Field, @@ -14,7 +14,7 @@ unconstrained fn compute_unconstrained ([u8; OB], Field) where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> ([u8; OB], Field) where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { compute( contract_address, randomness, @@ -26,7 +26,7 @@ unconstrained fn compute_unconstrained( +fn compute( contract_address: AztecAddress, randomness: Field, ovsk_app: Field, @@ -34,7 +34,7 @@ fn compute( ivpk: IvpkM, recipient: AztecAddress, event: Event -) -> ([u8; OB], Field) where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> ([u8; OB], Field) where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { let encrypted_log: [u8; OB] = compute_encrypted_event_log( contract_address, randomness, @@ -48,7 +48,7 @@ fn compute( (encrypted_log, log_hash) } -fn emit_with_keys( +fn emit_with_keys( context: &mut PrivateContext, randomness: Field, event: Event, @@ -56,18 +56,18 @@ fn emit_with_keys( ivpk: IvpkM, iv: AztecAddress, inner_compute: fn(AztecAddress, Field, Field, OvpkM, IvpkM, AztecAddress, Event) -> ([u8; OB], Field) -) where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { let contract_address: AztecAddress = context.this_address(); let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); let (encrypted_log, log_hash) = inner_compute(contract_address, randomness, ovsk_app, ovpk, ivpk, iv, event); context.emit_raw_event_log_with_masked_address(randomness, encrypted_log, log_hash); } -pub fn encode_and_encrypt_event( +pub fn encode_and_encrypt_event( context: &mut PrivateContext, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let ovpk = get_public_keys(ov).ovpk_m; let ivpk = get_public_keys(iv).ivpk_m; @@ -76,11 +76,11 @@ pub fn encode_and_encrypt_event( } } -pub fn encode_and_encrypt_event_unconstrained( +pub fn encode_and_encrypt_event_unconstrained( context: &mut PrivateContext, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let ovpk = get_public_keys(ov).ovpk_m; let ivpk = get_public_keys(iv).ivpk_m; @@ -89,12 +89,12 @@ pub fn encode_and_encrypt_event_unconstrained( +pub fn encode_and_encrypt_event_with_randomness( context: &mut PrivateContext, randomness: Field, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext, Field)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext, Field)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let ovpk = get_public_keys(ov).ovpk_m; let ivpk = get_public_keys(iv).ivpk_m; @@ -102,12 +102,12 @@ pub fn encode_and_encrypt_event_with_randomness( +pub fn encode_and_encrypt_event_with_randomness_unconstrained( context: &mut PrivateContext, randomness: Field, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext, Field)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext, Field)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let ovpk = get_public_keys(ov).ovpk_m; let ivpk = get_public_keys(iv).ivpk_m; @@ -115,49 +115,49 @@ pub fn encode_and_encrypt_event_with_randomness_unconstrained( +pub fn encode_and_encrypt_event_with_keys( context: &mut PrivateContext, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let randomness = unsafe_rand(); emit_with_keys(context, randomness, e, ovpk, ivpk, recipient, compute); } } -pub fn encode_and_encrypt_event_with_keys_unconstrained( +pub fn encode_and_encrypt_event_with_keys_unconstrained( context: &mut PrivateContext, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { let randomness = unsafe_rand(); emit_with_keys(context, randomness, e, ovpk, ivpk, recipient, compute_unconstrained); } } -pub fn encode_and_encrypt_event_with_keys_with_randomness( +pub fn encode_and_encrypt_event_with_keys_with_randomness( context: &mut PrivateContext, randomness: Field, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, Field, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(&mut PrivateContext, Field, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { emit_with_keys(context, randomness, e, ovpk, ivpk, recipient, compute); } } -pub fn encode_and_encrypt_event_with_keys_with_randomness_unconstrained( +pub fn encode_and_encrypt_event_with_keys_with_randomness_unconstrained( context: &mut PrivateContext, randomness: Field, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, Field, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; NB]: LensForEncryptedEvent { +) -> fn[(&mut PrivateContext, Field, OvpkM, IvpkM, AztecAddress)](Event) -> () where Event: EventInterface, [u8; N * 32 + 64]: LensForEncryptedEvent { | e: Event | { emit_with_keys(context, randomness, e, ovpk, ivpk, recipient, compute_unconstrained); } diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr index 3ddfdaee050..fc55b43ea64 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/encrypted_note_emission.nr @@ -5,7 +5,7 @@ use crate::{ }; use dep::protocol_types::{hash::sha256_to_field, address::AztecAddress, abis::note_hash::NoteHash}; -fn compute_raw_note_log( +fn compute_raw_note_log( context: PrivateContext, note: Note, ovsk_app: Field, @@ -13,7 +13,7 @@ fn compute_raw_note_log( ivpk: IvpkM, recipient: AztecAddress, num_public_values: u8 // Number of values to be appended to the log in public (used in partial note flow). -) -> (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { let note_header = note.get_header(); let note_hash_counter = note_header.note_hash_counter; let storage_slot = note_header.storage_slot; @@ -39,23 +39,23 @@ fn compute_raw_note_log( (note_hash_counter, encrypted_log, log_hash) } -unconstrained fn compute_raw_note_log_unconstrained( +unconstrained fn compute_raw_note_log_unconstrained( context: PrivateContext, note: Note, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress, num_public_values: u8 // Number of values to be appended to the log in public (used in partial note flow). -) -> (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> (u32, [u8; M], Field) where Note: NoteInterface, [Field; N]: LensForEncryptedLog { let ovsk_app = get_ovsk_app(ovpk.hash()); compute_raw_note_log(context, note, ovsk_app, ovpk, ivpk, recipient, num_public_values) } -pub fn encode_and_encrypt_note( +pub fn encode_and_encrypt_note( context: &mut PrivateContext, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { | e: NoteEmission | { let ovpk = get_public_keys(ov).ovpk_m; let ivpk = get_public_keys(iv).ivpk_m; @@ -69,11 +69,11 @@ pub fn encode_and_encrypt_note( } } -pub fn encode_and_encrypt_note_unconstrained( +pub fn encode_and_encrypt_note_unconstrained( context: &mut PrivateContext, ov: AztecAddress, iv: AztecAddress -) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> fn[(AztecAddress, AztecAddress, &mut PrivateContext)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { | e: NoteEmission | { // Note: We could save a lot of gates by obtaining the following keys in an unconstrained context but this // function is currently not used anywhere so we are not optimizing it. @@ -93,12 +93,12 @@ pub fn encode_and_encrypt_note_unconstrained( +pub fn encode_and_encrypt_note_with_keys( context: &mut PrivateContext, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { | e: NoteEmission | { let ovsk_app: Field = context.request_ovsk_app(ovpk.hash()); @@ -111,12 +111,12 @@ pub fn encode_and_encrypt_note_with_keys( +pub fn encode_and_encrypt_note_with_keys_unconstrained( context: &mut PrivateContext, ovpk: OvpkM, ivpk: IvpkM, recipient: AztecAddress -) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { +) -> fn[(&mut PrivateContext, OvpkM, IvpkM, AztecAddress)](NoteEmission) -> () where Note: NoteInterface, [Field; N]: LensForEncryptedLog { | e: NoteEmission | { // Number of public values is always 0 here because `encode_and_encrypt_note_with_keys_unconstrained(...)` is only called // in the non-partial note flow. diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr index f0cb22180b1..99f84f33bf8 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/incoming_body.nr @@ -5,20 +5,17 @@ use dep::protocol_types::{scalar::Scalar}; use std::aes128::aes128_encrypt; use crate::keys::{point_to_symmetric_key::point_to_symmetric_key, public_keys::IvpkM}; -struct EncryptedLogIncomingBody { - plaintext: [u8; M] +struct EncryptedLogIncomingBody { + plaintext: [u8; N * 32 + 64] } -impl EncryptedLogIncomingBody { - pub fn from_note(note: T, storage_slot: Field) -> Self where T: NoteInterface { +impl EncryptedLogIncomingBody { + pub fn from_note(note: T, storage_slot: Field) -> Self where T: NoteInterface { let mut plaintext = note.to_be_bytes(storage_slot); EncryptedLogIncomingBody { plaintext } } - pub fn from_event( - event: T, - randomness: Field - ) -> Self where T: EventInterface { + pub fn from_event(event: T, randomness: Field) -> Self where T: EventInterface { let mut plaintext = event.private_to_be_bytes(randomness); EncryptedLogIncomingBody { plaintext } } @@ -43,7 +40,7 @@ mod test { }; use crate::{ - note::{note_header::NoteHeader, note_interface::NoteInterface}, + note::{note_header::NoteHeader, note_interface::{NoteInterface, PartialNote, NullifiableNote}}, encrypted_logs::incoming_body::EncryptedLogIncomingBody, event::event_interface::EventInterface, context::PrivateContext, keys::public_keys::IvpkM }; @@ -56,12 +53,26 @@ mod test { } global ADDRESS_NOTE_LEN: u32 = 3; - global ADDRESS_NOTE_BYTES_LEN: u32 = 32 * 3 + 64; - impl NoteInterface for AddressNote { - fn compute_note_hiding_point(_self: Self) -> Point { - crate::generators::Ga1 + impl NullifiableNote for AddressNote { + fn compute_nullifier( + _self: Self, + _context: &mut PrivateContext, + _note_hash_for_nullify: Field + ) -> Field { + 1 + } + + fn compute_nullifier_without_context(_self: Self) -> Field { + 1 } + } + + struct AddressNoteHidingPoint { + inner: Point + } + + impl NoteInterface for AddressNote { fn get_note_type_id() -> Field { 1 @@ -75,18 +86,6 @@ mod test { self.header = header; } - fn compute_nullifier( - _self: Self, - _context: &mut PrivateContext, - _note_hash_for_nullify: Field - ) -> Field { - 1 - } - - fn compute_nullifier_without_context(_self: Self) -> Field { - 1 - } - fn serialize_content(self) -> [Field; ADDRESS_NOTE_LEN] { [self.address.to_field(), self.owner.to_field(), self.randomness] } @@ -100,10 +99,10 @@ mod test { } } - fn to_be_bytes(self, storage_slot: Field) -> [u8; ADDRESS_NOTE_BYTES_LEN] { + fn to_be_bytes(self, storage_slot: Field) -> [u8; ADDRESS_NOTE_LEN * 32 + 64] { let serialized_note = self.serialize_content(); - let mut buffer: [u8; ADDRESS_NOTE_BYTES_LEN] = [0; ADDRESS_NOTE_BYTES_LEN]; + let mut buffer: [u8; ADDRESS_NOTE_LEN * 32 + 64] = [0; ADDRESS_NOTE_LEN * 32 + 64]; let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); let note_type_id_bytes: [u8; 32] = AddressNote::get_note_type_id().to_be_bytes(); @@ -121,6 +120,10 @@ mod test { } buffer } + + fn compute_note_hash(self) -> Field { + crate::generators::Ga1.x + } } impl AddressNote { @@ -187,10 +190,8 @@ mod test { } global TEST_EVENT_LEN: u32 = 3; - global TEST_EVENT_BYTES_LEN: u32 = 32 * 3 + 64; - global TEST_EVENT_BYTES_LEN_WITHOUT_RANDOMNESS: u32 = 32 * 3 + 32; - impl EventInterface for TestEvent { + impl EventInterface for TestEvent { fn get_event_type_id() -> EventSelector { comptime { @@ -198,8 +199,8 @@ mod test { } } - fn private_to_be_bytes(self, randomness: Field) -> [u8; TEST_EVENT_BYTES_LEN] { - let mut buffer: [u8; TEST_EVENT_BYTES_LEN] = [0; TEST_EVENT_BYTES_LEN]; + fn private_to_be_bytes(self, randomness: Field) -> [u8; TEST_EVENT_LEN * 32 + 64] { + let mut buffer: [u8; TEST_EVENT_LEN * 32 + 64] = [0; TEST_EVENT_LEN * 32 + 64]; let randomness_bytes: [u8; 32] = randomness.to_be_bytes(); let event_type_id_bytes: [u8; 32] = TestEvent::get_event_type_id().to_field().to_be_bytes(); @@ -221,8 +222,8 @@ mod test { buffer } - fn to_be_bytes(self) -> [u8; TEST_EVENT_BYTES_LEN_WITHOUT_RANDOMNESS] { - let mut buffer: [u8; TEST_EVENT_BYTES_LEN_WITHOUT_RANDOMNESS] = [0; TEST_EVENT_BYTES_LEN_WITHOUT_RANDOMNESS]; + fn to_be_bytes(self) -> [u8; TEST_EVENT_LEN * 32 + 32] { + let mut buffer: [u8; TEST_EVENT_LEN * 32 + 32] = [0; TEST_EVENT_LEN * 32 + 32]; let event_type_id_bytes: [u8; 32] = TestEvent::get_event_type_id().to_field().to_be_bytes(); diff --git a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr index 4764132dbc6..1b04aae870a 100644 --- a/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr +++ b/noir-projects/aztec-nr/aztec/src/encrypted_logs/payload.nr @@ -14,7 +14,7 @@ use crate::{ keys::public_keys::{OvpkM, IvpkM} }; -pub fn compute_encrypted_event_log( +pub fn compute_encrypted_event_log( contract_address: AztecAddress, randomness: Field, ovsk_app: Field, @@ -22,7 +22,7 @@ pub fn compute_encrypted_event_log ivpk: IvpkM, recipient: AztecAddress, event: Event -) -> [u8; OB] where Event: EventInterface { +) -> [u8; OB] where Event: EventInterface { let (eph_sk, eph_pk) = generate_ephemeral_key_pair(); let header = EncryptedLogHeader::new(contract_address); @@ -65,7 +65,7 @@ pub fn compute_encrypted_event_log encrypted_bytes } -pub fn compute_encrypted_note_log( +pub fn compute_encrypted_note_log( contract_address: AztecAddress, storage_slot: Field, ovsk_app: Field, @@ -74,7 +74,7 @@ pub fn compute_encrypted_note_log( recipient: AztecAddress, note: Note, num_public_values: u8 // Number of values to be appended to the log in public (used in partial note flow). -) -> [u8; M] where Note: NoteInterface { +) -> [u8; M] where Note: NoteInterface { let (eph_sk, eph_pk) = generate_ephemeral_key_pair(); let header = EncryptedLogHeader::new(contract_address); diff --git a/noir-projects/aztec-nr/aztec/src/event/event_interface.nr b/noir-projects/aztec-nr/aztec/src/event/event_interface.nr index 58a7ea2ba79..08498f1373d 100644 --- a/noir-projects/aztec-nr/aztec/src/event/event_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/event/event_interface.nr @@ -1,8 +1,8 @@ use dep::protocol_types::abis::event_selector::EventSelector; -trait EventInterface { - fn private_to_be_bytes(self, randomness: Field) -> [u8; NB]; - fn to_be_bytes(self) -> [u8; MB]; +pub trait EventInterface { + fn private_to_be_bytes(self, randomness: Field) -> [u8; N * 32 + 64]; + fn to_be_bytes(self) -> [u8; N * 32 + 32]; fn get_event_type_id() -> EventSelector; fn emit(self, _emit: fn[Env](Self) -> ()); } diff --git a/noir-projects/aztec-nr/aztec/src/generators.nr b/noir-projects/aztec-nr/aztec/src/generators.nr index d6318f2bec9..8b4b827e3df 100644 --- a/noir-projects/aztec-nr/aztec/src/generators.nr +++ b/noir-projects/aztec-nr/aztec/src/generators.nr @@ -4,20 +4,24 @@ use dep::protocol_types::point::Point; global Ga1 = Point { x: 0x30426e64aee30e998c13c8ceecda3a77807dbead52bc2f3bf0eae851b4b710c1, y: 0x113156a068f603023240c96b4da5474667db3b8711c521c748212a15bc034ea6, is_infinite: false }; global Ga2 = Point { x: 0x2825c79cc6a5cbbeef7d6a8f1b6a12b312aa338440aefeb4396148c89147c049, y: 0x129bfd1da54b7062d6b544e7e36b90736350f6fba01228c41c72099509f5701e, is_infinite: false }; global Ga3 = Point { x: 0x0edb1e293c3ce91bfc04e3ceaa50d2c541fa9d091c72eb403efb1cfa2cb3357f, y: 0x1341d675fa030ece3113ad53ca34fd13b19b6e9762046734f414824c4d6ade35, is_infinite: false }; +global Ga4 = Point { x: 0x0e0dad2250583f2a9f0acb04ededf1701b85b0393cae753fe7e14b88af81cb52, y: 0x0973b02c5caac339ee4ad5dab51329920f7bf1b6a07e1dabe5df67040b300962, is_infinite: false }; +global Ga5 = Point { x: 0x2f3342e900e8c488a28931aae68970738fdc68afde2910de7b320c00c902087d, y: 0x1bf958dc63cb09d59230603a0269ae86d6f92494da244910351f1132df20fc08, is_infinite: false }; // If you change this update `G_SLOT` in `yarn-project/simulator/src/client/test_utils.ts` as well global G_slot = Point { x: 0x041223147b680850dc82e8a55a952d4df20256fe0593d949a9541ca00f0abf15, y: 0x0a8c72e60d0e60f5d804549d48f3044d06140b98ed717a9b532af630c1530791, is_infinite: false }; mod test { - use crate::generators::{Ga1, Ga2, Ga3, G_slot}; + use crate::generators::{Ga1, Ga2, Ga3, Ga4, Ga5, G_slot}; use dep::protocol_types::point::Point; use std::hash::derive_generators; #[test] fn test_generators() { - let generators: [Point; 4] = derive_generators("aztec_nr_generators".as_bytes(), 0); + let generators: [Point; 6] = derive_generators("aztec_nr_generators".as_bytes(), 0); assert_eq(generators[0], Ga1); assert_eq(generators[1], Ga2); assert_eq(generators[2], Ga3); + assert_eq(generators[4], Ga4); + assert_eq(generators[5], Ga5); assert_eq(generators[3], G_slot); } } diff --git a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr index 142c2321c08..4e245febc9f 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_inclusion.nr @@ -2,19 +2,19 @@ use dep::protocol_types::merkle_tree::root::root_from_sibling_path; use dep::protocol_types::header::Header; use crate::{ - note::{utils::compute_note_hash_for_nullify, note_interface::NoteInterface}, + note::{utils::compute_note_hash_for_nullify, note_interface::{NoteInterface, NullifiableNote}}, oracle::get_membership_witness::get_note_hash_membership_witness }; trait ProveNoteInclusion { - fn prove_note_inclusion(header: Header, note: Note) where Note: NoteInterface; + fn prove_note_inclusion(header: Header, note: Note) where Note: NoteInterface + NullifiableNote; } impl ProveNoteInclusion for Header { - fn prove_note_inclusion( + fn prove_note_inclusion( self, note: Note - ) where Note: NoteInterface { + ) where Note: NoteInterface + NullifiableNote { let note_hash = compute_note_hash_for_nullify(note); let witness = unsafe { diff --git a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr index 5b96acac152..eba2cfc2d05 100644 --- a/noir-projects/aztec-nr/aztec/src/history/note_validity.nr +++ b/noir-projects/aztec-nr/aztec/src/history/note_validity.nr @@ -1,17 +1,17 @@ -use crate::{context::PrivateContext, note::note_interface::NoteInterface}; +use crate::{context::PrivateContext, note::note_interface::{NoteInterface, NullifiableNote}}; use dep::protocol_types::header::Header; trait ProveNoteValidity { - fn prove_note_validity(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface; + fn prove_note_validity(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface + NullifiableNote; } impl ProveNoteValidity for Header { - fn prove_note_validity( + fn prove_note_validity( self, note: Note, context: &mut PrivateContext - ) where Note: NoteInterface { + ) where Note: NoteInterface + NullifiableNote { self.prove_note_inclusion(note); self.prove_note_not_nullified(note, context); } diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr index 3e25d9d7d2d..81aee809ebc 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_inclusion.nr @@ -3,7 +3,7 @@ use dep::protocol_types::header::Header; use crate::{ context::PrivateContext, oracle::get_nullifier_membership_witness::get_nullifier_membership_witness, - note::{utils::compute_siloed_nullifier, note_interface::NoteInterface} + note::{utils::compute_siloed_nullifier, note_interface::{NoteInterface, NullifiableNote}} }; trait ProveNullifierInclusion { @@ -32,16 +32,16 @@ impl ProveNullifierInclusion for Header { } trait ProveNoteIsNullified { - fn prove_note_is_nullified(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface; + fn prove_note_is_nullified(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface + NullifiableNote; } impl ProveNoteIsNullified for Header { // docs:start:prove_note_is_nullified - fn prove_note_is_nullified( + fn prove_note_is_nullified( self, note: Note, context: &mut PrivateContext - ) where Note: NoteInterface { + ) where Note: NoteInterface + NullifiableNote { let nullifier = compute_siloed_nullifier(note, context); self.prove_nullifier_inclusion(nullifier); diff --git a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr index d72e0812663..977a5477547 100644 --- a/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr +++ b/noir-projects/aztec-nr/aztec/src/history/nullifier_non_inclusion.nr @@ -1,7 +1,8 @@ use dep::protocol_types::merkle_tree::root::root_from_sibling_path; use dep::protocol_types::{header::Header, utils::field::{full_field_less_than, full_field_greater_than}}; use crate::{ - context::PrivateContext, note::{utils::compute_siloed_nullifier, note_interface::NoteInterface}, + context::PrivateContext, + note::{utils::compute_siloed_nullifier, note_interface::{NoteInterface, NullifiableNote}}, oracle::get_nullifier_membership_witness::get_low_nullifier_membership_witness }; @@ -41,16 +42,16 @@ impl ProveNullifierNonInclusion for Header { } trait ProveNoteNotNullified { - fn prove_note_not_nullified(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface; + fn prove_note_not_nullified(header: Header, note: Note, context: &mut PrivateContext) where Note: NoteInterface + NullifiableNote; } impl ProveNoteNotNullified for Header { // docs:start:prove_note_not_nullified - fn prove_note_not_nullified( + fn prove_note_not_nullified( self, note: Note, context: &mut PrivateContext - ) where Note: NoteInterface { + ) where Note: NoteInterface + NullifiableNote { let nullifier = compute_siloed_nullifier(note, context); self.prove_nullifier_non_inclusion(nullifier); diff --git a/noir-projects/aztec-nr/aztec/src/lib.nr b/noir-projects/aztec-nr/aztec/src/lib.nr index b699ecd2652..beb2d4b81dc 100644 --- a/noir-projects/aztec-nr/aztec/src/lib.nr +++ b/noir-projects/aztec-nr/aztec/src/lib.nr @@ -15,5 +15,6 @@ mod encrypted_logs; mod unencrypted_logs; pub use dep::protocol_types; mod utils; +mod macros; mod test; diff --git a/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr new file mode 100644 index 00000000000..be9e588cb90 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/events/mod.nr @@ -0,0 +1,76 @@ +use protocol_types::meta::flatten_to_fields; +use super::utils::compute_event_selector; + +comptime fn generate_event_interface(s: StructDefinition) -> Quoted { + let name = s.name(); + let typ = s.as_type(); + let (fields, _) = flatten_to_fields(quote { self }, typ, &[quote {self.header}]); + let content_len = fields.len(); + + let event_type_id = compute_event_selector(s); + + quote { + impl aztec::event::event_interface::EventInterface<$content_len> for $name { + + fn private_to_be_bytes(self, randomness: Field) -> [u8; $content_len * 32 + 64] { + let mut buffer: [u8; $content_len * 32 + 64] = [0; $content_len * 32 + 64]; + + let randomness_bytes: [u8; 32] = randomness.to_be_bytes(); + let event_type_id_bytes: [u8; 32] = $name::get_event_type_id().to_field().to_be_bytes(); + + for i in 0..32 { + buffer[i] = randomness_bytes[i]; + buffer[32 + i] = event_type_id_bytes[i]; + } + + let serialized_event = self.serialize(); + + for i in 0..serialized_event.len() { + let bytes: [u8; 32] = serialized_event[i].to_be_bytes(); + for j in 0..32 { + buffer[64 + i * 32 + j] = bytes[j]; + } + } + + buffer + } + + fn to_be_bytes(self) -> [u8; $content_len * 32 + 32] { + let mut buffer: [u8; $content_len * 32 + 32] = [0; $content_len * 32 + 32]; + + let event_type_id_bytes: [u8; 32] = $name::get_event_type_id().to_field().to_be_bytes(); + + for i in 0..32 { + buffer[32 + i] = event_type_id_bytes[i]; + } + + let serialized_event = self.serialize(); + + for i in 0..serialized_event.len() { + let bytes: [u8; 32] = serialized_event[i].to_be_bytes(); + for j in 0..32 { + buffer[64 + i * 32 + j] = bytes[j]; + } + } + + buffer + } + + fn get_event_type_id() -> aztec::protocol_types::abis::event_selector::EventSelector { + aztec::protocol_types::abis::event_selector::EventSelector::from_field($event_type_id) + } + + fn emit(self, _emit: fn[Env](Self) -> ()) { + _emit(self); + } + } + } +} + +pub comptime fn event(s: StructDefinition) -> Quoted { + let event_interface = generate_event_interface(s); + s.add_attribute("abi(events)"); + quote { + $event_interface + } +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/interfaces.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/interfaces.nr new file mode 100644 index 00000000000..1f39f05c4fb --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/interfaces.nr @@ -0,0 +1,186 @@ +use std::{ + meta::{unquote, type_of}, collections::umap::UHashMap, + hash::{BuildHasherDefault, poseidon2::Poseidon2Hasher} +}; +use crate::macros::utils::{get_fn_visibility, is_fn_view, is_fn_private, add_to_field_slice, compute_fn_selector, is_fn_public}; + +comptime mut global STUBS: UHashMap> = UHashMap::default(); + +pub(crate) comptime fn create_fn_abi_export(f: FunctionDefinition) -> Quoted { + let name = f.name(); + // Remove first arg (inputs) + let mut parameters = f.parameters().map( + | (name, typ): (Quoted, Type) | { + quote { $name: $typ } + } + ).join(quote{,}); + + let parameters_struct_name = f"{name}_parameters".quoted_contents(); + let parameters = quote { + struct $parameters_struct_name { + $parameters + } + }; + + let return_value_type = f.return_type(); + let return_type = if return_value_type != type_of(()) { + quote { return_type: $return_value_type } + } else { + quote {} + }; + + let abi_struct_name = f"{name}_abi".quoted_contents(); + + let result = quote { + + $parameters + + #[abi(functions)] + struct $abi_struct_name { + parameters: $parameters_struct_name, + $return_type + } + }; + result +} + +pub comptime fn stub_fn(f: FunctionDefinition) -> Quoted { + let fn_name = f.name(); + let fn_parameters = f.parameters(); + let fn_return_type = f.return_type(); + + let fn_visibility = get_fn_visibility(f); + let is_static_call = is_fn_view(f); + let is_void = fn_return_type == type_of(()); + + let fn_visibility_capitalized = if is_fn_private(f) { + quote { Private } + } else { + quote { Public } + }; + let is_static_call_capitalized = if is_static_call { + quote { Static } + } else { + quote { } + }; + let is_void_capitalized = if is_void { quote { Void } } else { quote { } }; + + let args_acc_name = quote { args_acc }; + let args_acc = fn_parameters.fold( + quote { + let mut $args_acc_name = &[]; + }, + |args_hasher, param: (Quoted, Type)| { + let (name, typ) = param; + let appended_arg = add_to_field_slice(args_acc_name, name, typ); + quote { + $args_hasher + $appended_arg + } + } + ); + + let args_hash_name = if fn_visibility == quote { private } { + quote { args_hash } + } else { + quote {} + }; + + let args = if fn_visibility == quote { private } { + quote { + $args_acc + let $args_hash_name = dep::aztec::hash::hash_args($args_acc_name); + } + } else { + args_acc + }; + + let fn_parameters_list = fn_parameters.map( + | (name, typ): (Quoted, Type) | { + quote { $name: $typ } + } + ).join(quote{,}); + + let fn_name_str = fn_name.as_str_quote(); + + let fn_name_len: u32 = unquote!(quote { $fn_name_str.as_bytes().len()}); + + let arg_types_list: Quoted = fn_parameters.map(|(_, typ): (_, Type)| quote { $typ }).join(quote {,}); + let arg_types = if fn_parameters.len() == 1 { + // Extra colon to avoid it being interpreted as a parenthesized expression instead of a tuple + quote { ($arg_types_list,) } + } else { + quote { ($arg_types_list) } + }; + + let call_interface_generics = if is_void { + quote { $fn_name_len, $arg_types } + } else { + quote { $fn_name_len, $fn_return_type, $arg_types } + }; + + let call_interface_name = f"dep::aztec::context::call_interfaces::{fn_visibility_capitalized}{is_static_call_capitalized}{is_void_capitalized}CallInterface".quoted_contents(); + + let fn_selector: Field = compute_fn_selector(f); + + let gas_opts = if is_fn_public(f) { + quote { gas_opts: dep::aztec::context::gas::GasOpts::default() } + } else { + quote {} + }; + + let input_type = f"crate::context::inputs::{fn_visibility_capitalized}ContextInputs".quoted_contents().as_type(); + + let return_type_hint = if is_fn_private(f) { + quote { protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs }.as_type() + } else { + fn_return_type + }; + + let parameter_names = if fn_parameters.len() > 0 { + let params = fn_parameters.map(|(name, _): (Quoted, _)| name).join(quote {,}); + quote { inputs, $params } + } else { + quote {inputs} + }; + + let original = quote { + | inputs: $input_type | -> $return_type_hint { + $fn_name($parameter_names) + } + }; + + let args_hash = if fn_visibility == quote { private } { + quote { $args_hash_name, } + } else { + quote {} + }; + + quote { + pub fn $fn_name(self, $fn_parameters_list) -> $call_interface_name<$call_interface_generics> { + $args + let selector = dep::aztec::protocol_types::abis::function_selector::FunctionSelector::from_field($fn_selector); + $call_interface_name { + target_contract: self.target_contract, + selector, + name: $fn_name_str, + $args_hash + args: $args_acc_name, + original: $original, + is_static: $is_static_call, + $gas_opts + } + } + } +} + +/// Registers a function stub created via `stub_fn` as part of a module, +pub(crate) comptime fn register_stub(m: Module, stub: Quoted) { + let current_stubs = STUBS.get(m); + let stubs_to_insert = if current_stubs.is_some() { + current_stubs.unwrap().push_back(stub) + } else { + &[stub] + }; + STUBS.insert(m, stubs_to_insert); +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr new file mode 100644 index 00000000000..6ef4ac1ca2f --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/functions/mod.nr @@ -0,0 +1,328 @@ +mod interfaces; + +use std::meta::type_of; +use super::utils::{ + modify_fn_body, is_fn_private, get_fn_visibility, is_fn_view, is_fn_initializer, is_fn_internal, + fn_has_noinitcheck, add_to_hasher, module_has_storage, module_has_initializer +}; + +use interfaces::{create_fn_abi_export, register_stub, stub_fn}; + +// Functions can have multiple attributes applied to them, e.g. a single function can have #[public], #[view] and +// #[internal]. However. the order in which this will be evaluated is unknown, which makes combining them tricky. +// +// Our strategy is to have two mutually exclusive attributes, #[private] and #[public] (technically unconstrained is a +// hidden third kind), and make it so all functions must have one of them. These contain the code for all other +// attributes, but they only run it if the corresponding attribute has been applied to the function in question. +// +// For example, `#[private]` knows about `#[internal]` and what it should do, but it only does it if it sees that the +// private function in question also has the `internal` attribute applied. `#[internal]` itself does nothing - it is +// what we call a 'marker' attribute, that only exists for `#[private]` or `#[public]` to check if it's been applied. +// Therefore, the execution order of `#[internal]` and `#[private]` is irrelevant. + +/// Internal functions can only be called by the contract itself, typically from private into public. +pub comptime fn internal(_f: FunctionDefinition) { + // Marker attribute +} + +comptime fn create_internal_check(f: FunctionDefinition) -> Quoted { + let name = f.name(); + let assertion_message = f"Function {name} can only be called internally"; + quote { assert(context.msg_sender() == context.this_address(), $assertion_message); } +} + +/// View functions can only be called in a static execution context. +pub comptime fn view(_f: FunctionDefinition) { + // Marker attribute +} + +comptime fn create_view_check(f: FunctionDefinition) -> Quoted { + let name = f.name(); + let assertion_message = f"Function {name} can only be called statically"; + if is_fn_private(f) { + // Here `context` is of type context::PrivateContext + quote { assert(context.inputs.call_context.is_static_call == true, $assertion_message); } + } else { + // Here `context` is of type context::PublicContext + quote { assert(context.inputs.is_static_call == true, $assertion_message); } + } +} + +/// An initializer function is similar to a constructor: +/// - it can only be called once +/// - if there are multiple initializer functions, only one of them can be called +/// - no non-initializer functions can be called until an initializer has ben called (except `noinitcheck` functions) +pub comptime fn initializer(_f: FunctionDefinition) { + // Marker attribute +} + +/// Functions with noinitcheck can be called before contract initialization. +pub comptime fn noinitcheck(_f: FunctionDefinition) { + // Marker attribute +} + +comptime fn create_assert_correct_initializer_args(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::initializer::assert_initialization_matches_address_preimage_{fn_visibility}(context);".quoted_contents() +} + +comptime fn create_mark_as_initialized(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::initializer::mark_as_initialized_{fn_visibility}(&mut context);".quoted_contents() +} + +comptime fn create_init_check(f: FunctionDefinition) -> Quoted { + let fn_visibility = get_fn_visibility(f); + f"dep::aztec::initializer::assert_is_initialized_{fn_visibility}(&mut context);".quoted_contents() +} + +/// Private functions are executed client-side and preserve privacy. +pub comptime fn private(f: FunctionDefinition) -> Quoted { + let fn_abi = create_fn_abi_export(f); + let fn_stub = stub_fn(f); + register_stub(f.module(), fn_stub); + + let module_has_initializer = module_has_initializer(f.module()); + let module_has_storage = module_has_storage(f.module()); + + // Private functions undergo a lot of transformations from their Aztec.nr form into a circuit that can be fed to the + // Private Kernel Circuit. + + // First we change the function signature so that it also receives `PrivateContextInputs`, which contain information + // about the execution context (e.g. the caller). + let original_params = f.parameters(); + f.set_parameters( + &[ + ( + quote { inputs }, quote { crate::context::inputs::private_context_inputs::PrivateContextInputs }.as_type() + ) + ].append(original_params) + ); + + let mut body = f.body().as_block().unwrap(); + + // The original params are hashed and passed to the `context` object, so that the kernel can verify we're received + // the correct values. + + // TODO: Optimize args_hasher for small number of arguments + let args_hasher_name = quote { args_hasher }; + let args_hasher = original_params.fold( + quote { + let mut $args_hasher_name = dep::aztec::hash::ArgsHasher::new(); + }, + |args_hasher, param: (Quoted, Type)| { + let (name, typ) = param; + let appended_arg = add_to_hasher(args_hasher_name, name, typ); + quote { + $args_hasher + $appended_arg + } + } + ); + + let context_creation = quote { let mut context = dep::aztec::context::private_context::PrivateContext::new(inputs, args_hasher.hash()); }; + + // Modifications introduced by the different marker attributes. + + let internal_check = if is_fn_internal(f) { + create_internal_check(f) + } else { + quote {} + }; + + let view_check = if is_fn_view(f) { + create_view_check(f) + } else { + quote {} + }; + + let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { + (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) + } else { + (quote {}, quote {}) + }; + + let storage_init = if module_has_storage { + quote { let storage = Storage::init(&mut context); } + } else { + quote {} + }; + + // Initialization checks are not included in contracts that don't have initializers. + let init_check = if module_has_initializer & !is_fn_initializer(f) & !fn_has_noinitcheck(f) { + create_init_check(f) + } else { + quote {} + }; + + // Finally, we need to change the return type to be `PrivateCircuitPublicInputs`, which is what the Private Kernel + // circuit expects. + + let return_value_var_name = quote { macro__returned__values }; + + let return_value_type = f.return_type(); + let return_value = if body.len() == 0 { + quote {} + } else if return_value_type != type_of(()) { + // The original return value is passed to a second args hasher which the context receives. + + let (body_without_return, last_body_expr) = body.pop_back(); + let return_value = last_body_expr.quoted(); + let return_value_assignment = quote { let $return_value_var_name: $return_value_type = $return_value; }; + let return_hasher_name = quote { return_hasher }; + let return_value_into_hasher = add_to_hasher(return_hasher_name, return_value_var_name, return_value_type); + + body = body_without_return; + + quote { + let mut $return_hasher_name = dep::aztec::hash::ArgsHasher::new(); + $return_value_assignment + $return_value_into_hasher + context.set_return_hash($return_hasher_name); + } + } else { + let (body_without_return, last_body_expr) = body.pop_back(); + if !last_body_expr.has_semicolon() + & last_body_expr.as_for().is_none() + & last_body_expr.as_assert().is_none() + & last_body_expr.as_for_range().is_none() + & last_body_expr.as_assert_eq().is_none() + & last_body_expr.as_let().is_none() { + let unused_return_value_name = f"_{return_value_var_name}".quoted_contents(); + body = body_without_return.push_back(quote { let $unused_return_value_name = $last_body_expr; }.as_expr().unwrap()); + } + quote {} + }; + + let context_finish = quote { context.finish() }; + + let to_prepend = quote { + $args_hasher + $context_creation + $assert_initializer + $init_check + $internal_check + $view_check + $storage_init + }; + + let to_append = quote { + $return_value + $mark_as_initialized + $context_finish + }; + let modified_body = modify_fn_body(body, to_prepend, to_append); + f.set_body(modified_body); + f.add_attribute("recursive"); + f.set_return_public(true); + f.set_return_type( + quote { dep::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs }.as_type() + ); + + fn_abi +} + +/// Public functions are executed sequencer-side and do not preserve privacy, similar to the EVM. +pub comptime fn public(f: FunctionDefinition) -> Quoted { + let fn_abi = create_fn_abi_export(f); + let fn_stub = stub_fn(f); + register_stub(f.module(), fn_stub); + + let module_has_initializer = module_has_initializer(f.module()); + let module_has_storage = module_has_storage(f.module()); + + // Public functions undergo a lot of transformations from their Aztec.nr form into a circuit that can be fed to the + // Public Kernel Circuit. + + // First we change the function signature so that it also receives `PublicContextInputs`, which contain information + // about the execution context (e.g. the caller). + + let original_params = f.parameters(); + f.set_parameters( + &[ + ( + quote { inputs }, quote { crate::context::inputs::public_context_inputs::PublicContextInputs }.as_type() + ) + ].append(original_params) + ); + + // Unlike in the private case, in public the `context` does not need to receive the hash of the original params. + let context_creation = quote { let mut context = dep::aztec::context::public_context::PublicContext::new(inputs); }; + + // Modifications introduced by the different marker attributes. + + let internal_check = if is_fn_internal(f) { + create_internal_check(f) + } else { + quote {} + }; + + let view_check = if is_fn_view(f) { + create_view_check(f) + } else { + quote {} + }; + + let (assert_initializer, mark_as_initialized) = if is_fn_initializer(f) { + (create_assert_correct_initializer_args(f), create_mark_as_initialized(f)) + } else { + (quote {}, quote {}) + }; + + let storage_init = if module_has_storage { + quote { let storage = Storage::init(&mut context); } + } else { + quote {} + }; + + // Initialization checks are not included in contracts that don't have initializers. + let init_check = if module_has_initializer & !fn_has_noinitcheck(f) & !is_fn_initializer(f) { + create_init_check(f) + } else { + quote {} + }; + + let to_prepend = quote { + $context_creation + $assert_initializer + $init_check + $internal_check + $view_check + $storage_init + }; + + let to_append = quote { + $mark_as_initialized + }; + + let body = f.body().as_block().unwrap(); + let modified_body = modify_fn_body(body, to_prepend, to_append); + f.set_body(modified_body); + + // All public functions are automatically made unconstrained, even if they were not marked as such. This is because + // instead of compiling into a circuit, they will compile to bytecode that will be later transpiled into AVM + // bytecode. + f.set_unconstrained(true); + f.set_return_public(true); + + fn_abi +} + +pub comptime fn transform_unconstrained(f: FunctionDefinition) { + let context_creation = quote { let mut context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new(); }; + let module_has_storage = module_has_storage(f.module()); + + let storage_init = if module_has_storage { + quote { let storage = Storage::init(context); } + } else { + quote {} + }; + let to_prepend = quote { + $context_creation + $storage_init + }; + let body = f.body().as_block().unwrap(); + let modified_body = modify_fn_body(body, to_prepend, quote {}); + f.set_return_public(true); + f.set_body(modified_body); +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/mod.nr new file mode 100644 index 00000000000..23da22f2d1d --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/mod.nr @@ -0,0 +1,164 @@ +mod functions; +mod utils; +mod notes; +mod storage; +mod events; + +use functions::interfaces::STUBS; +use storage::STORAGE_LAYOUT_NAME; +use notes::{NOTES, generate_note_export}; + +use functions::transform_unconstrained; +use utils::module_has_storage; + +/// Marks a contract as an Aztec contract, generating the interfaces for its functions and notes, as well as injecting +/// the `compute_note_hash_and_optionally_a_nullifier` function PXE requires in order to validate notes. +pub comptime fn aztec(m: Module) -> Quoted { + let interface = generate_contract_interface(m); + + let unconstrained_functions = m.functions().filter( + | f: FunctionDefinition | f.is_unconstrained() & !f.has_named_attribute("test") & !f.has_named_attribute("public") + ); + for f in unconstrained_functions { + transform_unconstrained(f); + } + + let compute_note_hash_and_optionally_a_nullifier = generate_compute_note_hash_and_optionally_a_nullifier(); + let note_exports = generate_note_exports(); + + quote { + $note_exports + $interface + $compute_note_hash_and_optionally_a_nullifier + } +} + +comptime fn generate_contract_interface(m: Module) -> Quoted { + let module_name = m.name(); + let contract_stubs = STUBS.get(m); + let fn_stubs_quote = if contract_stubs.is_some() { + contract_stubs.unwrap().join(quote {}) + } else { + quote {} + }; + + let has_storage_layout = module_has_storage(m) & STORAGE_LAYOUT_NAME.get(m).is_some(); + let storage_layout_getter = if has_storage_layout { + let storage_layout_name = STORAGE_LAYOUT_NAME.get(m).unwrap(); + quote { + pub fn storage_layout() -> StorageLayout { + $storage_layout_name + } + } + } else { + quote {} + }; + + let library_storage_layout_getter = if has_storage_layout { + quote { + #[contract_library_method] + $storage_layout_getter + } + } else { + quote {} + }; + + quote { + struct $module_name { + target_contract: dep::aztec::protocol_types::address::AztecAddress + } + + impl $module_name { + $fn_stubs_quote + + pub fn at( + target_contract: aztec::protocol_types::address::AztecAddress + ) -> Self { + Self { target_contract } + } + + pub fn interface() -> Self { + Self { target_contract: aztec::protocol_types::address::AztecAddress::zero() } + } + + $storage_layout_getter + } + + #[contract_library_method] + pub fn at( + target_contract: aztec::protocol_types::address::AztecAddress + ) -> $module_name { + $module_name { target_contract } + } + + #[contract_library_method] + pub fn interface() -> $module_name { + $module_name { target_contract: aztec::protocol_types::address::AztecAddress::zero() } + } + + $library_storage_layout_getter + + } +} + +comptime fn generate_compute_note_hash_and_optionally_a_nullifier() -> Quoted { + let mut max_note_length: u32 = 0; + let notes = NOTES.entries(); + let body = if notes.len() > 0 { + max_note_length = notes.fold(0, | acc, (_, (_, len, _)): (Type, (StructDefinition, u32, Field)) | { + acc + len + }); + + let mut if_statements_list = &[]; + + for i in 0..notes.len() { + let (typ, (_, _, _)) = notes[i]; + let if_or_else_if = if i == 0 { + quote { if } + } else { + quote { else if } + }; + if_statements_list = if_statements_list.push_back(quote { + $if_or_else_if note_type_id == $typ::get_note_type_id() { + aztec::note::utils::compute_note_hash_and_optionally_a_nullifier($typ::deserialize_content, note_header, compute_nullifier, serialized_note) + } + }); + } + + let if_statements = if_statements_list.join(quote {}); + + quote { + let note_header = aztec::prelude::NoteHeader::new(contract_address, nonce, storage_slot); + $if_statements + else { + panic(f"Unknown note type ID") + } + } + } else { + quote { + panic(f"No notes defined") + } + }; + + quote { + unconstrained fn compute_note_hash_and_optionally_a_nullifier( + contract_address: aztec::protocol_types::address::AztecAddress, + nonce: Field, + storage_slot: Field, + note_type_id: Field, + compute_nullifier: bool, + serialized_note: [Field; $max_note_length], + ) -> pub [Field; 4] { + $body + } + } +} + +comptime fn generate_note_exports() -> Quoted { + let notes = NOTES.values(); + notes.map( + | (s, _, note_type_id): (StructDefinition, u32, Field) | { + generate_note_export(s, note_type_id) + } + ).join(quote {}) +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr new file mode 100644 index 00000000000..e00eecfcdfd --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr @@ -0,0 +1,388 @@ +use std::{ + meta::{type_of, unquote, typ::fresh_type_variable}, collections::umap::UHashMap, + hash::{BuildHasherDefault, poseidon2::Poseidon2Hasher} +}; +use protocol_types::meta::{flatten_to_fields, pack_from_fields}; +use crate::note::{note_header::NoteHeader, note_getter_options::PropertySelector, note_interface::NoteInterface}; + +comptime global NOTE_HEADER_TYPE = type_of(NoteHeader::empty()); + +comptime mut global NOTES: UHashMap> = UHashMap::default(); + +comptime fn compute_note_type_id(name: Quoted) -> Field { + let name_as_str_quote = name.as_str_quote(); + + unquote!( + quote { + let bytes = $name_as_str_quote.as_bytes(); + let hash = protocol_types::hash::poseidon2_hash_bytes(bytes); + let hash_bytes = hash.to_be_bytes::<4>(); + protocol_types::utils::field::field_from_bytes(hash_bytes, true) + } + ) +} + +comptime fn generate_note_interface( + s: StructDefinition, + note_type_id: Field, + hiding_point_name: Quoted, + indexed_fixed_fields: [(Quoted, Type, u32)], + indexed_nullable_fields: [(Quoted, Type, u32)] +) -> (Quoted, u32) { + let name = s.name(); + let typ = s.as_type(); + let (fields, aux_vars) = flatten_to_fields(quote { self }, typ, &[quote {self.header}]); + let aux_vars_for_serialization = if aux_vars.len() > 0 { + let joint = aux_vars.join(quote {;}); + quote { $joint; } + } else { + quote {} + }; + let serialized_fields = fields.join(quote {,}); + let content_len = fields.len(); + + let (deserialized_content, _) = pack_from_fields( + quote { self }, + typ, + quote { value }, + 0, + &[(quote {header}, quote { aztec::note::note_header::NoteHeader::empty() })] + ); + + let fixed_fields_args = indexed_fixed_fields.map(| (name, _, _): (Quoted, Type, u32) | quote{ self.$name }).push_back(quote { self.get_header().storage_slot }).join(quote {,}); + let nullable_fields_args = indexed_nullable_fields.map(|(name, _, _): (Quoted, Type, u32)| quote { self.$name }).join(quote {,}); + + (quote { + impl aztec::note::note_interface::NoteInterface<$content_len> for $name { + fn to_be_bytes(self, storage_slot: Field) -> [u8; $content_len * 32 + 64] { + let serialized_note = self.serialize_content(); + + let mut buffer: [u8; $content_len * 32 + 64] = [0; $content_len * 32 + 64]; + + let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); + let note_type_id_bytes: [u8; 32] = $name::get_note_type_id().to_be_bytes(); + + for i in 0..32 { + buffer[i] = storage_slot_bytes[i]; + buffer[32 + i] = note_type_id_bytes[i]; + } + + for i in 0..serialized_note.len() { + let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); + for j in 0..32 { + buffer[64 + i * 32 + j] = bytes[j]; + } + } + buffer + } + + fn deserialize_content(value: [Field; $content_len]) -> Self { + $deserialized_content + } + + fn serialize_content(self) -> [Field; $content_len] { + $aux_vars_for_serialization + [$serialized_fields] + } + + fn get_note_type_id() -> Field { + $note_type_id + } + + fn set_header(&mut self, header: $NOTE_HEADER_TYPE) { + self.header = header; + } + + fn get_header(self) -> $NOTE_HEADER_TYPE { + self.header + } + + fn compute_note_hash(self) -> Field { + $hiding_point_name::empty().new($fixed_fields_args).finalize($nullable_fields_args) + } + } + }, content_len) +} + +comptime fn generate_note_properties(s: StructDefinition) -> Quoted { + let name = s.name(); + + let struct_name = f"{name}Properties".quoted_contents(); + + let property_selector_type = type_of(PropertySelector { index: 0, offset: 0, length: 0 }); + let note_header_type: Type = type_of(NoteHeader::empty()); + + let non_header_fields = s.fields().filter(| (_, typ): (Quoted, Type) | typ != note_header_type); + + let properties_types = non_header_fields.map( + | (name, _): (Quoted, Type) | { + quote { $name: $property_selector_type } + } + ).join(quote {,}); + + // TODO #8694: Properly handle non-field types https://github.com/AztecProtocol/aztec-packages/issues/8694 + let mut properties_list = &[]; + for i in 0..non_header_fields.len() { + let (name, _) = non_header_fields[i]; + properties_list = properties_list.push_back(quote { $name: aztec::note::note_getter_options::PropertySelector { index: $i, offset: 0, length: 32 } }); + } + + let properties = properties_list.join(quote {,}); + + quote { + struct $struct_name { + $properties_types + } + + impl aztec::note::note_interface::NoteProperties<$struct_name> for $name { + fn properties() -> $struct_name { + $struct_name { + $properties + } + } + } + } +} + +pub(crate) comptime fn generate_note_export(s: StructDefinition, note_type_id: Field) -> Quoted { + let name = s.name(); + let global_export_name = f"{name}_EXPORTS".quoted_contents(); + let note_name_as_str = name.as_str_quote(); + let note_name_str_len = unquote!(quote { $note_name_as_str.as_bytes().len() }); + quote { + #[abi(notes)] + global $global_export_name: (Field, str<$note_name_str_len>) = ($note_type_id,$note_name_as_str); + } +} + +comptime fn generate_multi_scalar_mul(indexed_fields: [(Quoted, Type, u32)]) -> ([Quoted], [Quoted], [Quoted], Quoted) { + let mut generators_list = &[]; + let mut scalars_list = &[]; + let mut args_list = &[]; + let mut aux_vars_list = &[]; + // TODO(#8648): Generate generators at comptime instead of importing here. + for i in 0..indexed_fields.len() { + let (field_name, typ, index) = indexed_fields[i]; + let start_generator_index = index + 1; + let (flattened_field, aux_vars) = flatten_to_fields(field_name, typ, &[]); + for j in 0..flattened_field.len() { + let flattened_as_field = flattened_field[j]; + let generator_index = start_generator_index + j; + generators_list = generators_list.push_back(f"aztec::generators::Ga{generator_index}".quoted_contents()); + scalars_list = scalars_list.push_back(quote { std::hash::from_field_unsafe($flattened_as_field) }); + } + args_list = args_list.push_back(quote { $field_name: $typ }); + aux_vars_list = aux_vars_list.append(aux_vars); + } + + let aux_vars = if aux_vars_list.len() > 0 { + let joint = aux_vars_list.join(quote {;}); + quote { $joint; } + } else { + quote {} + }; + (generators_list, scalars_list, args_list, aux_vars) +} + +comptime fn generate_note_hiding_point( + s: StructDefinition, + indexed_fixed_fields: [(Quoted, Type, u32)], + indexed_nullable_fields: [(Quoted, Type, u32)] +) -> (Quoted, Quoted) { + let name = s.name(); + let hiding_point_name = f"{name}HidingPoint".quoted_contents(); + + let (new_generators_list, new_scalars_list, new_args_list, new_aux_vars) = generate_multi_scalar_mul(indexed_fixed_fields); + + let new_args = &[quote {mut self}].append(new_args_list).push_back(quote { storage_slot: Field }).join(quote {,}); + let new_generators = new_generators_list.push_back(quote { aztec::generators::G_slot }).join(quote {,}); + let new_scalars = new_scalars_list.push_back(quote { std::hash::from_field_unsafe(storage_slot) }).join(quote {,}); + + let (finalize_generators_list, finalize_scalars_list, finalize_args_list, finalize_aux_vars) = generate_multi_scalar_mul(indexed_nullable_fields); + + let finalize_args = if finalize_args_list.len() > 0 { + &[quote {self}].append(finalize_args_list).join(quote {,}) + } else { + quote {self} + }; + + let finalize_body = if indexed_nullable_fields.len() > 0 { + let finalize_generators = finalize_generators_list.join(quote {,}); + let finalize_scalars = finalize_scalars_list.join(quote {,}); + quote { + $finalize_aux_vars + let point = std::embedded_curve_ops::multi_scalar_mul( + [$finalize_generators], + [$finalize_scalars] + ) + self.inner; + point.x + } + } else { + quote { self.inner.x } + }; + + (quote { + struct $hiding_point_name { + inner: aztec::protocol_types::point::Point + } + + impl $hiding_point_name { + fn new($new_args) -> $hiding_point_name { + $new_aux_vars + let point = std::embedded_curve_ops::multi_scalar_mul( + [$new_generators], + [$new_scalars] + ); + self.inner = point; + self + } + + fn from_point(mut self, point: aztec::protocol_types::point::Point) -> $hiding_point_name { + self.inner = point; + self + } + + + fn finalize($finalize_args) -> Field { + $finalize_body + } + } + + impl aztec::protocol_types::traits::Serialize for $hiding_point_name { + fn serialize(self) -> [Field; aztec::protocol_types::point::POINT_LENGTH] { + self.inner.serialize() + } + } + + impl aztec::protocol_types::traits::Deserialize for $hiding_point_name { + fn deserialize(serialized: [Field; aztec::protocol_types::point::POINT_LENGTH]) -> $hiding_point_name { + $hiding_point_name { inner: aztec::protocol_types::point::Point::deserialize(serialized) } + } + } + + impl aztec::protocol_types::traits::Empty for $hiding_point_name { + fn empty() -> Self { + Self { inner: aztec::protocol_types::point::Point::empty() } + } + } + + impl Eq for $hiding_point_name { + fn eq(self, other: Self) -> bool { + self.inner == other.inner + } + } + + }, hiding_point_name) +} + +comptime fn generate_partial_note_impl(s: StructDefinition, hiding_point_name: Quoted) -> Quoted { + let name = s.name(); + quote { + impl aztec::note::note_interface::PartialNote<$hiding_point_name> for $name { + fn hiding_point() -> $hiding_point_name { + $hiding_point_name::empty() + } + } + } +} + +comptime fn register_note(note: StructDefinition, note_serialized_len: u32, note_type_id: Field) { + NOTES.insert(note.as_type(), (note, note_serialized_len, note_type_id)); +} + +comptime fn index_note_fields( + s: StructDefinition, + nullable_fields: [Quoted] +) -> ([(Quoted, Type, u32)], [(Quoted, Type, u32)]) { + let mut indexed_fixed_fields = &[]; + let mut indexed_nullable_fields = &[]; + let mut counter: u32 = 0; + for field in s.fields() { + let (name, typ) = field; + if (typ != NOTE_HEADER_TYPE) { + if nullable_fields.all(| field | field != name) { + indexed_fixed_fields = indexed_fixed_fields.push_back((name, typ, counter)); + } else { + indexed_nullable_fields = indexed_nullable_fields.push_back((name, typ, counter)); + } + } + let (flattened, _) = flatten_to_fields(name, typ, &[]); + counter+=flattened.len(); + } + (indexed_fixed_fields, indexed_nullable_fields) +} + +comptime fn common_note_annotation(s: StructDefinition) -> (Quoted, Field) { + // Automatically inject header if not present + let filtered_header = s.fields().filter(| (_, typ): (Quoted, Type) | typ == NOTE_HEADER_TYPE); + if (filtered_header.len() == 0) { + let new_fields = s.fields().push_back((quote { header }, NOTE_HEADER_TYPE)); + s.set_fields(new_fields); + } + let note_properties = generate_note_properties(s); + let note_type_id = compute_note_type_id(s.name()); + + (quote { + $note_properties + }, note_type_id) +} + +#[varargs] +pub comptime fn partial_note(s: StructDefinition, nullable_fields: [Quoted]) -> Quoted { + let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, nullable_fields); + + let (common, note_type_id) = common_note_annotation(s); + let (note_hiding_point, hiding_point_name) = generate_note_hiding_point(s, indexed_fixed_fields, indexed_nullable_fields); + let (note_interface_impl, note_serialized_len) = generate_note_interface( + s, + note_type_id, + hiding_point_name, + indexed_fixed_fields, + indexed_nullable_fields + ); + let partial_note_impl = generate_partial_note_impl(s, hiding_point_name); + register_note(s, note_serialized_len, note_type_id); + + quote { + $common + $note_hiding_point + $note_interface_impl + $partial_note_impl + } +} + +pub comptime fn note(s: StructDefinition) -> Quoted { + let (indexed_fixed_fields, indexed_nullable_fields) = index_note_fields(s, &[]); + let (common, note_type_id) = common_note_annotation(s); + let (note_hiding_point, hiding_point_name) = generate_note_hiding_point(s, indexed_fixed_fields, indexed_nullable_fields); + let (note_interface_impl, note_serialized_len) = generate_note_interface( + s, + note_type_id, + hiding_point_name, + indexed_fixed_fields, + indexed_nullable_fields + ); + register_note(s, note_serialized_len, note_type_id); + + quote { + $common + $note_hiding_point + $note_interface_impl + } +} + +pub comptime fn note_custom_interface(s: StructDefinition) -> Quoted { + let (common, note_type_id) = common_note_annotation(s); + let serialized_len_type = fresh_type_variable(); + let note_interface_impl = s.as_type().get_trait_impl(quote { NoteInterface<$serialized_len_type> }.as_trait_constraint()); + let name = s.name(); + assert(note_interface_impl.is_some(), f"Note {name} must implement NoteInterface trait"); + + let note_serialized_len = note_interface_impl.unwrap().trait_generic_args()[0].as_constant().unwrap(); + + register_note(s, note_serialized_len, note_type_id); + + quote { + $common + } +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr new file mode 100644 index 00000000000..c438d1b4759 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/storage/mod.nr @@ -0,0 +1,162 @@ +use std::{collections::umap::UHashMap, hash::{BuildHasherDefault, poseidon2::Poseidon2Hasher}}; + +use super::utils::get_serialized_size; +use super::utils::is_note; + +comptime mut global STORAGE_LAYOUT_NAME: UHashMap> = UHashMap::default(); + +/// Marks a struct as the one describing the storage layout of a contract. Only a single struct in the entire contract +/// should have this macro (or `storage_no_init`) applied to it. +/// The contract's storage is accessed via the `storage` variable, which will will automatically be made available in +/// all functions as an instance of the struct this macro was applied to. +pub comptime fn storage(s: StructDefinition) -> Quoted { + // This macro performs three things: + // - it marks the contract as having storage, so that `macros::utils::module_has_storage` will return true and + // functions will have the storage variable injected and initialized via the `init` function. + // - it implements said `init` function by allocating appropriate storage slots to each state variable. + // - it exposes the storage layout by creating a `StorageLayout` struct that is exposed via the `abi(storage)` + // macro. + + let mut slot: u32 = 1; + let mut storage_vars_constructors = &[]; + let mut storage_layout_fields = &[]; + let mut storage_layout_constructors = &[]; + + // TODO(#8658): uncomment the code below to inject the Context type parameter. + + //let mut new_storage_fields = &[]; + //let context_generic = s.add_generic("Context"); + for field in s.fields() { + let (name, typ) = field; + let (storage_field_constructor, serialized_size) = generate_storage_field_constructor(typ, quote { $slot }, false); + storage_vars_constructors = storage_vars_constructors.push_back(quote { $name: $storage_field_constructor }); + storage_layout_fields = storage_layout_fields.push_back(quote { $name: dep::aztec::prelude::Storable }); + storage_layout_constructors = storage_layout_constructors.push_back(quote { $name: dep::aztec::prelude::Storable { slot: $slot } }); + //let with_context_generic = add_context_generic(typ, context_generic); + //println(with_context_generic); + //new_storage_fields = new_storage_fields.push_back((name, with_context_generic )); + slot += serialized_size; + } + + //s.set_fields(new_storage_fields); + + let storage_vars_constructors = storage_vars_constructors.join(quote {,}); + let storage_impl = quote { + impl Storage { + fn init(context: Context) -> Self { + Self { + $storage_vars_constructors + } + } + } + }; + + let storage_layout_fields = storage_layout_fields.join(quote {,}); + let storage_layout_constructors = storage_layout_constructors.join(quote {,}); + + let module = s.module(); + let module_name = module.name(); + let storage_layout_name = f"STORAGE_LAYOUT_{module_name}".quoted_contents(); + STORAGE_LAYOUT_NAME.insert(module, storage_layout_name); + + quote { + $storage_impl + + struct StorageLayout { + $storage_layout_fields + } + + #[abi(storage)] + global $storage_layout_name = StorageLayout { + $storage_layout_constructors + }; + } +} + +/// Same as `storage`, except the user is in charge of providing an implementation of the `init` constructor function +/// with signature `fn init(context: Context) -> Self`, which allows for manual control of storage slot +/// allocation. Similarly, no `StorageLayout` struct will be created. +/// Only a single struct in the entire contract should have this macro (or `storage`) applied to it. +pub comptime fn storage_no_init(_s: StructDefinition) { + // All `storage` does is provide the `init` implementation, so we don't need to do anything here. Applying this + // macro however will cause for `macros::utils::module_has_storage` to return true, resulting in the injection of + // the `storage` variable. +} + +/// Returns the expression required to initialize a state variable with a given slot, along with its serialization size, +/// i.e. how many contiguous storage slots the variable requires. +comptime fn generate_storage_field_constructor(typ: Type, slot: Quoted, parent_is_map: bool) -> (Quoted, u32) { + assert( + typ.as_struct().is_some(), "Storage containers must be generic structs of the form `Container<_, Context>`, or Map" + ); + let (container_struct, generics) = typ.as_struct().unwrap(); + let struct_name = container_struct.name(); + + if is_storage_map(typ) { + // Map state variables recursively initialize their contents - this includes nested maps. + let (value_constructor, _) = generate_storage_field_constructor(generics[1], quote { slot }, true); + (quote { $struct_name::new(context, $slot, | context, slot | { $value_constructor }) }, 1) + } else { + let (container_struct, container_struct_generics) = typ.as_struct().unwrap(); + let container_struct_name = container_struct.name(); + + let serialized_size = if parent_is_map { + // Variables inside a map do not require contiguous slots since the map slot derivation is assumed to result + // in slots very far away from one another. + 1 + } else { + let stored_struct = container_struct_generics[0]; + if is_note(stored_struct) & (container_struct_name != quote { PublicMutable }) { + // Private notes always occupy a single slot, since the slot is only used as a state variable + // identifier. + + // Someone could store a Note in PublicMutable for whatever reason though. + // TODO(#8659): remove the PublicMutable exception above + 1 + } else { + get_serialized_size(stored_struct) + } + }; + + // We assume below that all state variables implement `fn new(context: Context, slot: Field) -> Self`. + (quote { $struct_name::new(context, $slot)}, serialized_size) + } +} + +/// Returns true if `typ` is `state_vars::map::Map`. +comptime fn is_storage_map(typ: Type) -> bool { + if typ.as_struct().is_some() { + let (def, generics) = typ.as_struct().unwrap(); + let maybe_map = if (def.name() == quote { Map }) & (generics.len() == 3) { + let maybe_key = generics[0]; + let maybe_value = generics[1]; + let maybe_context = generics[2]; + quote { crate::state_vars::map::Map<$maybe_key, $maybe_value, $maybe_context> }.as_type() + } else { + quote {()}.as_type() + }; + typ == maybe_map + } else { + false + } +} + +comptime fn add_context_generic(typ: Type, context_generic: Type) -> Type { + assert( + typ.as_struct().is_some(), "Storage containers must be generic structs of the form `Container<..., Context>`" + ); + if is_storage_map(typ) { + let (def, mut generics) = typ.as_struct().unwrap(); + let name = def.name(); + generics[generics.len() - 2] = add_context_generic(generics[1], context_generic); + generics[generics.len() - 1] = context_generic; + let generics = generics.map(|typ: Type| quote{$typ}).join(quote{,}); + quote { $name<$generics> }.as_type() + } else { + let (def, mut generics) = typ.as_struct().unwrap(); + let name = def.name(); + generics[generics.len() - 1] = context_generic; + let generics = generics.map(|typ: Type| quote{$typ}).join(quote{,}); + quote { $name<$generics> }.as_type() + } +} diff --git a/noir-projects/aztec-nr/aztec/src/macros/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/utils.nr new file mode 100644 index 00000000000..59543e106ad --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/macros/utils.nr @@ -0,0 +1,257 @@ +use std::meta::{typ::fresh_type_variable, unquote}; + +pub(crate) comptime fn get_fn_visibility(f: FunctionDefinition) -> Quoted { + if f.has_named_attribute("private") { + quote { private } + } else if f.has_named_attribute("public") { + quote { public } + } else { + panic(f"Function is neither private nor public") + } +} + +pub(crate) comptime fn is_fn_private(f: FunctionDefinition) -> bool { + f.has_named_attribute("private") +} + +pub(crate) comptime fn is_fn_public(f: FunctionDefinition) -> bool { + f.has_named_attribute("public") +} + +pub(crate) comptime fn is_fn_view(f: FunctionDefinition) -> bool { + f.has_named_attribute("view") +} + +pub(crate) comptime fn is_fn_internal(f: FunctionDefinition) -> bool { + f.has_named_attribute("internal") +} + +pub(crate) comptime fn is_fn_initializer(f: FunctionDefinition) -> bool { + f.has_named_attribute("initializer") +} + +pub(crate) comptime fn fn_has_noinitcheck(f: FunctionDefinition) -> bool { + f.has_named_attribute("noinitcheck") +} + +/// Takes a function body as a collection of expressions, and alters it by prepending and appending quoted values. +pub(crate) comptime fn modify_fn_body(body: [Expr], prepend: Quoted, append: Quoted) -> Expr { + // We need to quote the body before we can alter its contents, so we fold it by quoting each expression. + let mut body_quote = body.fold( + quote {}, + |full_quote: Quoted, expr: Expr| { + let expr_quote = expr.quoted(); + quote { + $full_quote + $expr_quote + } + } + ); + body_quote = quote { + { + $prepend + $body_quote + $append + } + }; + let body_expr = body_quote.as_expr(); + assert(body_expr.is_some(), f"Body is not an expression: {body_quote}"); + body_expr.unwrap() +} + +pub(crate) comptime fn add_to_field_slice(slice_name: Quoted, name: Quoted, typ: Type) -> Quoted { + if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { + quote { $slice_name = $slice_name.push_back($name as Field); } + } else if typ.as_struct().is_some() { + quote { $slice_name = $slice_name.append($name.serialize()); } + } else if typ.as_array().is_some() { + let (element_type, _) = typ.as_array().unwrap(); + let serialized_name = f"{name}_serialized".quoted_contents(); + quote { + let $serialized_name = $name.map(|x: $element_type | x.serialize()); + for i in 0..$name.len() { + $slice_name = $slice_name.append($serialized_name[i].as_slice()); + } + } + } else if typ.as_str().is_some() { + quote { + $slice_name = $slice_name.append($name.as_bytes().map(| byte: u8 | byte as Field).as_slice()); + } + } else { + panic(f"Cannot add to slice: unsupported type {typ} variable {name}") + } +} + +/// Adds a value to a hash::ArgsHasher. Structs and values inside arrays are required to implement the Serialize trait. +pub(crate) comptime fn add_to_hasher(hasher_name: Quoted, name: Quoted, typ: Type) -> Quoted { + if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { + quote { $hasher_name.add($name as Field); } + } else if typ.as_struct().is_some() { + quote { $hasher_name.add_multiple($name.serialize()); } + } else if typ.as_array().is_some() { + let (element_type, _) = typ.as_array().unwrap(); + let serialized_name = f"{name}_serialized".quoted_contents(); + quote { + let $serialized_name = $name.map(|x: $element_type | x.serialize()); + for i in 0..$name.len() { + $hasher_name.add_multiple($serialized_name[i]); + } + } + } else if typ.as_str().is_some() { + quote { + $hasher_name.add_multiple($name.as_bytes().map(| byte: u8 | byte as Field)); + } + } else { + panic(f"Cannot add to hasher: unsupported type {typ} of variable {name}") + } +} + +comptime fn signature_of_type(typ: Type) -> Quoted { + if typ.is_field() { + quote{Field} + } else if typ.as_integer().is_some() { + let (is_signed, bit_size) = typ.as_integer().unwrap(); + if is_signed { + f"i{bit_size}".quoted_contents() + } else { + f"u{bit_size}".quoted_contents() + } + } else if typ.is_bool() { + quote {bool} + } else if typ.as_str().is_some() { + let str_len_typ = typ.as_str().unwrap(); + let str_len = str_len_typ.as_constant().unwrap(); + f"str<{str_len}>".quoted_contents() + } else if typ.as_array().is_some() { + let (element_type, array_len) = typ.as_array().unwrap(); + let array_len = array_len.as_constant().unwrap(); + let element_typ_quote = signature_of_type(element_type); + f"[{element_typ_quote};{array_len}]".quoted_contents() + } else if typ.as_struct().is_some() { + let (s, _) = typ.as_struct().unwrap(); + let field_signatures = s.fields().map( + | (_, typ): (Quoted, Type) | { + signature_of_type(typ) + } + ).join(quote {,}); + f"({field_signatures})".quoted_contents() + } else if typ.as_tuple().is_some() { + // Note that tuples are handled the same way as structs + let types = typ.as_tuple().unwrap(); + let field_signatures = types.map( + | typ: Type | { + signature_of_type(typ) + } + ).join(quote {,}); + f"({field_signatures})".quoted_contents() + } else { + panic(f"Unsupported type {typ}") + } +} + +trait AsStrQuote { + fn as_str_quote(self) -> Self; +} + +impl AsStrQuote for Quoted { + // Used to convert an arbirary quoted type into a quoted string, removing whitespace between tokens + comptime fn as_str_quote(self) -> Quoted { + let tokens = self.tokens(); + let mut acc: [u8] = &[]; + let mut total_len: u32 = 0; + for token in tokens { + let token_as_fmt_str = f"{token}"; + let token_as_str = unquote!(quote {$token_as_fmt_str}); + let token_len = unquote!(quote { $token_as_str.as_bytes().len() }); + let token_as_bytes = unquote!(quote { $token_as_str.as_bytes().as_slice() }); + total_len += token_len; + acc = acc.append(token_as_bytes); + } + let result = unquote!( + quote { + let signature_as_array: [u8; $total_len] = $acc.as_array(); + signature_as_array.as_str_unchecked() + } + ); + quote { $result } + } +} + +pub(crate) comptime fn compute_fn_selector(f: FunctionDefinition) -> Field { + // The function selector is computed from the function signature, which is made up of the function name and types of + // parameters, but not including the return type. For example, given: + // + // fn foo(a: Field, b: AztecAddress) -> Field + // + // The signature will be "foo(Field,AztecAddress)". + + let fn_name = f.name(); + let args_signatures = f.parameters().map( + | (_, typ): (Quoted, Type) | { + signature_of_type(typ) + } + ).join(quote {,}); + let signature_quote = quote { $fn_name($args_signatures) }; + let signature_str_quote = signature_quote.as_str_quote(); + + let computation_quote = quote { + protocol_types::abis::function_selector::FunctionSelector::from_signature($signature_str_quote).to_field() + }; + unquote!(computation_quote) +} + +pub(crate) comptime fn compute_event_selector(s: StructDefinition) -> Field { + // The event selector is computed from the type signature of the struct in the event, similar to how one might type + // the constructor function. For example, given: + // + // struct Foo { + // a: Field, + // b: AztecAddress, + // } + // + // The signature will be "Foo(Field,AztecAddress)". + + let event_name = s.name(); + let args_signatures = s.fields().map( + | (_, typ): (Quoted, Type) | { + signature_of_type(typ) // signature_of_type can handle structs, so this supports nested structs + } + ).join(quote {,}); + let signature_quote = quote { $event_name($args_signatures) }; + let signature_str_quote = signature_quote.as_str_quote(); + + let computation_quote = quote { + protocol_types::abis::event_selector::EventSelector::from_signature($signature_str_quote).to_field() + }; + unquote!(computation_quote) +} + +pub(crate) comptime fn get_serialized_size(typ: Type) -> u32 { + let any = fresh_type_variable(); + let maybe_serialize_impl = typ.get_trait_impl(quote { protocol_types::traits::Serialize<$any> }.as_trait_constraint()); + assert( + maybe_serialize_impl.is_some(), f"Attempted to fetch serialization length, but {typ} does not implement the Serialize trait" + ); + let serialize_impl = maybe_serialize_impl.unwrap(); + serialize_impl.trait_generic_args()[0].as_constant().unwrap() +} + +pub(crate) comptime fn module_has_storage(m: Module) -> bool { + m.structs().any( + |s: StructDefinition| s.has_named_attribute("storage") | s.has_named_attribute("storage_no_init") + ) +} + +pub(crate) comptime fn module_has_initializer(m: Module) -> bool { + m.functions().any(|f: FunctionDefinition| is_fn_initializer(f)) +} + +pub(crate) comptime fn is_note(typ: Type) -> bool { + typ.as_struct().map_or( + false, + | struc: (StructDefinition, [Type]) | { + let (def, _) = struc; + def.has_named_attribute("note") | def.has_named_attribute("partial_note") | def.has_named_attribute("note_custom_interface") + } + ) +} diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index 0b88f56724f..58321fff061 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -1,23 +1,22 @@ use crate::context::{PrivateContext, PublicContext}; use crate::note::{ - note_header::NoteHeader, note_interface::NoteInterface, + note_header::NoteHeader, note_interface::{NoteInterface, NullifiableNote}, utils::{compute_note_hash_for_read_request, compute_note_hash_for_nullify_internal}, note_emission::NoteEmission }; use crate::oracle::notes::{notify_created_note, notify_nullified_note}; -pub fn create_note( +pub fn create_note( context: &mut PrivateContext, storage_slot: Field, note: &mut Note -) -> NoteEmission where Note: NoteInterface { +) -> NoteEmission where Note: NoteInterface + NullifiableNote { let contract_address = (*context).this_address(); let note_hash_counter = context.side_effect_counter; let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; note.set_header(header); - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; + let note_hash = note.compute_note_hash(); let serialized_note = Note::serialize_content(*note); assert( @@ -36,36 +35,35 @@ pub fn create_note( NoteEmission::new(*note) } -pub fn create_note_hash_from_public( +pub fn create_note_hash_from_public( context: &mut PublicContext, storage_slot: Field, note: &mut Note -) where Note: NoteInterface { +) where Note: NoteInterface + NullifiableNote { let contract_address = (*context).this_address(); // Public note hashes are transient, but have no side effect counters, so we just need note_hash_counter != 0 let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter: 1 }; note.set_header(header); - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; + let note_hash = note.compute_note_hash(); context.push_note_hash(note_hash); } // Note: This function is currently totally unused. -pub fn destroy_note( +pub fn destroy_note( context: &mut PrivateContext, note: Note -) where Note: NoteInterface { +) where Note: NoteInterface + NullifiableNote { let note_hash_for_read_request = compute_note_hash_for_read_request(note); destroy_note_unsafe(context, note, note_hash_for_read_request) } -pub fn destroy_note_unsafe( +pub fn destroy_note_unsafe( context: &mut PrivateContext, note: Note, note_hash_for_read_request: Field -) where Note: NoteInterface { +) where Note: NoteInterface + NullifiableNote { let note_hash_for_nullify = compute_note_hash_for_nullify_internal(note, note_hash_for_read_request); let nullifier = note.compute_nullifier(context, note_hash_for_nullify); diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr index 8162c7002ac..6f96902ecfe 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/mod.nr @@ -3,7 +3,7 @@ use crate::context::PrivateContext; use crate::note::{ constants::{GET_NOTE_ORACLE_RETURN_LENGTH, VIEW_NOTE_ORACLE_RETURN_LENGTH}, note_getter_options::{NoteGetterOptions, Select, Sort, SortOrder, NoteStatus, PropertySelector}, - note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, + note_interface::{NoteInterface, NullifiableNote}, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_for_read_request }; use crate::oracle; @@ -34,11 +34,11 @@ fn extract_property_value_from_selector( value_field } -fn check_note_header( +fn check_note_header( context: PrivateContext, storage_slot: Field, note: Note -) where Note: NoteInterface { +) where Note: NoteInterface { let header = note.get_header(); let contract_address = context.this_address(); assert(header.contract_address.eq(contract_address), "Mismatch note header contract address."); @@ -78,10 +78,10 @@ fn check_notes_order( } } -pub fn get_note( +pub fn get_note( context: &mut PrivateContext, storage_slot: Field -) -> (Note, Field) where Note: NoteInterface { +) -> (Note, Field) where Note: NoteInterface + NullifiableNote { let note = unsafe { get_note_internal(storage_slot) }; @@ -96,11 +96,11 @@ pub fn get_note( (note, note_hash_for_read_request) } -pub fn get_notes( +pub fn get_notes( context: &mut PrivateContext, storage_slot: Field, - options: NoteGetterOptions -) -> (BoundedVec, BoundedVec) where Note: NoteInterface + Eq { + options: NoteGetterOptions +) -> (BoundedVec, BoundedVec) where Note: NoteInterface + NullifiableNote + Eq { let opt_notes = unsafe { get_notes_internal(storage_slot, options) }; @@ -118,12 +118,12 @@ unconstrained fn apply_preprocessor( preprocessor(notes, preprocessor_args) } -fn constrain_get_notes_internal( +fn constrain_get_notes_internal( context: &mut PrivateContext, storage_slot: Field, opt_notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], - options: NoteGetterOptions -) -> (BoundedVec, BoundedVec) where Note: NoteInterface + Eq { + options: NoteGetterOptions +) -> (BoundedVec, BoundedVec) where Note: NoteInterface + NullifiableNote + Eq { // The filter is applied first to avoid pushing note read requests for notes we're not interested in. Note that // while the filter function can technically mutate the contents of the notes (as opposed to simply removing some), // the private kernel will later validate that these note actually exist, so transformations would cause for that @@ -163,7 +163,7 @@ fn constrain_get_notes_internal(storage_slot: Field) -> Note where Note: NoteInterface { +unconstrained fn get_note_internal(storage_slot: Field) -> Note where Note: NoteInterface { let placeholder_note = [Option::none()]; let placeholder_fields = [0; GET_NOTE_ORACLE_RETURN_LENGTH]; let placeholder_note_length = [0; N]; @@ -188,10 +188,10 @@ unconstrained fn get_note_internal(storage_slot: F )[0].unwrap() // Notice: we don't allow dummies to be returned from get_note (singular). } -unconstrained fn get_notes_internal( +unconstrained fn get_notes_internal( storage_slot: Field, - options: NoteGetterOptions -) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where Note: NoteInterface { + options: NoteGetterOptions +) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where Note: NoteInterface { // This function simply performs some transformations from NoteGetterOptions into the types required by the oracle. let (num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order) = flatten_options(options.selects, options.sorts); @@ -222,10 +222,10 @@ unconstrained fn get_notes_internal( +unconstrained pub fn view_notes( storage_slot: Field, - options: NoteViewerOptions -) -> BoundedVec where Note: NoteInterface { + options: NoteViewerOptions +) -> BoundedVec where Note: NoteInterface { let (num_selects, select_by_indexes, select_by_offsets, select_by_lengths, select_values, select_comparators, sort_by_indexes, sort_by_offsets, sort_by_lengths, sort_order) = flatten_options(options.selects, options.sorts); let placeholder_opt_notes = [Option::none(); MAX_NOTES_PER_PAGE]; let placeholder_fields = [0; VIEW_NOTE_ORACLE_RETURN_LENGTH]; diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr index dc3143108df..af928ee88b0 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter_options.nr @@ -62,7 +62,7 @@ fn return_all_notes( } // docs:start:NoteGetterOptions -struct NoteGetterOptions { +struct NoteGetterOptions { selects: BoundedVec, N>, sorts: BoundedVec, N>, limit: u32, @@ -83,7 +83,7 @@ struct NoteGetterOptions NoteGetterOptions { +impl NoteGetterOptions { // This method adds a `Select` criterion to the options. // It takes a property_selector indicating which field to select, // a value representing the specific value to match in that field, and @@ -129,7 +129,7 @@ impl NoteGetterOpt } } -impl NoteGetterOptions where Note: NoteInterface { +impl NoteGetterOptions where Note: NoteInterface { // This function initializes a NoteGetterOptions that simply returns the maximum number of notes allowed in a call. pub fn new() -> Self { Self { @@ -146,7 +146,7 @@ impl NoteGetterOptions w } } -impl NoteGetterOptions where Note: NoteInterface { +impl NoteGetterOptions where Note: NoteInterface { // This function initializes a NoteGetterOptions with a preprocessor, which takes the notes returned from // the database and preprocessor_args as its parameters. // `preprocessor_args` allows you to provide additional data or context to the custom preprocessor. @@ -168,7 +168,7 @@ impl NoteGetterOptions NoteGetterOptions where Note: NoteInterface { +impl NoteGetterOptions where Note: NoteInterface { // This function initializes a NoteGetterOptions with a filter, which takes // the notes returned from the database and filter_args as its parameters. // `filter_args` allows you to provide additional data or context to the custom filter. diff --git a/noir-projects/aztec-nr/aztec/src/note/note_header.nr b/noir-projects/aztec-nr/aztec/src/note/note_header.nr index bf71981024e..0308a9a5b6e 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_header.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_header.nr @@ -2,7 +2,7 @@ use dep::protocol_types::{address::AztecAddress, traits::{Empty, Serialize}}; global NOTE_HEADER_LENGTH: u32 = 4; -struct NoteHeader { +pub struct NoteHeader { contract_address: AztecAddress, nonce: Field, storage_slot: Field, diff --git a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr index 7631d30da12..c7ecc774ad4 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_interface.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_interface.nr @@ -1,9 +1,15 @@ use crate::context::PrivateContext; use crate::note::note_header::NoteHeader; -use dep::protocol_types::point::Point; -// docs:start:note_interface -trait NoteInterface { +pub trait NoteProperties { + fn properties() -> T; +} + +pub trait PartialNote { + fn hiding_point() -> T; +} + +pub trait NullifiableNote { // This function MUST be called with the correct note hash for consumption! It will otherwise silently fail and // compute an incorrect value. // The reason why we receive this as an argument instead of computing it ourselves directly is because the @@ -14,27 +20,32 @@ trait NoteInterface { // Unlike compute_nullifier, this function does not take a note hash since it'll only be invoked in unconstrained // contexts, where there is no gate count. fn compute_nullifier_without_context(self) -> Field; - - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation +} + +// docs:start:note_interface +// Autogenerated by the #[note] macro + +pub trait NoteInterface { + // Autogenerated by the #[note] macro fn serialize_content(self) -> [Field; N]; - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation + // Autogenerated by the #[note] macro fn deserialize_content(fields: [Field; N]) -> Self; - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation - fn compute_note_hiding_point(self) -> Point; - - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation + // Autogenerated by the #[note] macro fn get_header(self) -> NoteHeader; - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation + // Autogenerated by the #[note] macro fn set_header(&mut self, header: NoteHeader) -> (); - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation + // Autogenerated by the #[note] macro fn get_note_type_id() -> Field; - // Autogenerated by the #[aztec(note)] macro unless it is overridden by a custom implementation - fn to_be_bytes(self, storage_slot: Field) -> [u8; M]; + // Autogenerated by the #[note] macro + fn to_be_bytes(self, storage_slot: Field) -> [u8; N*32 + 64]; + + // Autogenerated by the #[note] macro + fn compute_note_hash(self) -> Field; } // docs:end:note_interface diff --git a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr index f8dca021500..677e6936dfd 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_viewer_options.nr @@ -5,7 +5,7 @@ use crate::note::note_interface::NoteInterface; use crate::note::constants::MAX_NOTES_PER_PAGE; // docs:start:NoteViewerOptions -struct NoteViewerOptions { +struct NoteViewerOptions { selects: BoundedVec, N>, sorts: BoundedVec, N>, limit: u32, @@ -14,8 +14,8 @@ struct NoteViewerOptions { } // docs:end:NoteViewerOptions -impl NoteViewerOptions { - pub fn new() -> NoteViewerOptions where Note: NoteInterface { +impl NoteViewerOptions { + pub fn new() -> NoteViewerOptions where Note: NoteInterface { NoteViewerOptions { selects: BoundedVec::new(), sorts: BoundedVec::new(), diff --git a/noir-projects/aztec-nr/aztec/src/note/utils.nr b/noir-projects/aztec-nr/aztec/src/note/utils.nr index 75cb366afe7..7b2f671f6ed 100644 --- a/noir-projects/aztec-nr/aztec/src/note/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/note/utils.nr @@ -1,4 +1,7 @@ -use crate::{context::PrivateContext, note::{note_header::NoteHeader, note_interface::NoteInterface}}; +use crate::{ + context::PrivateContext, + note::{note_header::NoteHeader, note_interface::{NullifiableNote, NoteInterface}} +}; use dep::protocol_types::{ hash::{ @@ -8,10 +11,10 @@ use dep::protocol_types::{ utils::arr_copy_slice }; -pub fn compute_siloed_nullifier( +pub fn compute_siloed_nullifier( note_with_header: Note, context: &mut PrivateContext -) -> Field where Note: NoteInterface { +) -> Field where Note: NoteInterface + NullifiableNote { let header = note_with_header.get_header(); let note_hash_for_nullify = compute_note_hash_for_nullify(note_with_header); let inner_nullifier = note_with_header.compute_nullifier(context, note_hash_for_nullify); @@ -20,9 +23,8 @@ pub fn compute_siloed_nullifier( } // TODO(#7775): make this not impossible to understand -pub fn compute_note_hash_for_read_request(note: Note) -> Field where Note: NoteInterface { - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; +pub fn compute_note_hash_for_read_request(note: Note) -> Field where Note: NoteInterface + NullifiableNote { + let note_hash = note.compute_note_hash(); let nonce = note.get_header().nonce; let counter = note.get_header().note_hash_counter; @@ -34,10 +36,10 @@ pub fn compute_note_hash_for_read_request(note: No } // TODO(#7775): make this not impossible to understand -pub fn compute_note_hash_for_nullify_internal( +pub fn compute_note_hash_for_nullify_internal( note: Note, note_hash_for_read_request: Field -) -> Field where Note: NoteInterface { +) -> Field where Note: NoteInterface + NullifiableNote { let header = note.get_header(); if header.note_hash_counter != 0 { @@ -57,16 +59,15 @@ pub fn compute_note_hash_for_nullify_internal( } // TODO(#7775): nuke this commented out code - kept it around as it contains comments which might be helpful when tackling #7775 -// pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { +// pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { // let header = note.get_header(); // // There are 3 cases for reading a note intended for consumption: -// // 1. The note was inserted in this transaction, is revertible, or is not nullified by a revertible nullifier in +// // 1. The note was inserted in this transaction, is revertible, or is not nullified by a revertible nullifier in // // the same transaction: (note_hash_counter != 0) & (nonce == 0) -// // 2. The note was inserted in this transaction, is non-revertible, and is nullified by a revertible nullifier in +// // 2. The note was inserted in this transaction, is non-revertible, and is nullified by a revertible nullifier in // // the same transaction: (note_hash_counter != 0) & (nonce != 0) // // 3. The note was inserted in a previous transaction: (note_hash_counter == 0) & (nonce != 0) -// // TODO(#7771): inject compute_note_hash(...) func to notes with macros. // let note_hash = note.compute_note_hiding_point().x; // if header.nonce == 0 { @@ -74,15 +75,15 @@ pub fn compute_note_hash_for_nullify_internal( // // If a note is transient, we just read the note_hash (kernel will hash it with nonce and silo by contract address). // note_hash // } else { -// // Case 2: If a note is non-revertible, and is nullified by a revertible nullifier, we cannot squash them in the -// // private reset circuit. Because if the tx reverts, we will have to keep the note hash and throw away the +// // Case 2: If a note is non-revertible, and is nullified by a revertible nullifier, we cannot squash them in the +// // private reset circuit. Because if the tx reverts, we will have to keep the note hash and throw away the // // nullifier. // // And if the tx does not revert, both will be emitted. In which case, the nullifier must be created in the app // // from the siloed note hash. // // The kernel circuit will check that a nullifier with non-zero note_nonce is linked to a note hash, whose // // siloed note hash matches the note hash specified in the nullifier. -// // Case 3: If a note is not from the current transaction, that means we are reading a settled note (from +// // Case 3: If a note is not from the current transaction, that means we are reading a settled note (from // // tree) created in a previous TX. So we need the siloed_note_hash which has already been hashed with // // nonce and then contract address. This hash will match the existing leaf in the note hash // // tree, so the kernel can just perform a membership check directly on this hash/leaf. @@ -99,22 +100,21 @@ pub fn compute_note_hash_for_nullify_internal( // } // } -pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface { +pub fn compute_note_hash_for_nullify(note: Note) -> Field where Note: NoteInterface + NullifiableNote { let note_hash_for_read_request = compute_note_hash_for_read_request(note); compute_note_hash_for_nullify_internal(note, note_hash_for_read_request) } -pub fn compute_note_hash_and_optionally_a_nullifier( +pub fn compute_note_hash_and_optionally_a_nullifier( deserialize_content: fn([Field; N]) -> T, note_header: NoteHeader, compute_nullifier: bool, serialized_note: [Field; S] -) -> [Field; 4] where T: NoteInterface { +) -> [Field; 4] where T: NoteInterface + NullifiableNote { let mut note = deserialize_content(arr_copy_slice(serialized_note, [0; N], 0)); note.set_header(note_header); - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; + let note_hash = note.compute_note_hash(); let unique_note_hash = compute_unique_note_hash(note_header.nonce, note_hash); let siloed_note_hash = compute_siloed_note_hash(note_header.contract_address, unique_note_hash); diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index b384ff82488..b3f95b57a4a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -86,7 +86,7 @@ unconstrained fn get_notes_oracle_wrapper( ) } -unconstrained pub fn get_notes( +unconstrained pub fn get_notes( storage_slot: Field, num_selects: u8, select_by_indexes: [u8; M], @@ -104,7 +104,7 @@ unconstrained pub fn get_notes; S], // TODO: Remove it and use `limit` to initialize the note array. placeholder_fields: [Field; NS], // TODO: Remove it and use `limit` to initialize the note array. _placeholder_note_length: [Field; N] // Turbofish hack? Compiler breaks calculating read_offset unless we add this parameter -) -> [Option; S] where Note: NoteInterface { +) -> [Option; S] where Note: NoteInterface { let fields = get_notes_oracle_wrapper( storage_slot, num_selects, diff --git a/noir-projects/aztec-nr/aztec/src/prelude.nr b/noir-projects/aztec-nr/aztec/src/prelude.nr index 21bac4ea1b4..793550c5668 100644 --- a/noir-projects/aztec-nr/aztec/src/prelude.nr +++ b/noir-projects/aztec-nr/aztec/src/prelude.nr @@ -11,8 +11,8 @@ pub use crate::{ }, context::{PrivateContext, PackedReturns, FunctionReturns}, note::{ - note_header::NoteHeader, note_interface::NoteInterface, note_getter_options::NoteGetterOptions, - note_viewer_options::NoteViewerOptions, + note_header::NoteHeader, note_interface::{NoteInterface, NullifiableNote}, + note_getter_options::NoteGetterOptions, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_and_optionally_a_nullifier as utils_compute_note_hash_and_optionally_a_nullifier } }; diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr index 6618de9374e..798d3dc4205 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_immutable.nr @@ -2,8 +2,9 @@ use dep::protocol_types::{constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER, use crate::context::{PrivateContext, UnconstrainedContext}; use crate::note::{ - lifecycle::create_note, note_getter::{get_note, view_notes}, note_interface::NoteInterface, - note_viewer_options::NoteViewerOptions, note_emission::NoteEmission + lifecycle::create_note, note_getter::{get_note, view_notes}, + note_interface::{NoteInterface, NullifiableNote}, note_viewer_options::NoteViewerOptions, + note_emission::NoteEmission }; use crate::oracle::notes::check_nullifier_exists; use crate::state_vars::storage::Storage; @@ -41,10 +42,10 @@ impl PrivateImmutable { impl PrivateImmutable { // docs:start:initialize - pub fn initialize( + pub fn initialize( self, note: &mut Note - ) -> NoteEmission where Note: NoteInterface { + ) -> NoteEmission where Note: NoteInterface + NullifiableNote { // Nullify the storage slot. let nullifier = self.compute_initialization_nullifier(); self.context.push_nullifier(nullifier); @@ -54,7 +55,7 @@ impl PrivateImmutable { // docs:end:initialize // docs:start:get_note - pub fn get_note(self) -> Note where Note: NoteInterface { + pub fn get_note(self) -> Note where Note: NoteInterface + NullifiableNote { let storage_slot = self.storage_slot; get_note(self.context, storage_slot).0 } @@ -71,7 +72,7 @@ impl PrivateImmutable { // view_note does not actually use the context, but it calls oracles that are only available in private // docs:start:view_note - unconstrained pub fn view_note(self) -> Note where Note: NoteInterface { + unconstrained pub fn view_note(self) -> Note where Note: NoteInterface + NullifiableNote { let mut options = NoteViewerOptions::new(); view_notes(self.storage_slot, options.set_limit(1)).get(0) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr index 1c7315a49e2..c41dbbaaa68 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr @@ -3,7 +3,8 @@ use dep::protocol_types::{constants::GENERATOR_INDEX__INITIALIZATION_NULLIFIER, use crate::context::{PrivateContext, UnconstrainedContext}; use crate::note::{ lifecycle::{create_note, destroy_note_unsafe}, note_getter::{get_note, view_notes}, - note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, note_emission::NoteEmission + note_interface::{NoteInterface, NullifiableNote}, note_viewer_options::NoteViewerOptions, + note_emission::NoteEmission }; use crate::oracle::notes::check_nullifier_exists; use crate::state_vars::storage::Storage; @@ -43,7 +44,7 @@ impl PrivateMutable { } } -impl PrivateMutable where Note: NoteInterface { +impl PrivateMutable where Note: NoteInterface + NullifiableNote { // docs:start:initialize pub fn initialize(self, note: &mut Note) -> NoteEmission { // Nullify the storage slot. @@ -102,7 +103,7 @@ impl PrivateMutable whe // docs:end:get_note } -impl PrivateMutable where Note: NoteInterface { +impl PrivateMutable where Note: NoteInterface + NullifiableNote { unconstrained pub fn is_initialized(self) -> bool { let nullifier = self.compute_initialization_nullifier(); check_nullifier_exists(nullifier) diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr index b115b8c3adc..0eb7822218f 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/private_set.nr @@ -4,7 +4,7 @@ use crate::note::{ constants::MAX_NOTES_PER_PAGE, lifecycle::{create_note, create_note_hash_from_public, destroy_note_unsafe}, note_getter::{get_notes, view_notes}, note_getter_options::NoteGetterOptions, - note_interface::NoteInterface, note_viewer_options::NoteViewerOptions, + note_interface::{NoteInterface, NullifiableNote}, note_viewer_options::NoteViewerOptions, utils::compute_note_hash_for_read_request, note_emission::NoteEmission }; use crate::state_vars::storage::Storage; @@ -27,7 +27,7 @@ impl PrivateSet { // docs:end:new } -impl PrivateSet where Note: NoteInterface { +impl PrivateSet where Note: NoteInterface + NullifiableNote { // docs:start:insert_from_public pub fn insert_from_public(self, note: &mut Note) { create_note_hash_from_public(self.context, self.storage_slot, note); @@ -35,7 +35,7 @@ impl PrivateSet where No // docs:end:insert_from_public } -impl PrivateSet where Note: NoteInterface + Eq { +impl PrivateSet where Note: NoteInterface + NullifiableNote + Eq { // docs:start:insert pub fn insert(self, note: &mut Note) -> NoteEmission { create_note(self.context, self.storage_slot, note) @@ -44,7 +44,7 @@ impl PrivateSet where N pub fn pop_notes( self, - options: NoteGetterOptions + options: NoteGetterOptions ) -> BoundedVec { let (notes, note_hashes) = get_notes(self.context, self.storage_slot, options); // We iterate in a range 0..options.limit instead of 0..notes.len() because options.limit is known at compile @@ -77,17 +77,17 @@ impl PrivateSet where N /// in significantly less constrains due to avoiding 1 read request check. pub fn get_notes( self, - options: NoteGetterOptions + options: NoteGetterOptions ) -> BoundedVec { get_notes(self.context, self.storage_slot, options).0 } } -impl PrivateSet where Note: NoteInterface { +impl PrivateSet where Note: NoteInterface + NullifiableNote { // docs:start:view_notes unconstrained pub fn view_notes( self, - options: NoteViewerOptions + options: NoteViewerOptions ) -> BoundedVec { view_notes(self.storage_slot, options) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr index fa7582aed7f..9c7b82231b9 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/public_immutable.nr @@ -24,7 +24,7 @@ impl PublicImmutable { // docs:end:public_immutable_struct_new } -impl PublicImmutable where T: Serialize + Deserialize { +impl PublicImmutable where T: Serialize + Deserialize { // docs:start:public_immutable_struct_write pub fn initialize(self, value: T) { // We check that the struct is not yet initialized by checking if the initialization slot is 0 @@ -46,7 +46,7 @@ impl PublicImmutable where // docs:end:public_immutable_struct_read } -impl PublicImmutable where T: Deserialize { +impl PublicImmutablewhere T: Deserialize { unconstrained pub fn read(self) -> T { self.context.storage_read(self.storage_slot) } diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/scheduled_delay_change/test.nr b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/scheduled_delay_change/test.nr index 33114b0e96f..cae523b1f5d 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/scheduled_delay_change/test.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/shared_mutable/scheduled_delay_change/test.nr @@ -184,7 +184,7 @@ fn test_schedule_change_to_longer_delay_after_change() { #[test] fn test_schedule_change_to_longer_delay_from_initial() { - let new = TEST_INITIAL_DELAY + 1; + let new: u32 = TEST_INITIAL_DELAY + 1; let current_block_number = 50; let mut delay_change = get_initial_delay_change(); diff --git a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr index 8ae9faf228c..abcf627f798 100644 --- a/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr +++ b/noir-projects/aztec-nr/aztec/src/state_vars/storage.nr @@ -1,15 +1,15 @@ use dep::protocol_types::traits::{Deserialize, Serialize}; -trait Storage where T: Serialize + Deserialize { +pub trait Storage where T: Serialize + Deserialize { fn get_storage_slot(self) -> Field { self.storage_slot } } // Struct representing an exportable storage variable in the contract -// Every entry in the storage struct will be exported in the compilation artifact as a +// Every entry in the storage struct will be exported in the compilation artifact as a // Storable entity, containing the storage slot -struct Storable { +pub struct Storable { slot: Field, } diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index aa6e3641fed..302cd1215f3 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -11,10 +11,10 @@ use crate::test::helpers::{cheatcodes, utils::{apply_side_effects_private, Deplo use crate::keys::constants::{NULLIFIER_INDEX, INCOMING_INDEX, OUTGOING_INDEX, TAGGING_INDEX}; use crate::hash::hash_args; -use crate::note::{note_header::NoteHeader, note_interface::NoteInterface}; +use crate::note::{note_header::NoteHeader, note_interface::{NoteInterface, NullifiableNote}}; use crate::oracle::{execution::{get_block_number, get_contract_address}, notes::notify_created_note}; -struct TestEnvironment {} +pub struct TestEnvironment {} impl TestEnvironment { fn new() -> Self { @@ -196,20 +196,19 @@ impl TestEnvironment { /// Manually adds a note to TXE. This needs to be called if you want to work with a note in your test with the note /// not having an encrypted log emitted. TXE alternative to `PXE.addNote(...)`. - pub fn add_note( + pub fn add_note( _self: Self, note: &mut Note, storage_slot: Field, contract_address: AztecAddress - ) where Note: NoteInterface { + ) where Note: NoteInterface + NullifiableNote { let original_contract_address = get_contract_address(); cheatcodes::set_contract_address(contract_address); let note_hash_counter = cheatcodes::get_side_effects_counter(); let header = NoteHeader { contract_address, storage_slot, nonce: 0, note_hash_counter }; note.set_header(header); - // TODO(#7771): inject compute_note_hash(...) func to notes with macros. - let note_hash = note.compute_note_hiding_point().x; + let note_hash = note.compute_note_hash(); let serialized_note = Note::serialize_content(*note); assert( notify_created_note( diff --git a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr index faa780954ac..3243409da9b 100644 --- a/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr +++ b/noir-projects/aztec-nr/aztec/src/test/mocks/mock_note.nr @@ -3,19 +3,39 @@ use crate::{ note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_nullify} }; -use dep::protocol_types::{address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::Point}; +use dep::protocol_types::{address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, traits::Eq}; use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; +use crate::note::note_interface::NullifiableNote; global MOCK_NOTE_LENGTH: u32 = 1; -// MOCK_NOTE_LENGTH * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global MOCK_NOTE_BYTES_LENGTH: u32 = 1 * 32 + 64; struct MockNote { header: NoteHeader, value: Field, } -impl NoteInterface for MockNote { +impl NullifiableNote for MockNote { + fn compute_nullifier(_self: Self, _context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { + // We don't use any kind of secret here since this is only a mock note and having it here would make tests + // more cumbersome + poseidon2_hash_with_separator( + [note_hash_for_nullify], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } + + fn compute_nullifier_without_context(self) -> Field { + // We don't use any kind of secret here since this is only a mock note and having it here would make tests + // more cumbersome + let note_hash_for_nullify = compute_note_hash_for_nullify(self); + poseidon2_hash_with_separator( + [note_hash_for_nullify], + GENERATOR_INDEX__NOTE_NULLIFIER as Field + ) + } +} + +impl NoteInterface for MockNote { fn serialize_content(self) -> [Field; MOCK_NOTE_LENGTH] { [self.value] } @@ -24,13 +44,6 @@ impl NoteInterface for MockNote { Self { value: fields[0], header: NoteHeader::empty() } } - fn compute_note_hiding_point(self: Self) -> Point { - assert(self.header.storage_slot != 0, "Storage slot must be set before computing note hiding point"); - // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let value_scalar = from_field_unsafe(self.value); - multi_scalar_mul([G_val], [value_scalar]) - } - fn get_header(self) -> NoteHeader { self.header } @@ -44,29 +57,10 @@ impl NoteInterface for MockNote { 4135 } - fn compute_nullifier(_self: Self, _context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { - // We don't use any kind of secret here since this is only a mock note and having it here would make tests - // more cumbersome - poseidon2_hash_with_separator( - [note_hash_for_nullify], - GENERATOR_INDEX__NOTE_NULLIFIER as Field - ) - } - - fn compute_nullifier_without_context(self) -> Field { - // We don't use any kind of secret here since this is only a mock note and having it here would make tests - // more cumbersome - let note_hash_for_nullify = compute_note_hash_for_nullify(self); - poseidon2_hash_with_separator( - [note_hash_for_nullify], - GENERATOR_INDEX__NOTE_NULLIFIER as Field - ) - } - - fn to_be_bytes(self, storage_slot: Field) -> [u8; MOCK_NOTE_BYTES_LENGTH] { + fn to_be_bytes(self, storage_slot: Field) -> [u8; MOCK_NOTE_LENGTH * 32 + 64] { let serialized_note = self.serialize_content(); - let mut buffer: [u8; MOCK_NOTE_BYTES_LENGTH] = [0; MOCK_NOTE_BYTES_LENGTH]; + let mut buffer: [u8; MOCK_NOTE_LENGTH * 32 + 64] = [0; MOCK_NOTE_LENGTH * 32 + 64]; let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); let note_type_id_bytes: [u8; 32] = MockNote::get_note_type_id().to_be_bytes(); @@ -84,6 +78,13 @@ impl NoteInterface for MockNote { } buffer } + + fn compute_note_hash(self: Self) -> Field { + assert(self.header.storage_slot != 0, "Storage slot must be set before computing note hash"); + // We use the unsafe version because the multi_scalar_mul will constrain the scalars. + let value_scalar = from_field_unsafe(self.value); + multi_scalar_mul([G_val], [value_scalar]).x + } } impl Eq for MockNote { diff --git a/noir-projects/aztec-nr/aztec/src/unencrypted_logs/unencrypted_event_emission.nr b/noir-projects/aztec-nr/aztec/src/unencrypted_logs/unencrypted_event_emission.nr index b5b20f2b096..f8bdcf3d53e 100644 --- a/noir-projects/aztec-nr/aztec/src/unencrypted_logs/unencrypted_event_emission.nr +++ b/noir-projects/aztec-nr/aztec/src/unencrypted_logs/unencrypted_event_emission.nr @@ -1,10 +1,10 @@ use crate::{context::PublicContext, event::event_interface::EventInterface}; use dep::protocol_types::{traits::Serialize}; -fn emit( +fn emit( context: &mut PublicContext, event: Event -) where Event: EventInterface, Event: Serialize, [Field; N]: LensForEventSelector { +) where Event: EventInterface, Event: Serialize, [Field; N]: LensForEventSelector { let selector = Event::get_event_type_id(); let serialized_event = event.serialize(); @@ -20,7 +20,7 @@ fn emit( context.emit_unencrypted_log(emitted_log); } -pub fn encode_event(context: &mut PublicContext) -> fn[(&mut PublicContext,)](Event) -> () where Event: EventInterface, Event: Serialize, [Field; N]: LensForEventSelector { +pub fn encode_event(context: &mut PublicContext) -> fn[(&mut PublicContext,)](Event) -> () where Event: EventInterface, Event: Serialize, [Field; N]: LensForEventSelector { | e: Event | { emit( context, diff --git a/noir-projects/aztec-nr/uint-note/src/uint_note.nr b/noir-projects/aztec-nr/uint-note/src/uint_note.nr index 41774e7536e..1062566fa7a 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -1,18 +1,10 @@ use dep::aztec::{ - generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot}, - prelude::{NoteInterface, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, - hash::poseidon2_hash_with_separator, traits::Serialize -}, - note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app + prelude::{NullifiableNote, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, macros::notes::note }; -use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; -global UINT_NOTE_LEN: u32 = 3; // 3 plus a header. -global UINT_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - -#[aztec(note)] +#[note] struct UintNote { // The integer stored by the note value: U128, @@ -22,7 +14,7 @@ struct UintNote { randomness: Field, } -impl NoteInterface for UintNote { +impl NullifiableNote for UintNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); poseidon2_hash_with_separator( @@ -42,66 +34,6 @@ impl NoteInterface for UintNote { GENERATOR_INDEX__NOTE_NULLIFIER ) } - - fn compute_note_hiding_point(self) -> Point { - // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let amount_scalar = from_field_unsafe(self.value.to_integer()); - let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash); - let randomness_scalar = from_field_unsafe(self.randomness); - let slot_scalar = from_field_unsafe(self.header.storage_slot); - // We compute the note hiding point as: - // `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot` - // instead of using pedersen or poseidon2 because it allows us to privately add and subtract from amount - // in public by leveraging homomorphism. - multi_scalar_mul( - [G_amt, G_npk, G_rnd, G_slot], - [amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar] - ) - } -} - -impl UintNote { - // TODO: Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have - // to modify macros and all the related funcs in it. - fn to_note_hiding_point(self) -> UintNoteHidingPoint { - UintNoteHidingPoint::new(self.compute_note_hiding_point()) - } -} - -struct UintNoteHidingPoint { - inner: Point -} - -impl UintNoteHidingPoint { - fn new(point: Point) -> Self { - Self { inner: point } - } - - fn add_amount(&mut self, amount: U128) { - self.inner = multi_scalar_mul([G_amt], [from_field_unsafe(amount.to_integer())]) + self.inner; - } - - fn add_npk_m_hash(&mut self, npk_m_hash: Field) { - self.inner = multi_scalar_mul([G_npk], [from_field_unsafe(npk_m_hash)]) + self.inner; - } - - fn add_randomness(&mut self, randomness: Field) { - self.inner = multi_scalar_mul([G_rnd], [from_field_unsafe(randomness)]) + self.inner; - } - - fn add_slot(&mut self, slot: Field) { - self.inner = multi_scalar_mul([G_slot], [from_field_unsafe(slot)]) + self.inner; - } - - fn finalize(self) -> Field { - self.inner.x - } -} - -impl Serialize for UintNoteHidingPoint { - fn serialize(self) -> [Field; POINT_LENGTH] { - self.inner.serialize() - } } impl Eq for UintNote { diff --git a/noir-projects/aztec-nr/value-note/src/utils.nr b/noir-projects/aztec-nr/value-note/src/utils.nr index 2caa0661397..d6a65d16af8 100644 --- a/noir-projects/aztec-nr/value-note/src/utils.nr +++ b/noir-projects/aztec-nr/value-note/src/utils.nr @@ -2,11 +2,11 @@ use dep::aztec::prelude::{AztecAddress, PrivateContext, PrivateSet, NoteGetterOp use dep::aztec::note::note_getter_options::SortOrder; use dep::aztec::encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys; use dep::aztec::keys::getters::get_public_keys; -use crate::{filter::filter_notes_min_sum, value_note::{ValueNote, VALUE_NOTE_LEN, VALUE_NOTE_BYTES_LEN}}; +use crate::{filter::filter_notes_min_sum, value_note::{ValueNote, VALUE_NOTE_LEN}}; // Sort the note values (0th field) in descending order. // Pick the fewest notes whose sum is equal to or greater than `amount`. -pub fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions { +pub fn create_note_getter_options_for_decreasing_balance(amount: Field) -> NoteGetterOptions { NoteGetterOptions::with_filter(filter_notes_min_sum, amount).sort(ValueNote::properties().value, SortOrder.DESC) } diff --git a/noir-projects/aztec-nr/value-note/src/value_note.nr b/noir-projects/aztec-nr/value-note/src/value_note.nr index 1b4c1fff073..62c5b7875d0 100644 --- a/noir-projects/aztec-nr/value-note/src/value_note.nr +++ b/noir-projects/aztec-nr/value-note/src/value_note.nr @@ -1,22 +1,18 @@ use dep::aztec::{ - generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot}, - protocol_types::{ - traits::Serialize, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator, - point::{Point, POINT_LENGTH} -}, - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_nullify}, + protocol_types::{traits::Serialize, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note, + note::{note_header::NoteHeader, note_interface::NullifiableNote, utils::compute_note_hash_for_nullify}, oracle::unsafe_rand::unsafe_rand, keys::getters::get_nsk_app, context::PrivateContext }; -use dep::std::{embedded_curve_ops::multi_scalar_mul}; -use std::hash::from_field_unsafe; global VALUE_NOTE_LEN: u32 = 3; // 3 plus a header. -// VALUE_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global VALUE_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; // docs:start:value-note-def -#[aztec(note)] -struct ValueNote { +// ValueNote is used as fn parameter in the Claim contract, so it has to implement the Serialize trait. +// It is important that the order of these annotations is preserved so that derive(Serialize) runs AFTER the note macro, which injects the note header. +#[note] +#[derive(Serialize)] +pub struct ValueNote { value: Field, // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. npk_m_hash: Field, @@ -24,7 +20,7 @@ struct ValueNote { } // docs:end:value-note-def -impl NoteInterface for ValueNote { +impl NullifiableNote for ValueNote { // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { @@ -51,22 +47,6 @@ impl NoteInterface for ValueNote { GENERATOR_INDEX__NOTE_NULLIFIER as Field ) } - - fn compute_note_hiding_point(self) -> Point { - // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let amount_scalar = from_field_unsafe(self.value); - let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash); - let randomness_scalar = from_field_unsafe(self.randomness); - let slot_scalar = from_field_unsafe(self.header.storage_slot); - // We compute the note hiding point as: - // `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot` - // instead of using pedersen or poseidon2 because it allows us to privately add and subtract from amount - // in public by leveraging homomorphism. - multi_scalar_mul( - [G_amt, G_npk, G_rnd, G_slot], - [amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar] - ) - } } impl ValueNote { @@ -75,24 +55,6 @@ impl ValueNote { let header = NoteHeader::empty(); ValueNote { value, npk_m_hash, randomness, header } } - - // TODO: Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have - // to modify macros and all the related funcs in it. - fn to_note_hiding_point(self) -> ValueNoteHidingPoint { - ValueNoteHidingPoint::new(self.compute_note_hiding_point()) - } -} - -impl Serialize<7> for ValueNote { - /// The following method needed to be implemented because the note is passed as an argument to a contract function - /// --> the serialize method is called by aztec-nr when computing an arguments hash. - /// Note that when the note is about to be encrypted and emitted as a log the to_be_bytes function auto-implemented - /// by aztec macros is called instead. - fn serialize(self) -> [Field; 7] { - let header = self.header.serialize(); - - [self.value, self.npk_m_hash, self.randomness, header[0], header[1], header[2], header[3]] - } } impl Eq for ValueNote { @@ -102,39 +64,3 @@ impl Eq for ValueNote { & (self.randomness == other.randomness) } } - -struct ValueNoteHidingPoint { - inner: Point -} - -impl ValueNoteHidingPoint { - fn new(point: Point) -> Self { - Self { inner: point } - } - - fn add_value(&mut self, value: U128) { - self.inner = multi_scalar_mul([G_amt], [from_field_unsafe(value.to_integer())]) + self.inner; - } - - fn add_npk_m_hash(&mut self, npk_m_hash: Field) { - self.inner = multi_scalar_mul([G_npk], [from_field_unsafe(npk_m_hash)]) + self.inner; - } - - fn add_randomness(&mut self, randomness: Field) { - self.inner = multi_scalar_mul([G_rnd], [from_field_unsafe(randomness)]) + self.inner; - } - - fn add_slot(&mut self, slot: Field) { - self.inner = multi_scalar_mul([G_slot], [from_field_unsafe(slot)]) + self.inner; - } - - fn finalize(self) -> Field { - self.inner.x - } -} - -impl Serialize for ValueNoteHidingPoint { - fn serialize(self) -> [Field; POINT_LENGTH] { - self.inner.serialize() - } -} diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index f78bd9c53da..dc5db7ace32 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -1,36 +1,39 @@ mod subscription_note; mod dapp_payload; +use dep::aztec::macros::aztec; + +#[aztec] contract AppSubscription { use crate::{dapp_payload::DAppPayload, subscription_note::SubscriptionNote}; use aztec::{ - prelude::{AztecAddress, Map, PrivateMutable, SharedImmutable}, + prelude::{AztecAddress, Map, PrivateMutable, SharedImmutable}, keys::getters::get_public_keys, + protocol_types::constants::MAX_FIELD_VALUE, utils::comparison::Comparator, encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note, encode_and_encrypt_note_with_keys}, - keys::getters::get_public_keys, protocol_types::constants::MAX_FIELD_VALUE, - utils::comparison::Comparator + macros::{storage::storage, functions::{public, initializer, private}} }; use authwit::auth::assert_current_call_valid_authwit; use token::Token; use router::utils::privately_check_block_number; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // The following is only needed in private but we use ShareImmutable here instead of PrivateImmutable because // the value can be publicly known and SharedImmutable provides us with a better devex here because we don't // have to bother with sharing the note between pixies of users. - target_address: SharedImmutable, - subscription_token_address: SharedImmutable, - subscription_recipient_address: SharedImmutable, - subscription_price: SharedImmutable, - subscriptions: Map>, - fee_juice_limit_per_tx: SharedImmutable, + target_address: SharedImmutable, + subscription_token_address: SharedImmutable, + subscription_recipient_address: SharedImmutable, + subscription_price: SharedImmutable, + subscriptions: Map, Context>, + fee_juice_limit_per_tx: SharedImmutable, } global SUBSCRIPTION_DURATION_IN_BLOCKS = 5; global SUBSCRIPTION_TXS = 5; - #[aztec(private)] + #[private] fn entrypoint(payload: DAppPayload, user_address: AztecAddress) { // Default msg_sender for entrypoints is now Fr.max_value rather than 0 addr (see #7190 & #7404) assert(context.msg_sender().to_field() == MAX_FIELD_VALUE); @@ -59,8 +62,8 @@ contract AppSubscription { payload.execute_calls(&mut context, storage.target_address.read_private()); } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor( target_address: AztecAddress, subscription_recipient_address: AztecAddress, @@ -75,7 +78,7 @@ contract AppSubscription { storage.fee_juice_limit_per_tx.initialize(fee_juice_limit_per_tx); } - #[aztec(private)] + #[private] fn subscribe(subscriber: AztecAddress, nonce: Field, expiry_block_number: Field, tx_count: Field) { assert(tx_count as u64 <= SUBSCRIPTION_TXS as u64); diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr index 5edfe215f69..39b0c2d040f 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/subscription_note.nr @@ -1,15 +1,11 @@ use dep::aztec::{ hash::poseidon2_hash_with_separator, note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, oracle::unsafe_rand::unsafe_rand, - prelude::{PrivateContext, NoteHeader, NoteInterface}, - protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER + prelude::{PrivateContext, NoteHeader, NullifiableNote}, + protocol_types::constants::GENERATOR_INDEX__NOTE_NULLIFIER, macros::notes::note }; -global SUBSCRIPTION_NOTE_LEN: u32 = 4; -// SUBSCRIPTION_NOTE_BYTES_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global SUBSCRIPTION_NOTE_BYTES_LEN: u32 = SUBSCRIPTION_NOTE_LEN * 32 + 64; - -#[aztec(note)] +#[note] struct SubscriptionNote { // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. npk_m_hash: Field, @@ -19,7 +15,7 @@ struct SubscriptionNote { randomness: Field, } -impl NoteInterface for SubscriptionNote { +impl NullifiableNote for SubscriptionNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); poseidon2_hash_with_separator( diff --git a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr index 11549fae5d5..18f4cd0aebb 100644 --- a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr @@ -2,31 +2,36 @@ mod test; // Test contract showing basic public access control that can be used in private. It uses a SharedMutable state variable to // publicly store the address of an authorized account that can call private functions. +use dep::aztec::macros::aztec; + +#[aztec] contract Auth { - use dep::aztec::protocol_types::address::AztecAddress; - use dep::aztec::state_vars::{PublicImmutable, SharedMutable}; + use dep::aztec::{ + protocol_types::address::AztecAddress, state_vars::{PublicImmutable, SharedMutable}, + macros::{storage::storage, functions::{private, public, initializer, view}} + }; // Authorizing a new address has a certain block delay before it goes into effect. global CHANGE_AUTHORIZED_DELAY_BLOCKS: u32 = 5; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // Admin can change the value of the authorized address via set_authorized() - admin: PublicImmutable, + admin: PublicImmutable, // docs:start:shared_mutable_storage - authorized: SharedMutable, + authorized: SharedMutable, // docs:end:shared_mutable_storage } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress) { assert(!admin.is_zero(), "invalid admin"); storage.admin.initialize(admin); } // docs:start:shared_mutable_schedule - #[aztec(public)] + #[public] fn set_authorized(authorized: AztecAddress) { assert_eq(storage.admin.read(), context.msg_sender(), "caller is not admin"); storage.authorized.schedule_value_change(authorized); @@ -34,8 +39,8 @@ contract Auth { } // docs:start:public_getter - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_authorized() -> AztecAddress { // docs:start:shared_mutable_get_current_public storage.authorized.get_current_value_in_public() @@ -43,8 +48,8 @@ contract Auth { } // docs:end:public_getter - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_scheduled_authorized() -> AztecAddress { // docs:start:shared_mutable_get_scheduled_public let (scheduled_value, _block_of_change): (AztecAddress, u32) = storage.authorized.get_scheduled_value_in_public(); @@ -52,18 +57,18 @@ contract Auth { scheduled_value } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_authorized_delay() -> pub u32 { storage.authorized.get_current_delay_in_public() } - #[aztec(public)] + #[public] fn set_authorized_delay(new_delay: u32) { storage.authorized.schedule_delay_change(new_delay); } - #[aztec(private)] + #[private] fn do_private_authorized_thing() { // Reading a value from authorized in private automatically adds an extra validity condition: the base rollup // circuit will reject this tx if included in a block past the block horizon, which is as far as the circuit can @@ -74,8 +79,8 @@ contract Auth { assert_eq(authorized, context.msg_sender(), "caller is not authorized"); } - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn get_authorized_in_private() -> AztecAddress { storage.authorized.get_current_value_in_private() } diff --git a/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr index 12b35516ba2..965f39ea20a 100644 --- a/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_registry_contract/src/main.nr @@ -1,12 +1,18 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract AuthRegistry { - use dep::aztec::{state_vars::{PublicMutable, Map}, protocol_types::address::AztecAddress}; + use dep::aztec::{ + state_vars::{PublicMutable, Map}, protocol_types::address::AztecAddress, + macros::{storage::storage, functions::{private, public, internal}} + }; use dep::authwit::auth::{IS_VALID_SELECTOR, compute_authwit_message_hash, assert_current_call_valid_authwit}; - #[aztec(storage)] - struct Storage { - reject_all: Map>, + #[storage] + struct Storage { + reject_all: Map, Context>, // on_behalf_of => authwit hash => authorized - approved_actions: Map>>, + approved_actions: Map, Context>, Context>, } /** @@ -15,26 +21,26 @@ contract AuthRegistry { * @param message_hash The message hash being authorized * @param authorize True if the caller is authorized to perform the message hash, false otherwise */ - #[aztec(public)] + #[public] fn set_authorized(message_hash: Field, authorize: bool) { storage.approved_actions.at(context.msg_sender()).at(message_hash).write(authorize); } /** * Updates the `reject_all` value for `msg_sender`. - * + * * When `reject_all` is `true` any `consume` on `msg_sender` will revert. - * + * * @param reject True if all actions should be rejected, false otherwise */ - #[aztec(public)] + #[public] fn set_reject_all(reject: bool) { storage.reject_all.at(context.msg_sender()).write(reject); } /** * Consumes an `inner_hash` on behalf of `on_behalf_of` if the caller is authorized to do so. - * + * * Will revert even if the caller is authorized if `reject_all` is set to true for `on_behalf_of`. * This is to support "mass-revoke". * @@ -42,7 +48,7 @@ contract AuthRegistry { * @param inner_hash The inner_hash of the authwit * @return `IS_VALID_SELECTOR` if the action was consumed, revert otherwise */ - #[aztec(public)] + #[public] fn consume(on_behalf_of: AztecAddress, inner_hash: Field) -> Field { assert_eq(false, storage.reject_all.at(on_behalf_of).read(), "rejecting all"); @@ -63,7 +69,7 @@ contract AuthRegistry { /** * Updates a public authwit using a private authwit - * + * * Useful for the case where you want someone else to insert a public authwit for you. * For example, if Alice wants Bob to insert an authwit in public, such that they can execute * a trade, Alice can create a private authwit, and Bob can call this function with it. @@ -72,7 +78,7 @@ contract AuthRegistry { * @param message_hash The message hash to authorize * @param authorize True if the message hash should be authorized, false otherwise */ - #[aztec(private)] + #[private] fn set_authorized_private(approver: AztecAddress, message_hash: Field, authorize: bool) { assert_current_call_valid_authwit(&mut context, approver); AuthRegistry::at(context.this_address())._set_authorized(approver, message_hash, authorize).enqueue(&mut context); @@ -81,38 +87,38 @@ contract AuthRegistry { /** * Internal function to update the `authorized` value for `approver` for `messageHash`. * Used along with `set_authorized_private` to update the public authwit. - * + * * @param approver The address of the approver * @param message_hash The message hash being authorized * @param authorize True if the caller is authorized to perform the message hash, false otherwise */ - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _set_authorized(approver: AztecAddress, message_hash: Field, authorize: bool) { storage.approved_actions.at(approver).at(message_hash).write(authorize); } /** * Fetches the `reject_all` value for `on_behalf_of`. - * + * * @param on_behalf_of The address to check * @return True if all actions are rejected, false otherwise */ - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn is_reject_all(on_behalf_of: AztecAddress) -> bool { storage.reject_all.at(on_behalf_of).read() } /** * Fetches the `authorized` value for `on_behalf_of` for `message_hash`. - * + * * @param on_behalf_of The address on whose behalf the action is being consumed * @param message_hash The message hash to check * @return True if the caller is authorized to perform the action, false otherwise */ - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn is_consumable(on_behalf_of: AztecAddress, message_hash: Field) -> bool { storage.approved_actions.at(on_behalf_of).at(message_hash).read() } diff --git a/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr index 997d53439a6..8fbf2ba8c82 100644 --- a/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_wit_test_contract/src/main.nr @@ -1,13 +1,17 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract AuthWitTest { - use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::{protocol_types::address::AztecAddress, macros::{functions::{private, public}}}; + use dep::authwit::auth::{assert_inner_hash_valid_authwit, assert_inner_hash_valid_authwit_public}; - #[aztec(private)] + #[private] fn consume(on_behalf_of: AztecAddress, inner_hash: Field) { assert_inner_hash_valid_authwit(&mut context, on_behalf_of, inner_hash); } - #[aztec(public)] + #[public] fn consume_public(on_behalf_of: AztecAddress, inner_hash: Field) { assert_inner_hash_valid_authwit_public(&mut context, on_behalf_of, inner_hash); } diff --git a/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr index 6e075e89a3e..8c74544a783 100644 --- a/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_initializer_test_contract/src/main.nr @@ -1,23 +1,25 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract AvmInitializerTest { // Libs - use dep::aztec::state_vars::PublicImmutable; - use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::{state_vars::PublicImmutable, macros::{storage::storage, functions::{initializer, public}}}; - #[aztec(storage)] - struct Storage { - immutable: PublicImmutable, + #[storage] + struct Storage { + immutable: PublicImmutable, } /************************************************************************ * Storage ************************************************************************/ - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor() { storage.immutable.initialize(42); } - #[aztec(public)] + #[public] fn read_storage_immutable() -> pub Field { storage.immutable.read() } diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index bb2456c71b1..e6861d4cc58 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -17,6 +17,9 @@ impl Deserialize<2> for Note { } } +use dep::aztec::macros::aztec; + +#[aztec] contract AvmTest { use crate::Note; @@ -32,55 +35,56 @@ contract AvmTest { use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, point::Point, scalar::Scalar}; use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm}; use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, storage::map::derive_storage_slot_in_map}; - use dep::aztec::context::gas::GasOpts; use dep::compressed_string::CompressedString; + use dep::aztec::macros::{storage::storage, functions::{public, private}}; + use dep::aztec::context::gas::GasOpts; - #[aztec(storage)] - struct Storage { - single: PublicMutable, - list: PublicMutable, - map: Map>, + #[storage] + struct Storage { + single: PublicMutable, + list: PublicMutable, + map: Map, Context>, } /************************************************************************ * Storage ************************************************************************/ - #[aztec(public)] + #[public] fn set_storage_single(a: Field) { storage.single.write(a); } - #[aztec(public)] + #[public] fn read_storage_single() -> Field { storage.single.read() } // should still be able to use ` -> pub *` for return type even though macro forces `pub` - #[aztec(public)] + #[public] fn set_read_storage_single(a: Field) -> pub Field { storage.single.write(a); storage.single.read() } - #[aztec(public)] + #[public] fn set_storage_list(a: Field, b: Field) { storage.list.write(Note { a, b }); } - #[aztec(public)] + #[public] fn read_storage_list() -> [Field; 2] { let note: Note = storage.list.read(); note.serialize() } - #[aztec(public)] + #[public] fn set_storage_map(to: AztecAddress, amount: u32) -> Field { storage.map.at(to).write(amount); // returns storage slot for key derive_storage_slot_in_map(storage.map.storage_slot, to) } - #[aztec(public)] + #[public] fn add_storage_map(to: AztecAddress, amount: u32) -> Field { let new_balance = storage.map.at(to).read().add(amount); storage.map.at(to).write(new_balance); @@ -88,12 +92,12 @@ contract AvmTest { derive_storage_slot_in_map(storage.map.storage_slot, to) } - #[aztec(public)] + #[public] fn read_storage_map(address: AztecAddress) -> u32 { storage.map.at(address).read() } - #[aztec(public)] + #[public] fn add_args_return(arg_a: Field, arg_b: Field) -> Field { arg_a + arg_b } @@ -101,47 +105,47 @@ contract AvmTest { /************************************************************************ * General Opcodes ************************************************************************/ - #[aztec(public)] + #[public] fn set_opcode_u8() -> u8 { 8 as u8 } - #[aztec(public)] + #[public] fn set_opcode_u32() -> u32 { 1 << 30 as u8 } - #[aztec(public)] + #[public] fn set_opcode_u64() -> u64 { 1 << 60 as u8 } - #[aztec(public)] + #[public] fn set_opcode_small_field() -> Field { big_field_128_bits } - #[aztec(public)] + #[public] fn set_opcode_big_field() -> Field { big_field_136_bits } - #[aztec(public)] + #[public] fn set_opcode_really_big_field() -> Field { big_field_254_bits } - #[aztec(public)] + #[public] fn add_u128(a: U128, b: U128) -> U128 { a + b } - #[aztec(public)] + #[public] fn modulo2(a: u64) -> u64 { a % 2 } - #[aztec(public)] + #[public] fn elliptic_curve_add_and_double() -> Point { let g = Point { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; @@ -150,7 +154,7 @@ contract AvmTest { added } - #[aztec(public)] + #[public] fn variable_base_msm() -> Point { let g = Point { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; let scalar = Scalar { lo: 3, hi: 0 }; @@ -159,7 +163,7 @@ contract AvmTest { triple_g } - #[aztec(public)] + #[public] fn pedersen_commit(x: Field, y: Field) -> EmbeddedCurvePoint { let commitment = dep::std::hash::pedersen_commitment_with_separator([x, y], 20); commitment @@ -169,20 +173,20 @@ contract AvmTest { * Misc ************************************************************************/ - #[aztec(public)] + #[public] fn u128_addition_overflow() -> U128 { let max_u128: U128 = U128::from_hex("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); let one: U128 = U128::from_integer(1); max_u128 + one } - #[aztec(public)] + #[public] fn u128_from_integer_overflow() -> U128 { let should_overflow: Field = 2.pow_32(128); // U128::max() + 1; U128::from_integer(should_overflow) } - #[aztec(public)] + #[public] fn to_radix_le(input: Field) -> [u8; 10] { input.to_le_radix(/*base=*/ 2) } @@ -196,12 +200,12 @@ contract AvmTest { inner_helper_with_failed_assertion(); } - #[aztec(public)] + #[public] fn assertion_failure() { helper_with_failed_assertion() } - #[aztec(public)] + #[public] fn debug_logging() { dep::aztec::oracle::debug_log::debug_log("just text"); dep::aztec::oracle::debug_log::debug_log_format("second: {1}", [1, 2, 3, 4]); @@ -209,20 +213,20 @@ contract AvmTest { dep::aztec::oracle::debug_log::debug_log("tabs and newlines\n\t- first\n\t- second"); } - #[aztec(public)] + #[public] fn assert_same(arg_a: Field, arg_b: Field) -> pub Field { assert(arg_a == arg_b, "Values are not equal"); 1 } - #[aztec(public)] + #[public] fn assert_calldata_copy(args: [Field; 3]) { let offset = 2; // Make this 0 when PublicContextInputs is removed. let cd: [Field; 3] = dep::aztec::context::public_context::calldata_copy(offset, 3); assert(cd == args, "Calldata copy failed"); } - #[aztec(public)] + #[public] fn return_oracle() -> [Field; 3] { dep::aztec::context::public_context::avm_return([1, 2, 3]); [4, 5, 6] // Should not get here. @@ -231,32 +235,32 @@ contract AvmTest { /************************************************************************ * Hashing functions ************************************************************************/ - #[aztec(public)] + #[public] fn keccak_hash(data: [u8; 10]) -> [u8; 32] { std::hash::keccak256(data, data.len() as u32) } - #[aztec(public)] + #[public] fn keccak_f1600(data: [u64; 25]) -> [u64; 25] { std::hash::keccak::keccakf1600(data) } - #[aztec(public)] + #[public] fn poseidon2_hash(data: [Field; 10]) -> Field { std::hash::poseidon2::Poseidon2::hash(data, data.len()) } - #[aztec(public)] + #[public] fn sha256_hash(data: [u8; 10]) -> [u8; 32] { std::hash::sha256(data) } - #[aztec(public)] + #[public] fn pedersen_hash(data: [Field; 10]) -> Field { std::hash::pedersen_hash(data) } - #[aztec(public)] + #[public] fn pedersen_hash_with_index(data: [Field; 10]) -> Field { std::hash::pedersen_hash_with_separator(data, /*index=*/ 20) } @@ -264,7 +268,7 @@ contract AvmTest { /************************************************************************ * Contract instance ************************************************************************/ - #[aztec(public)] + #[public] fn test_get_contract_instance_raw() { let fields = get_contract_instance_internal_avm(context.this_address()); // The values here should match those in `avm_simulator.test.ts>Contract>GETCONTRACTINSTANCE deserializes correctly` @@ -277,7 +281,7 @@ contract AvmTest { assert(fields[5] == 0x161718); } - #[aztec(public)] + #[public] fn test_get_contract_instance() { let ci = get_contract_instance_avm(context.this_address()); assert(ci.is_some(), "Contract instance not found!"); @@ -286,78 +290,78 @@ contract AvmTest { /************************************************************************ * AvmContext functions ************************************************************************/ - #[aztec(public)] + #[public] fn get_address() -> AztecAddress { context.this_address() } - #[aztec(public)] + #[public] fn get_storage_address() -> AztecAddress { context.storage_address() } - #[aztec(public)] + #[public] fn get_sender() -> AztecAddress { context.msg_sender() } - #[aztec(public)] + #[public] fn get_function_selector() -> FunctionSelector { context.selector() } - #[aztec(public)] + #[public] fn get_transaction_fee() -> Field { context.transaction_fee() } - #[aztec(public)] + #[public] fn get_chain_id() -> Field { context.chain_id() } - #[aztec(public)] + #[public] fn get_version() -> Field { context.version() } - #[aztec(public)] + #[public] fn get_block_number() -> Field { context.block_number() } - #[aztec(public)] + #[public] fn get_timestamp() -> u64 { context.timestamp() } - #[aztec(public)] + #[public] fn get_fee_per_l2_gas() -> Field { context.fee_per_l2_gas() } - #[aztec(public)] + #[public] fn get_fee_per_da_gas() -> Field { context.fee_per_da_gas() } - #[aztec(public)] + #[public] fn get_l2_gas_left() -> Field { context.l2_gas_left() } - #[aztec(public)] + #[public] fn get_da_gas_left() -> Field { context.da_gas_left() } - #[aztec(public)] + #[public] fn assert_timestamp(expected_timestamp: u64) { let timestamp = context.timestamp(); assert(timestamp == expected_timestamp, "timestamp does not match"); } - #[aztec(public)] + #[public] fn check_selector() { assert( context.selector() == comptime { @@ -366,12 +370,12 @@ contract AvmTest { ); } - #[aztec(public)] + #[public] fn get_args_hash(_a: u8, _fields: [Field; 3]) -> Field { context.get_args_hash() } - #[aztec(public)] + #[public] fn emit_unencrypted_log() { context.emit_unencrypted_log(/*message=*/ [10, 20, 30]); context.emit_unencrypted_log(/*message=*/ "Hello, world!"); @@ -379,36 +383,36 @@ contract AvmTest { context.emit_unencrypted_log(/*message=*/ s); } - #[aztec(public)] + #[public] fn note_hash_exists(note_hash: Field, leaf_index: Field) -> bool { context.note_hash_exists(note_hash, leaf_index) } // Use the standard context interface to emit a new note hash - #[aztec(public)] + #[public] fn new_note_hash(note_hash: Field) { context.push_note_hash(note_hash); } // Use the standard context interface to emit a new nullifier - #[aztec(public)] + #[public] fn new_nullifier(nullifier: Field) { context.push_nullifier(nullifier); } // Use the standard context interface to check for a nullifier - #[aztec(public)] + #[public] fn nullifier_exists(nullifier: Field) -> bool { context.nullifier_exists(nullifier, context.storage_address()) } - #[aztec(public)] + #[public] fn assert_nullifier_exists(nullifier: Field) { assert(context.nullifier_exists(nullifier, context.storage_address()), "Nullifier doesn't exist!"); } // Use the standard context interface to emit a new nullifier - #[aztec(public)] + #[public] fn emit_nullifier_and_check(nullifier: Field) { context.push_nullifier(nullifier); let exists = context.nullifier_exists(nullifier, context.storage_address()); @@ -416,19 +420,19 @@ contract AvmTest { } // Create the same nullifier twice (shouldn't work!) - #[aztec(public)] + #[public] fn nullifier_collision(nullifier: Field) { context.push_nullifier(nullifier); // Can't do this twice! context.push_nullifier(nullifier); } - #[aztec(public)] + #[public] fn l1_to_l2_msg_exists(msg_hash: Field, msg_leaf_index: Field) -> bool { context.l1_to_l2_msg_exists(msg_hash, msg_leaf_index) } - #[aztec(public)] + #[public] fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { context.message_portal(recipient, content) } @@ -436,7 +440,7 @@ contract AvmTest { /************************************************************************ * Nested calls ************************************************************************/ - #[aztec(public)] + #[public] fn nested_call_to_add_with_gas( arg_a: Field, arg_b: Field, @@ -447,30 +451,30 @@ contract AvmTest { } // Use the `call_public_function` wrapper to initiate a nested call to the add function - #[aztec(public)] + #[public] fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { AvmTest::at(context.this_address()).add_args_return(arg_a, arg_b).call(&mut context) } // Indirectly call_static the external call opcode to initiate a nested call to the add function - #[aztec(public)] + #[public] fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { AvmTest::at(context.this_address()).add_args_return(arg_a, arg_b).view(&mut context) } // Indirectly call_static `set_storage_single`. Should revert since it's accessing storage. - #[aztec(public)] + #[public] fn nested_static_call_to_set_storage() { AvmTest::at(context.this_address()).set_storage_single(20).view(&mut context); } - #[aztec(public)] + #[public] fn create_same_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { context.push_nullifier(nullifier); AvmTest::at(nestedAddress).new_nullifier(nullifier).call(&mut context); } - #[aztec(public)] + #[public] fn create_different_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { context.push_nullifier(nullifier); AvmTest::at(nestedAddress).new_nullifier(nullifier + 1).call(&mut context); @@ -479,7 +483,7 @@ contract AvmTest { /** * Enqueue a public call from private */ - #[aztec(private)] + #[private] fn enqueue_public_from_private() { AvmTest::at(context.this_address()).set_opcode_u8().enqueue_view(&mut context); AvmTest::at(context.this_address()).set_read_storage_single(5).enqueue(&mut context); @@ -491,7 +495,7 @@ contract AvmTest { * calls - but not blackboxes!), since otherwise the whole call will * be optimized away. ************************************************************************/ - #[aztec(public)] + #[public] fn bulk_testing(args_field: [Field; 10], args_u8: [u8; 10]) { // Using "inputs" here is a bit of a hack. It's the PublicContextInputs injected // by the public macros. It's needed by the non-nested call to an entry point function. diff --git a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr index 5f7690615da..2116a55fa3a 100644 --- a/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/benchmarking_contract/src/main.nr @@ -3,20 +3,23 @@ // would alter the metrics we're capturing in the benchmarks, and we want to keep the // subject being tested as unmodified as possible so we can detect metric changes that +use dep::aztec::macros::aztec; + +#[aztec] contract Benchmarking { - use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, Map, PublicMutable, PrivateSet}; - use dep::value_note::{utils::{increment, decrement}, value_note::ValueNote}; + use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, Map, PublicMutable, PrivateSet}; + use dep::value_note::{utils::increment, value_note::ValueNote}; - use dep::aztec::context::gas::GasOpts; + use dep::aztec::macros::{storage::storage, functions::{private, public}}; - #[aztec(storage)] - struct Storage { - notes: Map>, - balances: Map>, + #[storage] + struct Storage { + notes: Map, Context>, + balances: Map, Context>, } // Creates a new value note for the target owner. Use this method to seed an initial set of notes. - #[aztec(private)] + #[private] fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { // docs:start:increment_valuenote increment(storage.notes.at(owner), value, owner, outgoing_viewer); @@ -27,7 +30,7 @@ contract Benchmarking { // multiple txs that will land on the same block. // See https://discourse.aztec.network/t/utxo-concurrency-issues-for-private-state/635 // by @rahul-kothari for a full explanation on why this is needed. - #[aztec(private)] + #[private] fn recreate_note(owner: AztecAddress, outgoing_viewer: AztecAddress, index: u32) { let owner_notes = storage.notes.at(owner); let mut getter_options = NoteGetterOptions::new(); @@ -37,7 +40,7 @@ contract Benchmarking { } // Reads and writes to public storage and enqueues a call to another public function. - #[aztec(public)] + #[public] fn increment_balance(owner: AztecAddress, value: Field) { let current = storage.balances.at(owner).read(); storage.balances.at(owner).write(current + value); @@ -45,7 +48,7 @@ contract Benchmarking { } // Emits a public log. - #[aztec(public)] + #[public] fn broadcast(owner: AztecAddress) { context.emit_unencrypted_log(storage.balances.at(owner).read()); } diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/game.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/game.nr index 82c3a1cfa61..4eb8d5879de 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/game.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/game.nr @@ -2,7 +2,7 @@ use dep::aztec::protocol_types::{address::AztecAddress, traits::{Serialize, Dese use crate::cards::Card; global NUMBER_OF_PLAYERS: u32 = 2; -global NUMBER_OF_CARDS_DECK: u64 = 2; +global NUMBER_OF_CARDS_DECK: u32 = 2; struct PlayerEntry { address: AztecAddress, @@ -44,8 +44,8 @@ struct Game { started: bool, finished: bool, claimed: bool, - current_player: u64, - current_round: u64, + current_player: u32, + current_round: u32, } global GAME_SERIALIZED_LEN: u32 = 15; @@ -88,8 +88,8 @@ impl Deserialize for Game { started: fields[10] as bool, finished: fields[11] as bool, claimed: fields[12] as bool, - current_player: fields[13] as u64, - current_round: fields[14] as u64 + current_player: fields[13] as u32, + current_round: fields[14] as u32 } } } @@ -144,10 +144,10 @@ impl Game { assert(self.started, "Game not started"); assert(!self.finished, "Game finished"); - let round_offset = self.current_round * (NUMBER_OF_PLAYERS as u64); + let round_offset = self.current_round * NUMBER_OF_PLAYERS; self.rounds_cards[round_offset + self.current_player] = card; - self.current_player = (self.current_player + 1) % (NUMBER_OF_PLAYERS as u64); + self.current_player = (self.current_player + 1) % NUMBER_OF_PLAYERS; if self.current_player == 0 { self._finish_round(); diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr index df8318028a1..62367267c5d 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/main.nr @@ -1,25 +1,29 @@ mod cards; mod game; +use dep::aztec::macros::aztec; + +#[aztec] contract CardGame { - use dep::aztec::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}; + use dep::aztec::protocol_types::address::AztecAddress; use dep::aztec::{hash::pedersen_hash, state_vars::{Map, PublicMutable}}; - use dep::value_note::{balance_utils, value_note::{ValueNote, VALUE_NOTE_LEN}}; + use dep::aztec::note::constants::MAX_NOTES_PER_PAGE; - use dep::aztec::note::{note_header::NoteHeader, constants::MAX_NOTES_PER_PAGE}; + use crate::cards::{Deck, Card, get_pack_cards, compute_deck_strength}; + use crate::game::{PLAYABLE_CARDS, PlayerEntry, Game}; + use dep::aztec::macros::{storage::storage, functions::{private, public, internal}}; - use crate::cards::{PACK_CARDS, Deck, Card, get_pack_cards, compute_deck_strength}; - use crate::game::{NUMBER_OF_PLAYERS, NUMBER_OF_CARDS_DECK, PLAYABLE_CARDS, PlayerEntry, Game, GAME_SERIALIZED_LEN}; + use value_note::value_note::ValueNote; - #[aztec(storage)] - struct Storage { - collections: Map, - game_decks: Map>, - games: Map>, + #[storage] + struct Storage { + collections: Map, Context>, + game_decks: Map, Context>, Context>, + games: Map, Context>, } - #[aztec(private)] + #[private] fn buy_pack(seed: Field // The randomness used to generate the cards. Passed in for now. ) { let buyer = context.msg_sender(); @@ -29,7 +33,7 @@ contract CardGame { let _inserted_cards = collection.add_cards(cards, buyer); } - #[aztec(private)] + #[private] fn join_game(game: u32, cards_fields: [Field; 2]) { let cards = cards_fields.map(|card_field| Card::from_field(card_field)); @@ -43,8 +47,8 @@ contract CardGame { CardGame::at(context.this_address()).on_game_joined(game, player, strength as u32).enqueue(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn on_game_joined(game: u32, player: AztecAddress, deck_strength: u32) { let game_storage = storage.games.at(game as Field); @@ -54,7 +58,7 @@ contract CardGame { game_storage.write(game_data); } - #[aztec(public)] + #[public] fn start_game(game: u32) { let game_storage = storage.games.at(game as Field); @@ -63,7 +67,7 @@ contract CardGame { game_storage.write(game_data); } - #[aztec(private)] + #[private] fn play_card(game: u32, card: Card) { let player = context.msg_sender(); @@ -75,8 +79,8 @@ contract CardGame { // docs:end:call_public_function } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn on_card_played(game: u32, player: AztecAddress, card_as_field: Field) { let game_storage = storage.games.at(game as Field); @@ -90,7 +94,7 @@ contract CardGame { game_storage.write(game_data); } - #[aztec(private)] + #[private] fn claim_cards(game: u32, cards_fields: [Field; PLAYABLE_CARDS]) { let player = context.msg_sender(); let cards = cards_fields.map(|card_field| Card::from_field(card_field)); @@ -100,8 +104,8 @@ contract CardGame { CardGame::at(context.this_address()).on_cards_claimed(game, player, pedersen_hash(cards_fields, 0)).enqueue(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn on_cards_claimed(game: u32, player: AztecAddress, cards_hash: Field) { let game_storage = storage.games.at(game as Field); let mut game_data = game_storage.read(); diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index 46f23ea20f3..a6fb7ffbf11 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -1,36 +1,39 @@ // A contract used along with `Parent` contract to test nested calls. +use dep::aztec::macros::aztec; + +#[aztec] contract Child { - use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize, Map}; + use dep::aztec::prelude::{AztecAddress, PublicMutable, PrivateSet, Map}; use dep::aztec::{ - context::gas::GasOpts, protocol_types::{abis::call_context::CallContext}, - note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, + note::{note_getter_options::NoteGetterOptions}, encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, - keys::getters::get_public_keys, utils::comparison::Comparator + keys::getters::get_public_keys, utils::comparison::Comparator, + macros::{storage::storage, functions::{private, public, internal}} }; use dep::value_note::value_note::ValueNote; - #[aztec(storage)] - struct Storage { - current_value: PublicMutable, - a_map_with_private_values: Map>, + #[storage] + struct Storage { + current_value: PublicMutable, + a_map_with_private_values: Map, Context>, } // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. - #[aztec(private)] + #[private] fn value(input: Field) -> Field { input + context.chain_id() + context.version() } // Returns a sum of the input and the chain id and version of the contract in private circuit public input's return_values. // Can only be called from this contract. - #[aztec(private)] - #[aztec(internal)] + #[private] + #[internal] fn value_internal(input: Field) -> Field { input + context.chain_id() + context.version() } // Returns base_value + chain_id + version + block_number + timestamp - #[aztec(public)] + #[public] fn pub_get_value(base_value: Field) -> Field { let return_value = base_value + context.chain_id() @@ -42,7 +45,7 @@ contract Child { } // Sets `current_value` to `new_value` - #[aztec(public)] + #[public] fn pub_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); context.emit_unencrypted_log(new_value); @@ -50,7 +53,7 @@ contract Child { new_value } - #[aztec(private)] + #[private] fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { let owner_keys = get_public_keys(owner); @@ -59,7 +62,7 @@ contract Child { new_value } - #[aztec(private)] + #[private] fn private_get_value(amount: Field, owner: AztecAddress) -> Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); @@ -68,7 +71,7 @@ contract Child { } // Increments `current_value` by `new_value` - #[aztec(public)] + #[public] fn pub_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -78,8 +81,8 @@ contract Child { } // Increments `current_value` by `new_value`. Can only be called from this contract. - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn pub_inc_value_internal(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -88,21 +91,21 @@ contract Child { new_value } - #[aztec(public)] + #[public] fn set_value_twice_with_nested_first() { let _result = Child::at(context.this_address()).pub_set_value(10).call(&mut context); storage.current_value.write(20); context.emit_unencrypted_log(20); } - #[aztec(public)] + #[public] fn set_value_twice_with_nested_last() { storage.current_value.write(20); context.emit_unencrypted_log(20); let _result = Child::at(context.this_address()).pub_set_value(10).call(&mut context); } - #[aztec(public)] + #[public] fn set_value_with_two_nested_calls() { Child::at(context.this_address()).set_value_twice_with_nested_first().call(&mut context); Child::at(context.this_address()).set_value_twice_with_nested_last().call(&mut context); diff --git a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr index e39e00df441..93d42b7e9ee 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr @@ -1,28 +1,31 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract Claim { use dep::aztec::{ - note::utils::compute_note_hash_for_nullify, - protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}, - state_vars::SharedImmutable + note::utils::compute_note_hash_for_nullify, protocol_types::address::AztecAddress, + state_vars::SharedImmutable, + macros::{storage::storage, functions::{private, public, initializer}} }; use dep::value_note::value_note::ValueNote; - use dep::token::Token; + use token::Token; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // Address of a contract based on whose notes we distribute the rewards - target_contract: SharedImmutable, + target_contract: SharedImmutable, // Token to be distributed as a reward when claiming - reward_token: SharedImmutable, + reward_token: SharedImmutable, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(target_contract: AztecAddress, reward_token: AztecAddress) { storage.target_contract.initialize(target_contract); storage.reward_token.initialize(reward_token); } - #[aztec(private)] + #[private] fn claim(proof_note: ValueNote, recipient: AztecAddress) { // 1) Check that the note corresponds to the target contract and belongs to the sender let target_address = storage.target_contract.read_private(); diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr index 3cbea7c57ca..5705f9378d1 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/class_registered.nr @@ -4,7 +4,7 @@ use dep::aztec::protocol_types::{ traits::Serialize }; -// #[aztec(event)] +// #[event] struct ContractClassRegistered { contract_class_id: ContractClassId, version: Field, diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr index 3e23539c72f..11735b33316 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/private_function_broadcasted.nr @@ -13,8 +13,8 @@ use dep::aztec::protocol_types::{ struct InnerPrivateFunction { selector: FunctionSelector, - metadata_hash: Field, - vk_hash: Field, + metadata_hash: Field, + vk_hash: Field, } impl Serialize<3> for InnerPrivateFunction { @@ -25,8 +25,8 @@ impl Serialize<3> for InnerPrivateFunction { struct PrivateFunction { selector: FunctionSelector, - metadata_hash: Field, - vk_hash: Field, + metadata_hash: Field, + vk_hash: Field, bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS], } @@ -43,7 +43,7 @@ impl Serialize for } } -// #[aztec(event)] +// #[event] struct ClassPrivateFunctionBroadcasted { contract_class_id: ContractClassId, artifact_metadata_hash: Field, diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr index 382f94aac9f..28a6e791e85 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/events/unconstrained_function_broadcasted.nr @@ -12,7 +12,7 @@ use dep::aztec::protocol_types::{ struct InnerUnconstrainedFunction { selector: FunctionSelector, - metadata_hash: Field, + metadata_hash: Field, } impl Serialize<2> for InnerUnconstrainedFunction { @@ -23,7 +23,7 @@ impl Serialize<2> for InnerUnconstrainedFunction { struct UnconstrainedFunction { selector: FunctionSelector, - metadata_hash: Field, + metadata_hash: Field, bytecode: [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS], } @@ -39,7 +39,7 @@ impl Serialize, + #[storage] + struct Storage { + counters: Map, Context>, } // docs:end:storage_struct // docs:start:constructor - #[aztec(private)] - #[aztec(initializer)] + #[initializer] + #[private] // We can name our initializer anything we want as long as it's marked as aztec(initializer) fn initialize(headstart: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let counters = storage.counters; @@ -23,47 +27,45 @@ contract Counter { // docs:end:constructor // docs:start:increment - #[aztec(private)] + #[private] fn increment(owner: AztecAddress, outgoing_viewer: AztecAddress) { - dep::aztec::oracle::debug_log::debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); + unsafe { + dep::aztec::oracle::debug_log::debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); + } let counters = storage.counters; counters.at(owner).add(1, owner, outgoing_viewer); } // docs:end:increment - // docs:start:get_counter unconstrained fn get_counter(owner: AztecAddress) -> pub Field { let counters = storage.counters; balance_utils::get_balance(counters.at(owner).set) } - // docs:end:get_counter + // docs:end:get_counter // docs:start:test_imports use dep::aztec::test::helpers::test_environment::TestEnvironment; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; // docs:end:test_imports - // docs:start:txe_test_increment #[test] - fn test_increment() { + unconstrained fn test_increment() { // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.create_account(); let outgoing_viewer = env.create_account(); let initial_value: Field = 5; env.impersonate(owner); - // Deploy contract and initialize let initializer = Counter::interface().initialize(initial_value as u64, owner, outgoing_viewer); let counter_contract = env.deploy_self("Counter").with_private_initializer(initializer); let contract_address = counter_contract.to_address(); - // docs:start:txe_test_read_notes // Read the stored value in the note env.impersonate(contract_address); - let counter_slot = Counter::storage().counters.slot; + let counter_slot = Counter::storage_layout().counters.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); let notes: BoundedVec = view_notes(owner_slot, options); @@ -72,7 +74,6 @@ contract Counter { initial_note_value == initial_value, f"Expected {initial_value} but got {initial_note_value}" ); // docs:end:txe_test_read_notes - // Increment the counter let increment_call_interface = Counter::at(contract_address).increment(owner, outgoing_viewer); env.call_private_void(increment_call_interface); diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index 504c46216db..bd34f60917d 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -1,4 +1,7 @@ // docs:start:empty-contract +use dep::aztec::macros::aztec; + +#[aztec] contract Crowdfunding { // docs:end:empty-contract @@ -6,33 +9,36 @@ contract Crowdfunding { use dep::aztec::{ encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, keys::getters::get_public_keys, prelude::{AztecAddress, PrivateSet, SharedImmutable}, - utils::comparison::Comparator + utils::comparison::Comparator, unencrypted_logs::unencrypted_event_emission::encode_event, + macros::{storage::storage, events::event, functions::{public, initializer, private, internal}}, + protocol_types::traits::Serialize }; - use dep::aztec::unencrypted_logs::unencrypted_event_emission::encode_event; + use std::meta::derive; // docs:start:import_valuenote use dep::value_note::value_note::ValueNote; // docs:end:import_valuenote - use dep::token::Token; + use token::Token; use router::utils::privately_check_timestamp; // docs:end:all-deps - #[aztec(event)] + #[event] + #[derive(Serialize)] struct WithdrawalProcessed { - who: Field, - amount: Field, + who: AztecAddress, + amount: u64, } // docs:start:storage - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // Token used for donations (e.g. DAI) - donation_token: SharedImmutable, + donation_token: SharedImmutable, // Crowdfunding campaign operator - operator: SharedImmutable, + operator: SharedImmutable, // End of the crowdfunding campaign after which no more donations are accepted - deadline: SharedImmutable, + deadline: SharedImmutable, // Notes emitted to donors when they donate (can be used as proof to obtain rewards, eg in Claim contracts) - donation_receipts: PrivateSet, + donation_receipts: PrivateSet, } // docs:end:storage @@ -40,8 +46,8 @@ contract Crowdfunding { // docs:start:init // docs:start:init-header // docs:start:init-header-error - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] // this-will-error:init-header-error fn init(donation_token: AztecAddress, operator: AztecAddress, deadline: u64) { // docs:end:init-header @@ -53,7 +59,7 @@ contract Crowdfunding { // docs:end:init // docs:start:donate - #[aztec(private)] + #[private] fn donate(amount: u64) { // 1) Check that the deadline has not passed --> we do that via the router contract to conceal which contract // is performing the check. @@ -80,7 +86,7 @@ contract Crowdfunding { // docs:start:operator-withdrawals // Withdraws balance to the operator. Requires that msg_sender() is the operator. - #[aztec(private)] + #[private] fn withdraw(amount: u64) { // 1) Check that msg_sender() is the operator let operator_address = storage.operator.read_private(); @@ -93,9 +99,9 @@ contract Crowdfunding { } // docs:end:operator-withdrawals - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _publish_donation_receipts(amount: u64, to: AztecAddress) { - WithdrawalProcessed { amount: amount as Field, who: to.to_field() }.emit(encode_event(&mut context)); + WithdrawalProcessed { amount, who: to }.emit(encode_event(&mut context)); } } diff --git a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr index 11f03378e7c..2548052f7ec 100644 --- a/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegated_on_contract/src/main.nr @@ -1,22 +1,23 @@ // A contract used along with `Parent` contract to test nested calls. +use dep::aztec::macros::aztec; + +#[aztec] contract DelegatedOn { - use dep::aztec::prelude::{ - AztecAddress, FunctionSelector, NoteHeader, NoteGetterOptions, NoteViewerOptions, PublicMutable, - PrivateSet, PrivateContext, Map - }; + use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, PublicMutable, PrivateSet, Map}; use dep::aztec::{ encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, - keys::getters::get_public_keys, utils::comparison::Comparator + keys::getters::get_public_keys, utils::comparison::Comparator, + macros::{storage::storage, functions::{private, public}} }; use dep::value_note::value_note::ValueNote; - #[aztec(storage)] - struct Storage { - current_value: PublicMutable, - a_map_with_private_values: Map>, + #[storage] + struct Storage { + current_value: PublicMutable, + a_map_with_private_values: Map, Context>, } - #[aztec(private)] + #[private] fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); @@ -25,13 +26,13 @@ contract DelegatedOn { new_value } - #[aztec(public)] + #[public] fn public_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); new_value } - #[aztec(private)] + #[private] fn get_private_value(amount: Field, owner: AztecAddress) -> pub Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); @@ -39,7 +40,7 @@ contract DelegatedOn { notes.get(0).value } - unconstrained fn view_public_value() -> pub Field { + unconstrained fn view_public_value() -> Field { storage.current_value.read() } } diff --git a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr index 402230a2fb8..80e095d0e43 100644 --- a/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/delegator_contract/src/main.nr @@ -1,17 +1,19 @@ // A contract used along with `Parent` contract to test nested calls. +use dep::aztec::macros::aztec; + +#[aztec] contract Delegator { - use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, PublicMutable, PrivateSet, Map}; + use dep::aztec::prelude::{AztecAddress, PublicMutable, PrivateSet, Map, NoteGetterOptions}; use dep::value_note::value_note::ValueNote; use dep::delegated_on::DelegatedOn; - use dep::aztec::utils::comparison::Comparator; + use dep::aztec::{utils::comparison::Comparator, macros::{storage::storage, functions::{private, public}}}; - #[aztec(storage)] - struct Storage { - current_value: PublicMutable, - a_map_with_private_values: Map>, + #[storage] + struct Storage { + current_value: PublicMutable, + a_map_with_private_values: Map, Context>, } - - #[aztec(private)] + #[private] fn private_delegate_set_value( target_contract: AztecAddress, value: Field, @@ -20,26 +22,22 @@ contract Delegator { // Call the target private function DelegatedOn::at(target_contract).private_set_value(value, owner).delegate_call(&mut context) } - - #[aztec(private)] + #[private] fn enqueued_delegate_set_value(target_contract: AztecAddress, value: Field) { DelegatedOn::at(target_contract).public_set_value(value).delegate_enqueue(&mut context) } - - #[aztec(public)] + #[public] fn public_delegate_set_value(target_contract: AztecAddress, value: Field) -> Field { DelegatedOn::at(target_contract).public_set_value(value).delegate_call(&mut context) } - - #[aztec(private)] - fn get_private_value(amount: Field, owner: AztecAddress) -> pub Field { + #[private] + fn get_private_value(amount: Field, owner: AztecAddress) -> Field { let mut options = NoteGetterOptions::new(); options = options.select(ValueNote::properties().value, Comparator.EQ, amount).set_limit(1); let notes = storage.a_map_with_private_values.at(owner).get_notes(options); notes.get_unchecked(0).value } - - unconstrained fn view_public_value() -> pub Field { + unconstrained fn view_public_value() -> Field { storage.current_value.read() } } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index ad002512902..b5a6465d2c3 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -11,42 +11,49 @@ mod types; // also has `options.nr` which shows various ways of using `NoteGetterOptions` to query notes // it also shows what our macros do behind the scenes! +use dep::aztec::macros::aztec; + +#[aztec] contract DocsExample { // how to import dependencies defined in your workspace use dep::aztec::prelude::{ AztecAddress, NoteViewerOptions, PrivateContext, Map, PublicMutable, PublicImmutable, PrivateMutable, PrivateImmutable, PrivateSet, SharedImmutable }; - use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note, encode_and_encrypt_note_with_keys}; - use dep::aztec::keys::getters::get_public_keys; + use dep::aztec::{ + encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note, encode_and_encrypt_note_with_keys}, + keys::getters::get_public_keys, + macros::{storage::storage, functions::{public, private, internal, view}} + }; + // how to import methods from other files/folders within your workspace use crate::types::{card_note::CardNote, leader::Leader}; - #[aztec(storage)] - struct Storage { + #[storage_no_init] + struct Storage { // Shows how to create a custom struct in PublicMutable // docs:start:storage-leader-declaration - leader: PublicMutable, + leader: PublicMutable, // docs:end:storage-leader-declaration // docs:start:storage-private-mutable-declaration - legendary_card: PrivateMutable, + legendary_card: PrivateMutable, // docs:end:storage-private-mutable-declaration // just used for docs example to show how to create a private mutable map. - profiles: Map>, + profiles: Map, Context>, // docs:start:storage-set-declaration - set: PrivateSet, + set: PrivateSet, // docs:end:storage-set-declaration // docs:start:storage-private-immutable-declaration - private_immutable: PrivateImmutable, + private_immutable: PrivateImmutable, // docs:end:storage-private-immutable-declaration // docs:start:storage-shared-immutable-declaration - shared_immutable: SharedImmutable, + shared_immutable: SharedImmutable, // docs:end:storage-shared-immutable-declaration // docs:start:storage-minters-declaration - minters: Map>, + minters: Map, Context>, // docs:end:storage-minters-declaration // docs:start:storage-public-immutable-declaration - public_immutable: PublicImmutable, + public_immutable: PublicImmutable, // docs:end:storage-public-immutable-declaration } @@ -91,13 +98,13 @@ contract DocsExample { } } - #[aztec(public)] + #[public] fn initialize_shared_immutable(points: u8) { let mut new_leader = Leader { account: context.msg_sender(), points }; storage.shared_immutable.initialize(new_leader); } - #[aztec(private)] + #[private] fn match_shared_immutable(account: AztecAddress, points: u8) { let expected = Leader { account, points }; let read = storage.shared_immutable.read_private(); @@ -106,8 +113,8 @@ contract DocsExample { assert(read.points == expected.points, "Invalid points"); } - #[aztec(private)] - fn get_shared_immutable_constrained_private_indirect() -> pub Leader { + #[private] + fn get_shared_immutable_constrained_private_indirect() -> Leader { // This is a private function that calls another private function // and returns the response. // Used to test that we can retrieve values through calls and @@ -117,8 +124,8 @@ contract DocsExample { leader } - #[aztec(public)] - fn get_shared_immutable_constrained_public_indirect() -> pub Leader { + #[public] + fn get_shared_immutable_constrained_public_indirect() -> Leader { // This is a public function that calls another public function // and returns the response. // Used to test that we can retrieve values through calls and @@ -128,29 +135,29 @@ contract DocsExample { leader } - #[aztec(public)] - #[aztec(view)] - fn get_shared_immutable_constrained_public() -> pub Leader { + #[public] + #[view] + fn get_shared_immutable_constrained_public() -> Leader { storage.shared_immutable.read_public() } - #[aztec(public)] - fn get_shared_immutable_constrained_public_multiple() -> pub [Leader; 5] { + #[public] + fn get_shared_immutable_constrained_public_multiple() -> [Leader; 5] { let a = storage.shared_immutable.read_public(); [a, a, a, a, a] } - #[aztec(private)] - #[aztec(view)] - fn get_shared_immutable_constrained_private() -> pub Leader { + #[private] + #[view] + fn get_shared_immutable_constrained_private() -> Leader { storage.shared_immutable.read_private() } - unconstrained fn get_shared_immutable() -> pub Leader { + unconstrained fn get_shared_immutable() -> Leader { storage.shared_immutable.read_public() } - #[aztec(public)] + #[public] fn initialize_public_immutable(points: u8) { // docs:start:initialize_public_immutable let mut new_leader = Leader { account: context.msg_sender(), points }; @@ -158,14 +165,14 @@ contract DocsExample { // docs:end:initialize_public_immutable } - unconstrained fn get_public_immutable() -> pub Leader { + unconstrained fn get_public_immutable() -> Leader { // docs:start:read_public_immutable storage.public_immutable.read() // docs:end:read_public_immutable } // docs:start:initialize-private-mutable - #[aztec(private)] + #[private] fn initialize_private_immutable(randomness: Field, points: u8) { let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); @@ -174,7 +181,7 @@ contract DocsExample { } // docs:end:initialize-private-mutable - #[aztec(private)] + #[private] // msg_sender() is 0 at deploy time. So created another function fn initialize_private(randomness: Field, points: u8) { let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); @@ -184,7 +191,7 @@ contract DocsExample { storage.legendary_card.initialize(&mut legendary_card).emit(encode_and_encrypt_note(&mut context, context.msg_sender(), context.msg_sender())); } - #[aztec(private)] + #[private] fn insert_notes(amounts: [u8; 3]) { let sender_keys = get_public_keys(context.msg_sender()); let sender_npk_m_hash = sender_keys.npk_m.hash(); @@ -201,23 +208,20 @@ contract DocsExample { ); } } - - #[aztec(private)] + #[private] fn insert_note(amount: u8, randomness: Field) { let sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); let mut note = CardNote::new(amount, randomness, sender_npk_m_hash); storage.set.insert(&mut note).emit(encode_and_encrypt_note(&mut context, context.msg_sender(), context.msg_sender())); } - // docs:start:state_vars-NoteGetterOptionsComparatorExampleNoir - unconstrained fn read_note(comparator: u8, amount: Field) -> pub BoundedVec { + unconstrained fn read_note(comparator: u8, amount: Field) -> BoundedVec { let mut options = NoteViewerOptions::new(); storage.set.view_notes(options.select(CardNote::properties().points, comparator, amount)) } // docs:end:state_vars-NoteGetterOptionsComparatorExampleNoir - - #[aztec(private)] + #[private] fn update_legendary_card(randomness: Field, points: u8) { let sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); @@ -225,8 +229,7 @@ contract DocsExample { storage.legendary_card.replace(&mut new_card).emit(encode_and_encrypt_note(&mut context, context.msg_sender(), context.msg_sender())); DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue(&mut context); } - - #[aztec(private)] + #[private] fn increase_legendary_points() { // Ensure `points` > current value // Also serves as a e2e test that you can `get_note()` and then `replace()` @@ -235,77 +238,61 @@ contract DocsExample { // docs:start:state_vars-PrivateMutableGet let card = storage.legendary_card.get_note().note; - // docs:end:state_vars-PrivateMutableGet - let points = card.points + 1; - let mut new_card = CardNote::new(points, card.randomness, sender_npk_m_hash); // docs:start:state_vars-PrivateMutableReplace storage.legendary_card.replace(&mut new_card).emit(encode_and_encrypt_note(&mut context, context.msg_sender(), context.msg_sender())); // docs:end:state_vars-PrivateMutableReplace - DocsExample::at(context.this_address()).update_leader(context.msg_sender(), points).enqueue(&mut context); } - - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { 1 } - - #[aztec(public)] + #[public] fn spend_public_authwit(inner_hash: Field) -> Field { 1 } - - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn update_leader(account: AztecAddress, points: u8) { let new_leader = Leader { account, points }; storage.leader.write(new_leader); } - - unconstrained fn get_leader() -> pub Leader { + unconstrained fn get_leader() -> Leader { storage.leader.read() } - - unconstrained fn get_legendary_card() -> pub CardNote { + unconstrained fn get_legendary_card() -> CardNote { storage.legendary_card.view_note() } - // docs:start:private_mutable_is_initialized - unconstrained fn is_legendary_initialized() -> pub bool { + unconstrained fn is_legendary_initialized() -> bool { storage.legendary_card.is_initialized() } // docs:end:private_mutable_is_initialized - // docs:start:get_note-private-immutable - #[aztec(private)] + #[private] fn get_imm_card() -> CardNote { storage.private_immutable.get_note() } // docs:end:get_note-private-immutable - - unconstrained fn view_imm_card() -> pub CardNote { + unconstrained fn view_imm_card() -> CardNote { storage.private_immutable.view_note() } - - unconstrained fn is_priv_imm_initialized() -> pub bool { + unconstrained fn is_priv_imm_initialized() -> bool { storage.private_immutable.is_initialized() } - /// Macro equivalence section use dep::aztec::protocol_types::abis::private_circuit_public_inputs::PrivateCircuitPublicInputs; use dep::aztec::context::inputs::PrivateContextInputs; - // docs:start:simple_macro_example - #[aztec(private)] + #[private] fn simple_macro_example(a: Field, b: Field) -> Field { a + b } // docs:end:simple_macro_example - // docs:start:simple_macro_example_expanded fn simple_macro_example_expanded( // ************************************************************ @@ -314,7 +301,6 @@ contract DocsExample { inputs: PrivateContextInputs, // docs:end:context-example-inputs // ************************************************************ - // Our original inputs! a: Field, b: Field // The actual return type of our circuit is the PrivateCircuitPublicInputs struct, this will be the @@ -329,20 +315,16 @@ contract DocsExample { args_hasher.add(a); args_hasher.add(b); // docs:end:context-example-hasher - // The context object is created with the inputs and the hash of the inputs // docs:start:context-example-context let mut context = PrivateContext::new(inputs, args_hasher.hash()); // docs:end:context-example-context - // docs:start:storage-example-context let mut storage = Storage::init(&mut context); // docs:end:storage-example-context // ************************************************************ - // Our actual program let result = a + b; - // ************************************************************ // Return values are pushed into the context // docs:start:context-example-context-return @@ -350,7 +332,6 @@ contract DocsExample { return_hasher.add(result); context.set_return_hash(return_hasher); // docs:end:context-example-context-return - // The context is returned to be consumed by the kernel circuit! // docs:start:context-example-finish context.finish() diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr index b3a56d2cbdf..b0216a98b51 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/options.nr @@ -1,4 +1,4 @@ -use crate::types::card_note::{CardNote, CARD_NOTE_LEN, CARD_NOTE_BYTES_LEN}; +use crate::types::card_note::{CARD_NOTE_LEN, CardNote}; use dep::aztec::prelude::NoteGetterOptions; use dep::aztec::protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL; @@ -10,7 +10,7 @@ use dep::aztec::{note::note_getter_options::SortOrder, utils::comparison::Compar pub fn create_npk_card_getter_options( account_npk_m_hash: Field, offset: u32 -) -> NoteGetterOptions { +) -> NoteGetterOptions { let mut options = NoteGetterOptions::new(); options.select( CardNote::properties().npk_m_hash, @@ -25,7 +25,7 @@ pub fn create_exact_card_getter_options( points: u8, secret: Field, account_npk_m_hash: Field -) -> NoteGetterOptions { +) -> NoteGetterOptions { let mut options = NoteGetterOptions::new(); options.select(CardNote::properties().points, Comparator.EQ, points as Field).select(CardNote::properties().randomness, Comparator.EQ, secret).select( CardNote::properties().npk_m_hash, @@ -53,13 +53,13 @@ pub fn filter_min_points( // docs:end:state_vars-OptionFilter // docs:start:state_vars-NoteGetterOptionsFilter -pub fn create_cards_with_min_points_getter_options(min_points: u8) -> NoteGetterOptions { +pub fn create_cards_with_min_points_getter_options(min_points: u8) -> NoteGetterOptions { NoteGetterOptions::with_filter(filter_min_points, min_points).sort(CardNote::properties().points, SortOrder.ASC) } // docs:end:state_vars-NoteGetterOptionsFilter // docs:start:state_vars-NoteGetterOptionsPickOne -pub fn create_largest_card_getter_options() -> NoteGetterOptions { +pub fn create_largest_card_getter_options() -> NoteGetterOptions { let mut options = NoteGetterOptions::new(); options.sort(CardNote::properties().points, SortOrder.DESC).set_limit(1) } diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr index d177384f869..8c4c3a2c39c 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/types/card_note.nr @@ -1,15 +1,14 @@ -use dep::aztec::prelude::{NoteInterface, NoteHeader, PrivateContext}; +use dep::aztec::prelude::{NullifiableNote, PrivateContext, NoteHeader}; use dep::aztec::{ note::{utils::compute_note_hash_for_nullify}, keys::getters::get_nsk_app, - protocol_types::{traits::Serialize, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} + protocol_types::{traits::Serialize, constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note }; -global CARD_NOTE_LEN: u32 = 3; -// CARD_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global CARD_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - // docs:start:state_vars-CardNote -#[aztec(note)] +global CARD_NOTE_LEN: u32 = 3; // 3 plus a header. + +#[note] struct CardNote { points: u8, randomness: Field, @@ -27,7 +26,7 @@ impl CardNote { // docs:end:cardnote_impl // docs:start:note_interface -impl NoteInterface for CardNote { +impl NullifiableNote for CardNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); poseidon2_hash_with_separator( diff --git a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr index 72959179f75..8e118629ed8 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_token_contract/src/main.nr @@ -1,19 +1,23 @@ // docs:start:easy_private_token_contract +use dep::aztec::macros::aztec; + +#[aztec] contract EasyPrivateToken { use dep::aztec::prelude::{AztecAddress, Map}; use dep::value_note::balance_utils; use dep::easy_private_state::EasyPrivateUint; + use dep::aztec::macros::{storage::storage, functions::{initializer, private}}; - #[aztec(storage)] - struct Storage { - balances: Map, + #[storage] + struct Storage { + balances: Map, Context>, } /** * initialize the contract's initial state variables. */ - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(initial_supply: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; @@ -21,7 +25,7 @@ contract EasyPrivateToken { } // Mints `amount` of tokens to `owner`. - #[aztec(private)] + #[private] fn mint(amount: u64, owner: AztecAddress, outgoing_viewer: AztecAddress) { let balances = storage.balances; @@ -29,7 +33,7 @@ contract EasyPrivateToken { } // Transfers `amount` of tokens from `sender` to a `recipient`. - #[aztec(private)] + #[private] fn transfer( amount: u64, sender: AztecAddress, diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index 3bc0c0b687b..55539d9e4b5 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -1,21 +1,27 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract EasyPrivateVoting { // docs:start:imports use dep::aztec::prelude::{AztecAddress, Map, PublicMutable, SharedImmutable}; - use dep::aztec::keys::getters::get_public_keys; + use dep::aztec::{ + keys::getters::get_public_keys, + macros::{storage::storage, functions::{public, initializer, private, internal}} + }; // docs:end:imports // docs:start:storage_struct - #[aztec(storage)] - struct Storage { - admin: PublicMutable, // admin can end vote - tally: Map>, // we will store candidate as key and number of votes as value - vote_ended: PublicMutable, // vote_ended is boolean - active_at_block: SharedImmutable, // when people can start voting + #[storage] + struct Storage { + admin: PublicMutable, // admin can end vote + tally: Map, Context>, // we will store candidate as key and number of votes as value + vote_ended: PublicMutable, // vote_ended is boolean + active_at_block: SharedImmutable, // when people can start voting } // docs:end:storage_struct // docs:start:constructor - #[aztec(public)] - #[aztec(initializer)] // annotation to mark function as a constructor + #[public] + #[initializer] // annotation to mark function as a constructor fn constructor(admin: AztecAddress) { storage.admin.write(admin); storage.vote_ended.write(false); @@ -24,7 +30,7 @@ contract EasyPrivateVoting { // docs:end:constructor // docs:start:cast_vote - #[aztec(private)] // annotation to mark function as private and expose private context + #[private] // annotation to mark function as private and expose private context fn cast_vote(candidate: Field) { let msg_sender_npk_m_hash = get_public_keys(context.msg_sender()).npk_m.hash(); @@ -36,8 +42,8 @@ contract EasyPrivateVoting { // docs:end:cast_vote // docs:start:add_to_tally_public - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn add_to_tally_public(candidate: Field) { assert(storage.vote_ended.read() == false, "Vote has ended"); // assert that vote has not ended let new_tally = storage.tally.at(candidate).read() + 1; @@ -46,7 +52,7 @@ contract EasyPrivateVoting { // docs:end:add_to_tally_public // docs:start:end_vote - #[aztec(public)] + #[public] fn end_vote() { assert(storage.admin.read().eq(context.msg_sender()), "Only admin can end votes"); // assert that caller is admin storage.vote_ended.write(true); diff --git a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr index 3ee2ebfd294..5bdce740498 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_k_account_contract/src/main.nr @@ -1,10 +1,14 @@ // Account contract that uses ECDSA signatures for authentication on the same curve as Ethereum. // The signing key is stored in an immutable private note and should be different from the signing key. +use dep::aztec::macros::aztec; + +#[aztec] contract EcdsaKAccount { use dep::aztec::prelude::{PrivateContext, PrivateImmutable}; use dep::aztec::{ encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, - keys::getters::get_public_keys + keys::getters::get_public_keys, + macros::{storage::storage, functions::{private, initializer, view, noinitcheck}} }; use dep::authwit::{ @@ -14,14 +18,14 @@ contract EcdsaKAccount { use dep::ecdsa_public_key_note::EcdsaPublicKeyNote; - #[aztec(storage)] - struct Storage { - public_key: PrivateImmutable, + #[storage] + struct Storage { + public_key: PrivateImmutable, } // Creates a new account out of an ECDSA public key to use for signature verification - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(signing_pub_key_x: [u8; 32], signing_pub_key_y: [u8; 32]) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -34,15 +38,15 @@ contract EcdsaKAccount { } // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts - #[aztec(private)] + #[private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } - #[aztec(private)] - #[aztec(noinitcheck)] - #[aztec(view)] + #[private] + #[noinitcheck] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr b/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr index 1847b6cd5b4..cec2dab9a5e 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_public_key_note/src/lib.nr @@ -1,17 +1,19 @@ -use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteHeader, NoteInterface, NoteGetterOptions, PrivateContext}; +use dep::aztec::prelude::{NoteHeader, NoteInterface, NullifiableNote, PrivateContext}; use dep::aztec::{ note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, - protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note_custom_interface, generators::Ga1 as Gx_1, generators::Ga2 as Gx_2, + generators::Ga3 as Gy_1, generators::Ga4 as Gy_2, generators::Ga5 as Gnpk_m_hash, generators::G_slot }; +use std::hash::from_field_unsafe; + global ECDSA_PUBLIC_KEY_NOTE_LEN: u32 = 5; -// ECDSA_PUBLIC_KEY_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global ECDSA_PUBLIC_KEY_NOTE_BYTES_LEN: u32 = 5 * 32 + 64; // Stores an ECDSA public key composed of two 32-byte elements // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? -#[aztec(note)] +#[note_custom_interface] struct EcdsaPublicKeyNote { x: [u8; 32], y: [u8; 32], @@ -19,7 +21,7 @@ struct EcdsaPublicKeyNote { npk_m_hash: Field, } -impl NoteInterface for EcdsaPublicKeyNote { +impl NoteInterface for EcdsaPublicKeyNote { // Cannot use the automatic serialization since x and y don't fit. Serialize the note as 5 fields where: // [0] = x[0..31] (upper bound excluded) // [1] = x[31] @@ -32,11 +34,11 @@ impl NoteInterface f let mut mul: Field = 1; for i in 1..32 { - let byte_x: Field = self.x[31 - i] as Field; - x = x + (byte_x * mul); - let byte_y: Field = self.y[31 - i] as Field; - y = y + (byte_y * mul); - mul *= 256; + let byte_x: Field = self.x[31 - i] as Field; + x = x + (byte_x * mul); + let byte_y: Field = self.y[31 - i] as Field; + y = y + (byte_y * mul); + mul *= 256; } let last_x = self.x[31] as Field; @@ -65,24 +67,84 @@ impl NoteInterface f EcdsaPublicKeyNote { x, y, npk_m_hash: serialized_note[4], header: NoteHeader::empty() } } + fn to_be_bytes(self, storage_slot: Field) -> [u8; ECDSA_PUBLIC_KEY_NOTE_LEN * 32 + 64] { + let serialized_note = self.serialize_content(); + + let mut buffer: [u8; ECDSA_PUBLIC_KEY_NOTE_LEN * 32 + 64] = [0; ECDSA_PUBLIC_KEY_NOTE_LEN * 32 + 64]; + + let storage_slot_bytes: [u8; 32] = storage_slot.to_be_bytes(); + let note_type_id_bytes: [u8; 32] = EcdsaPublicKeyNote::get_note_type_id().to_be_bytes(); + + for i in 0..32 { + buffer[i] = storage_slot_bytes[i]; + buffer[32 + i] = note_type_id_bytes[i]; + } + + for i in 0..serialized_note.len() { + let bytes: [u8; 32] = serialized_note[i].to_be_bytes(); + for j in 0..32 { + buffer[64 + i * 32 + j] = bytes[j]; + } + } + buffer + } + + fn get_note_type_id() -> Field { + comptime + { + let bytes = "EcdsaPublicKeyNote".as_bytes(); + let hash = aztec::protocol_types::hash::poseidon2_hash_bytes(bytes); + let hash_bytes = hash.to_be_bytes::<4>(); + aztec::protocol_types::utils::field::field_from_bytes(hash_bytes, true) + } + } + + fn get_header(self) -> NoteHeader { + self.header + } + + fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + fn compute_note_hash(self) -> Field { + let serialized = self.serialize_content(); + std::embedded_curve_ops::multi_scalar_mul( + [Gx_1, Gx_2, Gy_1, Gy_2, Gnpk_m_hash, G_slot], + [ + from_field_unsafe(serialized[0]), + from_field_unsafe(serialized[1]), + from_field_unsafe(serialized[2]), + from_field_unsafe(serialized[3]), + from_field_unsafe(serialized[4]), + from_field_unsafe(self.get_header().storage_slot) + ] + ).x + } +} + +impl NullifiableNote for EcdsaPublicKeyNote { + fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); - poseidon2_hash_with_separator([ + poseidon2_hash_with_separator( + [ note_hash_for_nullify, secret ], - GENERATOR_INDEX__NOTE_NULLIFIER as Field, + GENERATOR_INDEX__NOTE_NULLIFIER as Field ) } fn compute_nullifier_without_context(self) -> Field { let note_hash_for_nullify = compute_note_hash_for_nullify(self); let secret = get_nsk_app(self.npk_m_hash); - poseidon2_hash_with_separator([ + poseidon2_hash_with_separator( + [ note_hash_for_nullify, - secret, + secret ], - GENERATOR_INDEX__NOTE_NULLIFIER as Field, + GENERATOR_INDEX__NOTE_NULLIFIER as Field ) } } diff --git a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr index f5c7860212d..06335e30a91 100644 --- a/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/ecdsa_r_account_contract/src/main.nr @@ -1,9 +1,13 @@ // Account contract that uses ECDSA signatures for authentication on random version of the p256 curve (to use with touchID). +use dep::aztec::macros::aztec; + +#[aztec] contract EcdsaRAccount { use dep::aztec::prelude::{PrivateContext, PrivateImmutable}; use dep::aztec::{ encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, - keys::getters::get_public_keys + keys::getters::get_public_keys, + macros::{storage::storage, functions::{private, initializer, view, noinitcheck}} }; use dep::authwit::{ @@ -13,14 +17,14 @@ contract EcdsaRAccount { use dep::ecdsa_public_key_note::EcdsaPublicKeyNote; - #[aztec(storage)] - struct Storage { - public_key: PrivateImmutable, + #[storage] + struct Storage { + public_key: PrivateImmutable, } // Creates a new account out of an ECDSA public key to use for signature verification - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(signing_pub_key_x: [u8; 32], signing_pub_key_y: [u8; 32]) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -33,15 +37,15 @@ contract EcdsaRAccount { } // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts - #[aztec(private)] + #[private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } - #[aztec(private)] - #[aztec(noinitcheck)] - #[aztec(view)] + #[private] + #[noinitcheck] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr index 35a243c0235..f121333cf2e 100644 --- a/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr @@ -1,9 +1,12 @@ // Sample escrow contract that stores a balance of a private token on behalf of an owner. +use dep::aztec::macros::aztec; + +#[aztec] contract Escrow { use dep::aztec::prelude::{AztecAddress, PrivateImmutable}; use dep::aztec::{ encrypted_logs::encrypted_note_emission::encode_and_encrypt_note_with_keys, - keys::getters::get_public_keys + keys::getters::get_public_keys, macros::{storage::storage, functions::{private, initializer}} }; // docs:start:addressnote_import @@ -11,14 +14,14 @@ contract Escrow { // docs:end:addressnote_import use dep::token::Token; - #[aztec(storage)] - struct Storage { - owner: PrivateImmutable, + #[storage] + struct Storage { + owner: PrivateImmutable, } // Creates a new instance - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(owner: AztecAddress) { let owner_keys = get_public_keys(owner); let msg_sender_keys = get_public_keys(context.msg_sender()); @@ -31,7 +34,7 @@ contract Escrow { } // Withdraws balance. Requires that msg.sender is the owner. - #[aztec(private)] + #[private] fn withdraw(token: AztecAddress, amount: Field, recipient: AztecAddress) { let sender = context.msg_sender(); diff --git a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr index a5d008e160e..5718d59a554 100644 --- a/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fee_juice_contract/src/main.nr @@ -1,5 +1,8 @@ mod lib; +use dep::aztec::macros::aztec; + +#[aztec] contract FeeJuice { use dep::aztec::{ protocol_types::{ @@ -7,7 +10,8 @@ contract FeeJuice { constants::{DEPLOYER_CONTRACT_ADDRESS, REGISTERER_CONTRACT_ADDRESS, FEE_JUICE_INITIAL_MINT} }, state_vars::{SharedImmutable, PublicMutable, Map}, - oracle::get_contract_instance::get_contract_instance + oracle::get_contract_instance::get_contract_instance, + macros::{storage::storage, functions::{private, public, view, internal}} }; use dep::deployer::ContractInstanceDeployer; @@ -15,17 +19,17 @@ contract FeeJuice { use crate::lib::get_bridge_gas_msg_hash; - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // This map is accessed directly by protocol circuits to check balances for fee payment. // Do not change this storage layout unless you also update the base rollup circuits. - balances: Map>, - portal_address: SharedImmutable, + balances: Map, Context>, + portal_address: SharedImmutable, } // Not flagged as initializer to reduce cost of checking init nullifier in all functions. // This function should be called as entrypoint to initialize the contract by minting itself funds. - #[aztec(private)] + #[private] fn deploy( artifact_hash: Field, private_functions_root: Field, @@ -70,13 +74,13 @@ contract FeeJuice { // We purposefully not set this function as an initializer so we do not bind // the contract to a specific L1 portal address, since the Fee Juice address // is a hardcoded constant in the rollup circuits. - #[aztec(public)] + #[public] fn set_portal(portal_address: EthAddress) { assert(storage.portal_address.read_public().is_zero()); storage.portal_address.initialize(portal_address); } - #[aztec(private)] + #[private] fn claim(to: AztecAddress, amount: Field, secret: Field) { let content_hash = get_bridge_gas_msg_hash(to, amount); let portal_address = storage.portal_address.read_private(); @@ -91,23 +95,23 @@ contract FeeJuice { FeeJuice::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.balances.at(to).read().add(U128::from_integer(amount)); storage.balances.at(to).write(new_balance); } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn check_balance(fee_limit: Field) { let fee_limit = U128::from_integer(fee_limit); assert(storage.balances.at(context.msg_sender()).read() >= fee_limit, "Balance too low"); } // utility function for testing - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn balance_of_public(owner: AztecAddress) -> pub Field { storage.balances.at(owner).read().to_field() } diff --git a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr index 7c301aa6b33..33c966b0c45 100644 --- a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr @@ -1,25 +1,29 @@ mod lib; +use dep::aztec::macros::aztec; + +#[aztec] contract FPC { use dep::aztec::{ protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress}, - state_vars::SharedImmutable + state_vars::SharedImmutable, + macros::{storage::storage, functions::{private, public, initializer}} }; use dep::token::Token; use crate::lib::compute_rebate; - #[aztec(storage)] - struct Storage { - other_asset: SharedImmutable, + #[storage] + struct Storage { + other_asset: SharedImmutable, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(other_asset: AztecAddress) { storage.other_asset.initialize(other_asset); } - #[aztec(private)] + #[private] fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) { assert(asset == storage.other_asset.read_private()); Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); @@ -35,7 +39,7 @@ contract FPC { ); } - #[aztec(private)] + #[private] fn fee_entrypoint_public(amount: Field, asset: AztecAddress, nonce: Field) { FPC::at(context.this_address()).prepare_fee(context.msg_sender(), amount, asset, nonce).enqueue(&mut context); context.set_as_fee_payer(); @@ -50,24 +54,24 @@ contract FPC { ); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn prepare_fee(from: AztecAddress, amount: Field, asset: AztecAddress, nonce: Field) { // docs:start:public_call Token::at(asset).transfer_public(from, context.this_address(), amount, nonce).call(&mut context); // docs:end:public_call } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn pay_refund(refund_address: AztecAddress, amount: Field, asset: AztecAddress) { // Just do public refunds for the present let refund = compute_rebate(context, amount); Token::at(asset).transfer_public(context.this_address(), refund_address, refund, 0).call(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn pay_refund_with_shielded_rebate(amount: Field, asset: AztecAddress, secret_hash: Field) { let refund = compute_rebate(context, amount); Token::at(asset).shield(context.this_address(), refund, secret_hash, 0).call(&mut context); diff --git a/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr index 6e125f3979a..df4ad22d792 100644 --- a/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/import_test_contract/src/main.nr @@ -1,15 +1,20 @@ // Contract that uses the autogenerated interface of the Test contract for calling its functions. // Used for testing calling into other contracts via autogenerated interfaces. +use dep::aztec::macros::aztec; + +#[aztec] contract ImportTest { use dep::aztec::prelude::AztecAddress; use dep::test::{Test, Test::DeepStruct, Test::DummyNote}; + use dep::aztec::macros::functions::{private, public}; + // Calls the test_code_gen on the Test contract at the target address // Used for testing calling a function with arguments of multiple types // See yarn-project/simulator/src/client/private_execution.ts // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts - #[aztec(private)] + #[private] fn main_contract(target: AztecAddress) -> Field { Test::at(target).test_code_gen( 1, @@ -33,7 +38,7 @@ contract ImportTest { // Calls the get_this_address on the Test contract at the target address // Used for testing calling a function with no arguments // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts - #[aztec(private)] + #[private] fn call_no_args(target: AztecAddress) -> AztecAddress { Test::at(target).get_this_address().call(&mut context) } @@ -41,7 +46,7 @@ contract ImportTest { // Calls the emit_nullifier_public on the Test contract at the target address // Used for testing calling a public function // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts - #[aztec(private)] + #[private] fn call_public_fn(target: AztecAddress) { Test::at(target).emit_nullifier_public(1).enqueue(&mut context); } @@ -49,7 +54,7 @@ contract ImportTest { // Calls the emit_nullifier_public on the Test contract at the target address // Used for testing calling a public function from another public function // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts - #[aztec(public)] + #[public] fn pub_call_public_fn(target: AztecAddress) { Test::at(target).emit_nullifier_public(1).call(&mut context); } diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index d5d6cfe78c1..b67fdd30cfd 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -1,28 +1,34 @@ // A demonstration of inclusion and non-inclusion proofs. +use dep::aztec::macros::aztec; + +#[aztec] contract InclusionProofs { use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, Map, PrivateSet, PublicMutable}; use dep::aztec::{encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, keys::getters::get_public_keys}; - use dep::aztec::{note::note_getter_options::NoteStatus}; + use dep::aztec::{ + note::note_getter_options::NoteStatus, + macros::{storage::storage, functions::{private, public, initializer}} + }; // docs:start:value_note_imports use dep::value_note::value_note::ValueNote; // docs:end:value_note_imports - #[aztec(storage)] - struct Storage { - private_values: Map>, - public_value: PublicMutable, - public_unused_value: PublicMutable, + #[storage] + struct Storage { + private_values: Map, Context>, + public_value: PublicMutable, + public_unused_value: PublicMutable, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(public_value: Field) { storage.public_value.write(public_value); } // docs:start:create_note // Creates a value note owned by `owner`. - #[aztec(private)] + #[private] fn create_note(owner: AztecAddress, value: Field) { let owner_private_values = storage.private_values.at(owner); let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); @@ -32,7 +38,7 @@ contract InclusionProofs { } // docs:end:create_note - #[aztec(private)] + #[private] fn test_note_inclusion( owner: AztecAddress, use_block_number: bool, @@ -62,7 +68,7 @@ contract InclusionProofs { // docs:end:prove_note_inclusion } - #[aztec(private)] + #[private] fn test_note_inclusion_fail_case( owner: AztecAddress, use_block_number: bool, @@ -82,7 +88,7 @@ contract InclusionProofs { } // Proves that the note was not yet nullified at block `block_number`. - #[aztec(private)] + #[private] fn test_note_not_nullified( owner: AztecAddress, use_block_number: bool, @@ -111,7 +117,7 @@ contract InclusionProofs { // docs:end:prove_note_not_nullified } - #[aztec(private)] + #[private] fn test_note_validity( owner: AztecAddress, use_block_number: bool, @@ -139,7 +145,7 @@ contract InclusionProofs { } // docs:start:nullify_note - #[aztec(private)] + #[private] fn nullify_note(owner: AztecAddress) { let private_values = storage.private_values.at(owner); let mut options = NoteGetterOptions::new(); @@ -152,7 +158,7 @@ contract InclusionProofs { // Proves nullifier existed at block `block_number`. // Note: I am not getting a nullifier of the note that was created in this contract in this function because it is // currently not possible to obtain a nullified note from PXE. - #[aztec(private)] + #[private] fn test_nullifier_inclusion( nullifier: Field, use_block_number: bool, @@ -168,18 +174,18 @@ contract InclusionProofs { // docs:end:prove_nullifier_inclusion } - #[aztec(public)] + #[public] fn push_nullifier_public(nullifier: Field) { context.push_nullifier(nullifier); } // Proves nullifier existed at latest block - #[aztec(public)] + #[public] fn test_nullifier_inclusion_from_public(nullifier: Field) { assert(context.nullifier_exists(nullifier, context.this_address())); } - #[aztec(private)] + #[private] fn test_storage_historical_read_unset_slot(block_number: u32 // The block at which we'll read the public storage value ) { let header = context.get_header_at(block_number); @@ -193,7 +199,7 @@ contract InclusionProofs { // docs:end:public_storage_historical_read } - #[aztec(private)] + #[private] fn test_storage_historical_read( expected: Field, use_block_number: bool, @@ -211,7 +217,7 @@ contract InclusionProofs { } // Proves that a contract was publicly deployed and/or initialized at block `block_number`. - #[aztec(private)] + #[private] fn test_contract_inclusion( contract_address: AztecAddress, block_number: u32, @@ -233,7 +239,7 @@ contract InclusionProofs { } // Proves that a contract was NOT publicly deployed and/or initialized at block `block_number`. - #[aztec(private)] + #[private] fn test_contract_non_inclusion( contract_address: AztecAddress, block_number: u32, diff --git a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr index 93d7052e876..66d2af3a916 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -10,6 +10,9 @@ mod helpers; // - Update accumulator should be for specific asset, just abusing only 1 asset atm. // - A way to repay all debt at once // - Liquidations +use dep::aztec::macros::aztec; + +#[aztec] contract Lending { use dep::aztec::prelude::{AztecAddress, Map, PublicMutable}; @@ -20,23 +23,25 @@ contract Lending { use dep::token::Token; use dep::price_feed::PriceFeed; + use dep::aztec::macros::{storage::storage, functions::{private, public, initializer, internal}}; + // Storage structure, containing all storage, and specifying what slots they use. - #[aztec(storage)] - struct Storage { - collateral_asset: PublicMutable, - stable_coin: PublicMutable, - assets: Map>, - collateral: Map>, - static_debt: Map>, // abusing keys very heavily + #[storage] + struct Storage { + collateral_asset: PublicMutable, + stable_coin: PublicMutable, + assets: Map, Context>, + collateral: Map, Context>, + static_debt: Map, Context>, // abusing keys very heavily } // Constructs the contract. - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor( ) {} - #[aztec(public)] + #[public] fn init( oracle: AztecAddress, loan_to_value: Field, @@ -63,7 +68,7 @@ contract Lending { } // Create a position. - #[aztec(public)] + #[public] fn update_accumulator() -> Asset { let asset_loc = storage.assets.at(0); let mut asset: Asset = asset_loc.read(); @@ -88,7 +93,7 @@ contract Lending { asset } - #[aztec(private)] + #[private] fn deposit_private( from: AztecAddress, amount: Field, @@ -108,7 +113,7 @@ contract Lending { // docs:end:enqueue_public } - #[aztec(public)] + #[public] fn deposit_public(amount: Field, nonce: Field, on_behalf_of: Field, collateral_asset: AztecAddress) { let _ = Token::at(collateral_asset).transfer_public(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context); let _ = Lending::at(context.this_address())._deposit( @@ -118,8 +123,8 @@ contract Lending { ).call(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _deposit(owner: AztecAddress, amount: Field, collateral_asset: AztecAddress) { let _asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); @@ -131,19 +136,19 @@ contract Lending { coll_loc.write(collateral + amount); } - #[aztec(private)] + #[private] fn withdraw_private(secret: Field, to: AztecAddress, amount: Field) { let on_behalf_of = compute_identifier(secret, 0, context.msg_sender().to_field()); Lending::at(context.this_address())._withdraw(AztecAddress::from_field(on_behalf_of), to, amount).enqueue(&mut context); } - #[aztec(public)] + #[public] fn withdraw_public(to: AztecAddress, amount: Field) { let _ = Lending::at(context.this_address())._withdraw(context.msg_sender(), to, amount).call(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _withdraw(owner: AztecAddress, recipient: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; @@ -179,19 +184,19 @@ contract Lending { let _ = Token::at(collateral_asset).transfer_public(context.this_address(), recipient, amount, 0).call(&mut context); } - #[aztec(private)] + #[private] fn borrow_private(secret: Field, to: AztecAddress, amount: Field) { let on_behalf_of = compute_identifier(secret, 0, context.msg_sender().to_field()); let _ = Lending::at(context.this_address())._borrow(AztecAddress::from_field(on_behalf_of), to, amount).enqueue(&mut context); } - #[aztec(public)] + #[public] fn borrow_public(to: AztecAddress, amount: Field) { let _ = Lending::at(context.this_address())._borrow(context.msg_sender(), to, amount).call(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _borrow(owner: AztecAddress, to: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; @@ -223,7 +228,7 @@ contract Lending { let _ = Token::at(stable_coin).mint_public(to, amount).call(&mut context); } - #[aztec(private)] + #[private] fn repay_private( from: AztecAddress, amount: Field, @@ -239,14 +244,14 @@ contract Lending { let _ = Lending::at(context.this_address())._repay(AztecAddress::from_field(on_behalf_of), amount, stable_coin).enqueue(&mut context); } - #[aztec(public)] + #[public] fn repay_public(amount: Field, nonce: Field, owner: AztecAddress, stable_coin: AztecAddress) { let _ = Token::at(stable_coin).burn_public(context.msg_sender(), amount, nonce).call(&mut context); let _ = Lending::at(context.this_address())._repay(owner, amount, stable_coin).call(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _repay(owner: AztecAddress, amount: Field, stable_coin: AztecAddress) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); @@ -264,14 +269,14 @@ contract Lending { storage.static_debt.at(owner).write(debt_returns.static_debt.to_integer()); } - #[aztec(public)] - #[aztec(view)] - fn get_asset(asset_id: Field) -> pub Asset { + #[public] + #[view] + fn get_asset(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_position(owner: AztecAddress) -> pub Position { let collateral = storage.collateral.at(owner).read(); let static_debt = storage.static_debt.at(owner).read(); @@ -283,8 +288,8 @@ contract Lending { Position { collateral, static_debt, debt } } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_assets() -> pub [AztecAddress; 2] { [storage.collateral_asset.read(), storage.stable_coin.read()] } diff --git a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr index f14d751d344..2351f61ecc0 100644 --- a/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/multi_call_entrypoint_contract/src/main.nr @@ -1,9 +1,13 @@ // An entrypoint contract that allows everything to go through. Only used for testing // Pair this with SignerlessWallet to perform multiple actions before any account contracts are deployed (and without authentication) +use dep::aztec::macros::aztec; + +#[aztec] contract MultiCallEntrypoint { use dep::authwit::entrypoint::app::AppPayload; + use dep::aztec::macros::functions::private; - #[aztec(private)] + #[private] fn entrypoint(app_payload: AppPayload) { app_payload.execute_calls(&mut context); } diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index 02e84a806ec..03cb0d0a8d3 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -1,50 +1,57 @@ mod types; mod test; +use dep::aztec::macros::aztec; + // Minimal NFT implementation with `AuthWit` support that allows minting in public-only and transfers in both public // and private. +#[aztec] contract NFT { use dep::compressed_string::FieldCompressedString; use dep::aztec::{ prelude::{NoteGetterOptions, NoteViewerOptions, Map, PublicMutable, SharedImmutable, PrivateSet, AztecAddress}, encrypted_logs::{encrypted_note_emission::encode_and_encrypt_note_with_keys}, hash::pedersen_hash, keys::getters::get_public_keys, note::constants::MAX_NOTES_PER_PAGE, - protocol_types::traits::is_empty, utils::comparison::Comparator + protocol_types::traits::is_empty, utils::comparison::Comparator, + protocol_types::{point::Point, traits::Serialize}, + macros::{storage::storage, events::event, functions::{private, public, view, internal, initializer}} }; use dep::authwit::auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, compute_authwit_nullifier}; - use crate::types::nft_note::{NFTNote, NFTNoteHidingPoint}; + use std::{embedded_curve_ops::EmbeddedCurvePoint, meta::derive}; + use crate::types::nft_note::NFTNote; global TRANSIENT_STORAGE_SLOT_PEDERSEN_INDEX = 3; // TODO(#8467): Rename this to Transfer - calling this NFTTransfer to avoid export conflict with the Transfer event // in the Token contract. - #[aztec(event)] + #[event] + #[derive(Serialize)] struct NFTTransfer { from: AztecAddress, to: AztecAddress, token_id: Field, } - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // The symbol of the NFT - symbol: SharedImmutable, + symbol: SharedImmutable, // The name of the NFT - name: SharedImmutable, + name: SharedImmutable, // The admin of the contract - admin: PublicMutable, + admin: PublicMutable, // Addresses that can mint - minters: Map>, + minters: Map, Context>, // Contains the NFTs owned by each address in private. - private_nfts: Map>, + private_nfts: Map, Context>, // A map from token ID to a boolean indicating if the NFT exists. - nft_exists: Map>, + nft_exists: Map, Context>, // A map from token ID to the public owner of the NFT. - public_owners: Map>, + public_owners: Map, Context>, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>) { assert(!admin.is_zero(), "invalid admin"); storage.admin.write(admin); @@ -53,19 +60,19 @@ contract NFT { storage.symbol.initialize(FieldCompressedString::from_string(symbol)); } - #[aztec(public)] + #[public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.admin.write(new_admin); } - #[aztec(public)] + #[public] fn set_minter(minter: AztecAddress, approve: bool) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not an admin"); storage.minters.at(minter).write(approve); } - #[aztec(public)] + #[public] fn mint(to: AztecAddress, token_id: Field) { assert(token_id != 0, "zero token ID not supported"); assert(storage.minters.at(context.msg_sender()).read(), "caller is not a minter"); @@ -76,43 +83,43 @@ contract NFT { storage.public_owners.at(token_id).write(to); } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn public_get_name() -> pub FieldCompressedString { storage.name.read_public() } - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_get_name() -> pub FieldCompressedString { storage.name.read_private() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_admin() -> Field { storage.admin.read().to_field() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn is_minter(minter: AztecAddress) -> bool { storage.minters.at(minter).read() } - #[aztec(public)] + #[public] fn transfer_in_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit_public(&mut context, from); @@ -132,7 +139,7 @@ contract NFT { /// as an argument to the followup call to `finalize_transfer_to_private`. // TODO(#8238): Remove the `note_randomness` argument below once we have partial notes delivery (then we can just // fetch the randomness from oracle). - #[aztec(private)] + #[private] fn prepare_transfer_to_private( from: AztecAddress, to: AztecAddress, @@ -142,7 +149,7 @@ contract NFT { // We create a partial NFT note hiding point with unpopulated/zero token id for 'to' let to_npk_m_hash = get_public_keys(to).npk_m.hash(); let to_note_slot = storage.private_nfts.at(to).storage_slot; - let hiding_point = NFTNoteHidingPoint::new(to_npk_m_hash, to_note_slot, note_randomness); + let hiding_point = NFTNote::hiding_point().new(to_npk_m_hash, note_randomness, to_note_slot); // We make the msg_sender/transfer_preparer part of the slot preimage to ensure he cannot interfere with // non-sender's slots @@ -157,12 +164,12 @@ contract NFT { TRANSIENT_STORAGE_SLOT_PEDERSEN_INDEX ); - NFT::at(context.this_address())._store_point_in_transient_storage(hiding_point, slot).enqueue(&mut context); + NFT::at(context.this_address())._store_point_in_transient_storage(hiding_point.inner, slot).enqueue(&mut context); } - #[aztec(public)] - #[aztec(internal)] - fn _store_point_in_transient_storage(point: NFTNoteHidingPoint, slot: Field) { + #[public] + #[internal] + fn _store_point_in_transient_storage(point: Point, slot: Field) { // We don't perform check for the overwritten value to be non-zero because the slots are siloed to `to` // and hence `to` can interfere only with his own execution. context.storage_write(slot, point); @@ -172,8 +179,11 @@ contract NFT { /// The transfer must be prepared by calling `prepare_transfer_to_private` first. /// The `transfer_preparer_storage_slot_commitment` has to be computed off-chain the same way as was done /// in the preparation call. - #[aztec(public)] - fn finalize_transfer_to_private(token_id: Field, transfer_preparer_storage_slot_commitment: Field) { + #[public] + fn finalize_transfer_to_private( + token_id: Field, + transfer_preparer_storage_slot_commitment: Field + ) { // We don't need to support authwit here because `prepare_transfer_to_private` allows us to set arbitrary // `from` and `from` will always be the msg sender here. let from = context.msg_sender(); @@ -188,7 +198,7 @@ contract NFT { ); // Read the hiding point from "transient" storage and check it's not empty to ensure the transfer was prepared - let mut hiding_point: NFTNoteHidingPoint = context.storage_read(hiding_point_slot); + let mut hiding_point = NFTNote::hiding_point().from_point(context.storage_read(hiding_point_slot)); assert(!is_empty(hiding_point), "transfer not prepared"); // Set the public NFT owner to zero @@ -200,21 +210,24 @@ contract NFT { // At last we reset public storage to zero to achieve the effect of transient storage - kernels will squash // the writes - context.storage_write(hiding_point_slot, NFTNoteHidingPoint::empty()); + context.storage_write( + hiding_point_slot, + NFTNote::hiding_point().from_point(Point::empty()) + ); } /** * Cancel a private authentication witness. * @param inner_hash The inner hash of the authwit to cancel. */ - #[aztec(private)] + #[private] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = context.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); context.push_nullifier(nullifier); } - #[aztec(private)] + #[private] fn transfer_in_private(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -236,7 +249,7 @@ contract NFT { nfts.at(to).insert(&mut new_note).emit(encode_and_encrypt_note_with_keys(&mut context, from_ovpk_m, to_keys.ivpk_m, to)); } - #[aztec(private)] + #[private] fn transfer_to_public(from: AztecAddress, to: AztecAddress, token_id: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -252,15 +265,15 @@ contract NFT { NFT::at(context.this_address())._finish_transfer_to_public(to, token_id).enqueue(&mut context); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _finish_transfer_to_public(to: AztecAddress, token_id: Field) { storage.public_owners.at(token_id).write(to); } // Returns zero address when the token does not have a public owner. Reverts if the token does not exist. - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn owner_of(token_id: Field) -> AztecAddress { assert(storage.nft_exists.at(token_id).read(), "token does not exist"); storage.public_owners.at(token_id).read() @@ -269,10 +282,7 @@ contract NFT { /// Returns an array of token IDs owned by `owner` in private and a flag indicating whether a page limit was /// reached. Starts getting the notes from page with index `page_index`. Zero values in the array are placeholder /// values for non-existing notes. - unconstrained fn get_private_nfts( - owner: AztecAddress, - page_index: u32 - ) -> pub ([Field; MAX_NOTES_PER_PAGE], bool) { + unconstrained fn get_private_nfts(owner: AztecAddress, page_index: u32) -> pub ([Field; MAX_NOTES_PER_PAGE], bool) { let offset = page_index * MAX_NOTES_PER_PAGE; let mut options = NoteViewerOptions::new(); let notes = storage.private_nfts.at(owner).view_notes(options.set_offset(offset)); diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_to_private.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_to_private.nr index 63b3c612460..51350ae3adf 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_to_private.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/transfer_to_private.nr @@ -51,7 +51,7 @@ unconstrained fn transfer_to_private_to_a_different_account() { // Store the finalized note in the cache let mut context = env.private(); let recipient_npk_m_hash = get_public_keys(recipient).npk_m.hash(); - let private_nfts_recipient_slot = derive_storage_slot_in_map(NFT::storage().private_nfts.slot, recipient); + let private_nfts_recipient_slot = derive_storage_slot_in_map(NFT::storage_layout().private_nfts.slot, recipient); env.add_note( &mut NFTNote { token_id, npk_m_hash: recipient_npk_m_hash, randomness: note_randomness, header: NoteHeader::empty() }, diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr index c9b7f125483..518079d6c0a 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/test/utils.nr @@ -73,7 +73,7 @@ pub fn setup_mint_and_transfer_to_private(with_account_contracts: bool) -> (&mut // Store the finalized note in the cache let mut context = env.private(); let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); - let private_nfts_owner_slot = derive_storage_slot_in_map(NFT::storage().private_nfts.slot, owner); + let private_nfts_owner_slot = derive_storage_slot_in_map(NFT::storage_layout().private_nfts.slot, owner); env.add_note( &mut NFTNote { @@ -94,7 +94,7 @@ pub fn get_nft_exists(nft_contract_address: AztecAddress, token_id: Field) -> bo cheatcodes::set_contract_address(nft_contract_address); let block_number = get_block_number(); - let nft_exists_slot = NFT::storage().nft_exists.slot; + let nft_exists_slot = NFT::storage_layout().nft_exists.slot; let nft_slot = derive_storage_slot_in_map(nft_exists_slot, token_id); let exists: bool = storage_read(nft_contract_address, nft_slot, block_number); cheatcodes::set_contract_address(current_contract_address); diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr index 5a50ab3877a..2d982a55ea7 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/types/nft_note.nr @@ -1,20 +1,11 @@ use dep::aztec::{ - generators::{Ga1 as G_tid, Ga2 as G_npk, Ga3 as G_rnd, G_slot}, note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, - oracle::unsafe_rand::unsafe_rand, - prelude::{AztecAddress, NoteInterface, NoteHeader, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, - hash::poseidon2_hash_with_separator, traits::{Empty, Eq, Deserialize, Serialize} -} + oracle::unsafe_rand::unsafe_rand, prelude::{NullifiableNote, NoteHeader, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator, traits::{Empty, Eq}}, + macros::notes::partial_note }; -use std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; - -global NFT_NOTE_LEN: u32 = 3; -// NFT_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global NFT_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; -#[aztec(note)] +#[partial_note(quote { token_id})] struct NFTNote { // ID of the token token_id: Field, @@ -24,7 +15,7 @@ struct NFTNote { randomness: Field, } -impl NoteInterface for NFTNote { +impl NullifiableNote for NFTNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); poseidon2_hash_with_separator( @@ -47,19 +38,6 @@ impl NoteInterface for NFTNote { GENERATOR_INDEX__NOTE_NULLIFIER as Field ) } - - fn compute_note_hiding_point(self) -> Point { - // We use the unsafe version because `multi_scalar_mul` will constrain the scalars. - let token_id_scalar = from_field_unsafe(self.token_id); - let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash); - let randomness_scalar = from_field_unsafe(self.randomness); - let slot_scalar = from_field_unsafe(self.header.storage_slot); - - multi_scalar_mul( - [G_tid, G_npk, G_rnd, G_slot], - [token_id_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar] - ) - } } impl NFTNote { @@ -78,54 +56,3 @@ impl Eq for NFTNote { & (self.randomness == other.randomness) } } - -// TODO(#8290): Auto-generate this -struct NFTNoteHidingPoint { - inner: Point -} - -impl NFTNoteHidingPoint { - // TODO(#8238): Remove the randomness argument below - fn new(npk_m_hash: Field, storage_slot: Field, randomness: Field) -> Self { - // TODO(#8238): And uncomment this - // let randomness = unsafe { - // unsafe_rand() - // }; - let note = NFTNote { - header: NoteHeader { contract_address: AztecAddress::zero(), nonce: 0, storage_slot, note_hash_counter: 0 }, - token_id: 0, - npk_m_hash, - randomness - }; - Self { inner: note.compute_note_hiding_point() } - } - - fn finalize(self, token_id: Field) -> Field { - let finalized_hiding_point = multi_scalar_mul([G_tid], [from_field_unsafe(token_id)]) + self.inner; - finalized_hiding_point.x - } -} - -impl Serialize for NFTNoteHidingPoint { - fn serialize(self) -> [Field; POINT_LENGTH] { - self.inner.serialize() - } -} - -impl Deserialize for NFTNoteHidingPoint { - fn deserialize(serialized: [Field; POINT_LENGTH]) -> NFTNoteHidingPoint { - NFTNoteHidingPoint { inner: Point::deserialize(serialized) } - } -} - -impl Empty for NFTNoteHidingPoint { - fn empty() -> Self { - Self { inner: Point::empty() } - } -} - -impl Eq for NFTNoteHidingPoint { - fn eq(self, other: Self) -> bool { - self.inner == other.inner - } -} diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index 7825f85a167..ac1526eca14 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -1,17 +1,19 @@ // A contract used along with `Child` contract to test nested calls. +use dep::aztec::macros::aztec; + +#[aztec] contract Parent { use dep::aztec::prelude::{AztecAddress, FunctionSelector}; - use dep::aztec::context::gas::GasOpts; + use dep::aztec::{context::gas::GasOpts, macros::functions::{private, public}}; // Private function to call another private function in the target_contract using the provided selector - #[aztec(private)] + #[private] fn entry_point(target_contract: AztecAddress, target_selector: FunctionSelector) -> Field { // Call the target private function context.call_private_function(target_contract, target_selector, [0]).unpack_into() } - // Public function to directly call another public function to the target_contract using the selector and value provided - #[aztec(public)] + #[public] fn pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -24,9 +26,8 @@ contract Parent { GasOpts::default() ).deserialize_into() } - // Same as pub_entry_point, but calls the target contract twice, using the return value from the first invocation as the argument for the second. - #[aztec(public)] + #[public] fn pub_entry_point_twice( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -45,9 +46,8 @@ contract Parent { GasOpts::default() ).deserialize_into() } - // Private function to enqueue a call to the target_contract address using the selector and argument provided - #[aztec(private)] + #[private] fn enqueue_call_to_child( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -55,11 +55,10 @@ contract Parent { ) { context.call_public_function(target_contract, target_selector, [target_value]); } - // Private function that enqueues two calls to a child contract: // - one through a nested call to enqueue_call_to_child with value 10, // - followed by one issued directly from this function with value 20. - #[aztec(private)] + #[private] fn enqueue_calls_to_child_with_nested_first( target_contract: AztecAddress, target_selector: FunctionSelector @@ -74,11 +73,10 @@ contract Parent { ); context.call_public_function(target_contract, target_selector, [20]); } - // Private function that enqueues two calls to a child contract: // - one issued directly from this function with value 20, // - followed by one through a nested call to enqueue_call_to_child with value 10. - #[aztec(private)] + #[private] fn enqueue_calls_to_child_with_nested_last( target_contract: AztecAddress, target_selector: FunctionSelector @@ -93,9 +91,8 @@ contract Parent { [target_contract.to_field(), target_selector.to_field(), 10] ); } - // Private function to enqueue a call to the target_contract address using the selector and argument provided - #[aztec(private)] + #[private] fn enqueue_call_to_child_twice( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -106,9 +103,8 @@ contract Parent { // Enqueue the second public call context.call_public_function(target_contract, target_selector, [target_value + 1]); } - // Private function to enqueue a call to the pub_entry_point function of this same contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_call_to_pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -124,9 +120,8 @@ contract Parent { [target_contract.to_field(), target_selector.to_field(), target_value] ); } - // Private function to enqueue two calls to the pub_entry_point function of this same contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_calls_to_pub_entry_point( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -136,21 +131,18 @@ contract Parent { FunctionSelector::from_signature("pub_entry_point((Field),(u32),Field)") }; let this_address = context.this_address(); - context.call_public_function( this_address, pub_entry_point_selector, [target_contract.to_field(), target_selector.to_field(), target_value] ); - context.call_public_function( this_address, pub_entry_point_selector, [target_contract.to_field(), target_selector.to_field(), target_value + 1] ); } - - #[aztec(private)] + #[private] fn private_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -159,8 +151,7 @@ contract Parent { // Call the target private function context.static_call_private_function(target_contract, target_selector, args).unpack_into() } - - #[aztec(private)] + #[private] fn private_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -169,9 +160,8 @@ contract Parent { // Call the target private function context.call_private_function(target_contract, target_selector, args).unpack_into() } - // Private function to set a static context and verify correct propagation for nested private calls - #[aztec(private)] + #[private] fn private_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -185,13 +175,11 @@ contract Parent { private_call_selector, [target_contract.to_field(), target_selector.to_field(), args[0], args[1]] ).unpack_into(); - // Copy the return value from the call to this function's return values return_value } - // Public function to directly call another public function to the target_contract using the selector and value provided - #[aztec(public)] + #[public] fn public_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -204,9 +192,8 @@ contract Parent { GasOpts::default() ).deserialize_into() } - // Public function to set a static context and verify correct propagation for nested public calls - #[aztec(public)] + #[public] fn public_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -222,9 +209,8 @@ contract Parent { GasOpts::default() ).deserialize_into() } - // Private function to enqueue a static call to the pub_entry_point function of another contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_static_nested_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -239,9 +225,8 @@ contract Parent { [target_contract.to_field(), target_selector.to_field(), args[0]] ); } - // Private function to enqueue a static call to the pub_entry_point function of another contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_static_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -250,25 +235,21 @@ contract Parent { // Call the target private function context.static_call_public_function(target_contract, target_selector, args); } - use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; use dep::child_contract::Child; use dep::value_note::value_note::ValueNote; - #[test] fn test_private_call() { // Setup env, generate keys let mut env = TestEnvironment::new(); let owner = env.create_account(); - // Deploy child contract let child_contract = env.deploy("./@child_contract", "Child").without_initializer(); let child_contract_address = child_contract.to_address(); cheatcodes::advance_blocks_by(1); - // Set value in child through parent let value_to_set = 7; let parent_private_set_call_interface = Parent::interface().private_call( @@ -282,7 +263,7 @@ contract Parent { assert(result == value_to_set); // Read the stored value in the note. We have to change the contract address to the child contract in order to read its notes env.impersonate(child_contract_address); - let counter_slot = Child::storage().a_map_with_private_values.slot; + let counter_slot = Child::storage_layout().a_map_with_private_values.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); let notes: BoundedVec = view_notes(owner_slot, options); diff --git a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr index 2ea20972cb0..9663eebb80f 100644 --- a/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/pending_note_hashes_contract/src/main.nr @@ -2,6 +2,9 @@ // read (eventually even nullified) in the same TX. This contract // also contains some "bad" test cases to ensure that notes cannot // be read/nullified before their creation etc. +use dep::aztec::macros::aztec; + +#[aztec] contract PendingNoteHashes { // Libs use dep::aztec::prelude::{AztecAddress, FunctionSelector, NoteGetterOptions, PrivateContext, Map, PrivateSet}; @@ -10,10 +13,11 @@ contract PendingNoteHashes { use dep::aztec::encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note, encode_and_encrypt_note_with_keys}; use dep::aztec::note::note_emission::NoteEmission; use dep::aztec::keys::getters::get_public_keys; + use dep::aztec::macros::{storage::storage, functions::private}; - #[aztec(storage)] - struct Storage { - balances: Map>, + #[storage] + struct Storage { + balances: Map, Context>, } // TODO(dbanks12): consolidate code into internal helper functions @@ -22,7 +26,7 @@ contract PendingNoteHashes { // Confirm can access pending note hashes by creating / inserting a note and then // getting / reading that note all in the same contract function // Realistic way to describe this test is "Mint note A, then burn note A in the same transaction" - #[aztec(private)] + #[private] fn test_insert_then_get_then_nullify_flat( amount: Field, owner: AztecAddress, @@ -49,7 +53,7 @@ contract PendingNoteHashes { } // Confirm cannot access note hashes inserted later in same function - #[aztec(private)] + #[private] fn test_bad_get_then_insert_flat(amount: Field, owner: AztecAddress) -> Field { let owner_balance = storage.balances.at(owner); @@ -69,11 +73,11 @@ contract PendingNoteHashes { } // Dummy nested/inner function (to pass a function which does nothing) - #[aztec(private)] + #[private] fn dummy(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) {} // Nested/inner function to create and insert a note - #[aztec(private)] + #[private] fn insert_note(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let owner_balance = storage.balances.at(owner); @@ -88,7 +92,7 @@ contract PendingNoteHashes { // Nested/inner function to create and insert a note // TESTING: inserts a static randomness value to test notes with // the same note hash are dealt with correctly - #[aztec(private)] + #[private] fn insert_note_static_randomness( amount: Field, owner: AztecAddress, @@ -107,7 +111,7 @@ contract PendingNoteHashes { // Nested/inner function to create and insert a note // then emit another note log for the same note - #[aztec(private)] + #[private] fn insert_note_extra_emit(amount: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let mut owner_balance = storage.balances.at(owner); @@ -125,7 +129,7 @@ contract PendingNoteHashes { } // Nested/inner function to get a note and confirm it matches the expected value - #[aztec(private)] + #[private] fn get_then_nullify_note(expected_value: Field, owner: AztecAddress) -> Field { let owner_balance = storage.balances.at(owner); @@ -138,7 +142,7 @@ contract PendingNoteHashes { } // Nested/inner function to get a note and confirms that none is returned - #[aztec(private)] + #[private] fn get_note_zero_balance(owner: AztecAddress) { let owner_balance = storage.balances.at(owner); @@ -151,7 +155,7 @@ contract PendingNoteHashes { // Test pending note hashes with note insertion done in a nested call // and "read" / get of that pending note hash in another nested call // Realistic way to describe this test is "Mint note A, then burn note A in the same transaction" - #[aztec(private)] + #[private] fn test_insert_then_get_then_nullify_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -174,7 +178,7 @@ contract PendingNoteHashes { } // same test as above, but insert 2, get 2, nullify 2 - #[aztec(private)] + #[private] fn test_insert2_then_get2_then_nullify2_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -214,7 +218,7 @@ contract PendingNoteHashes { } // same test as above, but insert 2, get 1, nullify 1 - #[aztec(private)] + #[private] fn test_insert2_then_get2_then_nullify1_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -248,7 +252,7 @@ contract PendingNoteHashes { // insert 1 note, then get 2 notes (one pending, one persistent) and nullify both. // one nullifier will be squashed with the pending note, one will become persistent. // ONLY WORKS IF THERE IS A PERSISTENT NOTE TO GET - #[aztec(private)] + #[private] fn test_insert1_then_get2_then_nullify2_all_in_nested_calls( amount: Field, owner: AztecAddress, @@ -285,7 +289,7 @@ contract PendingNoteHashes { // nested call (later kernel iteration) should not be able to read the note hash despite // it being present at that stage in the kernel. // If we can somehow force the simulator to allow execution to succeed can ensure that this test fails in the kernel - // #[aztec(private)] + // #[private] //fn test_bad_get_in_nested_call_then_insert( // amount: Field, // owner: AztecAddress, @@ -293,7 +297,7 @@ contract PendingNoteHashes { //) { //} - #[aztec(private)] + #[private] fn test_recursively_create_notes( owner: AztecAddress, outgoing_viewer: AztecAddress, @@ -304,7 +308,7 @@ contract PendingNoteHashes { PendingNoteHashes::at(context.this_address()).recursively_destroy_and_create_notes(owner, outgoing_viewer, how_many_recursions).call(&mut context); } - #[aztec(private)] + #[private] fn recursively_destroy_and_create_notes( owner: AztecAddress, outgoing_viewer: AztecAddress, @@ -325,7 +329,7 @@ contract PendingNoteHashes { // TESTING: Forces the private context to accept a note log for a non-existent note // by using an existing note's counter via its header. This is used to check that // the pxe rejects the note log later. - #[aztec(private)] + #[private] fn test_emit_bad_note_log(owner: AztecAddress, outgoing_viewer: AztecAddress) { let owner_balance = storage.balances.at(owner); diff --git a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr index 3c919579009..f360c87ad74 100644 --- a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr @@ -1,23 +1,28 @@ mod asset; +use dep::aztec::macros::aztec; + +#[aztec] contract PriceFeed { - use dep::aztec::prelude::{AztecAddress, FunctionSelector, PrivateContext, Map, PublicMutable}; + use dep::aztec::prelude::{Map, PublicMutable}; use crate::asset::Asset; + use dep::aztec::macros::{storage::storage, functions::{public, view}}; + // Storage structure, containing all storage, and specifying what slots they use. - #[aztec(storage)] - struct Storage { - assets: Map>, + #[storage] + struct Storage { + assets: Map, Context>, } - #[aztec(public)] + #[public] fn set_price(asset_id: Field, price: Field) { let asset = storage.assets.at(asset_id); asset.write(Asset { price: U128::from_integer(price) }); } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_price(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } diff --git a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr index c8716b0f9d4..910cff462d0 100644 --- a/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/private_fpc_contract/src/main.nr @@ -1,23 +1,30 @@ mod settings; +use dep::aztec::macros::aztec; + +#[aztec] contract PrivateFPC { - use dep::aztec::{protocol_types::{address::AztecAddress, hash::compute_siloed_nullifier}, state_vars::SharedImmutable}; - use dep::token::Token; + use dep::aztec::{ + protocol_types::{address::AztecAddress, hash::compute_siloed_nullifier}, + state_vars::SharedImmutable, + macros::{storage::storage, functions::{private, initializer, public}} + }; + use token::Token; use crate::settings::Settings; - #[aztec(storage)] - struct Storage { - settings: SharedImmutable, + #[storage] + struct Storage { + settings: SharedImmutable, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(other_asset: AztecAddress, admin: AztecAddress) { let settings = Settings { other_asset, admin }; storage.settings.initialize(settings); } - #[aztec(private)] + #[private] fn fund_transaction_privately(amount: Field, asset: AztecAddress, user_randomness: Field) { // TODO: Once SharedImmutable performs only 1 merkle proof here, we'll save ~4k gates let settings = storage.settings.read_private(); diff --git a/noir-projects/noir-contracts/contracts/router_contract/src/main.nr b/noir-projects/noir-contracts/contracts/router_contract/src/main.nr index fd614c18c54..914b3829f75 100644 --- a/noir-projects/noir-contracts/contracts/router_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/router_contract/src/main.nr @@ -1,23 +1,26 @@ mod test; mod utils; +use dep::aztec::macros::aztec; + /// The purpose of this contract is to perform a check in public without revealing what contract enqued the public /// call. This is achieved by having a private function on this contract that enques the public call and hence /// the `msg_sender` in the public call is the address of this contract. +#[aztec] contract Router { - use aztec::utils::comparison::compare; + use aztec::{macros::functions::{private, public, view, internal}, utils::comparison::compare}; // docs:start:check_timestamp /// Asserts that the current timestamp in the enqueued public call satisfies the `operation` with respect /// to the `value. - #[aztec(private)] + #[private] fn check_timestamp(operation: u8, value: u64) { Router::at(context.this_address())._check_timestamp(operation, value).enqueue_view(&mut context); } - #[aztec(public)] - #[aztec(internal)] - #[aztec(view)] + #[public] + #[internal] + #[view] fn _check_timestamp(operation: u8, value: u64) { let lhs_field = context.timestamp() as Field; let rhs_field = value as Field; @@ -27,14 +30,14 @@ contract Router { /// Asserts that the current block number in the enqueued public call satisfies the `operation` with respect /// to the `value. - #[aztec(private)] + #[private] fn check_block_number(operation: u8, value: Field) { Router::at(context.this_address())._check_block_number(operation, value).enqueue_view(&mut context); } - #[aztec(public)] - #[aztec(internal)] - #[aztec(view)] + #[public] + #[internal] + #[view] fn _check_block_number(operation: u8, value: Field) { assert(compare(context.block_number(), operation, value), "Block number mismatch."); } diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr index 7685bc99b3f..da74e7c3104 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/main.nr @@ -2,6 +2,9 @@ mod public_key_note; // Account contract that uses Schnorr signatures for authentication. // The signing key is stored in an immutable private note and should be different from the encryption/nullifying key. +use dep::aztec::macros::aztec; + +#[aztec] contract SchnorrAccount { use dep::std; @@ -13,17 +16,18 @@ contract SchnorrAccount { }; use dep::aztec::{hash::compute_siloed_nullifier, keys::getters::get_public_keys}; use dep::aztec::oracle::get_nullifier_membership_witness::get_low_nullifier_membership_witness; + use dep::aztec::macros::{storage::storage, functions::{private, initializer, view, noinitcheck}}; use crate::public_key_note::PublicKeyNote; - #[aztec(storage)] - struct Storage { - signing_public_key: PrivateImmutable, + #[storage] + struct Storage { + signing_public_key: PrivateImmutable, } // Constructs the contract - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(signing_pub_key_x: Field, signing_pub_key_y: Field) { let this = context.this_address(); let this_keys = get_public_keys(this); @@ -36,16 +40,16 @@ contract SchnorrAccount { } // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts file - #[aztec(private)] - #[aztec(noinitcheck)] + #[private] + #[noinitcheck] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } - #[aztec(private)] - #[aztec(noinitcheck)] - #[aztec(view)] + #[private] + #[noinitcheck] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr index 55fe9390143..49e4a1f3916 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_account_contract/src/public_key_note.nr @@ -1,16 +1,13 @@ -use dep::aztec::prelude::{NoteHeader, NoteInterface, PrivateContext}; +use dep::aztec::prelude::{NoteHeader, NullifiableNote, PrivateContext}; use dep::aztec::{ note::utils::compute_note_hash_for_nullify, keys::getters::get_nsk_app, - protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note }; -global PUBLIC_KEY_NOTE_LEN: u32 = 3; -// PUBLIC_KEY_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global PUBLIC_KEY_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - // Stores a public key composed of two fields // TODO: Do we need to include a nonce, in case we want to read/nullify/recreate with the same pubkey value? -#[aztec(note)] +#[note] struct PublicKeyNote { x: Field, y: Field, @@ -18,7 +15,7 @@ struct PublicKeyNote { npk_m_hash: Field, } -impl NoteInterface for PublicKeyNote { +impl NullifiableNote for PublicKeyNote { fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); poseidon2_hash_with_separator( diff --git a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr index b6bca50d603..f61dea42972 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_hardcoded_account_contract/src/main.nr @@ -1,5 +1,8 @@ // docs:start:contract // Account contract that uses Schnorr signatures for authentication using a hardcoded public key. +use dep::aztec::macros::aztec; + +#[aztec] contract SchnorrHardcodedAccount { use dep::aztec::prelude::PrivateContext; use dep::authwit::{ @@ -7,18 +10,20 @@ contract SchnorrHardcodedAccount { auth_witness::get_auth_witness }; + use dep::aztec::macros::{functions::{private, view}}; + global public_key_x: Field = 0x16b93f4afae55cab8507baeb8e7ab4de80f5ab1e9e1f5149bf8cd0d375451d90; global public_key_y: Field = 0x208d44b36eb6e73b254921134d002da1a90b41131024e3b1d721259182106205; // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts - #[aztec(private)] + #[private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr index ec4c9037b63..b9f00815f63 100644 --- a/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/schnorr_single_key_account_contract/src/main.nr @@ -1,6 +1,9 @@ mod util; mod auth_oracle; +use dep::aztec::macros::aztec; + +#[aztec] contract SchnorrSingleKeyAccount { use dep::aztec::prelude::PrivateContext; @@ -8,15 +11,17 @@ contract SchnorrSingleKeyAccount { use crate::{util::recover_address, auth_oracle::get_auth_witness}; + use dep::aztec::macros::{functions::{private, view}}; + // Note: If you globally change the entrypoint signature don't forget to update account_entrypoint.ts - #[aztec(private)] + #[private] fn entrypoint(app_payload: AppPayload, fee_payload: FeePayload, cancellable: bool) { let actions = AccountActions::init(&mut context, is_valid_impl); actions.entrypoint(app_payload, fee_payload, cancellable); } - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn verify_private_authwit(inner_hash: Field) -> Field { let actions = AccountActions::init(&mut context, is_valid_impl); actions.verify_private_authwit(inner_hash) diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr index 78d0c5d162a..fae6a1bbad0 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/main.nr @@ -1,6 +1,9 @@ mod types; +use dep::aztec::macros::aztec; + // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. +#[aztec] contract Spam { use dep::aztec::{ @@ -13,18 +16,19 @@ contract Spam { MAX_NOTE_HASHES_PER_CALL, MAX_NULLIFIERS_PER_CALL, GENERATOR_INDEX__NOTE_NULLIFIER, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_CALL } - } + }, + macros::{storage::storage, functions::{private, public, internal}} }; use crate::types::{token_note::TokenNote, balance_set::BalanceSet}; - #[aztec(storage)] - struct Storage { - balances: Map>, - public_balances: Map>, + #[storage] + struct Storage { + balances: Map, Context>, + public_balances: Map, Context>, } - #[aztec(private)] + #[private] fn spam(nullifier_seed: Field, nullifier_count: u32, call_public: bool) { let caller = context.msg_sender(); let caller_keys = get_public_keys(caller); @@ -56,8 +60,8 @@ contract Spam { } } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn public_spam(start: u32, end: u32) { let one = U128::from_integer(1); for i in start..end { diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr index 516ca5341e7..a6496ac4c85 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/types/balance_set.nr @@ -1,5 +1,5 @@ // This file is copied from the token contract. -use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet}; +use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, NullifiableNote, PrivateSet}; use dep::aztec::{ context::{PrivateContext, UnconstrainedContext}, protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, @@ -19,14 +19,14 @@ impl BalanceSet { } impl BalanceSet { - unconstrained pub fn balance_of(self: Self) -> U128 where T: NoteInterface + OwnedNote { + unconstrained pub fn balance_of(self: Self) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { self.balance_of_with_offset(0) } - unconstrained pub fn balance_of_with_offset( + unconstrained pub fn balance_of_with_offset( self: Self, offset: u32 - ) -> U128 where T: NoteInterface + OwnedNote { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { let mut balance = U128::from_integer(0); // docs:start:view_notes let mut options = NoteViewerOptions::new(); @@ -46,11 +46,11 @@ impl BalanceSet { } impl BalanceSet { - pub fn add( + pub fn add( self: Self, owner_npk_m: NpkM, addend: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { if addend == U128::from_integer(0) { OuterNoteEmission::new(Option::none()) } else { @@ -63,11 +63,11 @@ impl BalanceSet { } } - pub fn sub( + pub fn sub( self: Self, owner_npk_m: NpkM, amount: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL); // try_sub may have substracted more or less than amount. We must ensure that we subtracted at least as much as @@ -85,11 +85,11 @@ impl BalanceSet { // The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count // scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of // `try_sub` subtracting an amount smaller than `target_amount`. - pub fn try_sub( + pub fn try_sub( self: Self, target_amount: U128, max_notes: u32 - ) -> U128 where T: NoteInterface + OwnedNote + Eq { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote + Eq { // We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because // we do not need to prove correct execution of the preprocessor. // Because the `min_sum` notes is not constrained, users could choose to e.g. not call it. However, all this @@ -115,10 +115,10 @@ impl BalanceSet { // The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to // 'min_sum' - all it does is remove extra notes if it does reach that value. // Note that proper usage of this preprocessor requires for notes to be sorted in descending order. -pub fn preprocess_notes_min_sum( +pub fn preprocess_notes_min_sum( notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: U128 -) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { +) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + NullifiableNote + OwnedNote { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = U128::from_integer(0); for i in 0..notes.len() { diff --git a/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr index 9d75a920b8a..0e544373b09 100644 --- a/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/spam_contract/src/types/token_note.nr @@ -1,25 +1,16 @@ use dep::aztec::{ - generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot}, - prelude::{NoteHeader, NoteInterface, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, - hash::poseidon2_hash_with_separator, traits::Serialize -}, + prelude::{NoteHeader, NullifiableNote, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, - keys::getters::get_nsk_app + keys::getters::get_nsk_app, macros::notes::note }; -use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; trait OwnedNote { fn new(amount: U128, owner_npk_m_hash: Field) -> Self; fn get_amount(self) -> U128; } - -global TOKEN_NOTE_LEN: u32 = 3; // 3 plus a header. -global TOKEN_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - // docs:start:TokenNote -#[aztec(note)] +#[note] struct TokenNote { // The amount of tokens in the note amount: U128, @@ -30,7 +21,7 @@ struct TokenNote { } // docs:end:TokenNote -impl NoteInterface for TokenNote { +impl NullifiableNote for TokenNote { // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); @@ -52,68 +43,6 @@ impl NoteInterface for TokenNote { GENERATOR_INDEX__NOTE_NULLIFIER ) } - - // docs:start:compute_note_hiding_point - fn compute_note_hiding_point(self) -> Point { - // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let amount_scalar = from_field_unsafe(self.amount.to_integer()); - let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash); - let randomness_scalar = from_field_unsafe(self.randomness); - let slot_scalar = from_field_unsafe(self.header.storage_slot); - // We compute the note hiding point as: - // `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot` - // instead of using pedersen or poseidon2 because it allows us to privately add and subtract from amount - // in public by leveraging homomorphism. - multi_scalar_mul( - [G_amt, G_npk, G_rnd, G_slot], - [amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar] - ) - } - // docs:end:compute_note_hiding_point -} - -impl TokenNote { - // TODO: Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have - // to modify macros and all the related funcs in it. - fn to_note_hiding_point(self) -> TokenNoteHidingPoint { - TokenNoteHidingPoint::new(self.compute_note_hiding_point()) - } -} - -struct TokenNoteHidingPoint { - inner: Point -} - -impl TokenNoteHidingPoint { - fn new(point: Point) -> Self { - Self { inner: point } - } - - fn add_amount(&mut self, amount: U128) { - self.inner = multi_scalar_mul([G_amt], [from_field_unsafe(amount.to_integer())]) + self.inner; - } - - fn add_npk_m_hash(&mut self, npk_m_hash: Field) { - self.inner = multi_scalar_mul([G_npk], [from_field_unsafe(npk_m_hash)]) + self.inner; - } - - fn add_randomness(&mut self, randomness: Field) { - self.inner = multi_scalar_mul([G_rnd], [from_field_unsafe(randomness)]) + self.inner; - } - - fn add_slot(&mut self, slot: Field) { - self.inner = multi_scalar_mul([G_slot], [from_field_unsafe(slot)]) + self.inner; - } - - fn finalize(self) -> Field { - self.inner.x - } -} - -impl Serialize for TokenNoteHidingPoint { - fn serialize(self) -> [Field; POINT_LENGTH] { - self.inner.serialize() - } } impl Eq for TokenNote { diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index ba8b82fee3e..fd229a997d6 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -1,23 +1,29 @@ // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. +use dep::aztec::macros::aztec; + +#[aztec] contract StatefulTest { use dep::aztec::prelude::{Map, PublicMutable, PrivateSet, AztecAddress, FunctionSelector}; use dep::value_note::{balance_utils, utils::{increment, decrement}, value_note::ValueNote}; - use dep::aztec::{initializer::assert_is_initialized_private}; - - #[aztec(storage)] - struct Storage { - notes: Map>, - public_values: Map>, + use dep::aztec::{ + initializer::assert_is_initialized_private, + macros::{storage::storage, functions::{private, public, initializer, noinitcheck, view}} + }; + + #[storage] + struct Storage { + notes: Map, Context>, + public_values: Map, Context>, } - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn constructor(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { StatefulTest::at(context.this_address()).create_note_no_init_check(owner, outgoing_viewer, value).call(&mut context); } - #[aztec(private)] - #[aztec(initializer)] + #[private] + #[initializer] fn wrong_constructor() { let selector = FunctionSelector::from_signature("not_exists(Field)"); let _res = context.call_public_function(context.this_address(), selector, [42]); @@ -25,13 +31,13 @@ contract StatefulTest { // Having _ignored_arg here as it makes the params the same as for the private constructor which makes // contract_class_registration tests way less cluttered. This is a test contract. Don't judge me. - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn public_constructor(owner: AztecAddress, _ignored_arg: AztecAddress, value: Field) { StatefulTest::at(context.this_address()).increment_public_value_no_init_check(owner, value).call(&mut context); } - #[aztec(private)] + #[private] fn create_note(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); @@ -39,8 +45,8 @@ contract StatefulTest { } } - #[aztec(private)] - #[aztec(noinitcheck)] + #[private] + #[noinitcheck] fn create_note_no_init_check(owner: AztecAddress, outgoing_viewer: AztecAddress, value: Field) { if (value != 0) { let loc = storage.notes.at(owner); @@ -48,7 +54,7 @@ contract StatefulTest { } } - #[aztec(private)] + #[private] fn destroy_and_create(recipient: AztecAddress, amount: Field) { assert_is_initialized_private(&mut context); let sender = context.msg_sender(); @@ -60,8 +66,8 @@ contract StatefulTest { increment(recipient_notes, amount, recipient, context.msg_sender()); } - #[aztec(private)] - #[aztec(noinitcheck)] + #[private] + #[noinitcheck] fn destroy_and_create_no_init_check(recipient: AztecAddress, amount: Field) { let sender = context.msg_sender(); @@ -72,14 +78,14 @@ contract StatefulTest { increment(recipient_notes, amount, recipient, context.msg_sender()); } - #[aztec(public)] + #[public] fn increment_public_value(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); } - #[aztec(public)] - #[aztec(noinitcheck)] + #[public] + #[noinitcheck] fn increment_public_value_no_init_check(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); @@ -94,9 +100,9 @@ contract StatefulTest { // docs:end:get_balance } - #[aztec(public)] - #[aztec(noinitcheck)] - #[aztec(view)] + #[public] + #[noinitcheck] + #[view] fn get_public_value(owner: AztecAddress) -> pub Field { storage.public_values.at(owner).read() } diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr index 4d6422e14fe..fbdcf745c57 100644 --- a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -1,24 +1,27 @@ // A contract used along with `StaticParent` contract to test static calls. +use dep::aztec::macros::aztec; + +#[aztec] contract StaticChild { - use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize}; + use dep::aztec::prelude::{AztecAddress, PublicMutable, PrivateSet}; use dep::aztec::{ - context::{PublicContext, gas::GasOpts}, protocol_types::{abis::{call_context::CallContext}}, - note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, + note::{note_getter_options::NoteGetterOptions}, encrypted_logs::encrypted_note_emission::encode_and_encrypt_note, - keys::getters::get_public_keys, utils::comparison::Comparator + keys::getters::get_public_keys, utils::comparison::Comparator, + macros::{storage::storage, functions::{private, public, view}} }; use dep::value_note::value_note::ValueNote; - #[aztec(storage)] - struct Storage { - current_value: PublicMutable, - a_private_value: PrivateSet, + #[storage] + struct Storage { + current_value: PublicMutable, + a_private_value: PrivateSet, } // Returns base_value + chain_id + version + block_number + timestamp statically - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn pub_get_value(base_value: Field) -> Field { let return_value = base_value + context.chain_id() @@ -30,7 +33,7 @@ contract StaticChild { } // Sets `current_value` to `new_value` - #[aztec(public)] + #[public] fn pub_set_value(new_value: Field) -> Field { storage.current_value.write(new_value); context.emit_unencrypted_log(new_value); @@ -38,8 +41,8 @@ contract StaticChild { } // View function that attempts to modify state. Should always fail regardless how it's called. - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_illegal_set_value(new_value: Field, owner: AztecAddress) -> Field { let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); let mut note = ValueNote::new(new_value, owner_npk_m_hash); @@ -48,7 +51,7 @@ contract StaticChild { } // Modify a note - #[aztec(private)] + #[private] fn private_set_value( new_value: Field, owner: AztecAddress, @@ -61,8 +64,8 @@ contract StaticChild { } // Retrieve note value statically - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_get_value(amount: Field, owner: AztecAddress) -> Field { let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); let mut options = NoteGetterOptions::new(); @@ -76,7 +79,7 @@ contract StaticChild { } // Increments `current_value` by `new_value` - #[aztec(public)] + #[public] fn pub_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); @@ -85,8 +88,8 @@ contract StaticChild { } // View function that attempts to modify state. Should always fail regardless how it's called. - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn pub_illegal_inc_value(new_value: Field) -> Field { let old_value = storage.current_value.read(); storage.current_value.write(old_value + new_value); diff --git a/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr index 1b30deb1b8f..d363d073fb0 100644 --- a/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr @@ -1,12 +1,15 @@ // A contract used along with `StaticChild` contract to test static calls. +use dep::aztec::macros::aztec; + +#[aztec] contract StaticParent { use dep::aztec::prelude::{AztecAddress, FunctionSelector}; - use dep::aztec::context::gas::GasOpts; + use dep::aztec::{context::gas::GasOpts, macros::functions::{private, public, view}}; use dep::static_child_contract::StaticChild; // Public function to directly call another public function to the target_contract using the selector and value provided - #[aztec(public)] + #[public] fn public_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -21,7 +24,7 @@ contract StaticParent { } // Private function to directly call another private function to the target_contract using the selector and args provided - #[aztec(private)] + #[private] fn private_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -31,7 +34,7 @@ contract StaticParent { } // Just like function above but with 3 args. - #[aztec(private)] + #[private] fn private_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -41,7 +44,7 @@ contract StaticParent { } // Private function to enqueue a call to a public function of another contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -50,7 +53,7 @@ contract StaticParent { context.call_public_function(target_contract, target_selector, args); } - #[aztec(private)] + #[private] fn private_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -60,7 +63,7 @@ contract StaticParent { } // Private function to statically call another private function to the target_contract using the selector and values provided - #[aztec(private)] + #[private] fn private_static_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -70,7 +73,7 @@ contract StaticParent { } // Same as above but using a specific function from the interface - #[aztec(private)] + #[private] fn private_get_value_from_child( target_contract: AztecAddress, value: Field, @@ -80,7 +83,7 @@ contract StaticParent { } // Private function to set a static context and verify correct propagation for nested private calls - #[aztec(private)] + #[private] fn private_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -90,7 +93,7 @@ contract StaticParent { } // Just like function above but with 3 args. - #[aztec(private)] + #[private] fn private_nested_static_call_3_args( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -100,7 +103,7 @@ contract StaticParent { } // Public function to statically call another public function to the target_contract using the selector and value provided - #[aztec(public)] + #[public] fn public_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -115,13 +118,13 @@ contract StaticParent { } // Same as above but using a specific function from the interface - #[aztec(public)] + #[public] fn public_get_value_from_child(target_contract: AztecAddress, value: Field) -> Field { StaticChild::at(target_contract).pub_get_value(value).view(&mut context) } // Public function to set a static context and verify correct propagation for nested public calls - #[aztec(public)] + #[public] fn public_nested_static_call( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -132,7 +135,7 @@ contract StaticParent { } // Private function to enqueue a static call to a public function of another contract, passing the target arguments provided - #[aztec(private)] + #[private] fn enqueue_static_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, @@ -142,13 +145,13 @@ contract StaticParent { } // Same as above but using a specific function from the interface - #[aztec(private)] + #[private] fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); } // Private function to set a static context and verify correct propagation of nested enqueuing of public calls - #[aztec(private)] + #[private] fn enqueue_static_nested_call_to_pub_function( target_contract: AztecAddress, target_selector: FunctionSelector, diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr index 462d125c5f2..c5afcfbfaec 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/main.nr @@ -1,6 +1,9 @@ mod test_note; // A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. +use dep::aztec::macros::aztec; + +#[aztec] contract Test { use dep::aztec::prelude::{ @@ -33,15 +36,19 @@ contract Test { note_getter_options::NoteStatus }, deploy::deploy_contract as aztec_deploy_contract, - oracle::{encryption::aes128_encrypt, unsafe_rand::unsafe_rand}, utils::comparison::Comparator + oracle::{encryption::aes128_encrypt, unsafe_rand::unsafe_rand}, utils::comparison::Comparator, + macros::{storage::storage, events::event, functions::{private, public, internal}} }; use dep::token_portal_content_hash_lib::{get_mint_private_content_hash, get_mint_public_content_hash}; use dep::value_note::value_note::ValueNote; - use std::embedded_curve_ops::fixed_base_scalar_mul as derive_public_key; + // TODO investigate why the macros require EmbeddedCurvePoint and EmbeddedCurveScalar + use std::embedded_curve_ops::{EmbeddedCurveScalar, EmbeddedCurvePoint, fixed_base_scalar_mul as derive_public_key}; + use std::meta::derive; use crate::test_note::TestNote; - #[aztec(event)] + #[event] + #[derive(Serialize)] struct ExampleEvent { value0: Field, value1: Field, @@ -50,18 +57,18 @@ contract Test { value4: Field, } - #[aztec(storage)] - struct Storage { - example_constant: PrivateImmutable, - example_set: PrivateSet, + #[storage] + struct Storage { + example_constant: PrivateImmutable, + example_set: PrivateSet, } - #[aztec(private)] + #[private] fn get_ovsk_app(ovpk_m_hash: Field) -> Field { context.request_ovsk_app(ovpk_m_hash) } - #[aztec(private)] + #[private] fn get_master_incoming_viewing_public_key(address: AztecAddress) -> [Field; 2] { let ivpk_m = get_public_keys(address).ivpk_m; @@ -69,12 +76,12 @@ contract Test { } // Get the address of this contract (taken from the input context) - #[aztec(private)] + #[private] fn get_this_address() -> AztecAddress { context.this_address() } - #[aztec(private)] + #[private] fn set_tx_max_block_number(max_block_number: u32, enqueue_public_call: bool) { context.set_tx_max_block_number(max_block_number); @@ -83,11 +90,11 @@ contract Test { } } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn dummy_public_call() {} - #[aztec(private)] + #[private] fn call_create_note( value: Field, owner: AztecAddress, @@ -104,7 +111,7 @@ contract Test { create_note(&mut context, storage_slot, &mut note).emit(encode_and_encrypt_note(&mut context, outgoing_viewer, owner)); } - #[aztec(private)] + #[private] fn call_get_notes(storage_slot: Field, active_or_nullified: bool) -> Field { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -120,7 +127,7 @@ contract Test { notes.get(0).value } - #[aztec(private)] + #[private] fn call_get_notes_many(storage_slot: Field, active_or_nullified: bool) -> [Field; 2] { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -166,7 +173,7 @@ contract Test { [notes.get(0).value, notes.get(1).value] } - #[aztec(private)] + #[private] fn call_destroy_note(storage_slot: Field) { assert( storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant" @@ -181,7 +188,7 @@ contract Test { destroy_note_unsafe(&mut context, note, note_hash); } - #[aztec(private)] + #[private] fn test_code_gen( a_field: Field, a_bool: bool, @@ -208,7 +215,7 @@ contract Test { args.hash() } - #[aztec(private)] + #[private] fn test_setting_teardown() { context.set_public_teardown_function( context.this_address(), @@ -219,13 +226,13 @@ contract Test { ); } - #[aztec(private)] + #[private] fn test_setting_fee_payer() { context.set_as_fee_payer(); } // Purely exists for testing - #[aztec(public)] + #[public] fn create_l2_to_l1_message_public(amount: Field, secret_hash: Field, portal_address: EthAddress) { // Create a commitment to the amount let note = DummyNote::new(amount, secret_hash); @@ -234,32 +241,32 @@ contract Test { context.message_portal(portal_address, note.get_commitment()); } - #[aztec(public)] + #[public] fn create_l2_to_l1_message_arbitrary_recipient_public(content: Field, recipient: EthAddress) { // Public oracle call to emit new commitment. context.message_portal(recipient, content); } - #[aztec(private)] + #[private] fn create_l2_to_l1_message_arbitrary_recipient_private(content: Field, recipient: EthAddress) { // Public oracle call to emit new commitment. context.message_portal(recipient, content); } // Purely exists for testing - #[aztec(public)] + #[public] fn emit_nullifier_public(nullifier: Field) { context.push_nullifier(nullifier); } // Forcefully emits a nullifier (for testing purposes) - #[aztec(private)] + #[private] fn emit_nullifier(nullifier: Field) { context.push_nullifier(nullifier); } // For testing non-note encrypted logs - #[aztec(private)] + #[private] fn emit_array_as_encrypted_log( fields: [Field; 5], owner: AztecAddress, @@ -302,7 +309,7 @@ contract Test { } } - #[aztec(private)] + #[private] fn emit_encrypted_logs_nested(value: Field, owner: AztecAddress, outgoing_viewer: AztecAddress) { let mut storage_slot = storage.example_constant.get_storage_slot() + 1; Test::at(context.this_address()).call_create_note(value, owner, outgoing_viewer, storage_slot).call(&mut context); @@ -317,14 +324,14 @@ contract Test { } // docs:start:is-time-equal - #[aztec(public)] + #[public] fn is_time_equal(time: u64) -> u64 { assert(context.timestamp() == time); time } // docs:end:is-time-equal - #[aztec(public)] + #[public] fn emit_unencrypted(value: Field) { // docs:start:emit_unencrypted context.emit_unencrypted_log(/*message=*/ value); @@ -333,7 +340,7 @@ contract Test { // docs:end:emit_unencrypted } - #[aztec(public)] + #[public] fn consume_mint_public_message( to: AztecAddress, amount: Field, @@ -346,7 +353,7 @@ contract Test { context.consume_l1_to_l2_message(content_hash, secret, portal_address, message_leaf_index); } - #[aztec(private)] + #[private] fn consume_mint_private_message( secret_hash_for_redeeming_minted_notes: Field, amount: Field, @@ -362,7 +369,7 @@ contract Test { ); } - #[aztec(public)] + #[public] fn consume_message_from_arbitrary_sender_public( content: Field, secret: Field, @@ -373,45 +380,41 @@ contract Test { context.consume_l1_to_l2_message(content, secret, sender, message_leaf_index); } - #[aztec(private)] - fn consume_message_from_arbitrary_sender_private( - content: Field, - secret: Field, - sender: EthAddress - ) { + #[private] + fn consume_message_from_arbitrary_sender_private(content: Field, secret: Field, sender: EthAddress) { // Consume message and emit nullifier context.consume_l1_to_l2_message(content, secret, sender); } - #[aztec(private)] + #[private] fn set_constant(value: Field) { let mut note = TestNote::new(value); storage.example_constant.initialize(&mut note).discard(); } - #[aztec(private)] + #[private] fn assert_private_global_vars(chain_id: Field, version: Field) { assert(context.chain_id() == chain_id, "Invalid chain id"); assert(context.version() == version, "Invalid version"); } - #[aztec(private)] + #[private] fn encrypt(input: [u8; 64], iv: [u8; 16], key: [u8; 16]) -> [u8; 80] { aes128_encrypt(input, iv, key) } - #[aztec(private)] + #[private] fn encrypt_with_padding(input: [u8; 65], iv: [u8; 16], key: [u8; 16]) -> [u8; 80] { aes128_encrypt(input, iv, key) } - #[aztec(private)] + #[private] fn compute_note_header_ciphertext(secret: Scalar, ivpk: IvpkM) -> [u8; 48] { EncryptedLogHeader::new(context.this_address()).compute_ciphertext(secret, ivpk) } // 64 bytes + 32 * #fields + 16 = 112 bytes - #[aztec(private)] + #[private] fn compute_incoming_log_body_ciphertext( secret: Scalar, ivpk: IvpkM, @@ -422,7 +425,7 @@ contract Test { EncryptedLogIncomingBody::from_note(note, storage_slot).compute_ciphertext(secret, ivpk).as_array() } - #[aztec(private)] + #[private] fn compute_outgoing_log_body_ciphertext( eph_sk: Scalar, recipient: AztecAddress, @@ -433,7 +436,7 @@ contract Test { EncryptedLogOutgoingBody::new(eph_sk, recipient, recipient_ivpk).compute_ciphertext(ovsk_app, eph_pk) } - #[aztec(public)] + #[public] fn assert_public_global_vars( chain_id: Field, version: Field, @@ -450,23 +453,23 @@ contract Test { assert(context.fee_per_l2_gas() == fee_per_l2_gas, "Invalid fee per l2 gas"); } - #[aztec(private)] + #[private] fn assert_header_private(header_hash: Field) { assert(context.historical_header.hash() == header_hash, "Invalid header hash"); } // TODO(4840): add AVM opcodes for getting header (members) - //#[aztec(public)] + //#[public] //fn assert_header_public(header_hash: Field) { // assert(context.historical_header.hash() == header_hash, "Invalid header hash"); //} - #[aztec(private)] + #[private] fn deploy_contract(target: AztecAddress) { aztec_deploy_contract(&mut context, target); } - #[aztec(private)] + #[private] // Adapted from TokenContract#redeem_shield but without an initcheck so it can be run in simulator/src/client/private_execution.test.ts fn consume_note_from_secret(secret: Field) { let notes_set = storage.example_set; @@ -483,7 +486,7 @@ contract Test { } // This function is used in the e2e_state_vars to test the SharedMutablePrivateGetter in isolation - #[aztec(private)] + #[private] fn test_shared_mutable_private_getter( contract_address_to_read: AztecAddress, storage_slot_of_shared_mutable: Field @@ -500,7 +503,7 @@ contract Test { ret.to_field() } - #[aztec(private)] + #[private] fn test_nullifier_key_freshness(address: AztecAddress, public_nullifying_key: Point) { assert_eq(get_public_keys(address).npk_m.inner, public_nullifying_key); } diff --git a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr index a01a0fcab4f..2adc5bf355a 100644 --- a/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr +++ b/noir-projects/noir-contracts/contracts/test_contract/src/test_note.nr @@ -1,19 +1,18 @@ -use dep::aztec::{note::{note_header::NoteHeader, note_interface::NoteInterface}, context::PrivateContext}; - -global TEST_NOTE_LEN: u32 = 1; -// TEST_NOTE_LENGTH * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global TEST_NOTE_BYTES_LENGTH: u32 = 1 * 32 + 64; +use dep::aztec::{ + note::{note_header::NoteHeader, note_interface::NullifiableNote}, context::PrivateContext, + macros::notes::note +}; // A note which stores a field and is expected to be passed around using the `addNote` function. // WARNING: This Note is not private as it does not contain randomness and hence it can be easy to perform // serialized_note attack on it. This note has been developed purely for testing purposes so that it can easily be // manually added to PXE. Do not use for real applications. -#[aztec(note)] +#[note] struct TestNote { value: Field, } -impl NoteInterface for TestNote { +impl NullifiableNote for TestNote { fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { // This note is expected to be shared between users and for this reason can't be nullified using a secret. diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr index 55384dd4b57..d3c6e29746d 100644 --- a/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/test_log_contract/src/main.nr @@ -1,33 +1,42 @@ +use dep::aztec::macros::aztec; + +#[aztec] contract TestLog { + use std::meta::derive; use dep::aztec::prelude::PrivateSet; - use dep::aztec::protocol_types::{scalar::Scalar, address::AztecAddress}; + use dep::aztec::protocol_types::{scalar::Scalar, address::AztecAddress, traits::Serialize}; use dep::aztec::keys::public_keys::IvpkM; use dep::value_note::value_note::ValueNote; use dep::aztec::encrypted_logs::incoming_body::EncryptedLogIncomingBody; use dep::aztec::encrypted_logs::encrypted_event_emission::encode_and_encrypt_event_with_randomness; use dep::aztec::unencrypted_logs::unencrypted_event_emission::encode_event; + use dep::aztec::macros::{storage::storage, events::event, functions::{private, public}}; + + use std::embedded_curve_ops::EmbeddedCurveScalar; - #[aztec(event)] + #[event] + #[derive(Serialize)] struct ExampleEvent0 { value0: Field, value1: Field, } - #[aztec(event)] + #[event] + #[derive(Serialize)] struct ExampleEvent1 { value2: AztecAddress, value3: u8, } - #[aztec(storage)] - struct Storage { - example_set: PrivateSet, + #[storage] + struct Storage { + example_set: PrivateSet, } // EXAMPLE_EVENT_0_BYTES_LEN + 16 global EXAMPLE_EVENT_0_CIPHERTEXT_BYTES_LEN = 144; - #[aztec(private)] + #[private] fn compute_incoming_log_body_ciphertext( secret: Scalar, ivpk: IvpkM, @@ -41,7 +50,7 @@ contract TestLog { ).compute_ciphertext(secret, ivpk).as_array() } - #[aztec(private)] + #[private] fn emit_encrypted_events(other: AztecAddress, randomness: [Field; 2], preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; @@ -79,7 +88,7 @@ contract TestLog { ); } - #[aztec(public)] + #[public] fn emit_unencrypted_events(preimages: [Field; 4]) { let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] }; diff --git a/noir-projects/noir-contracts/contracts/test_log_contract/src/test.nr b/noir-projects/noir-contracts/contracts/test_log_contract/src/test.nr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index 3c1c6498077..45cc7e76994 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -8,13 +8,17 @@ mod types; // Including a nonce in the message hash ensures that the message can only be used once. // The SharedMutables are used for access control related to minters and blacklist. +use dep::aztec::macros::aztec; + +#[aztec] contract TokenBlacklist { // Libs use dep::aztec::{ hash::compute_secret_hash, prelude::{AztecAddress, Map, NoteGetterOptions, PrivateSet, PublicMutable, SharedMutable}, - encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note, encode_and_encrypt_note_unconstrained}, - utils::comparison::Comparator + encrypted_logs::encrypted_note_emission::{encode_and_encrypt_note_unconstrained, encode_and_encrypt_note}, + utils::comparison::Comparator, + macros::{storage::storage, functions::{private, public, initializer, view, internal}} }; use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; @@ -24,42 +28,42 @@ contract TokenBlacklist { // Changing an address' roles has a certain block delay before it goes into effect. global CHANGE_ROLES_DELAY_BLOCKS: u32 = 2; - #[aztec(storage)] - struct Storage { - balances: BalancesMap, - total_supply: PublicMutable, - pending_shields: PrivateSet, - public_balances: Map>, - roles: Map>, + #[storage] + struct Storage { + balances: BalancesMap, + total_supply: PublicMutable, + pending_shields: PrivateSet, + public_balances: Map, Context>, + roles: Map, Context>, } // docs:start:constructor - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress) { let admin_roles = UserFlags { is_admin: true, is_minter: false, is_blacklisted: false }; storage.roles.at(admin).schedule_value_change(admin_roles); } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn total_supply() -> pub Field { storage.total_supply.read().to_field() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn balance_of_public(owner: AztecAddress) -> pub Field { storage.public_balances.at(owner).read().to_field() } - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_roles(user: AztecAddress) -> UserFlags { storage.roles.at(user).get_current_value_in_public() } - #[aztec(public)] + #[public] fn update_roles(user: AztecAddress, roles: UserFlags) { let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); assert(caller_roles.is_admin, "caller is not admin"); @@ -67,7 +71,7 @@ contract TokenBlacklist { storage.roles.at(user).schedule_value_change(roles); } - #[aztec(public)] + #[public] fn mint_public(to: AztecAddress, amount: Field) { let to_roles = storage.roles.at(to).get_current_value_in_public(); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); @@ -83,7 +87,7 @@ contract TokenBlacklist { storage.total_supply.write(supply); } - #[aztec(public)] + #[public] fn mint_private(amount: Field, secret_hash: Field) { let caller_roles = storage.roles.at(context.msg_sender()).get_current_value_in_public(); assert(caller_roles.is_minter, "caller is not minter"); @@ -96,7 +100,7 @@ contract TokenBlacklist { pending_shields.insert_from_public(&mut note); } - #[aztec(public)] + #[public] fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -118,7 +122,7 @@ contract TokenBlacklist { pending_shields.insert_from_public(&mut note); } - #[aztec(public)] + #[public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -139,7 +143,7 @@ contract TokenBlacklist { storage.public_balances.at(to).write(to_balance); } - #[aztec(public)] + #[public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_public(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -158,7 +162,7 @@ contract TokenBlacklist { storage.total_supply.write(new_supply); } - #[aztec(private)] + #[private] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let to_roles = storage.roles.at(to).get_current_value_in_private(); assert(!to_roles.is_blacklisted, "Blacklisted: Recipient"); @@ -182,7 +186,7 @@ contract TokenBlacklist { storage.balances.add(to, U128::from_integer(amount)).emit(encode_and_encrypt_note(&mut context, caller, to)); } - #[aztec(private)] + #[private] fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -201,7 +205,7 @@ contract TokenBlacklist { } // docs:start:transfer_private - #[aztec(private)] + #[private] fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -219,7 +223,7 @@ contract TokenBlacklist { storage.balances.add(to, amount).emit(encode_and_encrypt_note_unconstrained(&mut context, from, to)); } - #[aztec(private)] + #[private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { let from_roles = storage.roles.at(from).get_current_value_in_private(); assert(!from_roles.is_blacklisted, "Blacklisted: Sender"); @@ -237,15 +241,15 @@ contract TokenBlacklist { /// Internal /// - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); storage.public_balances.at(to).write(new_balance); } - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _reduce_total_supply(amount: Field) { // Only to be called from burn. let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr index f1a6d44fdbb..78ebac2f496 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/balances_map.nr @@ -1,4 +1,4 @@ -use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet, Map}; +use dep::aztec::prelude::{AztecAddress, NoteGetterOptions, NoteViewerOptions, NoteInterface, NullifiableNote, PrivateSet, Map}; use dep::aztec::{ context::{PrivateContext, UnconstrainedContext}, protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, @@ -24,18 +24,18 @@ impl BalancesMap { } impl BalancesMap { - unconstrained pub fn balance_of( + unconstrained pub fn balance_of( self: Self, owner: AztecAddress - ) -> U128 where T: NoteInterface + OwnedNote { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { self.balance_of_with_offset(owner, 0) } - unconstrained pub fn balance_of_with_offset( + unconstrained pub fn balance_of_with_offset( self: Self, owner: AztecAddress, offset: u32 - ) -> U128 where T: NoteInterface + OwnedNote { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { let mut balance = U128::from_integer(0); // docs:start:view_notes let mut options = NoteViewerOptions::new(); @@ -55,11 +55,12 @@ impl BalancesMap { } impl BalancesMap { - pub fn add( + + pub fn add( self: Self, owner: AztecAddress, addend: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { if addend == U128::from_integer(0) { OuterNoteEmission::new(Option::none()) } else { @@ -75,11 +76,11 @@ impl BalancesMap { } } - pub fn sub( + pub fn sub( self: Self, owner: AztecAddress, subtrahend: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); let notes = self.map.at(owner).pop_notes(options); @@ -100,10 +101,10 @@ impl BalancesMap { } } -pub fn filter_notes_min_sum( +pub fn filter_notes_min_sum( notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: U128 -) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { +) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = U128::from_integer(0); for i in 0..notes.len() { diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr index 5023d643877..f48c5d090be 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/token_note.nr @@ -1,8 +1,8 @@ use dep::aztec::{ - prelude::{NoteHeader, NoteInterface, PrivateContext}, + prelude::{NoteHeader, NullifiableNote, PrivateContext}, protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, - keys::getters::get_nsk_app + keys::getters::get_nsk_app, macros::notes::note }; trait OwnedNote { @@ -10,11 +10,7 @@ trait OwnedNote { fn get_amount(self) -> U128; } -global TOKEN_NOTE_LEN: u32 = 3; // 3 plus a header. -// TOKEN_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global TOKEN_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - -#[aztec(note)] +#[note] struct TokenNote { // The amount of tokens in the note amount: U128, @@ -24,7 +20,7 @@ struct TokenNote { randomness: Field, } -impl NoteInterface for TokenNote { +impl NullifiableNote for TokenNote { // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr index b6c641d48a7..3d297e6ab7c 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/types/transparent_note.nr @@ -1,40 +1,22 @@ // docs:start:token_types_all use dep::aztec::{ note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_nullify}, - prelude::{NoteHeader, NoteInterface, PrivateContext}, - protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} + prelude::{NoteHeader, NullifiableNote, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note }; -global TRANSPARENT_NOTE_LEN: u32 = 2; -// TRANSPARENT_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global TRANSPARENT_NOTE_BYTES_LEN: u32 = 2 * 32 + 64; - // Transparent note represents a note that is created in the clear (public execution), but can only be spent by those // that know the preimage of the "secret_hash" (the secret). This is typically used when shielding a token balance. // Owner of the tokens provides a "secret_hash" as an argument to the public "shield" function and then the tokens // can be redeemed in private by presenting the preimage of the "secret_hash" (the secret). -#[aztec(note)] +#[note] struct TransparentNote { amount: Field, secret_hash: Field, } -struct TransparentNoteProperties { - amount: PropertySelector, - secret_hash: PropertySelector, -} - -impl NoteInterface for TransparentNote { - - // Custom serialization to avoid disclosing the secret field - fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { - [self.amount, self.secret_hash] - } - - // Custom deserialization since we don't have access to the secret plaintext - fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { - TransparentNote { amount: serialized_note[0], secret_hash: serialized_note[1], header: NoteHeader::empty() } - } +impl NullifiableNote for TransparentNote { fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { self.compute_nullifier_without_context() @@ -62,15 +44,6 @@ impl TransparentNote { pub fn new(amount: Field, secret_hash: Field) -> Self { TransparentNote { amount, secret_hash, header: NoteHeader::empty() } } - - // CUSTOM FUNCTIONS FOR THIS NOTE TYPE - // Custom serialization forces us to manually create the metadata struct and its getter - pub fn properties() -> TransparentNoteProperties { - TransparentNoteProperties { - amount: PropertySelector { index: 0, offset: 0, length: 32 }, - secret_hash: PropertySelector { index: 1, offset: 0, length: 32 } - } - } } impl Eq for TransparentNote { diff --git a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr index d88eca6b200..76c04213dc3 100644 --- a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr @@ -5,44 +5,49 @@ // And corresponds to a Token on L2 that uses the `AuthWit` accounts pattern. // Bridge has to be set as a minter on the token before it can be used +use dep::aztec::macros::aztec; + +#[aztec] contract TokenBridge { use dep::aztec::prelude::{AztecAddress, EthAddress, PublicMutable, SharedImmutable}; use dep::token_portal_content_hash_lib::{get_mint_public_content_hash, get_mint_private_content_hash, get_withdraw_content_hash}; use dep::token::Token; + + use dep::aztec::macros::{storage::storage, functions::{public, initializer, private, internal}}; // docs:end:token_bridge_imports // docs:start:token_bridge_storage_and_constructor // Storage structure, containing all storage, and specifying what slots they use. - #[aztec(storage)] - struct Storage { - token: PublicMutable, - portal_address: SharedImmutable, + #[storage] + struct Storage { + token: PublicMutable, + portal_address: SharedImmutable, } // Constructs the contract. - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(token: AztecAddress, portal_address: EthAddress) { storage.token.write(token); storage.portal_address.initialize(portal_address); } // docs:end:token_bridge_storage_and_constructor - #[aztec(private)] + #[private] fn get_portal_address() -> EthAddress { storage.portal_address.read_private() } - #[aztec(public)] + #[public] fn get_portal_address_public() -> EthAddress { storage.portal_address.read_public() } // docs:start:claim_public // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly - #[aztec(public)] + #[public] fn claim_public(to: AztecAddress, amount: Field, secret: Field, message_leaf_index: Field) { let content_hash = get_mint_public_content_hash(to, amount); @@ -62,7 +67,7 @@ contract TokenBridge { // docs:start:exit_to_l1_public // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures - #[aztec(public)] + #[public] fn exit_to_l1_public( recipient: EthAddress, // ethereum address to withdraw to amount: Field, @@ -81,7 +86,7 @@ contract TokenBridge { // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount in private assets // User needs to call token.redeem_shield() to get the private assets // TODO(#8416): Consider creating a truly private claim flow. - #[aztec(private)] + #[private] fn claim_private( secret_hash_for_redeeming_minted_notes: Field, // secret hash used to redeem minted notes at a later time. This enables anyone to call this function and mint tokens to a user on their behalf amount: Field, @@ -106,7 +111,7 @@ contract TokenBridge { // docs:start:exit_to_l1_private // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately // Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures - #[aztec(private)] + #[private] fn exit_to_l1_private( token: AztecAddress, recipient: EthAddress, // ethereum address to withdraw to @@ -129,8 +134,8 @@ contract TokenBridge { /// docs:end:exit_to_l1_private // docs:start:get_token - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_token() -> AztecAddress { storage.token.read() } @@ -140,16 +145,16 @@ contract TokenBridge { // This is a public call as we need to read from public storage. // Also, note that user hashes their secret in private and only sends the hash in public // meaning only user can `redeem_shield` at a later time with their secret. - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _call_mint_on_token(amount: Field, secret_hash: Field) { Token::at(storage.token.read()).mint_private(amount, secret_hash).call(&mut context); } // docs:end:call_mint_on_token // docs:start:assert_token_is_same - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _assert_token_is_same(token: AztecAddress) { assert(storage.token.read().eq(token), "Token address is not the same as seen in storage"); } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 81cb3a7f8da..a5ec69962f3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -3,51 +3,52 @@ mod types; mod test; +use dep::aztec::macros::aztec; + // Minimal token implementation that supports `AuthWit` accounts. // The auth message follows a similar pattern to the cross-chain message and includes a designated caller. // The designated caller is ALWAYS used here, and not based on a flag as cross-chain. // message hash = H([caller, contract, selector, ...args]) // To be read as `caller` calls function at `contract` defined by `selector` with `args` // Including a nonce in the message hash ensures that the message can only be used once. - +#[aztec] contract Token { // Libs + use std::meta::derive; use dep::compressed_string::FieldCompressedString; use dep::aztec::{ context::{PrivateContext, PrivateCallInterface}, hash::compute_secret_hash, - prelude::{ - NoteGetterOptions, Map, PublicMutable, SharedImmutable, PrivateSet, AztecAddress, - FunctionSelector, NoteHeader - }, + prelude::{NoteGetterOptions, Map, PublicMutable, SharedImmutable, PrivateSet, AztecAddress, FunctionSelector}, encrypted_logs::{ encrypted_note_emission::{encode_and_encrypt_note_with_keys, encode_and_encrypt_note_with_keys_unconstrained}, encrypted_event_emission::encode_and_encrypt_event_with_keys_unconstrained }, - keys::getters::get_public_keys, utils::comparison::Comparator + keys::getters::get_public_keys, + macros::{storage::storage, events::event, functions::{initializer, private, view, public}}, + utils::comparison::Comparator, protocol_types::{point::Point, traits::Serialize} }; // docs:start:import_authwit use dep::authwit::auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public, compute_authwit_nullifier}; // docs:end:import_authwit - use crate::types::{ - transparent_note::TransparentNote, token_note::{TokenNote, TokenNoteHidingPoint}, - balance_set::BalanceSet - }; + use crate::types::{transparent_note::TransparentNote, token_note::TokenNote, balance_set::BalanceSet}; + // docs:end::imports // In the first transfer iteration we are computing a lot of additional information (validating inputs, retrieving // keys, etc.), so the gate count is already relatively high. We therefore only read a few notes to keep the happy // case with few constraints. - global INITIAL_TRANSFER_CALL_MAX_NOTES = 2; + global INITIAL_TRANSFER_CALL_MAX_NOTES: u32 = 2; // All the recursive call does is nullify notes, meaning the gate count is low, but it is all constant overhead. We // therefore read more notes than in the base case to increase the efficiency of the overhead, since this results in // an overall small circuit regardless. - global RECURSIVE_TRANSFER_CALL_MAX_NOTES = 8; + global RECURSIVE_TRANSFER_CALL_MAX_NOTES: u32 = 8; - #[aztec(event)] + #[event] + #[derive(Serialize)] struct Transfer { from: AztecAddress, to: AztecAddress, @@ -55,33 +56,33 @@ contract Token { } // docs:start:storage_struct - #[aztec(storage)] - struct Storage { + #[storage] + struct Storage { // docs:start:storage_admin - admin: PublicMutable, + admin: PublicMutable, // docs:end:storage_admin // docs:start:storage_minters - minters: Map>, + minters: Map, Context>, // docs:end:storage_minters // docs:start:storage_balances - balances: Map>, + balances: Map, Context>, // docs:end:storage_balances - total_supply: PublicMutable, + total_supply: PublicMutable, // docs:start:storage_pending_shields - pending_shields: PrivateSet, + pending_shields: PrivateSet, // docs:end:storage_pending_shields - public_balances: Map>, - symbol: SharedImmutable, - name: SharedImmutable, + public_balances: Map, Context>, + symbol: SharedImmutable, + name: SharedImmutable, // docs:start:storage_decimals - decimals: SharedImmutable, + decimals: SharedImmutable, // docs:end:storage_decimals } // docs:end:storage_struct // docs:start:constructor - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) { assert(!admin.is_zero(), "invalid admin"); storage.admin.write(admin); @@ -93,9 +94,8 @@ contract Token { // docs:end:initialize_decimals } // docs:end:constructor - // docs:start:set_admin - #[aztec(public)] + #[public] fn set_admin(new_admin: AztecAddress) { assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); // docs:start:write_admin @@ -103,81 +103,71 @@ contract Token { // docs:end:write_admin } // docs:end:set_admin - - #[aztec(public)] - #[aztec(view)] - fn public_get_name() -> pub FieldCompressedString { + #[public] + #[view] + fn public_get_name() -> FieldCompressedString { storage.name.read_public() } - #[aztec(private)] - #[aztec(view)] - fn private_get_name() -> pub FieldCompressedString { + #[private] + #[view] + fn private_get_name() -> FieldCompressedString { storage.name.read_private() } - - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } - - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } - - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn public_get_decimals() -> pub u8 { // docs:start:read_decimals_public storage.decimals.read_public() // docs:end:read_decimals_public } - - #[aztec(private)] - #[aztec(view)] + #[private] + #[view] fn private_get_decimals() -> pub u8 { // docs:start:read_decimals_private storage.decimals.read_private() // docs:end:read_decimals_private } - // docs:start:admin - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn get_admin() -> Field { storage.admin.read().to_field() } // docs:end:admin - // docs:start:is_minter - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn is_minter(minter: AztecAddress) -> bool { storage.minters.at(minter).read() } // docs:end:is_minter - // docs:start:total_supply - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn total_supply() -> Field { storage.total_supply.read().to_integer() } // docs:end:total_supply - // docs:start:balance_of_public - #[aztec(public)] - #[aztec(view)] + #[public] + #[view] fn balance_of_public(owner: AztecAddress) -> Field { storage.public_balances.at(owner).read().to_integer() } // docs:end:balance_of_public - // docs:start:set_minter - #[aztec(public)] + #[public] fn set_minter(minter: AztecAddress, approve: bool) { // docs:start:read_admin assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); @@ -187,9 +177,8 @@ contract Token { // docs:end:write_minter } // docs:end:set_minter - // docs:start:mint_public - #[aztec(public)] + #[public] fn mint_public(to: AztecAddress, amount: Field) { // docs:start:read_minter assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); @@ -197,49 +186,42 @@ contract Token { let amount = U128::from_integer(amount); let new_balance = storage.public_balances.at(to).read().add(amount); let supply = storage.total_supply.read().add(amount); - storage.public_balances.at(to).write(new_balance); storage.total_supply.write(supply); } // docs:end:mint_public - // docs:start:mint_private - #[aztec(public)] + #[public] fn mint_private(amount: Field, secret_hash: Field) { assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); let pending_shields = storage.pending_shields; let mut note = TransparentNote::new(amount, secret_hash); let supply = storage.total_supply.read().add(U128::from_integer(amount)); - storage.total_supply.write(supply); // docs:start:insert_from_public pending_shields.insert_from_public(&mut note); // docs:end:insert_from_public } // docs:end:mint_private - // TODO: Nuke this - test functions do not belong to token contract! - #[aztec(private)] + #[private] fn privately_mint_private_note(amount: Field) { let caller = context.msg_sender(); let caller_keys = get_public_keys(caller); storage.balances.at(caller).add(caller_keys.npk_m, U128::from_integer(amount)).emit( encode_and_encrypt_note_with_keys(&mut context, caller_keys.ovpk_m, caller_keys.ivpk_m, caller) ); - Token::at(context.this_address()).assert_minter_and_mint(context.msg_sender(), amount).enqueue(&mut context); } - - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn assert_minter_and_mint(minter: AztecAddress, amount: Field) { assert(storage.minters.at(minter).read(), "caller is not minter"); let supply = storage.total_supply.read() + U128::from_integer(amount); storage.total_supply.write(supply); } - // docs:start:shield - #[aztec(public)] + #[public] fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. @@ -247,38 +229,31 @@ contract Token { } else { assert(nonce == 0, "invalid nonce"); } - let amount = U128::from_integer(amount); let from_balance = storage.public_balances.at(from).read().sub(amount); - let pending_shields = storage.pending_shields; let mut note = TransparentNote::new(amount.to_field(), secret_hash); - storage.public_balances.at(from).write(from_balance); pending_shields.insert_from_public(&mut note); } // docs:end:shield - // docs:start:transfer_public - #[aztec(public)] + #[public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit_public(&mut context, from); } else { assert(nonce == 0, "invalid nonce"); } - let amount = U128::from_integer(amount); let from_balance = storage.public_balances.at(from).read().sub(amount); storage.public_balances.at(from).write(from_balance); - let to_balance = storage.public_balances.at(to).read().add(amount); storage.public_balances.at(to).write(to_balance); } // docs:end:transfer_public - // docs:start:burn_public - #[aztec(public)] + #[public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { // docs:start:assert_current_call_valid_authwit_public if (!from.eq(context.msg_sender())) { @@ -287,21 +262,17 @@ contract Token { assert(nonce == 0, "invalid nonce"); } // docs:end:assert_current_call_valid_authwit_public - let amount = U128::from_integer(amount); let from_balance = storage.public_balances.at(from).read().sub(amount); storage.public_balances.at(from).write(from_balance); - let new_supply = storage.total_supply.read().sub(amount); storage.total_supply.write(new_supply); } // docs:end:burn_public - // docs:start:redeem_shield - #[aztec(private)] + #[private] fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { let secret_hash = compute_secret_hash(secret); - // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and // a secret_hash stored in a field with index 1 (select(1, secret_hash)). let mut options = NoteGetterOptions::new(); @@ -310,10 +281,8 @@ contract Token { Comparator.EQ, secret_hash ).set_limit(1); - let notes = storage.pending_shields.pop_notes(options); assert(notes.len() == 1, "note not popped"); - // Add the token note to user's balances set // Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send // outgoing logs to that address and to send outgoing logs you need to get a hold of ovsk_m. @@ -323,9 +292,8 @@ contract Token { storage.balances.at(to).add(to_keys.npk_m, U128::from_integer(amount)).emit(encode_and_encrypt_note_with_keys(&mut context, from_keys.ovpk_m, to_keys.ivpk_m, to)); } // docs:end:redeem_shield - // docs:start:unshield - #[aztec(private)] + #[private] fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); @@ -335,13 +303,11 @@ contract Token { let from_keys = get_public_keys(from); storage.balances.at(from).sub(from_keys.npk_m, U128::from_integer(amount)).emit(encode_and_encrypt_note_with_keys(&mut context, from_keys.ovpk_m, from_keys.ivpk_m, from)); - Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } // docs:end:unshield - // docs:start:transfer - #[aztec(private)] + #[private] fn transfer(to: AztecAddress, amount: Field) { let from = context.msg_sender(); @@ -349,7 +315,6 @@ contract Token { let to_keys = get_public_keys(to); let amount = U128::from_integer(amount); - // We reduce `from`'s balance by amount by recursively removing notes over potentially multiple calls. This // method keeps the gate count for each individual call low - reading too many notes at once could result in // circuits in which proving is not feasible. @@ -363,15 +328,12 @@ contract Token { amount, INITIAL_TRANSFER_CALL_MAX_NOTES ); - storage.balances.at(from).add(from_keys.npk_m, change).emit( encode_and_encrypt_note_with_keys_unconstrained(&mut context, from_keys.ovpk_m, from_keys.ivpk_m, from) ); - storage.balances.at(to).add(to_keys.npk_m, amount).emit( encode_and_encrypt_note_with_keys_unconstrained(&mut context, from_keys.ovpk_m, to_keys.ivpk_m, to) ); - // We don't constrain encryption of the note log in `transfer` (unlike in `transfer_from`) because the transfer // function is only designed to be used in situations where the event is not strictly necessary (e.g. payment to // another person where the payment is considered to be successful when the other party successfully decrypts a @@ -381,7 +343,6 @@ contract Token { ); } // docs:end:transfer - #[contract_library_method] fn subtract_balance( context: &mut PrivateContext, @@ -391,13 +352,11 @@ contract Token { max_notes: u32 ) -> U128 { let subtracted = storage.balances.at(account).try_sub(amount, max_notes); - // Failing to subtract any amount means that the owner was unable to produce more notes that could be nullified. // We could in some cases fail early inside try_sub if we detected that fewer notes than the maximum were // returned and we were still unable to reach the target amount, but that'd make the code more complicated, and // optimizing for the failure scenario is not as important. assert(subtracted > U128::from_integer(0), "Balance too low"); - if subtracted >= amount { // We have achieved our goal of nullifying notes that add up to more than amount, so we return the change subtracted - amount @@ -408,7 +367,6 @@ contract Token { compute_recurse_subtract_balance_call(*context, account, remaining).call(context) } } - // TODO(#7729): apply no_predicates to the contract interface method directly instead of having to use a wrapper // like we do here. #[no_predicates] @@ -420,11 +378,10 @@ contract Token { ) -> PrivateCallInterface<25, U128, (AztecAddress, Field)> { Token::at(context.this_address())._recurse_subtract_balance(account, remaining.to_field()) } - // TODO(#7728): even though the amount should be a U128, we can't have that type in a contract interface due to // serialization issues. - #[aztec(internal)] - #[aztec(private)] + #[internal] + #[private] fn _recurse_subtract_balance(account: AztecAddress, amount: Field) -> U128 { subtract_balance( &mut context, @@ -434,22 +391,20 @@ contract Token { RECURSIVE_TRANSFER_CALL_MAX_NOTES ) } - /** * Cancel a private authentication witness. * @param inner_hash The inner hash of the authwit to cancel. */ // docs:start:cancel_authwit - #[aztec(private)] + #[private] fn cancel_authwit(inner_hash: Field) { let on_behalf_of = context.msg_sender(); let nullifier = compute_authwit_nullifier(on_behalf_of, inner_hash); context.push_nullifier(nullifier); } // docs:end:cancel_authwit - // docs:start:transfer_from - #[aztec(private)] + #[private] fn transfer_from(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { // docs:start:assert_current_call_valid_authwit if (!from.eq(context.msg_sender())) { @@ -471,23 +426,19 @@ contract Token { storage.balances.at(to).add(to_keys.npk_m, amount).emit(encode_and_encrypt_note_with_keys(&mut context, from_keys.ovpk_m, to_keys.ivpk_m, to)); } // docs:end:transfer_from - // docs:start:burn - #[aztec(private)] + #[private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { if (!from.eq(context.msg_sender())) { assert_current_call_valid_authwit(&mut context, from); } else { assert(nonce == 0, "invalid nonce"); } - let from_keys = get_public_keys(from); storage.balances.at(from).sub(from_keys.npk_m, U128::from_integer(amount)).emit(encode_and_encrypt_note_with_keys(&mut context, from_keys.ovpk_m, from_keys.ivpk_m, from)); - Token::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); } // docs:end:burn - /// We need to use different randomness for the user and for the fee payer notes because if the randomness values /// were the same we could fingerprint the user by doing the following: /// 1) randomness_influence = fee_payer_point - G_npk * fee_payer_npk = @@ -508,7 +459,7 @@ contract Token { /// Note 2: fee_payer_point and user_point above are public information because they are passed as args to /// the public `complete_refund(...)` function. // docs:start:setup_refund - #[aztec(private)] + #[private] fn setup_refund( fee_payer: AztecAddress, // Address of the entity which will receive the fee note. user: AztecAddress, // A user for which we are setting up the fee refund. @@ -519,12 +470,10 @@ contract Token { // 1. This function is called by fee paying contract (fee_payer) when setting up a refund so we need to support // the authwit flow here and check that the user really permitted fee_payer to set up a refund on their behalf. assert_current_call_valid_authwit(&mut context, user); - // 2. Get all the relevant keys let fee_payer_npk_m_hash = get_public_keys(fee_payer).npk_m.hash(); let user_keys = get_public_keys(user); let user_npk_m_hash = user_keys.npk_m.hash(); - // 3. Deduct the funded amount from the user's balance - this is a maximum fee a user is willing to pay // (called fee limit in aztec spec). The difference between fee limit and the actual tx fee will be refunded // to the user in the `complete_refund(...)` function. @@ -538,42 +487,23 @@ contract Token { storage.balances.at(user).add(user_keys.npk_m, change).emit( encode_and_encrypt_note_with_keys_unconstrained(&mut context, user_keys.ovpk_m, user_keys.ivpk_m, user) ); - - // 4. We create the partial notes for the fee payer and the user. - // --> Called "partial" because they don't have the amount set yet (that will be done in `complete_refund(...)`). - let fee_payer_partial_note = TokenNote { - header: NoteHeader { - contract_address: AztecAddress::zero(), - nonce: 0, - storage_slot: storage.balances.at(fee_payer).set.storage_slot, - note_hash_counter: 0 - }, - amount: U128::zero(), - npk_m_hash: fee_payer_npk_m_hash, - randomness: fee_payer_randomness - }; - let user_partial_note = TokenNote { - header: NoteHeader { - contract_address: AztecAddress::zero(), - nonce: 0, - storage_slot: storage.balances.at(user).set.storage_slot, - note_hash_counter: 0 - }, - amount: U128::zero(), - npk_m_hash: user_npk_m_hash, - randomness: user_randomness - }; - - // 5. Now we get the note hiding points. - let mut fee_payer_point = fee_payer_partial_note.to_note_hiding_point(); - let mut user_point = user_partial_note.to_note_hiding_point(); - - // 6. Set the public teardown function to `complete_refund(...)`. Public teardown is the only time when a public + // 4. Now we get the note hiding points. + let mut fee_payer_point = TokenNote::hiding_point().new( + fee_payer_npk_m_hash, + fee_payer_randomness, + storage.balances.at(fee_payer).set.storage_slot + ); + let mut user_point = TokenNote::hiding_point().new( + user_npk_m_hash, + user_randomness, + storage.balances.at(user).set.storage_slot + ); + // 5. Set the public teardown function to `complete_refund(...)`. Public teardown is the only time when a public // function has access to the final transaction fee, which is needed to compute the actual refund amount. context.set_public_teardown_function( context.this_address(), comptime { - FunctionSelector::from_signature("complete_refund(((Field,Field,bool)),((Field,Field,bool)),Field)") + FunctionSelector::from_signature("complete_refund((Field,Field,bool),(Field,Field,bool),Field)") }, [ fee_payer_point.inner.x, fee_payer_point.inner.y, fee_payer_point.inner.is_infinite as Field, user_point.inner.x, user_point.inner.y, user_point.inner.is_infinite as Field, funded_amount @@ -581,73 +511,50 @@ contract Token { ); } // docs:end:setup_refund - // TODO(#7728): even though the funded_amount should be a U128, we can't have that type in a contract interface due // to serialization issues. // docs:start:complete_refund - #[aztec(public)] - #[aztec(internal)] - fn complete_refund( - // TODO(#7771): the following makes macros crash --> try getting it work once we migrate to metaprogramming - // mut fee_payer_point: TokenNoteHidingPoint, - // mut user_point: TokenNoteHidingPoint, - fee_payer_point_immutable: TokenNoteHidingPoint, - user_point_immutable: TokenNoteHidingPoint, - funded_amount: Field - ) { - // TODO(#7771): nuke the following 2 lines once we have mutable args - let mut fee_payer_point = fee_payer_point_immutable; - let mut user_point = user_point_immutable; - + #[public] + #[internal] + fn complete_refund(fee_payer_point: Point, user_point: Point, funded_amount: Field) { + let mut fee_payer_hiding_point = TokenNote::hiding_point().from_point(fee_payer_point); + let mut user_hiding_point = TokenNote::hiding_point().from_point(user_point); // TODO(#7728): Remove the next line let funded_amount = U128::from_integer(funded_amount); let tx_fee = U128::from_integer(context.transaction_fee()); - // 1. We check that user funded the fee payer contract with at least the transaction fee. // TODO(#7796): we should try to prevent reverts here assert(funded_amount >= tx_fee, "funded amount not enough to cover tx fee"); - // 2. We compute the refund amount as the difference between funded amount and tx fee. let refund_amount = funded_amount - tx_fee; - - // 3. We add fee to the fee payer point and refund amount to the user point. - fee_payer_point.add_amount(tx_fee); - user_point.add_amount(refund_amount); - - // 4. We finalize the hiding points to get the note hashes. - let fee_payer_note_hash = fee_payer_point.finalize(); - let user_note_hash = user_point.finalize(); - + // 3. We finalize the hiding points with the correct amounts to get the note hashes. + let fee_payer_note_hash = fee_payer_hiding_point.finalize(tx_fee); + let user_note_hash = user_hiding_point.finalize(refund_amount); // 5. At last we emit the note hashes. context.push_note_hash(fee_payer_note_hash); context.push_note_hash(user_note_hash); // --> Once the tx is settled user and fee recipient can add the notes to their pixies. } // docs:end:complete_refund - /// Internal /// - // docs:start:increase_public_balance - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _increase_public_balance(to: AztecAddress, amount: Field) { let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); storage.public_balances.at(to).write(new_balance); } // docs:end:increase_public_balance - // docs:start:reduce_total_supply - #[aztec(public)] - #[aztec(internal)] + #[public] + #[internal] fn _reduce_total_supply(amount: Field) { // Only to be called from burn. let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); storage.total_supply.write(new_supply); } // docs:end:reduce_total_supply - /// Unconstrained /// - // docs:start:balance_of_private unconstrained pub(crate) fn balance_of_private(owner: AztecAddress) -> pub Field { storage.balances.at(owner).balance_of().to_field() diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 7fedc4b2808..b8064eabf75 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -77,7 +77,7 @@ unconstrained fn mint_private_success() { // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); @@ -108,7 +108,7 @@ unconstrained fn mint_private_failure_double_spend() { // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); @@ -167,7 +167,7 @@ unconstrained fn mint_private_failure_overflow_recipient() { // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); @@ -207,12 +207,12 @@ unconstrained fn mint_private_failure_overflow_total_supply() { // Store 2 notes in the cache so we can redeem it for owner and recipient env.add_note( &mut TransparentNote::new(mint_amount, secret_hash_owner), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); env.add_note( &mut TransparentNote::new(mint_amount, secret_hash_recipient), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/refunds.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/refunds.nr index 8371269af92..339bd464871 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/refunds.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/refunds.nr @@ -36,8 +36,8 @@ unconstrained fn setup_refund_success() { let user_npk_m_hash = get_public_keys(user).npk_m.hash(); let fee_payer_npk_m_hash = get_public_keys(fee_payer).npk_m.hash(); - let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, fee_payer); - let user_balances_slot = derive_storage_slot_in_map(Token::storage().balances.slot, user); + let fee_payer_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, fee_payer); + let user_balances_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, user); // When the refund was set up, we would've spent the note worth mint_amount, and inserted a note worth //`mint_amount - funded_amount`. When completing the refund, we would've constructed a hash corresponding to a note diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr index 9e3d7937abf..7e7eb3e5dc2 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr @@ -17,7 +17,7 @@ unconstrained fn shielding_on_behalf_of_self() { // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); @@ -50,7 +50,7 @@ unconstrained fn shielding_on_behalf_of_other() { // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 8757380022c..2a8b80ad477 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -58,7 +58,7 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. env.add_note( &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage().pending_shields.slot, + Token::storage_layout().pending_shields.slot, token_contract_address ); // docs:end:txe_test_add_note @@ -76,7 +76,7 @@ pub fn check_public_balance(token_contract_address: AztecAddress, address: Aztec cheatcodes::set_contract_address(token_contract_address); let block_number = get_block_number(); - let balances_slot = Token::storage().public_balances.slot; + let balances_slot = Token::storage_layout().public_balances.slot; let address_slot = derive_storage_slot_in_map(balances_slot, address); let amount: U128 = storage_read(token_contract_address, address_slot, block_number); assert(amount.to_field() == address_amount, "Public balance is not correct"); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/balance_set.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/balance_set.nr index 14956431bf9..c768d432277 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/balance_set.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/balance_set.nr @@ -1,13 +1,13 @@ -use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet, Point}; +use dep::aztec::prelude::{NoteGetterOptions, NoteViewerOptions, NoteInterface, PrivateSet}; use dep::aztec::{ context::{PrivateContext, UnconstrainedContext}, protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, - note::{note_getter::view_notes, note_emission::{NoteEmission, OuterNoteEmission}}, + note::{note_interface::NullifiableNote, note_getter::view_notes, note_emission::OuterNoteEmission}, keys::{getters::get_public_keys, public_keys::NpkM} }; use crate::types::token_note::OwnedNote; -struct BalanceSet { +pub struct BalanceSet { set: PrivateSet, } @@ -19,14 +19,14 @@ impl BalanceSet { } impl BalanceSet { - unconstrained pub fn balance_of(self: Self) -> U128 where T: NoteInterface + OwnedNote { + unconstrained pub fn balance_of(self: Self) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { self.balance_of_with_offset(0) } - unconstrained pub fn balance_of_with_offset( + unconstrained pub fn balance_of_with_offset( self: Self, offset: u32 - ) -> U128 where T: NoteInterface + OwnedNote { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote { let mut balance = U128::from_integer(0); // docs:start:view_notes let mut options = NoteViewerOptions::new(); @@ -46,11 +46,11 @@ impl BalanceSet { } impl BalanceSet { - pub fn add( + pub fn add( self: Self, owner_npk_m: NpkM, addend: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { if addend == U128::from_integer(0) { OuterNoteEmission::new(Option::none()) } else { @@ -63,11 +63,11 @@ impl BalanceSet { } } - pub fn sub( + pub fn sub( self: Self, owner_npk_m: NpkM, amount: U128 - ) -> OuterNoteEmission where T: NoteInterface + OwnedNote + Eq { + ) -> OuterNoteEmission where T: NoteInterface + NullifiableNote + OwnedNote + Eq { let subtracted = self.try_sub(amount, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL); // try_sub may have substracted more or less than amount. We must ensure that we subtracted at least as much as @@ -85,11 +85,11 @@ impl BalanceSet { // The `max_notes` parameter is used to fine-tune the number of constraints created by this function. The gate count // scales relatively linearly with `max_notes`, but a lower `max_notes` parameter increases the likelihood of // `try_sub` subtracting an amount smaller than `target_amount`. - pub fn try_sub( + pub fn try_sub( self: Self, target_amount: U128, max_notes: u32 - ) -> U128 where T: NoteInterface + OwnedNote + Eq { + ) -> U128 where T: NoteInterface + NullifiableNote + OwnedNote + Eq { // We are using a preprocessor here (filter applied in an unconstrained context) instead of a filter because // we do not need to prove correct execution of the preprocessor. // Because the `min_sum` notes is not constrained, users could choose to e.g. not call it. However, all this @@ -115,10 +115,10 @@ impl BalanceSet { // The preprocessor (a filter applied in an unconstrained context) does not check if total sum is larger or equal to // 'min_sum' - all it does is remove extra notes if it does reach that value. // Note that proper usage of this preprocessor requires for notes to be sorted in descending order. -pub fn preprocess_notes_min_sum( +pub fn preprocess_notes_min_sum( notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], min_sum: U128 -) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { +) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + NullifiableNote + OwnedNote { let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; let mut sum = U128::from_integer(0); for i in 0..notes.len() { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr index 5909159a5f8..365724e6868 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/token_note.nr @@ -1,26 +1,18 @@ use dep::aztec::{ - generators::{Ga1 as G_amt, Ga2 as G_npk, Ga3 as G_rnd, G_slot}, - prelude::{NoteHeader, NoteInterface, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, point::{Point, POINT_LENGTH}, scalar::Scalar, - hash::poseidon2_hash_with_separator, traits::Serialize -}, + prelude::{NoteHeader, NullifiableNote, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, note::utils::compute_note_hash_for_nullify, oracle::unsafe_rand::unsafe_rand, - keys::getters::get_nsk_app + keys::getters::get_nsk_app, macros::notes::partial_note }; -use dep::std::{embedded_curve_ops::multi_scalar_mul, hash::from_field_unsafe}; trait OwnedNote { fn new(amount: U128, owner_npk_m_hash: Field) -> Self; fn get_amount(self) -> U128; } -global TOKEN_NOTE_LEN: u32 = 3; // 3 plus a header. -global TOKEN_NOTE_BYTES_LEN: u32 = 3 * 32 + 64; - // docs:start:TokenNote -#[aztec(note)] -struct TokenNote { +#[partial_note(quote {amount})] +pub struct TokenNote { // The amount of tokens in the note amount: U128, // The nullifying public key hash is used with the nsk_app to ensure that the note can be privately spent. @@ -30,7 +22,7 @@ struct TokenNote { } // docs:end:TokenNote -impl NoteInterface for TokenNote { +impl NullifiableNote for TokenNote { // docs:start:nullifier fn compute_nullifier(self, context: &mut PrivateContext, note_hash_for_nullify: Field) -> Field { let secret = context.request_nsk_app(self.npk_m_hash); @@ -52,69 +44,6 @@ impl NoteInterface for TokenNote { GENERATOR_INDEX__NOTE_NULLIFIER ) } - - // docs:start:compute_note_hiding_point - fn compute_note_hiding_point(self) -> Point { - // We use the unsafe version because the multi_scalar_mul will constrain the scalars. - let amount_scalar = from_field_unsafe(self.amount.to_integer()); - let npk_m_hash_scalar = from_field_unsafe(self.npk_m_hash); - let randomness_scalar = from_field_unsafe(self.randomness); - let slot_scalar = from_field_unsafe(self.header.storage_slot); - // We compute the note hiding point as: - // `G_amt * amount + G_npk * npk_m_hash + G_rnd * randomness + G_slot * slot` - // instead of using pedersen or poseidon2 because it allows us to privately add and subtract from amount - // in public by leveraging homomorphism. - multi_scalar_mul( - [G_amt, G_npk, G_rnd, G_slot], - [amount_scalar, npk_m_hash_scalar, randomness_scalar, slot_scalar] - ) - } - // docs:end:compute_note_hiding_point -} - -impl TokenNote { - // TODO(#8290): Merge this func with `compute_note_hiding_point`. I (benesjan) didn't do it in the initial PR to not have - // to modify macros and all the related funcs in it. - fn to_note_hiding_point(self) -> TokenNoteHidingPoint { - TokenNoteHidingPoint::new(self.compute_note_hiding_point()) - } -} - -// TODO(#8290): Auto-generate this -struct TokenNoteHidingPoint { - inner: Point -} - -impl TokenNoteHidingPoint { - fn new(point: Point) -> Self { - Self { inner: point } - } - - fn add_amount(&mut self, amount: U128) { - self.inner = multi_scalar_mul([G_amt], [from_field_unsafe(amount.to_integer())]) + self.inner; - } - - fn add_npk_m_hash(&mut self, npk_m_hash: Field) { - self.inner = multi_scalar_mul([G_npk], [from_field_unsafe(npk_m_hash)]) + self.inner; - } - - fn add_randomness(&mut self, randomness: Field) { - self.inner = multi_scalar_mul([G_rnd], [from_field_unsafe(randomness)]) + self.inner; - } - - fn add_slot(&mut self, slot: Field) { - self.inner = multi_scalar_mul([G_slot], [from_field_unsafe(slot)]) + self.inner; - } - - fn finalize(self) -> Field { - self.inner.x - } -} - -impl Serialize for TokenNoteHidingPoint { - fn serialize(self) -> [Field; POINT_LENGTH] { - self.inner.serialize() - } } impl Eq for TokenNote { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr index 8c2b0c0e138..c0dbd3662f1 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr @@ -1,41 +1,21 @@ // docs:start:token_types_all use dep::aztec::{ - note::{note_getter_options::PropertySelector, utils::compute_note_hash_for_nullify}, - prelude::{NoteHeader, NoteInterface, PrivateContext}, - protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator} + note::utils::compute_note_hash_for_nullify, prelude::{NoteHeader, NullifiableNote, PrivateContext}, + protocol_types::{constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator}, + macros::notes::note }; -global TRANSPARENT_NOTE_LEN: u32 = 2; -// TRANSPARENT_NOTE_LEN * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) -global TRANSPARENT_NOTE_BYTES_LEN: u32 = 2 * 32 + 64; - // Transparent note represents a note that is created in the clear (public execution), but can only be spent by those // that know the preimage of the "secret_hash" (the secret). This is typically used when shielding a token balance. // Owner of the tokens provides a "secret_hash" as an argument to the public "shield" function and then the tokens // can be redeemed in private by presenting the preimage of the "secret_hash" (the secret). -#[aztec(note)] -struct TransparentNote { +#[note] +pub struct TransparentNote { amount: Field, secret_hash: Field, } -struct TransparentNoteProperties { - amount: PropertySelector, - secret_hash: PropertySelector, -} - -impl NoteInterface for TransparentNote { - - // Custom serialization to avoid disclosing the secret field - fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { - [self.amount, self.secret_hash] - } - - // Custom deserialization since we don't have access to the secret plaintext - fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { - TransparentNote { amount: serialized_note[0], secret_hash: serialized_note[1], header: NoteHeader::empty() } - } - +impl NullifiableNote for TransparentNote { fn compute_nullifier(self, _context: &mut PrivateContext, _note_hash_for_nullify: Field) -> Field { self.compute_nullifier_without_context() } @@ -62,15 +42,6 @@ impl TransparentNote { pub fn new(amount: Field, secret_hash: Field) -> Self { TransparentNote { amount, secret_hash, header: NoteHeader::empty() } } - - // CUSTOM FUNCTIONS FOR THIS NOTE TYPE - // Custom serialization forces us to manually create the metadata struct and its getter - pub fn properties() -> TransparentNoteProperties { - TransparentNoteProperties { - amount: PropertySelector { index: 0, offset: 0, length: 32 }, - secret_hash: PropertySelector { index: 1, offset: 0, length: 32 } - } - } } impl Eq for TransparentNote { diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index ed18788ebe0..0b1aeb7cd55 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -4,6 +4,9 @@ mod util; // Demonstrates how to use portal contracts to swap on L1 Uniswap with funds on L2 // Has two separate flows for private and public respectively // Uses the token bridge contract, which tells which input token we need to talk to and handles the exit funds to L1 +use dep::aztec::macros::aztec; + +#[aztec] contract Uniswap { use dep::aztec::prelude::{FunctionSelector, AztecAddress, EthAddress, SharedImmutable}; @@ -12,21 +15,22 @@ contract Uniswap { use dep::token::Token; use dep::token_bridge::TokenBridge; use crate::util::{compute_swap_private_content_hash, compute_swap_public_content_hash}; + use dep::aztec::macros::{storage::storage, functions::{public, initializer, view, internal, private}}; - #[aztec(storage)] - struct Storage { - portal_address: SharedImmutable, + #[storage] + struct Storage { + portal_address: SharedImmutable, } - #[aztec(public)] - #[aztec(initializer)] + #[public] + #[initializer] fn constructor(portal_address: EthAddress) { storage.portal_address.initialize(portal_address); } // docs:end:uniswap_setup // docs:start:swap_public - #[aztec(public)] + #[public] fn swap_public( sender: AztecAddress, input_asset_bridge: AztecAddress, @@ -87,7 +91,7 @@ contract Uniswap { // docs:end:swap_public // docs:start:swap_private - #[aztec(private)] + #[private] fn swap_private( input_asset: AztecAddress, // since private, we pass here and later assert that this is as expected by input_bridge input_asset_bridge: AztecAddress, @@ -150,13 +154,9 @@ contract Uniswap { // Assume `token` relates to `token_bridge` (ie token_bridge.token == token) // Note that private can't read public return values so created an internal public that handles everything // this method is used for both private and public swaps. - #[aztec(public)] - #[aztec(internal)] - fn _approve_bridge_and_exit_input_asset_to_L1( - token: AztecAddress, - token_bridge: AztecAddress, - amount: Field - ) { + #[public] + #[internal] + fn _approve_bridge_and_exit_input_asset_to_L1(token: AztecAddress, token_bridge: AztecAddress, amount: Field) { // Since we will authorize and instantly spend the funds, all in public, we can use the same nonce // every interaction. In practice, the authwit should be squashed, so this is also cheap! let nonce = 0xdeadbeef; @@ -181,9 +181,9 @@ contract Uniswap { // docs:end:authwit_uniswap_set // docs:start:assert_token_is_same - #[aztec(public)] - #[aztec(internal)] - #[aztec(view)] + #[public] + #[internal] + #[view] fn _assert_token_is_same(token: AztecAddress, token_bridge: AztecAddress) { assert( token.eq(TokenBridge::at(token_bridge).get_token().view(&mut context)), "input_asset address is not the same as seen in the bridge contract" diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/event_selector.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/event_selector.nr index 0a1bbf3444d..b456c2fd03f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/event_selector.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/event_selector.nr @@ -3,7 +3,7 @@ use crate::traits::{Serialize, Deserialize, FromField, ToField, Empty}; global SELECTOR_SIZE: u32 = 4; -struct EventSelector { +pub struct EventSelector { // 1st 4-bytes (big-endian leftmost) of abi-encoding of an event. inner: u32, } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_circuit_public_inputs.nr index 535a3e9ccb8..a954f5dbd1f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_circuit_public_inputs.nr @@ -49,7 +49,7 @@ impl PrivateCircuitPublicInputsArrayLengths { } // Public inputs to private app circuit. -struct PrivateCircuitPublicInputs { +pub struct PrivateCircuitPublicInputs { call_context: CallContext, args_hash: Field, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr index db9787ffbce..06605d2c553 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/address/aztec_address.nr @@ -6,7 +6,7 @@ use crate::{ }; // Aztec address -struct AztecAddress { +pub struct AztecAddress { inner : Field } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index bc68ac6eb31..f0a5de627fe 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -285,12 +285,16 @@ global NUM_BASE_PARITY_PER_ROOT_PARITY: u32 = 4; // Lengths of the different types of proofs in fields global RECURSIVE_PROOF_LENGTH: u32 = 439; global NESTED_RECURSIVE_PROOF_LENGTH: u32 = 439; -global TUBE_PROOF_LENGTH = RECURSIVE_PROOF_LENGTH; // in the future these can differ +global TUBE_PROOF_LENGTH: u32 = RECURSIVE_PROOF_LENGTH; // in the future these can differ -global VERIFICATION_KEY_LENGTH_IN_FIELDS = 128; - -// The length is 2 + AvmFlavor::NUM_PRECOMPUTED_ENTITIES * 4 +global VERIFICATION_KEY_LENGTH_IN_FIELDS: u32 = 128; +// VK is composed of +// - circuit size encoded as a fr field element (32 bytes) +// - num of inputs encoded as a fr field element (32 bytes) +// - 16 affine elements (curve base field fq) encoded as fr elements takes (16 * 4 * 32 bytes) +// 16 above refers to the constant AvmFlavor::NUM_PRECOMPUTED_ENTITIES global AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS: u32 = 2 + 16 * 4; + // `AVM_PROOF_LENGTH_IN_FIELDS` must be updated when AVM circuit changes. // To determine latest value, hover `COMPUTED_AVM_PROOF_LENGTH_IN_FIELDS` // in barretenberg/cpp/src/barretenberg/vm/avm/generated/flavor.hpp diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr index c9b1e034025..fbf8996bc2d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr @@ -29,5 +29,6 @@ mod recursion; mod data; mod storage; mod validate; +mod meta; pub use abis::kernel_circuit_public_inputs::{KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr new file mode 100644 index 00000000000..231bd8d320a --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/meta/mod.nr @@ -0,0 +1,227 @@ +use super::traits::{Serialize, Deserialize}; + +pub comptime fn pack_from_fields( + name: Quoted, + typ: Type, + buffer: Quoted, + already_consumed: u32, + replacements: [(Quoted, Quoted)] +) -> (Quoted, u32) { + let mut result = quote {}; + let mut consumed: u32 = 0; + + let found_replacements = replacements.filter(| (to_omit, _): (Quoted, Quoted) | to_omit == name); + + let replacement = if found_replacements.len() == 1 { + replacements[0].1 + } else { + quote {} + }; + + if replacement == quote {} { + if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { + result = quote { $buffer[$already_consumed] as $typ }; + consumed = 1; + } else if typ.as_struct().is_some() { + let (nested_def, _) = typ.as_struct().unwrap(); + let nested_name = nested_def.name(); + let mut deserialized_fields_list = &[]; + for field in nested_def.fields() { + let (field_name, field_type) = field; + let (deserialized_field, consumed_by_field) = pack_from_fields( + quote { $field_name }, + field_type, + quote { $buffer }, + consumed + already_consumed, + replacements + ); + consumed += consumed_by_field; + deserialized_fields_list = deserialized_fields_list.push_back(quote { $field_name: $deserialized_field }); + } + let deserialized_fields = deserialized_fields_list.join(quote {,}); + result = quote { + $nested_name { + $deserialized_fields + } + }; + } else if typ.as_array().is_some() { + let (element_type, array_len) = typ.as_array().unwrap(); + let array_len = array_len.as_constant().unwrap(); + let mut array_fields_list = &[]; + for _ in 0..array_len { + let (deserialized_field, consumed_by_field) = pack_from_fields( + quote { $name }, + element_type, + quote { $buffer }, + consumed + already_consumed, + replacements + ); + array_fields_list = array_fields_list.push_back(deserialized_field); + consumed += consumed_by_field; + } + let array_fields = array_fields_list.join(quote {,}); + result = quote { [ $array_fields ] }; + } else if typ.as_str().is_some() { + let length_type = typ.as_str().unwrap(); + let str_len = length_type.as_constant().unwrap(); + let mut byte_list = &[]; + for _ in 0..str_len { + let (deserialized_field, consumed_by_field) = pack_from_fields( + quote { $name }, + quote { u8}.as_type(), + quote { $buffer }, + consumed + already_consumed, + replacements + ); + byte_list = byte_list.push_back(deserialized_field); + consumed += consumed_by_field; + } + let bytes = byte_list.join(quote {,}); + result = quote { [ $bytes ].as_str_unchecked() }; + } else { + panic(f"Unsupported type for serialization of argument {name} and type {typ}") + } + } else { + result = replacement; + } + (result, consumed) +} + +pub comptime fn flatten_to_fields(name: Quoted, typ: Type, omit: [Quoted]) -> ([Quoted], [Quoted]) { + let mut fields = &[]; + let mut aux_vars = &[]; + + if omit.all(| to_omit | to_omit != name) { + if typ.is_field() | typ.as_integer().is_some() | typ.is_bool() { + fields = fields.push_back(quote { $name as Field }); + } else if typ.as_struct().is_some() { + let nested_struct = typ.as_struct().unwrap(); + let params = nested_struct.0.fields(); + let struct_flattened = params.map( + | (param_name, param_type): (Quoted, Type) | flatten_to_fields(quote {$name.$param_name}, param_type, omit) + ); + let struct_flattened_fields = struct_flattened.fold( + &[], + | acc: [Quoted], (fields, _): (_, [Quoted]) | acc.append(fields) + ); + let struct_flattened_aux_vars = struct_flattened.fold( + &[], + |acc: [Quoted], (_, aux_vars): ([Quoted], _) | acc.append(aux_vars) + ); + fields = fields.append(struct_flattened_fields); + aux_vars = aux_vars.append(struct_flattened_aux_vars); + } else if typ.as_array().is_some() { + let (element_type, array_len) = typ.as_array().unwrap(); + let array_len = array_len.as_constant().unwrap(); + for i in 0..array_len { + let (element_fields, element_aux_vars) = flatten_to_fields(quote { $name[$i] }, element_type, omit); + fields = fields.append(element_fields); + aux_vars = aux_vars.append(element_aux_vars); + } + } else if typ.as_str().is_some() { + let length_type = typ.as_str().unwrap(); + let str_len = length_type.as_constant().unwrap(); + let var_name = name.as_expr().unwrap().as_member_access().unwrap().1; + let as_bytes_name = f"{var_name}_as_bytes".quoted_contents(); + let as_bytes = quote { let $as_bytes_name = $name.as_bytes() }; + for i in 0..str_len { + fields = fields.push_back(quote { $as_bytes_name[$i] as Field } ); + } + aux_vars = aux_vars.push_back(as_bytes); + } else { + panic(f"Unsupported type for serialization of argument {name} and type {typ}") + } + } + (fields, aux_vars) +} + +pub(crate) comptime fn derive_serialize(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let (fields, aux_vars) = flatten_to_fields(quote { self }, typ, &[]); + let aux_vars_for_serialization = if aux_vars.len() > 0 { + let joint = aux_vars.join(quote {;}); + quote { $joint; } + } else { + quote {} + }; + + let field_serializations = fields.join(quote {,}); + let serialized_len = fields.len(); + quote { + impl Serialize<$serialized_len> for $typ { + fn serialize(self) -> [Field; $serialized_len] { + $aux_vars_for_serialization + [ $field_serializations ] + } + } + } +} + +pub(crate) comptime fn derive_deserialize(s: StructDefinition) -> Quoted { + let typ = s.as_type(); + let (fields, _) = flatten_to_fields(quote { self }, typ, &[]); + let serialized_len = fields.len(); + let (deserialized, _) = pack_from_fields(quote { self }, typ, quote { value }, 0, &[]); + quote { + impl Deserialize<$serialized_len> for $typ { + fn deserialize(value: [Field; $serialized_len]) -> Self { + $deserialized + } + } + } +} + +#[derive(Serialize, Deserialize, Eq)] +struct Smol { + a: Field, + b: Field, +} + +#[derive(Serialize, Deserialize, Eq)] +struct HasArray { + a: [Field; 2], + b: bool +} + +#[derive(Serialize, Deserialize, Eq)] +struct Fancier { + a: Smol, + b: [Field; 2], + c: [u8; 3], + d: str<16>, +} + +fn main() { + assert(false); +} + +#[test] +fn smol_test() { + let smol = Smol { a: 1, b: 2 }; + let serialized = smol.serialize(); + assert(serialized == [1, 2], serialized); + let deserialized = Smol::deserialize(serialized); + assert(deserialized == smol); +} + +#[test] +fn has_array_test() { + let has_array = HasArray { a: [1, 2], b: true }; + let serialized = has_array.serialize(); + assert(serialized == [1, 2, 1], serialized); + let deserialized = HasArray::deserialize(serialized); + assert(deserialized == has_array); +} + +#[test] +fn fancier_test() { + let fancier = Fancier { a: Smol { a: 1, b: 2 }, b: [0, 1], c: [1, 2, 3], d: "metaprogramming!" }; + let serialized = fancier.serialize(); + assert( + serialized == [ + 1, 2, 0, 1, 1, 2, 3, 0x6d, 0x65, 0x74, 0x61, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x21 + ], serialized + ); + let deserialized = Fancier::deserialize(serialized); + assert(deserialized == fancier); +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr index f7d4058bcb5..e2190c49967 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/traits.nr @@ -1,4 +1,5 @@ use crate::utils::field::field_from_bytes; +use crate::meta::{derive_deserialize, derive_serialize}; // Trait: is_empty // @@ -6,12 +7,12 @@ use crate::utils::field::field_from_bytes; // and it defines empty for the basic data types as 0. // // If a Field is equal to zero, then it is regarded as zero. -// We will go with this definition for now, however it can be problematic -// if a value can actually be zero. In a future refactor, we can +// We will go with this definition for now, however it can be problematic +// if a value can actually be zero. In a future refactor, we can // use the optional type for safety. Doing it now would lead to a worse devex // and would make it harder to sync up with the cpp code. // Preferred over Default trait to convey intent, as default doesn't necessarily mean empty. -trait Empty { +pub trait Empty { fn empty() -> Self; } @@ -55,11 +56,11 @@ pub fn is_empty_array(array: [T; N]) -> bool where T: Empty + Eq array.all(|elem| is_empty(elem)) } -trait Hash { +pub trait Hash { fn hash(self) -> Field; } -trait ToField { +pub trait ToField { fn to_field(self) -> Field; } @@ -148,7 +149,8 @@ impl FromField for U128 { } // docs:start:serialize -trait Serialize { +#[derive_via(derive_serialize)] +pub trait Serialize { fn serialize(self) -> [Field; N]; } // docs:end:serialize @@ -158,19 +160,21 @@ impl Serialize for [Field; N] { self } } + impl Serialize for str { fn serialize(self) -> [Field; N] { - let mut result = [0; N]; - let bytes: [u8; N] = self.as_bytes(); - for i in 0..N { - result[i] = field_from_bytes([bytes[i];1], true); + let bytes = self.as_bytes(); + let mut fields = [0; N]; + for i in 0..bytes.len() { + fields[i] = bytes[i] as Field; } - result + fields } } // docs:start:deserialize -trait Deserialize { +#[derive_via(derive_deserialize)] +pub trait Deserialize { fn deserialize(fields: [Field; N]) -> Self; } // docs:end:deserialize diff --git a/noir/noir-repo/aztec_macros/src/utils/checks.rs b/noir/noir-repo/aztec_macros/src/utils/checks.rs index 5232f67ae87..c067ec570c8 100644 --- a/noir/noir-repo/aztec_macros/src/utils/checks.rs +++ b/noir/noir-repo/aztec_macros/src/utils/checks.rs @@ -18,5 +18,8 @@ pub fn check_for_aztec_dependency( } pub fn has_aztec_dependency(crate_id: &CrateId, context: &HirContext) -> bool { - context.crate_graph[crate_id].dependencies.iter().any(|dep| dep.as_name() == "aztec") + context.crate_graph[crate_id] + .dependencies + .iter() + .any(|dep| dep.as_name() == "aztec-prevent-macro-injection") } diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 18a13517b75..81ac5df66ba 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -278,6 +278,7 @@ pub fn check_crate( crate_id: CrateId, options: &CompileOptions, ) -> CompilationResult<()> { + let options = CompileOptions { disable_macros: true, ..options.clone() }; let macros: &[&dyn MacroProcessor] = if options.disable_macros { &[] } else { &[&aztec_macros::AztecMacro] }; diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index 19fb03999f6..eeda122ec52 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -1,5 +1,5 @@ import { type FunctionCall, type TxExecutionRequest } from '@aztec/circuit-types'; -import { FunctionType, decodeReturnValues } from '@aztec/foundation/abi'; +import { FunctionType, decodeFromAbi } from '@aztec/foundation/abi'; import { type Wallet } from '../account/index.js'; import { BaseContractInteraction, type SendMethodOptions } from './base_contract_interaction.js'; @@ -95,7 +95,7 @@ export class BatchCall extends BaseContractInteraction { ? simulatedTx.privateReturnValues?.nested?.[resultIndex].values : simulatedTx.publicOutput?.publicReturnValues?.[resultIndex].values; - results[callIndex] = rawReturnValues ? decodeReturnValues(call.returnTypes, rawReturnValues) : []; + results[callIndex] = rawReturnValues ? decodeFromAbi(call.returnTypes, rawReturnValues) : []; }); return results; } diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index ec677ed6ecd..57b54e210d8 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -1,7 +1,7 @@ import { type Tx, type TxExecutionRequest, type TxHash, type TxReceipt } from '@aztec/circuit-types'; import { AztecAddress, CompleteAddress, EthAddress } from '@aztec/circuits.js'; import { type L1ContractAddresses } from '@aztec/ethereum'; -import { type ContractArtifact, type DecodedReturn, FunctionType } from '@aztec/foundation/abi'; +import { type AbiDecoded, type ContractArtifact, FunctionType } from '@aztec/foundation/abi'; import { type NodeInfo } from '@aztec/types/interfaces'; import { type MockProxy, mock } from 'jest-mock-extended'; @@ -127,7 +127,7 @@ describe('Contract Class', () => { wallet.createTxExecutionRequest.mockResolvedValue(mockTxRequest); wallet.getContractInstance.mockResolvedValue(contractInstance); wallet.sendTx.mockResolvedValue(mockTxHash); - wallet.simulateUnconstrained.mockResolvedValue(mockUnconstrainedResultValue as any as DecodedReturn); + wallet.simulateUnconstrained.mockResolvedValue(mockUnconstrainedResultValue as any as AbiDecoded); wallet.getTxReceipt.mockResolvedValue(mockTxReceipt); wallet.getNodeInfo.mockResolvedValue(mockNodeInfo); wallet.proveTx.mockResolvedValue(mockTx); diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index ca9daf825e5..8cf48a01389 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -4,7 +4,7 @@ import { type FunctionAbi, FunctionSelector, FunctionType, - decodeReturnValues, + decodeFromAbi, encodeArguments, } from '@aztec/foundation/abi'; @@ -110,6 +110,6 @@ export class ContractFunctionInteraction extends BaseContractInteraction { ? simulatedTx.privateReturnValues?.nested?.[0].values : simulatedTx.publicOutput?.publicReturnValues?.[0].values; - return rawReturnValues ? decodeReturnValues(this.functionDao.returnTypes, rawReturnValues) : []; + return rawReturnValues ? decodeFromAbi(this.functionDao.returnTypes, rawReturnValues) : []; } } diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 3c012b20f28..dba2233180c 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -144,7 +144,7 @@ export { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/c // TODO: These kinds of things have no place on our public api. // External devs will almost certainly have their own methods of doing these things. // If we want to use them in our own "aztec.js consuming code", import them from foundation as needed. -export { encodeArguments } from '@aztec/foundation/abi'; +export { encodeArguments, decodeFromAbi, type AbiType } from '@aztec/foundation/abi'; export { toBigIntBE } from '@aztec/foundation/bigint-buffer'; export { sha256 } from '@aztec/foundation/crypto'; export { makeFetch } from '@aztec/foundation/json-rpc/client'; diff --git a/yarn-project/builder/src/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts index 056ef1154b3..a703990bc61 100644 --- a/yarn-project/builder/src/contract-interface-gen/typescript.ts +++ b/yarn-project/builder/src/contract-interface-gen/typescript.ts @@ -1,7 +1,9 @@ import { type ABIParameter, + type ABIVariable, type ContractArtifact, type FunctionArtifact, + decodeFunctionSignature, getDefaultInitializer, isAztecAddressStruct, isEthAddressStruct, @@ -247,7 +249,7 @@ function generateEvents(events: any[] | undefined) { const eventsMetadata = events.map(event => { const eventName = event.path.split('::').at(-1); - const eventDefProps = event.fields.map((field: any) => `${field.name}: Fr`); + const eventDefProps = event.fields.map((field: ABIVariable) => `${field.name}: ${abiTypeToTypescript(field.type)}`); const eventDef = ` export type ${eventName} = { ${eventDefProps.join('\n')} @@ -255,14 +257,14 @@ function generateEvents(events: any[] | undefined) { `; const fieldNames = event.fields.map((field: any) => `"${field.name}"`); - const eventType = `${eventName}: {decode: (payload: L1EventPayload | undefined) => ${eventName} | undefined, eventSelector: EventSelector, fieldNames: string[] }`; - + const eventType = `${eventName}: {decode: (payload: L1EventPayload | UnencryptedL2Log | undefined) => ${eventName} | undefined, eventSelector: EventSelector, fieldNames: string[] }`; + // Reusing the decodeFunctionSignature + const eventSignature = decodeFunctionSignature(eventName, event.fields); + const eventSelector = `EventSelector.fromSignature('${eventSignature}')`; const eventImpl = `${eventName}: { - decode: this.decodeEvent(${event.fields.length}, EventSelector.fromSignature('${eventName}(${event.fields - .map(() => 'Field') - .join(',')})'), [${fieldNames}]), - eventSelector: EventSelector.fromSignature('${eventName}(${event.fields.map(() => 'Field').join(',')})'), - fieldNames: [${fieldNames}], + decode: this.decodeEvent(${eventSelector}, ${JSON.stringify(event, null, 4)}), + eventSelector: ${eventSelector}, + fieldNames: [${fieldNames}], }`; return { @@ -276,31 +278,32 @@ function generateEvents(events: any[] | undefined) { eventDefs: eventsMetadata.map(({ eventDef }) => eventDef).join('\n'), events: ` // Partial application is chosen is to avoid the duplication of so much codegen. - private static decodeEvent(fieldsLength: number, eventSelector: EventSelector, fields: string[]): (payload: L1EventPayload | undefined) => T | undefined { - return (payload: L1EventPayload | undefined): T | undefined => { - if (payload === undefined) { - return undefined; - } - if (!eventSelector.equals(payload.eventTypeId)) { - return undefined; - } - if (payload.event.items.length !== fieldsLength) { - throw new Error( - 'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length', - ); - } - - return fields.reduce( - (acc, curr, i) => ({ - ...acc, - [curr]: payload.event.items[i], - }), - {} as T, - ); - }; - } + private static decodeEvent( + eventSelector: EventSelector, + eventType: AbiType, + ): (payload: L1EventPayload | UnencryptedL2Log | undefined) => T | undefined { + return (payload: L1EventPayload | UnencryptedL2Log | undefined): T | undefined => { + if (payload === undefined) { + return undefined; + } + + if (payload instanceof L1EventPayload) { + if (!eventSelector.equals(payload.eventTypeId)) { + return undefined; + } + return decodeFromAbi([eventType], payload.event.items) as T; + } else { + let items = []; + for (let i = 0; i < payload.data.length; i += 32) { + items.push(new Fr(payload.data.subarray(i, i + 32))); + } + + return decodeFromAbi([eventType], items) as T; + } + }; + } - public static get events(): { ${eventsMetadata.map(({ eventType }) => eventType).join(', ')} } { + public static get events(): { ${eventsMetadata.map(({ eventType }) => eventType).join(', ')} } { return { ${eventsMetadata.map(({ eventImpl }) => eventImpl).join(',\n')} }; @@ -334,6 +337,7 @@ export function generateTypescriptContractInterface(input: ContractArtifact, art /* eslint-disable */ import { + type AbiType, AztecAddress, type AztecAddressLike, CompleteAddress, @@ -345,6 +349,7 @@ import { type ContractMethod, type ContractStorageLayout, type ContractNotes, + decodeFromAbi, DeployMethod, EthAddress, type EthAddressLike, @@ -358,6 +363,7 @@ import { NoteSelector, Point, type PublicKey, + type UnencryptedL2Log, type Wallet, type WrappedFieldLike, } from '@aztec/aztec.js'; diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index c494114350a..fc5a347f158 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -16,7 +16,12 @@ import { type NodeInfo } from '@aztec/types/interfaces'; import { type AuthWitness } from '../auth_witness.js'; import { type L2Block } from '../l2_block.js'; -import { type GetUnencryptedLogsResponse, type L1EventPayload, type LogFilter } from '../logs/index.js'; +import { + type GetUnencryptedLogsResponse, + type L1EventPayload, + type LogFilter, + type UnencryptedL2Log, +} from '../logs/index.js'; import { type IncomingNotesFilter } from '../notes/incoming_notes_filter.js'; import { type ExtendedNote, type OutgoingNotesFilter, type UniqueNote } from '../notes/index.js'; import { type SiblingPath } from '../sibling_path/sibling_path.js'; @@ -427,7 +432,7 @@ export interface PXE { * The shape of the event generated on the Contract. */ export interface EventMetadata { - decode(payload: L1EventPayload): T | undefined; + decode(payload: L1EventPayload | UnencryptedL2Log): T | undefined; eventSelector: EventSelector; fieldNames: string[]; } diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index 416f2af3ce2..f436552613f 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -65,15 +65,15 @@ describe('Logs', () => { expect(decryptedLog0?.payload.contractAddress).toStrictEqual(testLogContract.address); expect(decryptedLog0?.payload.randomness).toStrictEqual(randomness[0]); expect(decryptedLog0?.payload.eventTypeId).toStrictEqual( - EventSelector.fromField(new Fr(0x00000000000000000000000000000000000000000000000000000000d45c041b)), + EventSelector.fromSignature('ExampleEvent0(Field,Field)'), ); // We decode our event into the event type const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedLog0!.payload); // We check that the event was decoded correctly - expect(event0?.value0).toStrictEqual(preimage[0]); - expect(event0?.value1).toStrictEqual(preimage[1]); + expect(event0?.value0).toStrictEqual(preimage[0].toBigInt()); + expect(event0?.value1).toStrictEqual(preimage[1].toBigInt()); // We check that an event that does not match, is not decoded correctly due to an event type id mismatch const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedLog0!.payload); @@ -89,7 +89,7 @@ describe('Logs', () => { expect(decryptedLog1?.payload.contractAddress).toStrictEqual(testLogContract.address); expect(decryptedLog1?.payload.randomness).toStrictEqual(randomness[1]); expect(decryptedLog1?.payload.eventTypeId).toStrictEqual( - EventSelector.fromField(new Fr(0x00000000000000000000000000000000000000000000000000000000031b1167)), + EventSelector.fromSignature('ExampleEvent1((Field),u8)'), ); // We check our second event, which is a different type @@ -98,7 +98,7 @@ describe('Logs', () => { // We expect the fields to have been populated correctly expect(event1?.value2).toStrictEqual(preimage[2]); // We get the last byte here because value3 is of type u8 - expect(event1?.value3).toStrictEqual(new Fr(preimage[3].toBuffer().subarray(31))); + expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8())); // Again, trying to decode another event with mismatching data does not yield anything const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedLog1!.payload); @@ -177,11 +177,15 @@ describe('Logs', () => { const exampleEvent0Sort = (a: ExampleEvent0, b: ExampleEvent0) => (a.value0 > b.value0 ? 1 : -1); expect(collectedEvent0sWithIncoming.sort(exampleEvent0Sort)).toStrictEqual( - preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort), + preimage + .map(preimage => ({ value0: preimage[0].toBigInt(), value1: preimage[1].toBigInt() })) + .sort(exampleEvent0Sort), ); expect(collectedEvent0sWithOutgoing.sort(exampleEvent0Sort)).toStrictEqual( - preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort), + preimage + .map(preimage => ({ value0: preimage[0].toBigInt(), value1: preimage[1].toBigInt() })) + .sort(exampleEvent0Sort), ); expect([...collectedEvent0sWithIncoming, ...collectedEvent0sWithOutgoing].sort(exampleEvent0Sort)).toStrictEqual( @@ -194,7 +198,7 @@ describe('Logs', () => { .map(preimage => ({ value2: preimage[2], // We get the last byte here because value3 is of type u8 - value3: new Fr(preimage[3].toBuffer().subarray(31)), + value3: BigInt(preimage[3].toBuffer().subarray(31).readUint8()), })) .sort(exampleEvent1Sort), ); @@ -229,7 +233,9 @@ describe('Logs', () => { const exampleEvent0Sort = (a: ExampleEvent0, b: ExampleEvent0) => (a.value0 > b.value0 ? 1 : -1); expect(collectedEvent0s.sort(exampleEvent0Sort)).toStrictEqual( - preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort), + preimage + .map(preimage => ({ value0: preimage[0].toBigInt(), value1: preimage[1].toBigInt() })) + .sort(exampleEvent0Sort), ); const exampleEvent1Sort = (a: ExampleEvent1, b: ExampleEvent1) => (a.value2 > b.value2 ? 1 : -1); @@ -238,7 +244,7 @@ describe('Logs', () => { .map(preimage => ({ value2: preimage[2], // We get the last byte here because value3 is of type u8 - value3: new Fr(preimage[3].toBuffer().subarray(31)), + value3: BigInt(preimage[3].toBuffer().subarray(31).readUint8()), })) .sort(exampleEvent1Sort), ); diff --git a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts index 33f8cafae20..2b6f84a92a2 100644 --- a/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/private_refunds.test.ts @@ -87,7 +87,8 @@ describe('e2e_fees/private_refunds', () => { // the randomness. const refundNoteValue = t.gasSettings.getFeeLimit().sub(new Fr(transactionFee!)); const aliceNpkMHash = t.aliceWallet.getCompleteAddress().publicKeys.masterNullifierPublicKey.hash(); - const aliceRefundNote = new Note([refundNoteValue, aliceNpkMHash, aliceRandomness]); + // Amount has lo and hi limbs, hence the 0. + const aliceRefundNote = new Note([refundNoteValue, Fr.ZERO, aliceNpkMHash, aliceRandomness]); // 5. If the refund flow worked it should have added emitted a note hash of the note we constructed above and we // should be able to add the note to our PXE. Just calling `pxe.addNote(...)` is enough of a check that the note @@ -110,7 +111,8 @@ describe('e2e_fees/private_refunds', () => { // Note that FPC emits randomness as unencrypted log and the tx fee is publicly know so Bob is able to reconstruct // his note just from on-chain data. const bobNpkMHash = t.bobWallet.getCompleteAddress().publicKeys.masterNullifierPublicKey.hash(); - const bobFeeNote = new Note([new Fr(transactionFee!), bobNpkMHash, bobRandomness]); + // Amount has lo and hi limbs, hence the 0. + const bobFeeNote = new Note([new Fr(transactionFee!), Fr.ZERO, bobNpkMHash, bobRandomness]); // 7. Once again we add the note to PXE which computes the note hash and checks that it is in the note hash tree. // TODO(#8238): Implement proper note delivery diff --git a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts index 25535f55b1f..cdb65ecca6a 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts @@ -1,4 +1,4 @@ -import { BatchCall, EventType, Fr } from '@aztec/aztec.js'; +import { BatchCall, EventType } from '@aztec/aztec.js'; import { TokenContract } from '@aztec/noir-contracts.js'; import { TokenContractTest } from './token_contract_test.js'; @@ -45,7 +45,7 @@ describe('e2e_token_contract private transfer recursion', () => { expect(events[0]).toEqual({ from: accounts[0].address, to: accounts[1].address, - amount: new Fr(totalBalance), + amount: totalBalance, }); }); @@ -71,7 +71,7 @@ describe('e2e_token_contract private transfer recursion', () => { expect(events[0]).toEqual({ from: accounts[0].address, to: accounts[1].address, - amount: new Fr(toSend), + amount: toSend, }); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts index 170a5568153..5865ac3343a 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts @@ -46,7 +46,7 @@ describe('e2e_token_contract transfer private', () => { expect(events[0]).toEqual({ from: accounts[0].address, to: accounts[1].address, - amount: new Fr(amount), + amount: amount, }); }); diff --git a/yarn-project/foundation/src/abi/decoder.ts b/yarn-project/foundation/src/abi/decoder.ts index 1749e357fc6..3cba542cb49 100644 --- a/yarn-project/foundation/src/abi/decoder.ts +++ b/yarn-project/foundation/src/abi/decoder.ts @@ -6,21 +6,21 @@ import { isAztecAddressStruct } from './utils.js'; /** * The type of our decoded ABI. */ -export type DecodedReturn = bigint | boolean | AztecAddress | DecodedReturn[] | { [key: string]: DecodedReturn }; +export type AbiDecoded = bigint | boolean | AztecAddress | AbiDecoded[] | { [key: string]: AbiDecoded }; /** - * Decodes return values from a function call. - * Missing support for integer and string. + * Decodes values using a provided ABI. + * Missing support for signed integer. */ -class ReturnValuesDecoder { - constructor(private returnTypes: AbiType[], private flattened: Fr[]) {} +class AbiDecoder { + constructor(private types: AbiType[], private flattened: Fr[]) {} /** * Decodes a single return value from field to the given type. * @param abiType - The type of the return value. * @returns The decoded return value. */ - private decodeReturn(abiType: AbiType): DecodedReturn { + private decodeNext(abiType: AbiType): AbiDecoded { switch (abiType.kind) { case 'field': return this.getNextField().toBigInt(); @@ -34,18 +34,18 @@ class ReturnValuesDecoder { case 'array': { const array = []; for (let i = 0; i < abiType.length; i += 1) { - array.push(this.decodeReturn(abiType.type)); + array.push(this.decodeNext(abiType.type)); } return array; } case 'struct': { - const struct: { [key: string]: DecodedReturn } = {}; + const struct: { [key: string]: AbiDecoded } = {}; if (isAztecAddressStruct(abiType)) { return new AztecAddress(this.getNextField().toBuffer()); } for (const field of abiType.fields) { - struct[field.name] = this.decodeReturn(field.type); + struct[field.name] = this.decodeNext(field.type); } return struct; } @@ -59,7 +59,7 @@ class ReturnValuesDecoder { case 'tuple': { const array = []; for (const tupleAbiType of abiType.fields) { - array.push(this.decodeReturn(tupleAbiType)); + array.push(this.decodeNext(tupleAbiType)); } return array; } @@ -69,8 +69,8 @@ class ReturnValuesDecoder { } /** - * Gets the next field in the flattened return values. - * @returns The next field in the flattened return values. + * Gets the next field in the flattened buffer. + * @returns The next field in the flattened buffer. */ private getNextField(): Fr { const field = this.flattened.shift(); @@ -81,30 +81,29 @@ class ReturnValuesDecoder { } /** - * Decodes all the return values for the given function ABI. - * Aztec.nr support only single return value - * The return value can however be simple types, structs or arrays + * Decodes all the values for the given ABI. + * The decided value can be simple types, structs or arrays * @returns The decoded return values. */ - public decode(): DecodedReturn { - if (this.returnTypes.length > 1) { - throw new Error('Multiple return values not supported'); + public decode(): AbiDecoded { + if (this.types.length > 1) { + throw new Error('Multiple types not supported'); } - if (this.returnTypes.length === 0) { + if (this.types.length === 0) { return []; } - return this.decodeReturn(this.returnTypes[0]); + return this.decodeNext(this.types[0]); } } /** - * Decodes return values from a function call. - * @param abi - The ABI entry of the function. - * @param returnValues - The decoded return values. + * Decodes values in a flattened Field array using a provided ABI. + * @param abi - The ABI to use as reference. + * @param buffer - The flattened Field array to decode. * @returns */ -export function decodeReturnValues(returnTypes: AbiType[], returnValues: Fr[]) { - return new ReturnValuesDecoder(returnTypes, returnValues.slice()).decode(); +export function decodeFromAbi(typ: AbiType[], buffer: Fr[]) { + return new AbiDecoder(typ, buffer.slice()).decode(); } /** diff --git a/yarn-project/prover-client/package.json b/yarn-project/prover-client/package.json index 636bdcac423..9a99cddb3d8 100644 --- a/yarn-project/prover-client/package.json +++ b/yarn-project/prover-client/package.json @@ -96,4 +96,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 2d7c0cbde1f..b1cfcd20283 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -42,8 +42,8 @@ import { } from '@aztec/circuits.js'; import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash'; import { + type AbiDecoded, type ContractArtifact, - type DecodedReturn, EventSelector, FunctionSelector, encodeArguments, @@ -567,7 +567,7 @@ export class PXEService implements PXE { to: AztecAddress, _from?: AztecAddress, scopes?: AztecAddress[], - ): Promise { + ): Promise { // all simulations must be serialized w.r.t. the synchronizer return await this.jobQueue.put(async () => { // TODO - Should check if `from` has the permission to call the view function. @@ -964,17 +964,11 @@ export class PXEService implements PXE { } if (visibleEvent.payload.event.items.length !== eventMetadata.fieldNames.length) { throw new Error( - 'Something is weird here, we have matching FunctionSelectors, but the actual payload has mismatched length', + 'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length', ); } - return eventMetadata.fieldNames.reduce( - (acc, curr, i) => ({ - ...acc, - [curr]: visibleEvent.payload.event.items[i], - }), - {} as T, - ); + return eventMetadata.decode(visibleEvent.payload); }) .filter(visibleEvent => visibleEvent !== undefined) as T[]; @@ -1001,17 +995,11 @@ export class PXEService implements PXE { if (unencryptedLogBuf.byteLength !== eventMetadata.fieldNames.length * 32 + 32) { throw new Error( - 'Something is weird here, we have matching FunctionSelectors, but the actual payload has mismatched length', + 'Something is weird here, we have matching EventSelectors, but the actual payload has mismatched length', ); } - return eventMetadata.fieldNames.reduce( - (acc, curr, i) => ({ - ...acc, - [curr]: new Fr(unencryptedLogBuf.subarray(i * 32, i * 32 + 32)), - }), - {} as T, - ); + return eventMetadata.decode(unencryptedLog.log); }) .filter(unencryptedLog => unencryptedLog !== undefined) as T[]; diff --git a/yarn-project/simulator/src/client/simulator.test.ts b/yarn-project/simulator/src/client/simulator.test.ts index 601cb2aa6e6..1e4cbe6d876 100644 --- a/yarn-project/simulator/src/client/simulator.test.ts +++ b/yarn-project/simulator/src/client/simulator.test.ts @@ -55,7 +55,9 @@ describe('Simulator', () => { const storageSlot = TokenBlacklistContractArtifact.storageLayout['balances'].slot; const noteTypeId = TokenBlacklistContractArtifact.notes['TokenNote'].id; - const createNote = (amount = 123n) => new Note([new Fr(amount), ownerMasterNullifierPublicKey.hash(), Fr.random()]); + // Amount is a U128, with a lo and hi limbs + const createNote = (amount = 123n) => + new Note([new Fr(amount), new Fr(0), ownerMasterNullifierPublicKey.hash(), Fr.random()]); it('should compute note hashes and nullifier', async () => { oracle.getFunctionArtifactByName.mockResolvedValue(artifact); diff --git a/yarn-project/simulator/src/client/test_utils.ts b/yarn-project/simulator/src/client/test_utils.ts index 9253a36fcdb..af7fa0345d9 100644 --- a/yarn-project/simulator/src/client/test_utils.ts +++ b/yarn-project/simulator/src/client/test_utils.ts @@ -1,8 +1,35 @@ -import { Fq, Fr, GeneratorIndex, Point } from '@aztec/circuits.js'; +import { Fq, Fr, Point } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { pedersenCommit } from '@aztec/foundation/crypto'; // Copied over from `noir-projects/aztec-nr/aztec/src/generators.nr` +const GENERATORS = [ + new Point( + new Fr(0x30426e64aee30e998c13c8ceecda3a77807dbead52bc2f3bf0eae851b4b710c1n), + new Fr(0x113156a068f603023240c96b4da5474667db3b8711c521c748212a15bc034ea6n), + false, + ), + new Point( + new Fr(0x2825c79cc6a5cbbeef7d6a8f1b6a12b312aa338440aefeb4396148c89147c049n), + new Fr(0x129bfd1da54b7062d6b544e7e36b90736350f6fba01228c41c72099509f5701en), + false, + ), + new Point( + new Fr(0x0edb1e293c3ce91bfc04e3ceaa50d2c541fa9d091c72eb403efb1cfa2cb3357fn), + new Fr(0x1341d675fa030ece3113ad53ca34fd13b19b6e9762046734f414824c4d6ade35n), + false, + ), + new Point( + new Fr(0x0e0dad2250583f2a9f0acb04ededf1701b85b0393cae753fe7e14b88af81cb52n), + new Fr(0x0973b02c5caac339ee4ad5dab51329920f7bf1b6a07e1dabe5df67040b300962n), + false, + ), + new Point( + new Fr(0x2f3342e900e8c488a28931aae68970738fdc68afde2910de7b320c00c902087dn), + new Fr(0x1bf958dc63cb09d59230603a0269ae86d6f92494da244910351f1132df20fc08n), + false, + ), +]; + const G_SLOT = new Point( new Fr(0x041223147b680850dc82e8a55a952d4df20256fe0593d949a9541ca00f0abf15n), new Fr(0x0a8c72e60d0e60f5d804549d48f3044d06140b98ed717a9b532af630c1530791n), @@ -16,14 +43,14 @@ const G_SLOT = new Point( * @returns A note hash. */ export function computeNoteHash(storageSlot: Fr, noteContent: Fr[]): Fr { - // TODO(#7771): update this to do only 1 MSM call - const c = pedersenCommit( - noteContent.map(f => f.toBuffer()), - GeneratorIndex.NOTE_HIDING_POINT, - ); - const noteHidingPointBeforeSlotting = new Point(new Fr(c[0]), new Fr(c[1]), false); - const grumpkin = new Grumpkin(); + const noteHidingPointBeforeSlotting = noteContent + .slice(1) + .reduce( + (acc, item, i) => grumpkin.add(acc, grumpkin.mul(GENERATORS[i + 1], new Fq(item.toBigInt()))), + grumpkin.mul(GENERATORS[0], new Fq(noteContent[0].toBigInt())), + ); + const slotPoint = grumpkin.mul(G_SLOT, new Fq(storageSlot.toBigInt())); const noteHidingPoint = grumpkin.add(noteHidingPointBeforeSlotting, slotPoint); return noteHidingPoint.x; diff --git a/yarn-project/simulator/src/client/unconstrained_execution.ts b/yarn-project/simulator/src/client/unconstrained_execution.ts index 1a7b957f799..7fc622c6987 100644 --- a/yarn-project/simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/simulator/src/client/unconstrained_execution.ts @@ -1,9 +1,4 @@ -import { - type DecodedReturn, - type FunctionArtifact, - type FunctionSelector, - decodeReturnValues, -} from '@aztec/foundation/abi'; +import { type AbiDecoded, type FunctionArtifact, type FunctionSelector, decodeFromAbi } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -24,7 +19,7 @@ export async function executeUnconstrainedFunction( functionSelector: FunctionSelector, args: Fr[], log = createDebugLogger('aztec:simulator:unconstrained_execution'), -): Promise { +): Promise { log.verbose(`Executing unconstrained function ${contractAddress}:${functionSelector}(${artifact.name})`); const acir = artifact.bytecode; @@ -42,6 +37,6 @@ export async function executeUnconstrainedFunction( }); const returnWitness = witnessMapToFields(acirExecutionResult.returnWitness); - return decodeReturnValues(artifact.returnTypes, returnWitness); + return decodeFromAbi(artifact.returnTypes, returnWitness); } // docs:end:execute_unconstrained_function diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index 5756ab305a7..c06a4d3a7b3 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -19,7 +19,6 @@ import { AZTEC_INTERNAL_ATTRIBUTE, AZTEC_PRIVATE_ATTRIBUTE, AZTEC_PUBLIC_ATTRIBUTE, - AZTEC_PUBLIC_VM_ATTRIBUTE, AZTEC_VIEW_ATTRIBUTE, type NoirCompiledContract, } from '../noir/index.js'; @@ -189,10 +188,7 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: No function getFunctionType(fn: NoirCompiledContractFunction): FunctionType { if (fn.custom_attributes.includes(AZTEC_PRIVATE_ATTRIBUTE)) { return FunctionType.PRIVATE; - } else if ( - fn.custom_attributes.includes(AZTEC_PUBLIC_ATTRIBUTE) || - fn.custom_attributes.includes(AZTEC_PUBLIC_VM_ATTRIBUTE) - ) { + } else if (fn.custom_attributes.includes(AZTEC_PUBLIC_ATTRIBUTE)) { return FunctionType.PUBLIC; } else if (fn.is_unconstrained) { return FunctionType.UNCONSTRAINED; diff --git a/yarn-project/types/src/noir/index.ts b/yarn-project/types/src/noir/index.ts index a35b0615372..ca8d028bc22 100644 --- a/yarn-project/types/src/noir/index.ts +++ b/yarn-project/types/src/noir/index.ts @@ -7,12 +7,11 @@ import { type DebugInfo, } from '@aztec/foundation/abi'; -export const AZTEC_PRIVATE_ATTRIBUTE = 'aztec(private)'; -export const AZTEC_PUBLIC_ATTRIBUTE = 'aztec(public)'; -export const AZTEC_PUBLIC_VM_ATTRIBUTE = 'aztec(public-vm)'; -export const AZTEC_INTERNAL_ATTRIBUTE = 'aztec(internal)'; -export const AZTEC_INITIALIZER_ATTRIBUTE = 'aztec(initializer)'; -export const AZTEC_VIEW_ATTRIBUTE = 'aztec(view)'; +export const AZTEC_PRIVATE_ATTRIBUTE = 'private'; +export const AZTEC_PUBLIC_ATTRIBUTE = 'public'; +export const AZTEC_INTERNAL_ATTRIBUTE = 'internal'; +export const AZTEC_INITIALIZER_ATTRIBUTE = 'initializer'; +export const AZTEC_VIEW_ATTRIBUTE = 'view'; /** * An error could be a custom error of any regular type or a formatted string error.