Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f5498f
test(txe): cover unconstrained delivery
vezenovm Jul 6, 2026
0792034
test(txe): align tagging strategy oracle with PXE
vezenovm Jul 6, 2026
b8a3a01
refactor(txe): per-mode tagging secret strategy options
vezenovm Jul 7, 2026
1ed50ec
fix(e2e): pass delivery mode to next_index_for_secret
vezenovm Jul 7, 2026
3bc9732
test(txe): tighten tagging strategy coverage
vezenovm Jul 7, 2026
026ac0f
merge conflicts
vezenovm Jul 7, 2026
07ec09c
Merge branch 'merge-train/fairies-v5' into mv/f-700-f-765-txe-unconst…
vezenovm Jul 7, 2026
2aa0cdb
rename
vezenovm Jul 7, 2026
36c473e
test(txe): set both tagging secret strategies in one oracle call
vezenovm Jul 7, 2026
1daee1c
additioanl default test
vezenovm Jul 7, 2026
3495051
non-interactive in txe resolve tagging secret strat
vezenovm Jul 7, 2026
f1fe236
merge w/ parent
vezenovm Jul 7, 2026
62c76d3
remove deserde test
vezenovm Jul 7, 2026
7d64a26
.'
vezenovm Jul 7, 2026
da2d484
reduce diff
vezenovm Jul 7, 2026
ad28bcf
improve deserialization/serialization coverage
vezenovm Jul 7, 2026
51d3d4a
cleanup
vezenovm Jul 7, 2026
deb3c2f
test fix
vezenovm Jul 7, 2026
91933f0
test fix and docs
vezenovm Jul 7, 2026
32bad6b
chore(txe): update oracle interface hash
vezenovm Jul 7, 2026
0801ac4
more test cleanup
vezenovm Jul 7, 2026
6a1a6fd
fix: clarify resolved tagging strategy helpers
vezenovm Jul 8, 2026
4595810
merge tests
vezenovm Jul 8, 2026
a250dc7
pr review cleanup
vezenovm Jul 8, 2026
014b001
Merge branch 'merge-train/fairies-v5' into mv/f-700-f-765-txe-unconst…
vezenovm Jul 8, 2026
be81765
Merge branch 'merge-train/fairies-v5' into mv/f-700-f-765-txe-unconst…
vezenovm Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,23 @@ For an unconstrained self-send (the recipient is one of the wallet's own account

### In Noir tests

When testing in Noir, leaving the strategy unset makes `TestEnvironment` fall back to the bare PXE default. Set a strategy when creating the environment to exercise a specific one; it affects message delivery in private executions:
When testing in Noir, leaving the strategy unset makes `TestEnvironment` fall back to the bare PXE default. Set a strategy
when creating the environment to exercise a specific one; it affects message delivery in private executions that use the
default wallet strategy hook. Use `with_default_tag_secret_strategy` to configure the strategy for a specific delivery
mode:

```rust
let env = TestEnvironment::new_opts(
Comment thread
vezenovm marked this conversation as resolved.
TestEnvironmentOptions::new().with_tagging_secret_strategy(TaggingSecretStrategy::non_interactive_handshake()),
TestEnvironmentOptions::new().with_default_tag_secret_strategy(
MessageDelivery::onchain_unconstrained(),
TaggingSecretStrategy::non_interactive_handshake(),
),
);
```

Use `with_default_tag_secret_strategy_all_modes` only when the same strategy should apply to both constrained and
unconstrained delivery. Contract-fixed delivery derivations bypass this default strategy.

### In production

Pass a `resolveTaggingSecretStrategy` hook when [creating the PXE](#configuring-hooks). It receives a `TaggingSecretStrategyRequest` with the executing contract's address and the message's sender, recipient, and delivery mode (`'constrained'` or `'unconstrained'`), so a wallet can apply per-application or per-recipient policies, or surface the decision to the user, instead of returning a fixed value.
Expand Down
9 changes: 9 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.nr] `TestEnvironmentOptions::with_tagging_secret_strategy` replaced

`TestEnvironmentOptions::with_tagging_secret_strategy` is now `with_default_tag_secret_strategy_all_modes` for tests
that want the same default wallet strategy for both onchain delivery modes. The new naming reflects that these helpers
configure the TXE default wallet strategy hook; contract-fixed delivery derivations bypass that default.

For mode-specific defaults and hook semantics, see the
[`resolveTaggingSecretStrategy` test helper docs](../foundational-topics/pxe/execution_hooks.md#resolvetaggingsecretstrategy).

### [Aztec.nr] L1-to-L2 message consumption takes the secret as an array

`PrivateContext::consume_l1_to_l2_message` and `PublicContext::consume_l1_to_l2_message` now take the message secret as an arbitrary-length array `[Field; N]` instead of a single `Field`, so a consumer can derive its secret hash from more than one field. The helpers `compute_secret_hash` and `compute_l1_to_l2_message_nullifier` are likewise now generic over the secret length. A single-field secret behaves exactly as before (the hashes are unchanged for `N = 1`) — just wrap it in an array.
Expand Down
23 changes: 23 additions & 0 deletions noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,29 @@ mod test {
});
}

#[test(should_fail_with = "an unconstrained tagging secret cannot back constrained delivery")]
unconstrained fn constrained_delivery_rejects_a_resolved_unconstrained_secret() {
let env = TestEnvironment::new();
let secret: Field = 7;
let index: u32 = 0;

env.private_context(|context| {
mock_existing_handshake_secrets(Option::none());
let _ = OracleMock::mock("aztec_prv_resolveTaggingStrategy").returns(
ResolvedTaggingStrategy::unconstrained_secret(secret),
);
let _ = OracleMock::mock("aztec_prv_getNextTaggingIndex").returns(index);

let _ = derive_log_tag(
context,
OnchainDeliveryMode::onchain_constrained(),
SENDER,
RECIPIENT,
Option::none(),
);
});
}

#[test]
unconstrained fn constrained_delivery_emits_the_sequence_nullifier_and_constrained_tag() {
let env = TestEnvironment::new();
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/test/helpers/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod test_environment;
quote { private_call_new_flow_oracle }, // TODO: implement once we support more complex types
quote { public_call_new_flow_oracle }, // TODO: implement once we support more complex types
quote { set_private_txe_context_oracle }, // TODO: implement once we support more complex types
quote { set_tagging_secret_strategy }, // TODO: implement once we support more complex types
quote { set_tagging_secret_strategies }, // TODO: implement once we support more complex types
],
)]
pub mod txe_oracles;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use crate::protocol::{point::EmbeddedCurvePoint, traits::{Deserialize, Serialize}, utils::reader::Reader};
use crate::keys::ecdh_shared_secret::compute_app_siloed_shared_secret;
use crate::protocol::{
address::AztecAddress,
hash::poseidon2_hash,
point::EmbeddedCurvePoint,
traits::{Deserialize, Serialize, ToField},
utils::reader::Reader,
};

global NON_INTERACTIVE_HANDSHAKE: u8 = 1;
global ARBITRARY_SECRET: u8 = 2;
Expand All @@ -7,7 +14,7 @@ global INTERACTIVE_HANDSHAKE: u8 = 4;

/// How a message's tagging secret is chosen: the wallet's strategy.
///
/// This type only exists for tests: a Noir test sets it via `with_tagging_secret_strategy` on
/// This type only exists for tests: a Noir test sets it via `with_default_tag_secret_strategy` on
/// [`TestEnvironmentOptions`](crate::test::helpers::test_environment::TestEnvironmentOptions).
/// Production wallets express the strategy in PXE; the Noir path only consumes the resolved
/// [`ResolvedTaggingStrategy`](crate::messages::delivery::ResolvedTaggingStrategy).
Expand Down Expand Up @@ -45,18 +52,26 @@ impl TaggingSecretStrategy {
/// Validates a raw discriminant, as deserialization must always reject unknown values.
fn from_parts(kind: u8, secret: EmbeddedCurvePoint) -> Self {
let strategy = Self { kind, secret };
let is_no_payload_strategy =
(kind == NON_INTERACTIVE_HANDSHAKE) | (kind == INTERACTIVE_HANDSHAKE) | (kind == ADDRESS_DERIVED);
assert(
(
((kind == NON_INTERACTIVE_HANDSHAKE) | (kind == ADDRESS_DERIVED) | (kind == INTERACTIVE_HANDSHAKE))
& secret.is_infinite()
)
| (kind == ARBITRARY_SECRET),
(is_no_payload_strategy & secret.is_infinite()) | (kind == ARBITRARY_SECRET),
f"unrecognized tagging secret strategy kind: {kind}",
);
strategy
}
}

/// Computes the directional tagging secret PXE derives from an arbitrary shared secret point: the point is app-siloed
/// and the recipient is folded in for direction. Mirrors `AppTaggingSecret.computeDirectional` on the PXE side; tests
/// use it to derive the secret they expect an [`arbitrary_secret`](TaggingSecretStrategy::arbitrary_secret) strategy
/// to produce.
pub fn compute_directional_app_secret(secret: EmbeddedCurvePoint, app: AztecAddress, recipient: AztecAddress) -> Field {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that we have this, but I have two questions:

  1. Should it be here? Maybe it should be next to compute_app_siloed_shared_secret?
  2. Does it need to be pub, or can it be pub(crate)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I thought about moving it there too, but this is only used by tests so it felt better to leave in the test module.
  2. Yeah it can be pub(crate).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Makes sense
  2. Great

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we used this method in the new test in noir-projects/noir-contracts/contracts/test/onchain_delivery_test_contract/src/test.nr. I kept it as pub for now (I think another reason to leave it under the test module).

poseidon2_hash(
[compute_app_siloed_shared_secret(secret, app), recipient.to_field()],
)
}

impl Deserialize for TaggingSecretStrategy {
let N: u32 = 3;

Expand All @@ -81,14 +96,14 @@ mod test {
#[test]
fn strategy_roundtrips_through_serialization() {
let non_interactive = TaggingSecretStrategy::non_interactive_handshake();
let interactive = TaggingSecretStrategy::interactive_handshake();
let address = TaggingSecretStrategy::address_derived();
let provided = TaggingSecretStrategy::arbitrary_secret(EmbeddedCurvePoint { x: 7, y: 11 });
let interactive = TaggingSecretStrategy::interactive_handshake();

assert(TaggingSecretStrategy::deserialize(non_interactive.serialize()) == non_interactive);
assert(TaggingSecretStrategy::deserialize(interactive.serialize()) == interactive);
assert(TaggingSecretStrategy::deserialize(address.serialize()) == address);
assert(TaggingSecretStrategy::deserialize(provided.serialize()) == provided);
assert(TaggingSecretStrategy::deserialize(interactive.serialize()) == interactive);
}

#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
Expand All @@ -98,16 +113,22 @@ mod test {

#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
fn deserializing_handshake_with_noninfinite_point_fails() {
let _ = TaggingSecretStrategy::deserialize([1, 7, 11]);
let _ = TaggingSecretStrategy::deserialize([
TaggingSecretStrategy::non_interactive_handshake().kind as Field,
7,
11,
]);
}

#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
fn deserializing_address_derived_with_noninfinite_point_fails() {
let _ = TaggingSecretStrategy::deserialize([3, 7, 11]);
let _ = TaggingSecretStrategy::deserialize(
[TaggingSecretStrategy::address_derived().kind as Field, 7, 11],
);
}

#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
fn deserializing_interactive_handshake_with_noninfinite_point_fails() {
let _ = TaggingSecretStrategy::deserialize([4, 7, 11]);
let _ = TaggingSecretStrategy::deserialize([TaggingSecretStrategy::interactive_handshake().kind as Field, 7, 11]);
}
}
53 changes: 40 additions & 13 deletions noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
event::{event_interface::EventInterface, EventMessage},
hash::{compute_secret_hash, hash_args},
messages::{
delivery::OnchainDeliveryMode,
discovery::{
ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler, process_message::process_message_plaintext,
},
Expand Down Expand Up @@ -88,28 +89,53 @@ pub struct TestEnvironment {
/// methods setting each value, e.g.:
/// ```noir
/// let env = TestEnvironment::new_opts(
/// TestEnvironmentOptions::new().with_tagging_secret_strategy(TaggingSecretStrategy::non_interactive_handshake()),
/// TestEnvironmentOptions::new().with_default_tag_secret_strategy(
/// MessageDelivery::onchain_unconstrained(),
/// TaggingSecretStrategy::non_interactive_handshake(),
/// ),
/// );
/// ```
pub struct TestEnvironmentOptions {
tagging_secret_strategy: Option<TaggingSecretStrategy>,
default_unconstrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
default_constrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
}

impl TestEnvironmentOptions {
/// Creates a new `TestEnvironmentOptions` with default values.
pub fn new() -> Self {
Self { tagging_secret_strategy: Option::none() }
Self {
default_unconstrained_tagging_secret_strategy: Option::none(),
default_constrained_tagging_secret_strategy: Option::none(),
}
}

/// Sets the [`TaggingSecretStrategy`] the wallet reports through the
/// Sets the default [`TaggingSecretStrategy`] the wallet reports for `mode` through the
/// [`resolve_tagging_strategy`](crate::oracle::resolve_tagging_strategy) oracle, affecting message delivery in
/// private executions.
/// private executions that do not fix their own delivery derivation.
///
/// If not set, the wallet hook is left unconfigured and the private execution environment applies its own default.
pub fn with_tagging_secret_strategy(&mut self, strategy: TaggingSecretStrategy) -> Self {
self.tagging_secret_strategy = Option::some(strategy);
/// If no strategy is set for either mode, the wallet hook is left unconfigured and the private execution
/// environment applies its own default. A mode left unset while the other is configured falls back to the default
/// [`TaggingSecretStrategy::non_interactive_handshake`], the strategy a PXE `resolveTaggingSecretStrategy` hook
/// typically hardcodes for modes it does not customize.
pub fn with_default_tag_secret_strategy<M>(&mut self, mode: M, strategy: TaggingSecretStrategy) -> Self
where
M: Into<OnchainDeliveryMode>,
{
if mode.into() == OnchainDeliveryMode::onchain_unconstrained() {
self.default_unconstrained_tagging_secret_strategy = Option::some(strategy);
} else {
self.default_constrained_tagging_secret_strategy = Option::some(strategy);
}
*self
}

/// Sets `strategy` for both delivery modes: the equivalent of a PXE `resolveTaggingSecretStrategy` hook that
/// ignores the request's mode. See
/// [`with_default_tag_secret_strategy`](Self::with_default_tag_secret_strategy) to configure a single mode.
pub fn with_default_tag_secret_strategy_all_modes(&mut self, strategy: TaggingSecretStrategy) -> Self {
let _ = self.with_default_tag_secret_strategy(OnchainDeliveryMode::onchain_unconstrained(), strategy);
self.with_default_tag_secret_strategy(OnchainDeliveryMode::onchain_constrained(), strategy)
}
}

/// Configuration values for [`TestEnvironment::private_context_opts`]. Meant to be used by calling `new` and then
Expand Down Expand Up @@ -607,18 +633,19 @@ impl TestEnvironment {
///
/// ### Sample usage
/// ```noir
/// let strategy = TaggingSecretStrategy::non_interactive_handshake();
/// let env = TestEnvironment::new_opts(
/// TestEnvironmentOptions::new().with_tagging_secret_strategy(
/// TaggingSecretStrategy::non_interactive_handshake(),
/// ),
/// TestEnvironmentOptions::new().with_default_tag_secret_strategy_all_modes(strategy),
/// );
/// ```
pub unconstrained fn new_opts(options: TestEnvironmentOptions) -> Self {
assert_compatible_oracle_version();
txe_oracles::assert_compatible_txe_oracle_version();

// Forward the configured strategy to the wallet.
txe_oracles::set_tagging_secret_strategy(options.tagging_secret_strategy);
txe_oracles::set_tagging_secret_strategies(
options.default_unconstrained_tagging_secret_strategy,
options.default_constrained_tagging_secret_strategy,
);

Self {
// Use an offset to avoid secret collision with account secrets. Without this, when
Expand Down
Loading
Loading