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/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 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 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/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/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/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); 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,