Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 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,19 @@ 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. Use `with_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_tag_secret_strategy(
MessageDelivery::onchain_unconstrained(),
TaggingSecretStrategy::non_interactive_handshake(),
),
);
```

Use `with_tag_secret_strategy_all_modes` only when the same strategy should apply to both constrained and unconstrained delivery.

### 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
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_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]);
}
}
51 changes: 39 additions & 12 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_tag_secret_strategy(
/// MessageDelivery::onchain_unconstrained(),
/// TaggingSecretStrategy::non_interactive_handshake(),
/// ),
/// );
/// ```
pub struct TestEnvironmentOptions {
tagging_secret_strategy: Option<TaggingSecretStrategy>,
unconstrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
constrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
Comment thread
vezenovm marked this conversation as resolved.
Outdated
}

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

/// Sets the [`TaggingSecretStrategy`] the wallet reports through the
/// Sets the [`TaggingSecretStrategy`] the wallet reports for `mode` through the
/// [`resolve_tagging_strategy`](crate::oracle::resolve_tagging_strategy) oracle, affecting message delivery in
/// private executions.
///
/// 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_tag_secret_strategy<M>(&mut self, mode: M, strategy: TaggingSecretStrategy) -> Self
Comment thread
vezenovm marked this conversation as resolved.
Outdated
where
M: Into<OnchainDeliveryMode>,
{
if mode.into() == OnchainDeliveryMode::onchain_unconstrained() {
self.unconstrained_tagging_secret_strategy = Option::some(strategy);
} else {
self.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_tag_secret_strategy`](Self::with_tag_secret_strategy) to configure a
/// single mode.
pub fn with_tag_secret_strategy_all_modes(&mut self, strategy: TaggingSecretStrategy) -> Self {
Comment thread
vezenovm marked this conversation as resolved.
Outdated
let _ = self.with_tag_secret_strategy(OnchainDeliveryMode::onchain_unconstrained(), strategy);
self.with_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_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.unconstrained_tagging_secret_strategy,
options.constrained_tagging_secret_strategy,
);

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