Skip to content

Commit ff8944f

Browse files
authored
feat: merge-train/fairies (#23804)
BEGIN_COMMIT_OVERRIDE feat(aztec-nr): Add a delivery mode to handshake notes (#23783) fix(pxe): enforce full field consumption at oracle boundaries (#23802) chore(ci): Static oracle version check (#23805) END_COMMIT_OVERRIDE
2 parents aede507 + 39058b0 commit ff8944f

10 files changed

Lines changed: 564 additions & 206 deletions

File tree

noir-projects/aztec-nr/aztec/src/messages/message_delivery.nr

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ pub struct MessageDelivery {
2828
}
2929

3030
global OFFCHAIN: u8 = 1;
31-
global ONCHAIN_UNCONSTRAINED: u8 = 2;
32-
global ONCHAIN_CONSTRAINED: u8 = 3;
31+
/// Delivery variant identifier for on-chain message delivery without constrained encryption/tagging.
32+
pub global ONCHAIN_UNCONSTRAINED: u8 = 2;
33+
/// Delivery variant identifier for on-chain message delivery with constrained encryption/tagging.
34+
pub global ONCHAIN_CONSTRAINED: u8 = 3;
3335

3436
impl MessageDelivery {
3537
/// Delivers the message fully off-chain, with no guarantees whatsoever.

noir-projects/noir-contracts/contracts/message_discovery/handshake_registry_contract/src/main.nr

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub(crate) global NON_INTERACTIVE_HANDSHAKE: u8 = 1;
99
/// Registry for the constrained-delivery shared-secret handshake protocol.
1010
///
1111
/// The registry establishes a master shared-secret point `S` between a sender and a recipient and stores one current
12-
/// note for each `(recipient, sender)` pair. The raw `S` never leaves the registry: app contracts call
12+
/// note for each `(recipient, sender, mode)` tuple. The raw `S` never leaves the registry: app contracts call
1313
/// [`HandshakeRegistry::non_interactive_handshake`] to receive the secret already siloed to the caller, call
1414
/// [`HandshakeRegistry::get_app_siloed_secret`] offchain for an existing handshake, and use
1515
/// [`HandshakeRegistry::validate_handshake`] to check an app-siloed secret against the current stored handshake. The
@@ -25,7 +25,7 @@ pub contract HandshakeRegistry {
2525
macros::{functions::external, storage::storage},
2626
messages::{
2727
encryption::{aes128::AES128, message_encryption::MessageEncryption},
28-
message_delivery::MessageDelivery,
28+
message_delivery::{MessageDelivery, ONCHAIN_CONSTRAINED, ONCHAIN_UNCONSTRAINED},
2929
},
3030
protocol::{
3131
address::AztecAddress, constants::DOM_SEP__NON_INTERACTIVE_HANDSHAKE_LOG_TAG, hash::compute_log_tag,
@@ -36,18 +36,21 @@ pub contract HandshakeRegistry {
3636

3737
#[storage]
3838
struct Storage<Context> {
39-
/// One current [`HandshakeNote`] per `(recipient, sender)` pair. Re-handshaking for the same pair replaces the
40-
/// prior sender-owned note, so only the latest handshake remains valid.
41-
handshakes: Map<AztecAddress, Owned<PrivateMutable<HandshakeNote, Context>, Context>, Context>,
39+
/// One current [`HandshakeNote`] per `(recipient, sender, mode)` tuple. Re-handshaking for the same tuple
40+
/// replaces the prior sender-owned note, so only the latest handshake remains valid for that mode.
41+
handshakes: Map<AztecAddress, Map<u8, Owned<PrivateMutable<HandshakeNote, Context>, Context>, Context>, Context>,
4242
}
4343

4444
/// Performs a non-interactive handshake from `sender` to `recipient` and returns the app-siloed shared secret
4545
/// for the calling contract.
4646
///
47+
/// `mode` sets the delivery mode for messages tagged with this handshake ([`ONCHAIN_UNCONSTRAINED`] or
48+
/// [`ONCHAIN_CONSTRAINED`]); the handshake note itself is always stored onchain.
49+
///
4750
/// Generates a fresh ephemeral key pair `(eph_sk, eph_pk)`, computes the raw ECDH shared secret point
4851
/// `S = eph_sk * recipient_address_point`, and produces three effects:
4952
///
50-
/// 1. Inserts or replaces a [`HandshakeNote`] owned by `sender`, holding the raw point `S`.
53+
/// 1. Inserts or replaces a [`HandshakeNote`] owned by `sender` for `mode`, holding the raw point `S`.
5154
/// 2. Emits an encrypted private log under a recipient-keyed tag with payload `[eph_pk.x]`. The recipient
5255
/// discovers handshakes addressed to them by scanning their tag and recovers `S` from `eph_pk` via their own
5356
/// ECDH (`recipient_isk * eph_pk`). `eph_pk.y` is fixed positive by the
@@ -56,11 +59,15 @@ pub contract HandshakeRegistry {
5659
/// tag" into one call without a second hop into the registry.
5760
///
5861
/// # Panics
62+
/// If `mode` is not a recognized delivery mode.
63+
///
5964
/// If `recipient` is not a valid curve point. There are no upstream side effects in this call frame to
6065
/// protect, and a fallback would insert a permanent note recording a handshake with an invalid recipient,
6166
/// polluting registry state.
6267
#[external("private")]
63-
fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress) -> Field {
68+
fn non_interactive_handshake(sender: AztecAddress, recipient: AztecAddress, mode: u8) -> Field {
69+
assert((mode == ONCHAIN_UNCONSTRAINED) | (mode == ONCHAIN_CONSTRAINED), "unrecognized delivery mode");
70+
6471
let recipient_point = recipient.to_address_point().expect(f"recipient address is not on the curve");
6572

6673
let (eph_sk, eph_pk) = generate_positive_ephemeral_key_pair();
@@ -72,7 +79,7 @@ pub contract HandshakeRegistry {
7279
// discover the handshake via the encrypted log emitted below, not via the sender's note.
7380
// We use onchain unconstrained delivery rather than `OFFCHAIN` so the note is
7481
// discoverable via normal PXE sync.
75-
self.storage.handshakes.at(recipient).at(sender).initialize_or_replace(|_| note).deliver(
82+
self.storage.handshakes.at(recipient).at(mode).at(sender).initialize_or_replace(|_| note).deliver(
7683
MessageDelivery::onchain_unconstrained().with_sender(sender),
7784
);
7885

@@ -87,19 +94,23 @@ pub contract HandshakeRegistry {
8794
note.siloed_for(self.msg_sender())
8895
}
8996

90-
/// Asserts that `app_siloed_secret` is the silo of a stored handshake from `sender` to `recipient`, for the
91-
/// caller (`msg_sender()`).
97+
/// Asserts that `app_siloed_secret` is the silo of a stored handshake from `sender` to `recipient` in `mode`, for
98+
/// the caller (`msg_sender()`).
9299
///
93100
/// Apps that receive an `app_siloed_secret` from an untrusted source call this once to validate that secret
94101
/// against the registry's stored handshake.
95102
///
96103
/// # Panics
97-
/// If no stored handshake for `(sender, recipient)` silos to `app_siloed_secret` under the calling contract's
98-
/// address.
104+
/// If `mode` is not a recognized delivery mode.
105+
///
106+
/// If no stored handshake for `(sender, recipient, mode)` silos to `app_siloed_secret` under the calling
107+
/// contract's address.
99108
#[external("private")]
100-
fn validate_handshake(sender: AztecAddress, recipient: AztecAddress, app_siloed_secret: Field) {
109+
fn validate_handshake(sender: AztecAddress, recipient: AztecAddress, mode: u8, app_siloed_secret: Field) {
110+
assert((mode == ONCHAIN_UNCONSTRAINED) | (mode == ONCHAIN_CONSTRAINED), "unrecognized delivery mode");
111+
101112
let caller = self.msg_sender();
102-
let replacement_note_message = self.storage.handshakes.at(recipient).at(sender).get_note();
113+
let replacement_note_message = self.storage.handshakes.at(recipient).at(mode).at(sender).get_note();
103114
let note = replacement_note_message.get_note();
104115

105116
assert(note.siloed_for(caller) == app_siloed_secret, "no matching handshake");
@@ -109,23 +120,28 @@ pub contract HandshakeRegistry {
109120
replacement_note_message.deliver(MessageDelivery::onchain_unconstrained());
110121
}
111122

112-
/// Returns the app-siloed shared secret for an existing handshake.
123+
/// Returns the app-siloed shared secret for an existing handshake in `mode`.
124+
///
125+
/// This is the existing-handshake retrieval surface. It returns `silo(S, caller)`, never raw `S`; a different
126+
/// caller receives a different app-siloed value for the same registry note.
127+
/// Contracts should still call [HandshakeRegistry::validate_handshake] when they need a constrained proof that a
128+
/// supplied app-siloed secret matches the current handshake.
113129
///
114-
/// This is the existing-handshake retrieval surface for constrained delivery. It returns only `silo(S, caller)`,
115-
/// never raw `S`; a different caller receives a different app-siloed value for the same registry note. This is
116-
/// a utility function because it is a local read helper. Contracts should still call
117-
/// [HandshakeRegistry::validate_handshake] when they need a constrained proof that a supplied app-siloed secret
118-
/// matches the current handshake.
130+
/// # Panics
131+
/// If `mode` is not a recognized delivery mode.
119132
#[external("utility")]
120133
unconstrained fn get_app_siloed_secret(
121134
sender: AztecAddress,
122135
recipient: AztecAddress,
136+
mode: u8,
123137
// TODO(F-671): replace with `self.msg_sender()` once utility context exposes it. The
124-
// explicit param forces hooks to also gate on `params[2]`; otherwise any contract that
138+
// explicit param forces hooks to also gate on the caller; otherwise any contract that
125139
// can authorize (target, selector) can read another caller's siloed secret.
126140
caller: AztecAddress,
127141
) -> Option<Field> {
128-
let handshake = self.storage.handshakes.at(recipient).at(sender);
142+
assert((mode == ONCHAIN_UNCONSTRAINED) | (mode == ONCHAIN_CONSTRAINED), "unrecognized delivery mode");
143+
144+
let handshake = self.storage.handshakes.at(recipient).at(mode).at(sender);
129145

130146
if handshake.is_initialized() {
131147
Option::some(handshake.view_note().siloed_for(caller))

0 commit comments

Comments
 (0)