@@ -46,19 +46,36 @@ impl MessageDelivery {
4646 }
4747}
4848
49+ /// Handshake inputs that opt a message delivery into constrained tagging.
50+ ///
51+ /// Pass `Some` to [`do_private_message_delivery`] alongside [`MessageDelivery::onchain_constrained`] to derive
52+ /// the log tag from a handshake-registry secret and emit the per-send chain nullifier (see
53+ /// [`crate::messages::delivery::constrained_delivery`]). Pass `None` for the wallet-driven unconstrained tag.
54+ pub struct ConstrainedDelivery {
55+ /// Handshake registry to resolve the app-siloed shared secret from.
56+ pub registry : AztecAddress ,
57+ /// Account the message is sent on behalf of, bound into both the secret/index resolution and the chain nullifier.
58+ pub sender : AztecAddress ,
59+ }
60+
4961/// Performs private delivery of a message to `recipient` according to `delivery_mode`.
5062///
5163/// The message is encoded into plaintext and then encrypted for `recipient`. This function takes a _function_ that
5264/// returns the plaintext instead of taking the plaintext directly in order to not waste constraints encoding the
5365/// message in scenarios where the plaintext will be encrypted with unconstrained encryption.
5466///
5567/// `maybe_note_hash_counter` is only relevant for on-chain delivery modes (i.e. via protocol logs): if a newly created
56- /// note hash's side effect counter is passed, then the log will be squashed alongside the note should its nullifier be
57- /// emitted in the current transaction. This is typically only used for note messages: since the note will not actually
58- /// be created, there is no point in delivering the message .
68+ /// note hash's side effect counter is passed and constrained tagging is not requested, then the log will be squashed
69+ /// alongside the note should its nullifier be emitted in the current transaction. Constrained-tagged logs are not tied
70+ /// to note squashing because recipient discovery scans those tags sequentially .
5971///
6072/// `delivery_mode` must be a [`MessageDelivery`] value.
6173///
74+ /// `maybe_constrained_delivery` opts into constrained tagging: when `Some` (only valid with
75+ /// [`MessageDelivery::onchain_constrained`]) the log tag is derived from a handshake-registry secret and a chain
76+ /// nullifier is emitted. When `None` the wallet-driven unconstrained tag is used. Its some-ness must be a
77+ /// compile-time constant.
78+ ///
6279/// ## Privacy
6380///
6481/// The emitted log always has the same length regardless of `MESSAGE_PLAINTEXT_LEN`, because all message ciphertexts
@@ -69,17 +86,25 @@ pub fn do_private_message_delivery<Env, let MESSAGE_PLAINTEXT_LEN: u32>(
6986 maybe_note_hash_counter : Option <u32 >,
7087 recipient : AztecAddress ,
7188 delivery_mode : MessageDelivery ,
89+ maybe_constrained_delivery : Option <ConstrainedDelivery >,
7290) {
7391 // This function relies on `delivery_mode` being a constant in order to reduce circuit constraints when
7492 // unconstrained usage is requested. If `delivery_mode` were a runtime value the compiler would be unable to
7593 // perform dead-code elimination.
7694 assert_constant (delivery_mode );
7795
78- // The following maps out the 3 dimensions across which we configure message delivery.
96+ // The following maps out the dimensions across which we configure message delivery.
7997 let constrained_encryption = delivery_mode == MessageDelivery ::onchain_constrained ();
8098 let deliver_as_offchain_message = delivery_mode == MessageDelivery ::offchain ();
81- // TODO(#14565): Add constrained tagging
82- let _constrained_tagging = delivery_mode == MessageDelivery ::onchain_constrained ();
99+
100+ // Constrained tagging is opt-in via `maybe_constrained_delivery`. Its some-ness must be a compile-time constant.
101+ assert_constant (maybe_constrained_delivery .is_some ());
102+ let constrained_tagging = maybe_constrained_delivery .is_some ();
103+ if constrained_tagging {
104+ // Constrained tagging requires the constrained encryption that `ONCHAIN_CONSTRAINED` selects, otherwise the
105+ // sender could pair a correct tag with forge-able (unconstrained) ciphertext.
106+ assert (constrained_encryption , "constrained tagging requires ONCHAIN_CONSTRAINED delivery" );
107+ }
83108
84109 let contract_address = context .this_address ();
85110
@@ -91,20 +116,33 @@ pub fn do_private_message_delivery<Env, let MESSAGE_PLAINTEXT_LEN: u32>(
91116 if deliver_as_offchain_message {
92117 deliver_offchain_message (ciphertext , recipient );
93118 } else {
94- // TODO(#14565): constrained tagging is not yet implemented. Both modes currently use the unconstrained
95- // domain separator because the discovery tag always comes from an oracle. Once constrained tagging lands,
96- // this should branch on `constrained_tagging` to select the appropriate separator.
97- let discovery_tag = compute_discovery_tag (recipient );
98- let log_tag = compute_log_tag (discovery_tag , DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG );
119+ let log_tag = if constrained_tagging {
120+ let delivery_info = maybe_constrained_delivery .unwrap_unchecked ();
121+ constrained_delivery:: emit_nullifier_and_compute_constrained_tag (
122+ context ,
123+ delivery_info .registry ,
124+ delivery_info .sender ,
125+ recipient ,
126+ )
127+ } else {
128+ // TODO(#14565): Thread constrained tagging through high-level note/event delivery APIs so that
129+ // `ONCHAIN_CONSTRAINED` callers supply handshake info instead of taking this fallback.
130+ // `ONCHAIN_UNCONSTRAINED` deliveries are expected to derive the wallet-driven tag here.
131+ let discovery_tag = compute_discovery_tag (recipient );
132+ compute_log_tag (discovery_tag , DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG )
133+ };
99134
100135 // We forbid this value not being constant to avoid predicating the context calls below, which might result in
101136 // the context's arrays having unknown compile time write indices and hence dramatically increasing constraints
102137 // when accessing them. In practice this restriction is not a problem as we always know at compile time whether
103138 // we're emitting a note or non-note message.
104139 assert_constant (maybe_note_hash_counter .is_some ());
105140
141+ // TODO(F-664): Add e2e coverage for constrained note delivery through kernel squashing and recipient sync.
142+ let squashable_note_log = maybe_note_hash_counter .is_some () & !constrained_tagging ;
143+
106144 let log = BoundedVec ::from_array (ciphertext );
107- if maybe_note_hash_counter . is_some () {
145+ if squashable_note_log {
108146 // We associate the log with the note's side effect counter, so that if the note ends up being squashed in
109147 // the current transaction, the log will be removed as well.
110148 context .emit_raw_note_log_unsafe (log_tag , log , maybe_note_hash_counter .unwrap ());
0 commit comments