Skip to content

Commit 6dd1271

Browse files
authored
fix(txe): align tagging strategy oracle with PXE (#24561)
Fixes F-700 Fixes F-765 - makes TXE tagging secret strategy resolution mode-aware, matching PXE's resolveTaggingSecretStrategy hook request - supports configuring unconstrained and constrained delivery strategies independently in Noir tests - falls back to PXE's default strategy for unset modes, while preserving the no-hook path when no strategy is configured - adds coverage for resolved tagging strategy serialization/deserialization and per-mode TXE defaults - updates execution hook docs for the new Noir test helpers
1 parent 153d036 commit 6dd1271

24 files changed

Lines changed: 458 additions & 103 deletions

File tree

docs/docs-developers/docs/foundational-topics/pxe/execution_hooks.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,23 @@ For an unconstrained self-send (the recipient is one of the wallet's own account
8282

8383
### In Noir tests
8484

85-
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:
85+
When testing in Noir, leaving the strategy unset makes `TestEnvironment` fall back to the bare PXE default. Set a strategy
86+
when creating the environment to exercise a specific one; it affects message delivery in private executions that use the
87+
default wallet strategy hook. Use `with_default_tag_secret_strategy` to configure the strategy for a specific delivery
88+
mode:
8689

8790
```rust
8891
let env = TestEnvironment::new_opts(
89-
TestEnvironmentOptions::new().with_tagging_secret_strategy(TaggingSecretStrategy::non_interactive_handshake()),
92+
TestEnvironmentOptions::new().with_default_tag_secret_strategy(
93+
MessageDelivery::onchain_unconstrained(),
94+
TaggingSecretStrategy::non_interactive_handshake(),
95+
),
9096
);
9197
```
9298

99+
Use `with_default_tag_secret_strategy_all_modes` only when the same strategy should apply to both constrained and
100+
unconstrained delivery. Contract-fixed delivery derivations bypass this default strategy.
101+
93102
### In production
94103

95104
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.

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12+
### [Aztec.nr] `TestEnvironmentOptions::with_tagging_secret_strategy` replaced
13+
14+
`TestEnvironmentOptions::with_tagging_secret_strategy` is now `with_default_tag_secret_strategy_all_modes` for tests
15+
that want the same default wallet strategy for both onchain delivery modes. The new naming reflects that these helpers
16+
configure the TXE default wallet strategy hook; contract-fixed delivery derivations bypass that default.
17+
18+
For mode-specific defaults and hook semantics, see the
19+
[`resolveTaggingSecretStrategy` test helper docs](../foundational-topics/pxe/execution_hooks.md#resolvetaggingsecretstrategy).
20+
1221
### [Aztec.nr] L1-to-L2 message consumption takes the secret as an array
1322

1423
`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.

noir-projects/aztec-nr/aztec/src/messages/delivery/tag.nr

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,29 @@ mod test {
180180
});
181181
}
182182

183+
#[test(should_fail_with = "an unconstrained tagging secret cannot back constrained delivery")]
184+
unconstrained fn constrained_delivery_rejects_a_resolved_unconstrained_secret() {
185+
let env = TestEnvironment::new();
186+
let secret: Field = 7;
187+
let index: u32 = 0;
188+
189+
env.private_context(|context| {
190+
mock_existing_handshake_secrets(Option::none());
191+
let _ = OracleMock::mock("aztec_prv_resolveTaggingStrategy").returns(
192+
ResolvedTaggingStrategy::unconstrained_secret(secret),
193+
);
194+
let _ = OracleMock::mock("aztec_prv_getNextTaggingIndex").returns(index);
195+
196+
let _ = derive_log_tag(
197+
context,
198+
OnchainDeliveryMode::onchain_constrained(),
199+
SENDER,
200+
RECIPIENT,
201+
Option::none(),
202+
);
203+
});
204+
}
205+
183206
#[test]
184207
unconstrained fn constrained_delivery_emits_the_sequence_nullifier_and_constrained_tag() {
185208
let env = TestEnvironment::new();

noir-projects/aztec-nr/aztec/src/test/helpers/mod.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub mod test_environment;
1313
quote { private_call_new_flow_oracle }, // TODO: implement once we support more complex types
1414
quote { public_call_new_flow_oracle }, // TODO: implement once we support more complex types
1515
quote { set_private_txe_context_oracle }, // TODO: implement once we support more complex types
16-
quote { set_tagging_secret_strategy }, // TODO: implement once we support more complex types
16+
quote { set_tagging_secret_strategies }, // TODO: implement once we support more complex types
1717
],
1818
)]
1919
pub mod txe_oracles;

noir-projects/aztec-nr/aztec/src/test/helpers/tagging_secret_strategy.nr

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
use crate::protocol::{point::EmbeddedCurvePoint, traits::{Deserialize, Serialize}, utils::reader::Reader};
1+
use crate::keys::ecdh_shared_secret::compute_app_siloed_shared_secret;
2+
use crate::protocol::{
3+
address::AztecAddress,
4+
hash::poseidon2_hash,
5+
point::EmbeddedCurvePoint,
6+
traits::{Deserialize, Serialize, ToField},
7+
utils::reader::Reader,
8+
};
29

310
global NON_INTERACTIVE_HANDSHAKE: u8 = 1;
411
global ARBITRARY_SECRET: u8 = 2;
@@ -7,7 +14,7 @@ global INTERACTIVE_HANDSHAKE: u8 = 4;
714

815
/// How a message's tagging secret is chosen: the wallet's strategy.
916
///
10-
/// This type only exists for tests: a Noir test sets it via `with_tagging_secret_strategy` on
17+
/// This type only exists for tests: a Noir test sets it via `with_default_tag_secret_strategy` on
1118
/// [`TestEnvironmentOptions`](crate::test::helpers::test_environment::TestEnvironmentOptions).
1219
/// Production wallets express the strategy in PXE; the Noir path only consumes the resolved
1320
/// [`ResolvedTaggingStrategy`](crate::messages::delivery::ResolvedTaggingStrategy).
@@ -45,18 +52,26 @@ impl TaggingSecretStrategy {
4552
/// Validates a raw discriminant, as deserialization must always reject unknown values.
4653
fn from_parts(kind: u8, secret: EmbeddedCurvePoint) -> Self {
4754
let strategy = Self { kind, secret };
55+
let is_no_payload_strategy =
56+
(kind == NON_INTERACTIVE_HANDSHAKE) | (kind == INTERACTIVE_HANDSHAKE) | (kind == ADDRESS_DERIVED);
4857
assert(
49-
(
50-
((kind == NON_INTERACTIVE_HANDSHAKE) | (kind == ADDRESS_DERIVED) | (kind == INTERACTIVE_HANDSHAKE))
51-
& secret.is_infinite()
52-
)
53-
| (kind == ARBITRARY_SECRET),
58+
(is_no_payload_strategy & secret.is_infinite()) | (kind == ARBITRARY_SECRET),
5459
f"unrecognized tagging secret strategy kind: {kind}",
5560
);
5661
strategy
5762
}
5863
}
5964

65+
/// Computes the directional tagging secret PXE derives from an arbitrary shared secret point: the point is app-siloed
66+
/// and the recipient is folded in for direction. Mirrors `AppTaggingSecret.computeDirectional` on the PXE side; tests
67+
/// use it to derive the secret they expect an [`arbitrary_secret`](TaggingSecretStrategy::arbitrary_secret) strategy
68+
/// to produce.
69+
pub fn compute_directional_app_secret(secret: EmbeddedCurvePoint, app: AztecAddress, recipient: AztecAddress) -> Field {
70+
poseidon2_hash(
71+
[compute_app_siloed_shared_secret(secret, app), recipient.to_field()],
72+
)
73+
}
74+
6075
impl Deserialize for TaggingSecretStrategy {
6176
let N: u32 = 3;
6277

@@ -81,14 +96,14 @@ mod test {
8196
#[test]
8297
fn strategy_roundtrips_through_serialization() {
8398
let non_interactive = TaggingSecretStrategy::non_interactive_handshake();
99+
let interactive = TaggingSecretStrategy::interactive_handshake();
84100
let address = TaggingSecretStrategy::address_derived();
85101
let provided = TaggingSecretStrategy::arbitrary_secret(EmbeddedCurvePoint { x: 7, y: 11 });
86-
let interactive = TaggingSecretStrategy::interactive_handshake();
87102

88103
assert(TaggingSecretStrategy::deserialize(non_interactive.serialize()) == non_interactive);
104+
assert(TaggingSecretStrategy::deserialize(interactive.serialize()) == interactive);
89105
assert(TaggingSecretStrategy::deserialize(address.serialize()) == address);
90106
assert(TaggingSecretStrategy::deserialize(provided.serialize()) == provided);
91-
assert(TaggingSecretStrategy::deserialize(interactive.serialize()) == interactive);
92107
}
93108

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

99114
#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
100115
fn deserializing_handshake_with_noninfinite_point_fails() {
101-
let _ = TaggingSecretStrategy::deserialize([1, 7, 11]);
116+
let _ = TaggingSecretStrategy::deserialize([
117+
TaggingSecretStrategy::non_interactive_handshake().kind as Field,
118+
7,
119+
11,
120+
]);
102121
}
103122

104123
#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
105124
fn deserializing_address_derived_with_noninfinite_point_fails() {
106-
let _ = TaggingSecretStrategy::deserialize([3, 7, 11]);
125+
let _ = TaggingSecretStrategy::deserialize(
126+
[TaggingSecretStrategy::address_derived().kind as Field, 7, 11],
127+
);
107128
}
108129

109130
#[test(should_fail_with = "unrecognized tagging secret strategy kind")]
110131
fn deserializing_interactive_handshake_with_noninfinite_point_fails() {
111-
let _ = TaggingSecretStrategy::deserialize([4, 7, 11]);
132+
let _ = TaggingSecretStrategy::deserialize([TaggingSecretStrategy::interactive_handshake().kind as Field, 7, 11]);
112133
}
113134
}

noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::{
1313
event::{event_interface::EventInterface, EventMessage},
1414
hash::{compute_secret_hash, hash_args},
1515
messages::{
16+
delivery::OnchainDeliveryMode,
1617
discovery::{
1718
ComputeNoteHash, ComputeNoteNullifier, CustomMessageHandler, process_message::process_message_plaintext,
1819
},
@@ -88,28 +89,53 @@ pub struct TestEnvironment {
8889
/// methods setting each value, e.g.:
8990
/// ```noir
9091
/// let env = TestEnvironment::new_opts(
91-
/// TestEnvironmentOptions::new().with_tagging_secret_strategy(TaggingSecretStrategy::non_interactive_handshake()),
92+
/// TestEnvironmentOptions::new().with_default_tag_secret_strategy(
93+
/// MessageDelivery::onchain_unconstrained(),
94+
/// TaggingSecretStrategy::non_interactive_handshake(),
95+
/// ),
9296
/// );
9397
/// ```
9498
pub struct TestEnvironmentOptions {
95-
tagging_secret_strategy: Option<TaggingSecretStrategy>,
99+
default_unconstrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
100+
default_constrained_tagging_secret_strategy: Option<TaggingSecretStrategy>,
96101
}
97102

98103
impl TestEnvironmentOptions {
99104
/// Creates a new `TestEnvironmentOptions` with default values.
100105
pub fn new() -> Self {
101-
Self { tagging_secret_strategy: Option::none() }
106+
Self {
107+
default_unconstrained_tagging_secret_strategy: Option::none(),
108+
default_constrained_tagging_secret_strategy: Option::none(),
109+
}
102110
}
103111

104-
/// Sets the [`TaggingSecretStrategy`] the wallet reports through the
112+
/// Sets the default [`TaggingSecretStrategy`] the wallet reports for `mode` through the
105113
/// [`resolve_tagging_strategy`](crate::oracle::resolve_tagging_strategy) oracle, affecting message delivery in
106-
/// private executions.
114+
/// private executions that do not fix their own delivery derivation.
107115
///
108-
/// If not set, the wallet hook is left unconfigured and the private execution environment applies its own default.
109-
pub fn with_tagging_secret_strategy(&mut self, strategy: TaggingSecretStrategy) -> Self {
110-
self.tagging_secret_strategy = Option::some(strategy);
116+
/// If no strategy is set for either mode, the wallet hook is left unconfigured and the private execution
117+
/// environment applies its own default. A mode left unset while the other is configured falls back to the default
118+
/// [`TaggingSecretStrategy::non_interactive_handshake`], the strategy a PXE `resolveTaggingSecretStrategy` hook
119+
/// typically hardcodes for modes it does not customize.
120+
pub fn with_default_tag_secret_strategy<M>(&mut self, mode: M, strategy: TaggingSecretStrategy) -> Self
121+
where
122+
M: Into<OnchainDeliveryMode>,
123+
{
124+
if mode.into() == OnchainDeliveryMode::onchain_unconstrained() {
125+
self.default_unconstrained_tagging_secret_strategy = Option::some(strategy);
126+
} else {
127+
self.default_constrained_tagging_secret_strategy = Option::some(strategy);
128+
}
111129
*self
112130
}
131+
132+
/// Sets `strategy` for both delivery modes: the equivalent of a PXE `resolveTaggingSecretStrategy` hook that
133+
/// ignores the request's mode. See
134+
/// [`with_default_tag_secret_strategy`](Self::with_default_tag_secret_strategy) to configure a single mode.
135+
pub fn with_default_tag_secret_strategy_all_modes(&mut self, strategy: TaggingSecretStrategy) -> Self {
136+
let _ = self.with_default_tag_secret_strategy(OnchainDeliveryMode::onchain_unconstrained(), strategy);
137+
self.with_default_tag_secret_strategy(OnchainDeliveryMode::onchain_constrained(), strategy)
138+
}
113139
}
114140

115141
/// Configuration values for [`TestEnvironment::private_context_opts`]. Meant to be used by calling `new` and then
@@ -607,18 +633,19 @@ impl TestEnvironment {
607633
///
608634
/// ### Sample usage
609635
/// ```noir
636+
/// let strategy = TaggingSecretStrategy::non_interactive_handshake();
610637
/// let env = TestEnvironment::new_opts(
611-
/// TestEnvironmentOptions::new().with_tagging_secret_strategy(
612-
/// TaggingSecretStrategy::non_interactive_handshake(),
613-
/// ),
638+
/// TestEnvironmentOptions::new().with_default_tag_secret_strategy_all_modes(strategy),
614639
/// );
615640
/// ```
616641
pub unconstrained fn new_opts(options: TestEnvironmentOptions) -> Self {
617642
assert_compatible_oracle_version();
618643
txe_oracles::assert_compatible_txe_oracle_version();
619644

620-
// Forward the configured strategy to the wallet.
621-
txe_oracles::set_tagging_secret_strategy(options.tagging_secret_strategy);
645+
txe_oracles::set_tagging_secret_strategies(
646+
options.default_unconstrained_tagging_secret_strategy,
647+
options.default_constrained_tagging_secret_strategy,
648+
);
622649

623650
Self {
624651
// Use an offset to avoid secret collision with account secrets. Without this, when

0 commit comments

Comments
 (0)