From 288cba73ddd54bc9dd6b5310e9dcfe80aee8c731 Mon Sep 17 00:00:00 2001 From: Nicolas Chamo Date: Fri, 15 May 2026 12:13:47 -0300 Subject: [PATCH 1/4] fix(aztec-up): fall back to no timeout when /usr/bin/timeout absent (macOS) (#23310) --- aztec-up/bin/0.0.1/install | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/aztec-up/bin/0.0.1/install b/aztec-up/bin/0.0.1/install index db2ecec08d28..20d48879ad35 100755 --- a/aztec-up/bin/0.0.1/install +++ b/aztec-up/bin/0.0.1/install @@ -55,9 +55,21 @@ function echo_yellow { } function timeout { - if [ "${CI:-0}" = "1" ] || [ "${CI:-0}" = "true" ]; then + if [ "${CI:-0}" != "1" ] && [ "${CI:-0}" != "true" ]; then + shift + "$@" + return + fi + if [ -x /usr/bin/timeout ]; then + # Prefer coreutils `timeout` when available. Absolute path avoids re-entering this function. /usr/bin/timeout "$@" + elif perl -e1 2>/dev/null; then + # Fall back to perl's `alarm` when /usr/bin/timeout is missing. + local duration=$1 + shift + perl -e "alarm $duration; exec @ARGV" -- "$@" else + # No timeout backend available -- run unguarded rather than fail the install. shift "$@" fi From b1d440d1663cc9a77230d62ebca7b41849bff35f Mon Sep 17 00:00:00 2001 From: Martin Verzilli Date: Fri, 15 May 2026 17:39:14 +0200 Subject: [PATCH 2/4] chore: reduce compat e2e timeout (#23318) They are currently set at 5.5 hours, which is excessive (last few runs were all under 20 minutes) --- .github/workflows/ci3.yml | 4 ++-- ci.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci3.yml b/.github/workflows/ci3.yml index dab818bc7398..ea3c1c1d901e 100644 --- a/.github/workflows/ci3.yml +++ b/.github/workflows/ci3.yml @@ -476,7 +476,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Run Backwards Compatibility E2E Tests - timeout-minutes: 330 + timeout-minutes: 60 env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -489,7 +489,7 @@ jobs: CI3_INSTANCE_PROFILE_NAME: ${{ secrets.CI3_INSTANCE_PROFILE_NAME }} CI3_SECURITY_GROUP_ID: ${{ secrets.CI3_SECURITY_GROUP_ID }} RUN_ID: ${{ github.run_id }} - AWS_SHUTDOWN_TIME: 300 + AWS_SHUTDOWN_TIME: 60 run: ./.github/ci3.sh compat-e2e # Publishes the release (npm, Docker, GitHub release, aztec-up scripts, etc.). diff --git a/ci.sh b/ci.sh index 68675e915327..0a48e1b99ee1 100755 --- a/ci.sh +++ b/ci.sh @@ -311,7 +311,7 @@ case "$cmd" in # against contract artifacts from prior stable releases. export CI_DASHBOARD="releases" export JOB_ID="x-compat-e2e" - export AWS_SHUTDOWN_TIME=300 + export AWS_SHUTDOWN_TIME=60 rc=0 bootstrap_ec2 "./bootstrap.sh ci-compat-e2e" || rc=$? # On nightly tags compat-e2e is non-blocking (continue-on-error in ci3.yml), so From d3cf01b826b0ed512aacb18fe59ae472ccee5d6e Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 15 May 2026 15:11:23 -0400 Subject: [PATCH 3/4] feat(aztec-nr): V2 handshake registry for non interactive constrained delivery (#23278) Fixes https://linear.app/aztec-labs/issue/F-586/handshake-registry-non-interactive-handshake-function (updates work from https://github.com/AztecProtocol/aztec-packages/pull/22854) Updated the handshake registry following the new spec in https://www.notion.so/aztecnetwork/Plan-Onchain-constrained-delivery-34fa1f6b0e358063b64ecc25b768c359 - We store the raw handshake secret in the Handshake note - The raw secret never leaves the registry. The registry app-siloes the secret against the `msg_sender` and returns it from the both the handshake and its utility method for fetching the siloed secret. - The registry provides a validation method to check the app siloed secret is for a valid handshake secret - Various additional tests. Mainly making sure we reject invalid handshakes and that we never expose the raw handshake secret - Linked directly to https://linear.app/aztec-labs/issue/F-653/route-handshake-log-through-do-private-message-delivery-to-preserve to implement as a follow-up I decided to just update the handshake registry directly rather than duplicating it with the old one. I felt if we ever wanted to go back to the old spec, we have the git history which we can reference for the old contract. Note packing bug was revealed with this work: https://linear.app/aztec-labs/issue/F-665/note-properties-generates-incorrect-selectors-for-custom-packed-fields I attempted to fitler notes after calling `get_notes` which through testing revealed itself as the incorrect way to filter notes. This should be made easier as it is a foot-gun for developers: https://linear.app/aztec-labs/issue/F-666/get-notes-and-view-notes-make-it-easy-to-filter-after-pagination --------- Co-authored-by: Nicolas Chamo --- .../aztec/src/keys/ecdh_shared_secret.nr | 2 +- .../aztec/src/note/note_getter/test.nr | 3 +- .../src/handshake_note.nr | 43 ++- .../handshake_registry_contract/src/main.nr | 104 ++++--- .../handshake_registry_contract/src/test.nr | 290 ++++++++++++------ .../crates/types/src/constants.nr | 4 - .../crates/types/src/constants_tests.nr | 5 +- yarn-project/constants/src/constants.gen.ts | 1 - 8 files changed, 280 insertions(+), 172 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/keys/ecdh_shared_secret.nr b/noir-projects/aztec-nr/aztec/src/keys/ecdh_shared_secret.nr index 9bc189b02b67..4770d3e7cc65 100644 --- a/noir-projects/aztec-nr/aztec/src/keys/ecdh_shared_secret.nr +++ b/noir-projects/aztec-nr/aztec/src/keys/ecdh_shared_secret.nr @@ -27,7 +27,7 @@ pub fn derive_ecdh_shared_secret(secret: Scalar, public_key: Point) -> Point { /// Computes an app-siloed shared secret from a raw ECDH shared secret point and a contract address. /// /// `s_app = h(DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, S.x, S.y, contract_address)` -pub(crate) fn compute_app_siloed_shared_secret(shared_secret: Point, contract_address: AztecAddress) -> Field { +pub fn compute_app_siloed_shared_secret(shared_secret: Point, contract_address: AztecAddress) -> Field { poseidon2_hash_with_separator( [shared_secret.x, shared_secret.y, contract_address.to_field()], DOM_SEP__APP_SILOED_ECDH_SHARED_SECRET, diff --git a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr index 4399f6cd172c..a2ca3e046702 100644 --- a/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr +++ b/noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr @@ -8,6 +8,7 @@ use crate::{ note_getter_options::{NoteGetterOptions, PropertySelector, Sort, SortOrder}, }, oracle::random::random, + protocol::{address::AztecAddress, traits::FromField}, test::{helpers::test_environment::TestEnvironment, mocks::mock_note::MockNote}, utils::comparison::Comparator, }; @@ -18,8 +19,6 @@ fn sort_criterion(index: u8, order: u8) -> Option { ) } -use crate::protocol::{address::AztecAddress, traits::FromField}; - global storage_slot: Field = 42; global owner: AztecAddress = AztecAddress::from_field(50); diff --git a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/handshake_note.nr b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/handshake_note.nr index 1dcab11ae235..af84b0e17cf0 100644 --- a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/handshake_note.nr +++ b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/handshake_note.nr @@ -1,43 +1,36 @@ use aztec::{ + keys::ecdh_shared_secret::compute_app_siloed_shared_secret, macros::notes::note, - protocol::{ - address::AztecAddress, - constants::DOM_SEP__HANDSHAKE_SECRET_HASH, - hash::poseidon2_hash_with_separator, - point::Point, - traits::{Deserialize, Packable, Serialize}, - }, + protocol::{address::AztecAddress, point::Point, traits::{Deserialize, Packable, Serialize}}, }; /// A record of a handshake established by the note's owner (the sender). /// -/// Stored in [`crate::HandshakeRegistry`]'s `handshakes` set. Holds the **hash** of the master shared secret. -/// A contract that wants to use this handshake will later prove the note's existence and -/// use the kernel key-validation mechanism to derive its own app-siloed secret from the master, so -/// the master is never exposed to dependent contracts. +/// Stored in [`crate::HandshakeRegistry`]'s `handshakes` state. Holds the raw ECDH shared-secret point `S`. App +/// contracts never see `S` directly: they call the registry's private surface, which +/// silos `S` against the calling contract's address and hands back the app-siloed `Field`. #[derive(Deserialize, Eq, Packable, Serialize)] #[note] pub struct HandshakeNote { - /// Hash of the master shared secret: `poseidon2_hash_with_separator([S.x, S.y], DOM_SEP__HANDSHAKE_SECRET_HASH)` - /// over the raw ECDH point `S = eph_sk * recipient_address_point`. - pub secret_hash: Field, /// Stored so contracts can constrain the kind of handshake they accept (e.g. an app may want to require interactive /// handshakes only). - pub handshake_type: u8, + handshake_type: u8, /// The recipient this handshake authorizes. Part of the note preimage so a contract proving note /// existence can bind the proof to the intended recipient. - pub recipient: AztecAddress, + recipient: AztecAddress, + /// The raw ECDH shared-secret point `S = eph_sk * recipient_address_point`. Only this module can read it for + /// siloing, and it is never returned by an external function. + secret: Point, } impl HandshakeNote { - pub fn new(shared_secret: Point, handshake_type: u8, recipient: AztecAddress) -> Self { - Self { - secret_hash: poseidon2_hash_with_separator( - [shared_secret.x, shared_secret.y], - DOM_SEP__HANDSHAKE_SECRET_HASH, - ), - handshake_type, - recipient, - } + pub(crate) fn new(shared_secret: Point, handshake_type: u8, recipient: AztecAddress) -> Self { + Self { handshake_type, recipient, secret: shared_secret } + } + + /// Returns the app-siloed shared secret for `caller`, computed via aztec-nr's canonical + /// [`compute_app_siloed_shared_secret`]. + pub(crate) fn siloed_for(self, caller: AztecAddress) -> Field { + compute_app_siloed_shared_secret(self.secret, caller) } } diff --git a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/main.nr b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/main.nr index 08523cf279ec..0b77191be604 100644 --- a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/main.nr @@ -8,12 +8,12 @@ pub(crate) global NON_INTERACTIVE_HANDSHAKE: u8 = 1; /// Registry for the constrained-delivery shared-secret handshake protocol. /// -/// The registry's job is to establish a master shared secret between a sender and a recipient, and to store a -/// per-sender note recording that the handshake happened. A contract that later wants to use this -/// handshake retrieves the note via the [`HandshakeRegistry::get_handshake`] utility, asserts it exists in the -/// registry, -/// and uses the kernel key-validation mechanism to derive its own app-siloed shared secret from the master hash stored -/// in the note. +/// The registry establishes a master shared-secret point `S` between a sender and a recipient and stores one current +/// note for each `(recipient, sender)` pair. The raw `S` never leaves the registry: app contracts call +/// [`HandshakeRegistry::non_interactive_handshake`] to receive the secret already siloed to the caller, call +/// [`HandshakeRegistry::get_app_siloed_secret`] offchain for an existing handshake, and use +/// [`HandshakeRegistry::validate_handshake`] to check an app-siloed secret against the current stored handshake. The +/// private surfaces silo against `msg_sender()`, so a contract can only obtain or validate secrets siloed to itself. /// /// Currently only implements the non-interactive flow (see [`HandshakeRegistry::non_interactive_handshake`]). #[aztec] @@ -24,39 +24,41 @@ pub contract HandshakeRegistry { keys::{ecdh_shared_secret::derive_ecdh_shared_secret, ephemeral::generate_positive_ephemeral_key_pair}, macros::{functions::external, storage::storage}, messages::message_delivery::MessageDelivery, - note::note_viewer_options::NoteViewerOptions, oracle::notes::set_sender_for_tags, protocol::{ address::AztecAddress, constants::DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, hash::compute_log_tag, traits::ToField, }, - state_vars::{Owned, PrivateSet}, + state_vars::{Map, Owned, PrivateMutable}, }; #[storage] struct Storage { - /// Per-sender set of [`HandshakeNote`]s. The sender is chosen by the caller and is unconstrained throughout the - /// constrained-delivery protocol. - handshakes: Owned, Context>, + /// One current [`HandshakeNote`] per `(recipient, sender)` pair. Re-handshaking for the same pair replaces the + /// prior sender-owned note, so only the latest handshake remains valid. + handshakes: Map, Context>, Context>, } - /// Performs a non-interactive handshake from `sender` to `recipient`. + /// Performs a non-interactive handshake from `sender` to `recipient` and returns the app-siloed shared secret + /// for the calling contract. /// /// Generates a fresh ephemeral key pair `(eph_sk, eph_pk)`, computes the raw ECDH shared secret point - /// `S = eph_sk * recipient_address_point`, and produces two effects: + /// `S = eph_sk * recipient_address_point`, and produces three effects: /// - /// 1. Inserts a [`HandshakeNote`] owned by `sender`. + /// 1. Inserts or replaces a [`HandshakeNote`] owned by `sender`, holding the raw point `S`. /// 2. Emits a 1-field private log under a recipient-keyed tag with payload `[eph_pk.x]`. The recipient - /// discovers handshakes addressed to them by scanning their tag and recovers the master shared secret - /// from `eph_pk` via their own ECDH (`recipient_isk * eph_pk`). `eph_pk.y` is fixed positive by the + /// discovers handshakes addressed to them by scanning their tag and recovers `S` from `eph_pk` via their own + /// ECDH (`recipient_isk * eph_pk`). `eph_pk.y` is fixed positive by the /// [`generate_positive_ephemeral_key_pair`] convention, so only `eph_pk.x` is transmitted. + /// 3. Returns the app-siloed shared secret for `msg_sender()`, allowing the caller to fold "handshake + first + /// tag" into one call without a second hop into the registry. /// /// # Panics /// If `recipient` is not a valid curve point. There are no upstream side effects in this call frame to /// protect, and a fallback would insert a permanent note recording a handshake with an invalid recipient, /// polluting registry state. #[external("private")] - fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress) { + fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress) -> Field { let recipient_point = recipient.to_address_point().expect(f"recipient address is not on the curve"); let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair(); @@ -71,41 +73,65 @@ pub contract HandshakeRegistry { // discover the handshake via the recipient-keyed log emitted below, not via the sender's note. // We use onchain unconstrained delivery rather than `OFFCHAIN` so the note is // discoverable via normal PXE sync. - self.storage.handshakes.at(sender).insert(note).deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); + self.storage.handshakes.at(recipient).at(sender).initialize_or_replace(|_| note).deliver( + MessageDelivery.ONCHAIN_UNCONSTRAINED, + ); let log_tag = compute_log_tag( recipient.to_field(), DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, ); + + // TODO(F-653): Encrypt `eph_pk.x` to keep the log private self.context.emit_private_log_vec_unsafe(log_tag, BoundedVec::from_array([eph_pk.x])); + + note.siloed_for(self.msg_sender()) } - /// Returns the most recently inserted [`HandshakeNote`] held for `sender` matching - /// `(recipient, handshake_type)`, or `None` if no such note exists. + /// Asserts that `app_siloed_secret` is the silo of a stored handshake from `sender` to `recipient`, for the + /// caller (`msg_sender()`). /// - /// Returning the latest (rather than the first) lets a sender re-initiate by issuing a fresh handshake; e.g. - /// if the previous secret was leaked. + /// Apps that receive an `app_siloed_secret` from an untrusted source call this once to validate that secret + /// against the registry's stored handshake. /// - /// This is the discovery surface a contract uses to obtain a note hint before asserting existence - /// of the note at this registry's address. + /// # Panics + /// If no stored handshake for `(sender, recipient)` silos to `app_siloed_secret` under the calling contract's + /// address. + #[external("private")] + fn validate_handshake(sender: AztecAddress, recipient: AztecAddress, app_siloed_secret: Field) { + let caller = self.msg_sender(); + let replacement_note_message = self.storage.handshakes.at(recipient).at(sender).get_note(); + let note = replacement_note_message.get_note(); + + assert(note.siloed_for(caller) == app_siloed_secret, "no matching handshake"); + + // `PrivateMutable::get_note` proves the current note by nullifying and recreating it. Deliver the + // replacement to the sender so later validation calls can prove the same current handshake again. + replacement_note_message.deliver(MessageDelivery.ONCHAIN_UNCONSTRAINED); + } + + /// Returns the app-siloed shared secret for an existing handshake. + /// + /// This is the existing-handshake retrieval surface for constrained delivery. It returns only `silo(S, caller)`, + /// never raw `S`; a different caller receives a different app-siloed value for the same registry note. This is + /// a utility function because it is a local read helper. Contracts should still call + /// [HandshakeRegistry::validate_handshake] when they need a constrained proof that a supplied app-siloed secret + /// matches the current handshake. #[external("utility")] - unconstrained fn get_handshake( + unconstrained fn get_app_siloed_secret( sender: AztecAddress, recipient: AztecAddress, - handshake_type: u8, - ) -> Option { - // We pull all notes for `sender` and filter (recipient, handshake_type) below, walking from the end so the - // first hit is the most recently inserted match. - let notes = self.storage.handshakes.at(sender).view_notes(NoteViewerOptions::new()); - let mut found = Option::none(); - let len = notes.len(); - for i in 0..len { - let note = notes.get(len - 1 - i); - if (note.recipient == recipient) & (note.handshake_type == handshake_type) { - found = Option::some(note); - break; - } + // TODO(F-671): replace with `self.msg_sender()` once utility context exposes it. The + // explicit param forces hooks to also gate on `params[2]`; otherwise any contract that + // can authorize (target, selector) can read another caller's siloed secret. + caller: AztecAddress, + ) -> Option { + let handshake = self.storage.handshakes.at(recipient).at(sender); + + if handshake.is_initialized() { + Option::some(handshake.view_note().siloed_for(caller)) + } else { + Option::none() } - found } } diff --git a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/test.nr b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/test.nr index 53ce5d2731cc..87bbb5519f89 100644 --- a/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/test.nr @@ -1,4 +1,4 @@ -use crate::{HandshakeRegistry, NON_INTERACTIVE_HANDSHAKE}; +use crate::HandshakeRegistry; use aztec::{ protocol::{ @@ -7,7 +7,7 @@ use aztec::{ hash::{compute_log_tag, compute_siloed_private_log_first_field}, traits::{FromField, ToField}, }, - test::helpers::{test_environment::TestEnvironment, txe_oracles}, + test::helpers::{test_environment::{CallPrivateOptions, TestEnvironment}, txe_oracles}, }; unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress, AztecAddress, AztecAddress) { @@ -35,47 +35,19 @@ unconstrained fn setup_with_two_recipients() -> (TestEnvironment, AztecAddress, } #[test] -unconstrained fn non_interactive_handshake_stores_recipient_bound_note_at_sender() { +unconstrained fn non_interactive_handshake_stores_handshake_for_sender_and_recipient() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let returned_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - let note = env.execute_utility(registry.get_handshake(sender, recipient, NON_INTERACTIVE_HANDSHAKE)).expect( - f"handshake note should be present", + let utility_secret = env.execute_utility(registry.get_app_siloed_secret(sender, recipient, sender)).expect( + f"handshake secret should be present", ); - assert_eq(note.handshake_type, NON_INTERACTIVE_HANDSHAKE); - assert_eq(note.recipient, recipient); - // TODO: `secret_hash` is the hash of the raw ECDH point components. Without an storing the raw point in an oracle - // we - // can't reconstruct it from the recipient's side here, so we just check that something non-zero was stored. - assert(note.secret_hash != 0, "secret_hash should be set"); -} + assert_eq(utility_secret, returned_secret); + assert(returned_secret != 0, "returned app-siloed secret should be non-zero"); -#[test] -unconstrained fn handshakes_are_isolated_per_sender() { - let (env, registry_address, sender, other_sender, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - assert( - env.execute_utility(registry.get_handshake(other_sender, recipient, NON_INTERACTIVE_HANDSHAKE)).is_none(), - "other sender should not see primary sender's handshake", - ); - - env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); - - let sender_note = env.execute_utility(registry.get_handshake(sender, recipient, NON_INTERACTIVE_HANDSHAKE)).expect( - f"primary sender should see its handshake", - ); - let other_sender_note = env - .execute_utility(registry.get_handshake(other_sender, recipient, NON_INTERACTIVE_HANDSHAKE)) - .expect(f"other sender should see its handshake"); - - assert( - sender_note.secret_hash != other_sender_note.secret_hash, - "senders should not resolve to the same handshake", - ); + env.call_private(sender, registry.validate_handshake(sender, recipient, returned_secret)); } // The DH-direct flow lifts `recipient` to a curve point and fails loud if the address has no @@ -90,50 +62,21 @@ unconstrained fn handshake_to_invalid_recipient_panics() { let invalid_recipient = AztecAddress::from_field(3); assert(!invalid_recipient.is_valid()); - env.call_private(sender, registry.non_interactive_handshake(sender, invalid_recipient)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, invalid_recipient)); } -// A recipient who knows only their own address can compute the -// expected tag, find the log under it, and recover `eph_pk.x` from the payload. -#[test] -unconstrained fn non_interactive_handshake_emits_recipient_discoverable_log() { - let (env, registry_address, sender, _, recipient) = setup(); - let registry = HandshakeRegistry::at(registry_address); - - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - - // The function emits two private logs in order: log 0 is the encrypted note delivered via - // `MessageDelivery.ONCHAIN_UNCONSTRAINED`, log 1 is the recipient-keyed announcement carrying - // `eph_pk.x` under a recipient-derivable tag. - let logs = txe_oracles::get_last_tx_effects().private_logs; - assert_eq(logs.len(), 2); - - let raw_tag = compute_log_tag( - recipient.to_field(), - DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, - ); - let expected_siloed_tag = compute_siloed_private_log_first_field(registry_address, raw_tag); - - // siloed tag (1) + eph_pk.x (1) = 2 fields. - let log_data = logs.get(1); - assert_eq(log_data.len(), 2); - assert_eq(log_data.get(0), expected_siloed_tag); - let eph_pk_x = log_data.get(1); - assert(eph_pk_x != 0, "eph_pk.x should be non-zero"); -} - -// The handshake tag depends only on the recipient, so multiple senders posting to the same recipient -// land under one key. Each emitted log carries its own ephemeral key. +// The handshake tag depends only on the recipient, so multiple senders posting to +// the same recipient land under log tag. #[test] unconstrained fn two_senders_to_same_recipient_share_tag_with_distinct_payloads() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_first = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_first.len(), 2); - env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); let logs_second = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_second.len(), 2); @@ -142,35 +85,35 @@ unconstrained fn two_senders_to_same_recipient_share_tag_with_distinct_payloads( DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, ); let expected_siloed_tag = compute_siloed_private_log_first_field(registry_address, raw_tag); + + // Each announcement log carries [siloed tag, eph_pk.x] = 2 fields. + assert_eq(logs_first.get(1).len(), 2); + assert_eq(logs_second.get(1).len(), 2); assert_eq(logs_first.get(1).get(0), expected_siloed_tag); assert_eq(logs_second.get(1).get(0), expected_siloed_tag); let eph_pk_x_0 = logs_first.get(1).get(1); let eph_pk_x_1 = logs_second.get(1).get(1); + assert(eph_pk_x_0 != 0, "eph_pk.x should be non-zero"); + assert(eph_pk_x_1 != 0, "eph_pk.x should be non-zero"); assert(eph_pk_x_0 != eph_pk_x_1, "ephemeral keys should differ in handshakes from different senders"); } -// Two handshakes from the same sender to the same `(recipient, type)` are not deduplicated. -// Each call generates a fresh ephemeral key, so the registry accumulates distinct notes and emits -// distinct logs under the recipient's tag. +// Re-handshaking for the same `(sender, recipient)` replaces the prior current handshake and produces +// distinct `S` (hence distinct app-siloed secrets when siloed against the same caller). Each call emits a +// distinct ephemeral key in its log. #[test] -unconstrained fn duplicate_handshake_accumulates_distinct_secrets_and_returns_latest() { +unconstrained fn rehandshake_replaces_previous_secret_and_returns_latest() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let secret_first = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_first = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_first.len(), 2); - let first = env.execute_utility(registry.get_handshake(sender, recipient, NON_INTERACTIVE_HANDSHAKE)).expect( - f"first handshake note should be present", - ); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let secret_second = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); let logs_second = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_second.len(), 2); - let latest = env.execute_utility(registry.get_handshake(sender, recipient, NON_INTERACTIVE_HANDSHAKE)).expect( - f"second handshake note should be present", - ); let raw_tag = compute_log_tag( recipient.to_field(), @@ -185,7 +128,26 @@ unconstrained fn duplicate_handshake_accumulates_distinct_secrets_and_returns_la "ephemeral keys should differ across repeated handshakes", ); - assert(first.secret_hash != latest.secret_hash, "should return the most recent note, not the first"); + // Same caller and same `(sender, recipient)`, but two distinct raw `S` produce two distinct silos + assert(secret_first != secret_second, "successive handshakes should produce distinct app-siloed secrets"); + + let retrieved = env.execute_utility(registry.get_app_siloed_secret(sender, recipient, sender)).expect( + f"handshake secret should be present", + ); + assert_eq(retrieved, secret_second); + + env.call_private(sender, registry.validate_handshake(sender, recipient, secret_second)); +} + +#[test(should_fail_with = "no matching handshake")] +unconstrained fn rehandshake_revokes_previous_secret() { + let (env, registry_address, sender, _, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let secret_first = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + + env.call_private(sender, registry.validate_handshake(sender, recipient, secret_first)); } #[test] @@ -193,11 +155,11 @@ unconstrained fn different_recipients_have_different_tags() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); let logs_a = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_a.len(), 2); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); let logs_b = txe_oracles::get_last_tx_effects().private_logs; assert_eq(logs_b.len(), 2); @@ -215,33 +177,167 @@ unconstrained fn different_recipients_have_different_tags() { } #[test] -unconstrained fn get_handshake_returns_none_for_wrong_sender() { +unconstrained fn one_sender_can_have_active_handshakes_with_many_recipients() { + let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); + let registry = HandshakeRegistry::at(registry_address); + + let secret_a = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let secret_b = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); + + assert(secret_a != secret_b, "separate recipient handshakes should produce distinct secrets"); + + let retrieved_a = env.execute_utility(registry.get_app_siloed_secret(sender, recipient_a, sender)).expect( + f"handshake secret should be present for recipient A", + ); + let retrieved_b = env.execute_utility(registry.get_app_siloed_secret(sender, recipient_b, sender)).expect( + f"handshake secret should be present for recipient B", + ); + + assert_eq(retrieved_a, secret_a); + assert_eq(retrieved_b, secret_b); + + env.call_private(sender, registry.validate_handshake(sender, recipient_a, secret_a)); + env.call_private(sender, registry.validate_handshake(sender, recipient_b, secret_b)); +} + +#[test] +unconstrained fn get_app_siloed_secret_returns_none_for_wrong_sender() { let (env, registry_address, sender, other_sender, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); - assert(env.execute_utility(registry.get_handshake(other_sender, recipient, NON_INTERACTIVE_HANDSHAKE)).is_none()); + assert(env.execute_utility(registry.get_app_siloed_secret(other_sender, recipient, sender)).is_none()); } #[test] -unconstrained fn get_handshake_returns_none_for_wrong_recipient() { +unconstrained fn get_app_siloed_secret_returns_none_for_wrong_recipient() { let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); - assert(env.execute_utility(registry.get_handshake(sender, recipient_b, NON_INTERACTIVE_HANDSHAKE)).is_none()); + assert(env.execute_utility(registry.get_app_siloed_secret(sender, recipient_b, sender)).is_none()); } +#[test(should_fail_with = "no matching handshake")] +unconstrained fn validate_handshake_rejects_wrong_sender() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let _ = env.call_private(other_sender, registry.non_interactive_handshake(other_sender, recipient)); + + env.call_private_opts( + sender, + CallPrivateOptions::new().with_additional_scopes([other_sender]), + registry.validate_handshake(other_sender, recipient, secret), + ); +} + +#[test(should_fail_with = "no matching handshake")] +unconstrained fn validate_handshake_rejects_wrong_recipient() { + let (env, registry_address, sender, recipient_a, recipient_b) = setup_with_two_recipients(); + let registry = HandshakeRegistry::at(registry_address); + + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_a)); + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient_b)); + + env.call_private(sender, registry.validate_handshake(sender, recipient_b, secret)); +} + +// `PrivateMutable`'s init nullifier is derived from the owner's nullifier +// hiding key, so a caller can only initialize the `(recipient, sender)` slot for a `sender` +// whose keys it holds. Passing a different account as the `sender` therefore fails. +#[test(should_fail_with = "Key validation request denied")] +unconstrained fn non_interactive_handshake_rejects_other_sender() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let _ = env.call_private(sender, registry.non_interactive_handshake(other_sender, recipient)); +} + +// Counterpart with `non_interactive_handshake_rejects_other_sender`: with `other_sender` added to additional scopes, +// the caller has access to that account's nullifier hiding key, the init nullifier is producible, +// and the call succeeds. The gate is key access, not anything in the contract itself. +#[test] +unconstrained fn non_interactive_handshake_accepts_other_sender_in_additional_scopes() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let _ = env.call_private_opts( + sender, + CallPrivateOptions::new().with_additional_scopes([other_sender]), + registry.non_interactive_handshake(other_sender, recipient), + ); +} + +// TODO(F-671): drop the explicit `caller` arg and exercise real msg_sender via the test env +// once utility context exposes self.msg_sender(). #[test] -unconstrained fn get_handshake_returns_none_for_wrong_handshake_type() { +unconstrained fn get_app_siloed_secret_differs_per_msg_sender() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let sender_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let other_sender_secret = env + .execute_utility(registry.get_app_siloed_secret(sender, recipient, other_sender)) + .expect(f"handshake secret should be present for other caller"); + + assert(sender_secret != other_sender_secret, "different callers should receive different siloed secrets"); +} + +#[test] +unconstrained fn validate_handshake_accepts_secret_siloed_for_msg_sender() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let _ = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let other_sender_secret = env + .execute_utility(registry.get_app_siloed_secret(sender, recipient, other_sender)) + .expect(f"handshake secret should be present for other caller"); + + // `validate_handshake` calls `get_note` on a note owned by `sender`, which nullifies and + // recreates and therefore needs `sender`'s nsk. Other_sender's PXE gets it via scopes. + env.call_private_opts( + other_sender, + CallPrivateOptions::new().with_additional_scopes([sender]), + registry.validate_handshake(sender, recipient, other_sender_secret), + ); +} + +// TODO(F-671): drop the explicit `caller` arg used to obtain `other_sender_secret` and exercise +// real msg_sender via the test env once utility context exposes self.msg_sender(). +#[test(should_fail_with = "no matching handshake")] +unconstrained fn validate_handshake_rejects_secret_siloed_for_different_msg_sender() { + let (env, registry_address, sender, other_sender, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); + + let sender_secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + + env.call_private_opts( + other_sender, + CallPrivateOptions::new().with_additional_scopes([sender]), + registry.validate_handshake(sender, recipient, sender_secret), + ); +} + +// Tampering with the returned secret must not validate. `+ 1` on a Field is guaranteed to produce a +// different value. +#[test(should_fail_with = "no matching handshake")] +unconstrained fn validate_handshake_rejects_wrong_secret() { let (env, registry_address, sender, _, recipient) = setup(); let registry = HandshakeRegistry::at(registry_address); - env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + let secret = env.call_private(sender, registry.non_interactive_handshake(sender, recipient)); + + env.call_private(sender, registry.validate_handshake(sender, recipient, secret + 1)); +} + +#[test(should_fail_with = "Failed to get a note")] +unconstrained fn validate_handshake_panics_when_no_handshake() { + let (env, registry_address, sender, _, recipient) = setup(); + let registry = HandshakeRegistry::at(registry_address); - // Claim it was something other than the only currently-defined handshake type. - let unknown_handshake_type: u8 = NON_INTERACTIVE_HANDSHAKE + 1; - assert(env.execute_utility(registry.get_handshake(sender, recipient, unknown_handshake_type)).is_none()); + env.call_private(sender, registry.validate_handshake(sender, recipient, 1)); } 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 ce4e62575d0a..027f1e7ef243 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -805,10 +805,6 @@ pub global DOM_SEP__PRIVATE_INITIALIZATION_NULLIFIER: u32 = 3990889078; /// Domain separator for L1 to L2 message secret hashes. pub global DOM_SEP__SECRET_HASH: u32 = 4199652938; -/// Domain separator for the secret hash stored in handshake notes produced by the handshake registry contract. -/// Kept distinct from [`DOM_SEP__SECRET_HASH`] (which is specifically for L1 to L2 messages). -pub global DOM_SEP__HANDSHAKE_SECRET_HASH: u32 = 3596796143; - /// Domain separator for transaction nullifiers. /// /// Used to produce cancellable (replaceable) transactions. diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr index 2a37b72e524a..e4415d7b4b95 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants_tests.nr @@ -12,7 +12,7 @@ use crate::{ DOM_SEP__BLOCK_HEADER_HASH, DOM_SEP__BLOCK_HEADERS_HASH, DOM_SEP__CONTRACT_ADDRESS_V1, DOM_SEP__CONTRACT_CLASS_ID, DOM_SEP__ECDH_FIELD_MASK, DOM_SEP__ECDH_SUBKEY, DOM_SEP__EVENT_COMMITMENT, DOM_SEP__EVENT_LOG_TAG, DOM_SEP__FUNCTION_ARGS, - DOM_SEP__HANDSHAKE_SECRET_HASH, DOM_SEP__INITIALIZATION_NULLIFIER, DOM_SEP__INITIALIZER, + DOM_SEP__INITIALIZATION_NULLIFIER, DOM_SEP__INITIALIZER, DOM_SEP__IVSK_M, DOM_SEP__MERKLE_HASH, DOM_SEP__MESSAGE_NULLIFIER, DOM_SEP__NHK_M, DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, DOM_SEP__NOTE_COMPLETION_LOG_TAG, DOM_SEP__NOTE_HASH, DOM_SEP__NOTE_HASH_NONCE, DOM_SEP__NOTE_NULLIFIER, @@ -142,7 +142,7 @@ impl HashedValueTester::new(); + let mut tester = HashedValueTester::<70, 63>::new(); // ----------------- // Domain separators @@ -179,7 +179,6 @@ fn hashed_values_match_derived() { DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, "non_interactive_handshake_log_tag", ); - tester.assert_dom_sep_matches_derived(DOM_SEP__HANDSHAKE_SECRET_HASH, "handshake_secret_hash"); tester.assert_dom_sep_matches_derived(DOM_SEP__MESSAGE_NULLIFIER, "message_nullifier"); tester.assert_dom_sep_matches_derived(DOM_SEP__PRIVATE_FUNCTION_LEAF, "private_function_leaf"); tester.assert_dom_sep_matches_derived(DOM_SEP__PUBLIC_BYTECODE, "public_bytecode"); diff --git a/yarn-project/constants/src/constants.gen.ts b/yarn-project/constants/src/constants.gen.ts index 9e88cba9461b..65028bb96a5e 100644 --- a/yarn-project/constants/src/constants.gen.ts +++ b/yarn-project/constants/src/constants.gen.ts @@ -557,7 +557,6 @@ export enum DomainSeparator { PUBLIC_INITIALIZATION_NULLIFIER = 3342006647, PRIVATE_INITIALIZATION_NULLIFIER = 3990889078, SECRET_HASH = 4199652938, - HANDSHAKE_SECRET_HASH = 3596796143, TX_NULLIFIER = 1025801951, SIGNATURE_PAYLOAD = 463525807, BLOCK_HEADERS_HASH = 515582467, From 74d7416ddcb6e97f828ebf39f31b21a0945e98a0 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Fri, 15 May 2026 15:51:05 -0400 Subject: [PATCH 4/4] chore(aztec-nr): Public internal/utility methods self constructor (#23115) Resolves [F-640](https://linear.app/aztec-labs/issue/F-640/aztec-nr-macros-extract-public-self-construction-in-internalutility) Stacks on https://github.com/AztecProtocol/aztec-packages/pull/23062 This PR and its parent were ultimately attempts to reduce the static artifact size as a result of https://github.com/AztecProtocol/aztec-nr/issues/35. This PR and its parent https://github.com/AztecProtocol/aztec-packages/pull/23062 didn't end up showing any diff in the final byte code, but it significantly reduces macro code as can seen in the #23062 diff. It also assists the Noir optimizer/inliner as the construction of the `self` objects will be contained to a method rather than already inlined directly in each method. We leave inlining decisions to the compiler. This is better long-term to avoid blowups especially if we continue to add logic into the `self` constructors over time. --- .../external/public.nr | 53 ++++++++++++------- .../external/utility.nr | 46 +++++++++------- .../internal.nr | 19 +------ .../internals_functions_generation/mod.nr | 36 +++++++++---- .../snapshots__stderr.snap | 2 +- .../snapshots__stderr.snap | 2 +- .../snapshots__stderr.snap | 2 +- .../snapshots__stderr.snap | 2 +- .../amm_contract/snapshots__expanded.snap | 40 +++++++------- .../snapshots__expanded.snap | 15 ++++-- .../snapshots__expanded.snap | 15 ++++-- .../snapshots__expanded.snap | 15 ++++-- .../snapshots__expanded.snap | 15 ++++-- .../token_contract/snapshots__expanded.snap | 50 ++++++++--------- 14 files changed, 177 insertions(+), 135 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr index cd67cabb8c1c..51a902d3504f 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr @@ -6,6 +6,36 @@ use crate::macros::{ }, }; +/// Emits a per-contract helper that creates the `self` value from a `PublicContext` already in scope. +/// +/// This is the shared helper used by both: +/// - public external functions, via [`generate_public_self_creator`], which constructs a fresh `PublicContext` from +/// `calldata_copy` and then delegates to this helper; and +/// - public internal functions and `#[contract_library_method]` bodies, which already receive `context` as a +/// parameter and call this helper directly to skip the `calldata_copy` step. +pub(crate) comptime fn generate_public_self_creator_from_context(m: Module) -> Quoted { + let (storage_type, storage_init) = if module_has_storage(m) { + (quote { Storage }, quote { let storage = Storage::init(context); }) + } else { + // Contract does not have Storage defined, so we set storage to the unit type `()`. ContractSelfPublic requires + // a storage struct in its constructor. Using an Option type would lead to worse developer experience and + // higher constraint counts so we use the unit type `()` instead. + (quote { () }, quote { let storage = (); }) + }; + + quote { + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::ContractSelfPublic<$storage_type, CallSelf, CallSelfStatic, CallInternal> { + $storage_init + let self_address = context.this_address(); + let call_self: CallSelf = CallSelf { address: self_address, context }; + let call_self_static: CallSelfStatic = CallSelfStatic { address: self_address, context }; + let internal: CallInternal = CallInternal { context }; + aztec::contract_self::ContractSelfPublic::new(context, storage, call_self, call_self_static, internal) + } + } +} + /// Generates the per-contract helper that builds public `self`. /// /// Each public external function calls this helper instead of inlining the construction, so the same preamble does not @@ -16,23 +46,15 @@ use crate::macros::{ /// fields from calldata. Noir monomorphizes one copy per distinct `N`, so public functions with the same number of /// serialized args reuse the same compiled code. pub(crate) comptime fn generate_public_self_creator(m: Module) -> Quoted { - let (storage_type, storage_init) = if module_has_storage(m) { - (quote { Storage }, quote { let storage = Storage::init(context); }) + let storage_type = if module_has_storage(m) { + quote { Storage } } else { - // Contract does not have Storage defined, so we set storage to the unit type `()`. ContractSelfPublic requires - // a storage struct in its constructor. Using an Option type would lead to worse developer experience and - // higher constraint counts so we use the unit type `()` instead. - (quote { () }, quote { let storage = (); }) + quote { () } }; quote { #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::ContractSelfPublic< - $storage_type, - CallSelf, - CallSelfStatic, - CallInternal, - > { + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::ContractSelfPublic<$storage_type, CallSelf, CallSelfStatic, CallInternal> { // Unlike in the private case, in public the `context` does not need to receive the hash of the original // params. let context = aztec::context::PublicContext::new(|| { @@ -40,12 +62,7 @@ pub(crate) comptime fn generate_public_self_creator(m: Module) -> Quoted { let serialized_args : [Field; N] = aztec::oracle::avm::calldata_copy(1, N); aztec::hash::hash_args(serialized_args) }); - $storage_init - let self_address = context.this_address(); - let call_self: CallSelf = CallSelf { address: self_address, context }; - let call_self_static: CallSelfStatic = CallSelfStatic { address: self_address, context }; - let internal: CallInternal = CallInternal { context }; - aztec::contract_self::ContractSelfPublic::new(context, storage, call_self, call_self_static, internal) + __aztec_nr_internals__create_public_self_from_context(context) } } } diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr index 228ed0dde96f..fb6bae6f9955 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/utility.nr @@ -1,33 +1,41 @@ use crate::macros::utils::{fn_has_noinitcheck, module_has_initializer, module_has_storage}; -pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quoted { - let module_has_initializer = module_has_initializer(f.module()); - - // Initialize Storage if module has storage - let storage_init = if module_has_storage(f.module()) { - quote { - let storage = Storage::init(context); - } +/// Emits a per-contract helper that builds the `self` value used by every utility external function. +/// +/// Each utility external function calls this helper instead of inlining the construction, so the +/// `UtilityContext::new`/`Storage::init`/`CallSelfUtility`/`ContractSelfUtility::new` chain does not appear duplicated +/// in every utility function body. We let Noir's inliner decide whether to inline the helper at each call site rather +/// than forcing it via macro expansion. +/// +/// Unlike the public counterpart, this helper takes no arguments because `UtilityContext::new` does not need calldata. +pub(crate) comptime fn generate_utility_self_creator(m: Module) -> Quoted { + let (storage_type, storage_init) = if module_has_storage(m) { + (quote { Storage }, quote { let storage = Storage::init(context); }) } else { - // Contract does not have Storage defined, so we set storage to the unit type `()`. ContractSelfUtility - // requires a - // storage struct in its constructor. Using an Option type would lead to worse developer experience and higher + // Contract does not have Storage defined, so we set storage to the unit type `()`. ContractSelfUtility requires + // a storage struct in its constructor. Using an Option type would lead to worse developer experience and higher // constraint counts so we use the unit type `()` instead. - quote { - let storage = (); - } + (quote { () }, quote { let storage = (); }) }; - // Create utility context - let contract_self_creation = quote { - #[allow(unused_variables)] - let mut self = { + quote { + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_utility_self() -> aztec::contract_self::ContractSelfUtility<$storage_type, CallSelfUtility> { let context = aztec::context::UtilityContext::new(); $storage_init let self_address = context.this_address(); let call_self = CallSelfUtility { address: self_address }; aztec::contract_self::ContractSelfUtility::new(context, storage, call_self) - }; + } + } +} + +pub(crate) comptime fn generate_utility_external(f: FunctionDefinition) -> Quoted { + let module_has_initializer = module_has_initializer(f.module()); + + let contract_self_creation = quote { + #[allow(unused_variables)] + let mut self = __aztec_nr_internals__create_utility_self(); }; // Initialization checks are not included in contracts that don't have initializers. diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/internal.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/internal.nr index a913ff63df26..a9637ad0078e 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/internal.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/internal.nr @@ -68,16 +68,6 @@ pub(crate) comptime fn generate_public_internal(f: FunctionDefinition) -> Quoted $original_params }; - let storage_init = if module_has_storage(f.module()) { - quote { - let storage = Storage::init(context); - } - } else { - quote { - let storage = (); - } - }; - let body = f.body(); // Internal public functions are marked as unconstrained because they are inlined within external public functions, @@ -88,14 +78,7 @@ pub(crate) comptime fn generate_public_internal(f: FunctionDefinition) -> Quoted #[contract_library_method] unconstrained fn $fn_name($params) -> $return_type { #[allow(unused_variables)] - let mut self = { - $storage_init - let self_address = context.this_address(); - let call_self: CallSelf = CallSelf { address: self_address, context }; - let call_self_static: CallSelfStatic = CallSelfStatic { address: self_address, context }; - let internal: CallInternal = CallInternal { context }; - aztec::contract_self::ContractSelfPublic::new(context, storage, call_self, call_self_static, internal) - }; + let mut self = __aztec_nr_internals__create_public_self_from_context(context); $body } diff --git a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr index 030fc37c36ee..c11fbfb34a43 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr @@ -13,8 +13,8 @@ pub(crate) mod internal; use abi_export::create_fn_abi_export; use external::{ private::generate_private_external, - public::{generate_public_external, generate_public_self_creator}, - utility::generate_utility_external, + public::{generate_public_external, generate_public_self_creator, generate_public_self_creator_from_context}, + utility::{generate_utility_external, generate_utility_self_creator}, }; use internal::{generate_private_internal, generate_public_internal}; @@ -49,15 +49,35 @@ pub(crate) comptime fn process_functions(m: Module) -> Quoted { let transformed_utility_functions = utility_functions.map(|function| generate_utility_external(function)).join(quote {}); - // Emit a contract-level helper that constructs `self` for public functions. Each transformed public function calls - // this helper rather than inlining the preamble. The helper is only useful for public external functions, so we - // skip emitting it when the contract has none. + // INTERNAL FUNCTIONS + let private_internal_functions = internal_functions_registry::get_private_functions(m); + let public_internal_functions = internal_functions_registry::get_public_functions(m); + + // Emit per-contract helpers that construct `self` for public functions, so the preamble is not duplicated into + // every public function body. The creator takes a `PublicContext` and is shared by both public external functions + // (via `__aztec_nr_internals__create_public_self`) and public internal functions, which receive the context as a + // parameter. We only emit each helper if there is a caller for it. + let any_public_function = public_functions.len() > 0 | public_internal_functions.len() > 0; + let public_self_creator_from_context = if any_public_function { + generate_public_self_creator_from_context(m) + } else { + quote {} + }; let public_self_creator = if public_functions.len() > 0 { generate_public_self_creator(m) } else { quote {} }; + // Same idea for utility functions: emit a per-contract helper that builds the `self` value, so the construction is + // not duplicated into every utility body. UtilityContext takes no calldata, so a single helper is enough; there is + // no internal/external split to share with. + let utility_self_creator = if utility_functions.len() > 0 { + generate_utility_self_creator(m) + } else { + quote {} + }; + // Now that we have generated quotes of the new functions based on the original function definitions, we replace // the original functions' bodies with `static_assert(false, ...)` to prevent them from being called directly from // within the contract. We also need to set the return type to `()` to avoid compilation errors. @@ -74,10 +94,6 @@ pub(crate) comptime fn process_functions(m: Module) -> Quoted { "Direct invocation of utility functions is not supported. You attempted to call ", ); - // INTERNAL FUNCTIONS - let private_internal_functions = internal_functions_registry::get_private_functions(m); - let public_internal_functions = internal_functions_registry::get_public_functions(m); - let generated_private_internal_functions = private_internal_functions.map(|function| generate_private_internal(function)).join(quote {}); let generated_public_internal_functions = @@ -95,7 +111,9 @@ pub(crate) comptime fn process_functions(m: Module) -> Quoted { // We return the new functions' quotes to be injected into the contract. quote { + $public_self_creator_from_context $public_self_creator + $utility_self_creator $transformed_private_functions $transformed_public_functions $transformed_utility_functions diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap index 16b1da78250d..a65ed2dba256 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_from_wrong_type/snapshots__stderr.snap @@ -20,7 +20,7 @@ error: Argument from in function foo must be of type AztecAddress, but is of typ 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:114:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:72:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap index e91d74670519..57f6c20570f6 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_from_param/snapshots__stderr.snap @@ -20,7 +20,7 @@ error: Function foo does not have a from parameter. Please specify which one to 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:114:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:67:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap index 550d3fd5e887..a70f07d8bcc7 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_missing_nonce_param/snapshots__stderr.snap @@ -20,7 +20,7 @@ error: Function foo does not have a authwit_nonce. Please specify which one to u 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:114:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:81:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap index cc49e0eac228..6a4a3efa9cd0 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/authorize_once_nonce_wrong_type/snapshots__stderr.snap @@ -20,7 +20,7 @@ error: Argument authwit_nonce in function foo must be of type Field, but is of t 5: process_functions at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/mod.nr:48:41 6: generate_public_external - at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:114:9 + at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/public.nr:134:9 7: create_authorize_once_check at /noir-projects/aztec-nr/aztec/src/macros/internals_functions_generation/external/helpers.nr:86:9 diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap index 75d3f716c482..e671b64900c1 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap @@ -2444,11 +2444,7 @@ pub contract AMM { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { - let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { let storage: Storage = Storage::::init(context); let self_address: AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -2457,6 +2453,15 @@ pub contract AMM { aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { + let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals___add_liquidity(config: Config, refund_token0_partial_note: PartialUintNote, refund_token1_partial_note: PartialUintNote, liquidity_partial_note: PartialUintNote, amount0_max: u128, amount1_max: u128, amount0_min: u128, amount1_min: u128) { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<10>(); assert(self.msg_sender() == self.address, "Function _add_liquidity can only be called by the same contract"); @@ -2547,15 +2552,18 @@ pub contract AMM { aztec::macros::functions::initialization_utils::mark_as_initialized_from_public_initializer(self.context); } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_utility_self() -> aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> { + let context: aztec::context::UtilityContext = aztec::context::UtilityContext::new(); + let storage: Storage = Storage::::init(context); + let self_address: AztecAddress = context.this_address(); + let call_self: CallSelfUtility = CallSelfUtility { address: self_address}; + aztec::contract_self::contract_self_utility::ContractSelfUtility::, CallSelfUtility>::new(context, storage, call_self) + } + unconstrained fn __aztec_nr_internals__get_amount_in_for_exact_out(balance_in: u128, balance_out: u128, amount_out: u128) -> pub u128 { aztec::oracle::version::assert_compatible_oracle_version(); - let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = { - let context: aztec::context::UtilityContext = aztec::context::UtilityContext::new(); - let storage: Storage = Storage::::init(context); - let self_address: AztecAddress = context.this_address(); - let call_self: CallSelfUtility = CallSelfUtility { address: self_address}; - aztec::contract_self::contract_self_utility::ContractSelfUtility::, CallSelfUtility>::new(context, storage, call_self) - }; + let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = __aztec_nr_internals__create_utility_self(); aztec::macros::functions::initialization_utils::assert_is_initialized_utility(self.context); { get_amount_in(amount_out, balance_in, balance_out) @@ -2564,13 +2572,7 @@ pub contract AMM { unconstrained fn __aztec_nr_internals__get_amount_out_for_exact_in(balance_in: u128, balance_out: u128, amount_in: u128) -> pub u128 { aztec::oracle::version::assert_compatible_oracle_version(); - let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = { - let context: aztec::context::UtilityContext = aztec::context::UtilityContext::new(); - let storage: Storage = Storage::::init(context); - let self_address: AztecAddress = context.this_address(); - let call_self: CallSelfUtility = CallSelfUtility { address: self_address}; - aztec::contract_self::contract_self_utility::ContractSelfUtility::, CallSelfUtility>::new(context, storage, call_self) - }; + let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = __aztec_nr_internals__create_utility_self(); aztec::macros::functions::initialization_utils::assert_is_initialized_utility(self.context); { get_amount_out(amount_in, balance_in, balance_out) diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap index 031b5d82368a..e1341d2cba98 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap @@ -1221,11 +1221,7 @@ contract AvmGadgetsTest { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { - let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { let storage: () = (); let self_address: aztec::protocol::address::AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -1234,6 +1230,15 @@ contract AvmGadgetsTest { aztec::contract_self::contract_self_public::ContractSelfPublic::<(), CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { + let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals__keccak_f1600(data: [u64; 25]) -> pub [u64; 25] { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<25 * 1>(); { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap index e9fb1b7e909f..2ec9f691f21c 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap @@ -6001,11 +6001,7 @@ pub contract AvmTest { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { - let context: PublicContext = PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { let storage: Storage = Storage::::init(context); let self_address: AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -6014,6 +6010,15 @@ pub contract AvmTest { aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { + let context: PublicContext = PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals__add_args_return(arg_a: Field, arg_b: Field) -> pub Field { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<2>(); { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap index d6e9e6188bae..7bd15a82795d 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap @@ -867,11 +867,7 @@ pub contract PublicFnsWithEmitRepro { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { - let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { let storage: Storage = Storage::::init(context); let self_address: aztec::protocol::address::AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -880,6 +876,15 @@ pub contract PublicFnsWithEmitRepro { aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { + let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals__fn_01(v: u64) { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<1>(); { diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap index cce212d6c709..e08b63d47b43 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap @@ -794,11 +794,7 @@ contract StorageProofTest { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { - let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { let storage: () = (); let self_address: AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -807,6 +803,15 @@ contract StorageProofTest { aztec::contract_self::contract_self_public::ContractSelfPublic::<(), CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> { + let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals__account_proof(account: Account, root: [u64; 4], nodes: [Node; 15], node_length: u32) { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic<(), CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<1290>(); assert(self.context.is_static_call(), "Function account_proof can only be called statically"); diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap index dc4e567d517a..e7bc147862b9 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap @@ -3780,11 +3780,7 @@ pub contract Token { } #[contract_library_method] - unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { - let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { - let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); - aztec::hash::hash_args(serialized_args) - }); + unconstrained fn __aztec_nr_internals__create_public_self_from_context(context: aztec::context::PublicContext) -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { let storage: Storage = Storage::::init(context); let self_address: AztecAddress = context.this_address(); let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; @@ -3793,6 +3789,15 @@ pub contract Token { aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_public_self() -> aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> { + let context: aztec::context::PublicContext = aztec::context::PublicContext::new(|| -> Field { + let serialized_args: [Field; N] = aztec::oracle::avm::calldata_copy(1_u32, N); + aztec::hash::hash_args(serialized_args) + }); + __aztec_nr_internals__create_public_self_from_context(context) + } + unconstrained fn __aztec_nr_internals___finalize_mint_to_private_unsafe(minter_and_completer: AztecAddress, amount: u128, partial_note: PartialUintNote) { let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self::<3>(); assert(self.msg_sender() == self.address, "Function _finalize_mint_to_private_unsafe can only be called by the same contract"); @@ -3987,15 +3992,18 @@ pub contract Token { } } + #[contract_library_method] + unconstrained fn __aztec_nr_internals__create_utility_self() -> aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> { + let context: aztec::context::UtilityContext = aztec::context::UtilityContext::new(); + let storage: Storage = Storage::::init(context); + let self_address: AztecAddress = context.this_address(); + let call_self: CallSelfUtility = CallSelfUtility { address: self_address}; + aztec::contract_self::contract_self_utility::ContractSelfUtility::, CallSelfUtility>::new(context, storage, call_self) + } + unconstrained fn __aztec_nr_internals__balance_of_private(owner: AztecAddress) -> pub u128 { aztec::oracle::version::assert_compatible_oracle_version(); - let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = { - let context: aztec::context::UtilityContext = aztec::context::UtilityContext::new(); - let storage: Storage = Storage::::init(context); - let self_address: AztecAddress = context.this_address(); - let call_self: CallSelfUtility = CallSelfUtility { address: self_address}; - aztec::contract_self::contract_self_utility::ContractSelfUtility::, CallSelfUtility>::new(context, storage, call_self) - }; + let mut self: aztec::contract_self::contract_self_utility::ContractSelfUtility, CallSelfUtility> = __aztec_nr_internals__create_utility_self(); aztec::macros::functions::initialization_utils::assert_is_initialized_utility(self.context); { self.storage.balances.at(owner).balance_of() @@ -4050,14 +4058,7 @@ pub contract Token { #[contract_library_method] unconstrained fn __aztec_nr_internals___finalize_mint_to_private(context: aztec::context::PublicContext, completer: AztecAddress, amount: u128, partial_note: PartialUintNote) { - let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = { - let storage: Storage = Storage::::init(context); - let self_address: AztecAddress = context.this_address(); - let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; - let call_self_static: CallSelfStatic = CallSelfStatic:: { address: self_address, context: context}; - let internal: CallInternal = CallInternal:: { context: context}; - aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) - }; + let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self_from_context(context); { let supply: u128 = self.storage.total_supply.read().add(amount); self.storage.total_supply.write(supply); @@ -4067,14 +4068,7 @@ pub contract Token { #[contract_library_method] unconstrained fn __aztec_nr_internals___finalize_transfer_to_private(context: aztec::context::PublicContext, from_and_completer: AztecAddress, amount: u128, partial_note: PartialUintNote) { - let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = { - let storage: Storage = Storage::::init(context); - let self_address: AztecAddress = context.this_address(); - let call_self: CallSelf = CallSelf:: { address: self_address, context: context}; - let call_self_static: CallSelfStatic = CallSelfStatic:: { address: self_address, context: context}; - let internal: CallInternal = CallInternal:: { context: context}; - aztec::contract_self::contract_self_public::ContractSelfPublic::, CallSelf, CallSelfStatic, CallInternal>::new(context, storage, call_self, call_self_static, internal) - }; + let mut self: aztec::contract_self::contract_self_public::ContractSelfPublic, CallSelf, CallSelfStatic, CallInternal> = __aztec_nr_internals__create_public_self_from_context(context); { let balance_storage: PublicMutable = self.storage.public_balances.at(from_and_completer); let from_balance: u128 = balance_storage.read().sub(amount);