Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
c552943
feat(aztec-nr): add constrained delivery helper
vezenovm Jun 10, 2026
000162b
comments
vezenovm Jun 10, 2026
36681e6
fmt
vezenovm Jun 10, 2026
0271756
.
vezenovm Jun 10, 2026
98b556a
refactor(aztec-nr): rename calculate_secret_and_index to resolve_secr…
vezenovm Jun 10, 2026
5699c52
Merge branch 'merge-train/fairies-v5' into mv/f-669-constrained-deliv…
vezenovm Jun 10, 2026
389493b
Merge branch 'merge-train/fairies-v5' into mv/f-669-constrained-deliv…
vezenovm Jun 11, 2026
c334adc
some minor simplifications
vezenovm Jun 11, 2026
cd2fbea
shared unconstrained/constrained tag computaiton
vezenovm Jun 11, 2026
33a3b65
cleanup
vezenovm Jun 12, 2026
18a8df5
Merge branch 'merge-train/fairies-v5' into mv/f-669-constrained-deliv…
vezenovm Jun 12, 2026
b258ccd
chore: regenerate drifted contract snapshots
AztecBot Jun 12, 2026
153b84a
split into helpers
vezenovm Jun 12, 2026
a851c53
.
vezenovm Jun 12, 2026
170f8b9
Merge remote-tracking branch 'origin/mv/f-669-constrained-delivery-he…
vezenovm Jun 12, 2026
e371203
naming
vezenovm Jun 12, 2026
9d61ebb
.'
vezenovm Jun 12, 2026
df8704e
update tests
vezenovm Jun 12, 2026
09bae38
standard contract
vezenovm Jun 12, 2026
614e435
.'
vezenovm Jun 12, 2026
65ac26d
authorize get_app_siloed_secret and e2e test
vezenovm Jun 12, 2026
72009eb
remove old helper for public flow and lint
vezenovm Jun 12, 2026
dbf792b
.'
vezenovm Jun 12, 2026
6919a75
Merge branch 'merge-train/fairies-v5' into mv/f-669-constrained-deliv…
vezenovm Jun 15, 2026
58343a9
wallet pref todo
vezenovm Jun 15, 2026
dbc04f4
msg_sender default
vezenovm Jun 15, 2026
0eb6e78
Merge remote-tracking branch 'origin/mv/f-669-constrained-delivery-he…
vezenovm Jun 15, 2026
91b3840
fix(simulator): prevent circuit recorder from masking execution errors
vezenovm Jun 15, 2026
af18bc5
get_sender_for_tags
vezenovm Jun 16, 2026
fd84572
cleanup diff remove sender overrides, new tag module, comments cleanup
vezenovm Jun 16, 2026
48aa220
fix(simulator): guard recordCall against an absent recording
vezenovm Jun 16, 2026
bcf9c76
Merge branch 'mv/fix-circuit-recorder-finish-undefined' into mv/f-669…
vezenovm Jun 16, 2026
25b6540
comments
vezenovm Jun 16, 2026
2f8cf57
.
vezenovm Jun 16, 2026
446957f
Merge remote-tracking branch 'origin/merge-train/fairies-v5' into mv/…
vezenovm Jun 16, 2026
417482d
fix e2e test
vezenovm Jun 16, 2026
ba1be2d
cleamnup
vezenovm Jun 16, 2026
3d10cae
test pinning non-concurrent behavior of constrained delivery
vezenovm Jun 16, 2026
26aae03
e2e multiple constrained delivery sends in one tx
vezenovm Jun 16, 2026
0834425
pin additional batching tests to show e2e functionality
vezenovm Jun 16, 2026
00cc819
update comment
vezenovm Jun 16, 2026
bed55f4
fixed simulation stubs for accounts
vezenovm Jun 16, 2026
fbadea0
clean
vezenovm Jun 16, 2026
d7065a9
another test to use onchain_unconstrained
vezenovm Jun 16, 2026
a66cedd
update pending note hashes test
vezenovm Jun 16, 2026
54520c9
more tests and reorg
vezenovm Jun 16, 2026
4c13ccb
onchain_unconstrained for state_vars test
vezenovm Jun 16, 2026
7628eab
test(noir-contracts): switch StatefulTest note delivery to unconstrained
AztecBot Jun 16, 2026
fd07996
skip private state is zero w/o secret key test
vezenovm Jun 17, 2026
375800b
link to linear issue
vezenovm Jun 17, 2026
08c35b0
comments and remove redundant test
vezenovm Jun 17, 2026
269fb80
Merge branch 'merge-train/fairies-v5' into mv/f-669-constrained-deliv…
vezenovm Jun 17, 2026
99b0a9c
use default sender to simplify tests
vezenovm Jun 17, 2026
32cdf5d
fmt
vezenovm Jun 17, 2026
20ae9f6
.
vezenovm Jun 17, 2026
f6b9c71
Update noir-projects/noir-contracts/contracts/test/test_log_contract/…
vezenovm Jun 17, 2026
05ffc5a
comment updates
vezenovm Jun 17, 2026
45a9a9d
one more comment
vezenovm Jun 17, 2026
b6eb170
Update docs/docs-developers/docs/aztec-nr/framework-description/state…
vezenovm Jun 17, 2026
f73130b
pr review cleanup and comments
vezenovm Jun 17, 2026
41411a4
more snap and test removal
vezenovm Jun 17, 2026
73b3c40
constrained_delivery unit tests, re-org get_app_or.. to handshake mod…
vezenovm Jun 17, 2026
56d84b2
Merge remote-tracking branch 'origin/mv/f-669-constrained-delivery-he…
vezenovm Jun 17, 2026
833f58e
index >0 comment
vezenovm Jun 17, 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 @@ -140,13 +140,15 @@ self.storage.balances.at(admin).add(amount)

**Onchain delivery with guaranteed correct content.**

**WARNING**: This mode is [currently NOT fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained, meaning a malicious sender could prevent the recipient from finding the message.
Comment thread
vezenovm marked this conversation as resolved.

- **Use when:** The sender cannot be trusted to deliver correctly (e.g., paying fees, creating notes for others, multisig configuration changes). Use this when you need to prove to a contract that the delivery has been done correctly. You can imagine a private NFT sale escrow contract where the escrow would be holding the NFT (the contract itself would be the NFT note owner) and then the escrow would release the NFT to the buyer once the NFT buyer pays the seller. In this case the `NFTSale::buy(...)` function would trigger the payment token transfer from the buyer to the seller and it would need to use `ONCHAIN_CONSTRAINED` delivery otherwise the escrow contract would be willing to transfer the NFT without the NFT seller actually being able to then spend the money. Note that for the transfer of the NFT from the escrow contract to the buyer you could use `OFFCHAIN` delivery because the delivery and encryption would be done in the buyer's PXE and hence there is alignment.
- **Costs:** DA gas fees for the encrypted log, proving time overhead for encryption and tagging
- **Guarantees:** Recipient receives correctly encrypted content (once tag constraining is implemented, recipient will be able to find it)
- **Guarantees:** Recipient receives correctly encrypted content and can discover the message through constrained tags
- **Privacy:** High - encrypted log reveals minimal information

By default, constrained delivery uses the wallet-supplied sender for tags (typically the account that initiated the
transaction), the same default as unconstrained delivery. Use `.with_sender(...)` when the intended sender differs from
that account.

```rust
// Minting to an arbitrary recipient - must guarantee delivery
self.storage.balances.at(recipient).add(amount)
Expand Down Expand Up @@ -175,20 +177,26 @@ When your wallet submits a transaction, it tells PXE which address to use as the

### Discovering Notes from Unknown Senders

**You cannot receive notes from an unknown sender** without additional mechanisms. The tagging system requires you to know the sender's address in advance to compute the shared secret needed to find the note (i.e., the sender needs to be added to your wallet).
For address-secret tagging, **you cannot receive notes from an unknown sender** without additional mechanisms. The
tagging system requires you to know the sender's address in advance to compute the shared secret needed to find the note
(i.e., the sender needs to be added to your wallet). This applies to the default `onchain_unconstrained()` sender-for-tags
path.

There are three approaches to solve this:

**a) Brute force search** - Download every log and attempt to decrypt it. This becomes prohibitively expensive as the network grows.

**b) Known sender tagging** (current implementation) - Only receive notes from senders whose addresses you've registered in your PXE. This is very fast and allows you to block spammers by removing them from your sender list. However, you must know who might send you notes in advance.
**b) Known sender tagging** - Only receive notes from senders whose addresses you've registered in your PXE. This is very fast and allows you to block spammers by removing them from your sender list. However, you must know who might send you notes in advance.

**c) Handshaking protocols** (not yet implemented) - A two-phase approach where senders first perform a "handshake" that notifies you of their existence, then use regular tagging afterward. This trades off either privacy (public handshake events) or performance (scanning all handshake logs).
**c) Handshaking protocols** - A two-phase approach where senders first perform a handshake that notifies you of their
existence, then use regular tagging afterward. Constrained delivery uses this kind of approach through the standard
handshake registry: the sender emits a recipient-discoverable handshake, then uses regular constrained tags derived from
that handshake secret.

**Workarounds for receiving notes from unknown senders:**
- Require senders to register in a contract first, then search for notes from all registered senders
- Share sender addresses through offchain communication
- Implement a custom discovery mechanism in your contract
- Use constrained delivery when you need recipient-discoverable handshakes and constrained message tags

See the [Note Discovery](../../foundational-topics/advanced/storage/note_discovery.md) documentation for technical details on the tagging mechanism.

Expand Down Expand Up @@ -234,8 +242,8 @@ fn transfer(amount: u128, sender: AztecAddress, recipient: AztecAddress) {
#[external("private")]
#[initializer]
fn constructor(admin: AztecAddress) {
// Admin is the owner of the note and is motivated to receive it
// Use unconstrained delivery since we don't know if deployer is incentivized
// Admin is the owner of the note.
// Use constrained delivery since we don't know if the deployer is incentivized.
self.storage.admin
.initialize(AddressNote { address: admin }, admin)
.deliver(MessageDelivery::onchain_constrained());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ When working with private state variables, many operations return a `NoteMessage
#### Delivery Methods

Private notes need to be communicated to their recipients so they know the note exists and can use it. The [`NoteMessage`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/note/struct.NoteMessage) wrapper forces you to make an explicit choice about how this happens:
- [`MessageDelivery::onchain_constrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates). **Warning:** Currently [not fully constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565) - the log's tag is unconstrained.
- [`MessageDelivery::onchain_constrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Verified in the circuit (most secure, but highest cost) - Use when the sender cannot be trusted to deliver correctly (e.g., protocol fees, multisig config updates).
- [`MessageDelivery::onchain_unconstrained()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Message stored onchain but no guarantees on content - Use when the sender is incentivized to deliver correctly but may not have an offchain channel to the recipient.
- [`MessageDelivery::offchain()`](pathname:///aztec-nr-api/#api_ref_version/noir_aztec/messages/delivery/global.MessageDelivery): Lowest cost, no onchain data - Use when the sender and recipient can communicate and the sender is incentivized to deliver correctly.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@ This sender address is used along with the recipient address to compute the shar

#### Registering known senders

To discover notes from a particular sender, the recipient's PXE must know the sender's address in advance so it can compute the shared tagging secret. Register senders using the wallet API:
To discover address-secret notes from a particular sender, the recipient's PXE must know the sender's address in advance so it can compute the shared tagging secret. Register senders using the wallet API:

```typescript
// Register a sender so your PXE can discover notes from them
await wallet.registerSender(senderAddress);
```

Notes sent to yourself are always discoverable — the PXE automatically adds all local accounts as implicit senders.
Constrained-delivery handshakes are discovered through the handshake registry instead.

### The sync process

Expand Down Expand Up @@ -96,25 +97,28 @@ This means there's a practical limit on how many logs a single sender can emit t

### Limitations and solutions

#### You cannot receive tagged notes from an unknown sender
#### You cannot receive address-secret tagged notes from an unknown sender

Without knowing the sender's address, you cannot create the shared secret needed to derive the note tag. This is a fundamental limitation of the current tagging scheme.
Without knowing the sender's address, you cannot create the shared secret needed to derive an address-secret note tag.
This is a fundamental limitation of address-secret tagging.

There are three broad families of solutions to this problem:

**a) Brute force search** - Scan every single log and test if it decrypts. This has obvious performance issues as the network grows and becomes prohibitively expensive.

**b) Tagging with known sender** (current implementation) - You know who will send you messages and search for those specifically. This is very fast and allows you to remove senders who spam you. However, we don't currently have a mechanism for constraining this (i.e., guaranteeing that the recipient will find the message).
**b) Tagging with known sender** - You know who will send you messages and search for those specifically. This is very fast and allows you to remove senders who spam you. However, address-secret tagging requires knowing who might send you notes in advance.

**c) Tagging with handshaking** - An intermediate solution where you can be notified of new senders. A handshake occurs onchain that lets the recipient discover a new sender, and from that point on there's regular tagging. This design either:
**c) Tagging with handshaking** - An intermediate solution where the sender performs a handshake that lets the recipient discover a new sender, and from that point on there's regular tagging. This design either:
- Is fast but leaks privacy (e.g., a public event with "new handshake for Alice!")
- Is slow but doesn't leak (you brute force scan all logs from a handshake contract, testing if any handshakes are for you)

The handshaking design space is large — for example, you could set up infrastructure where a server searches handshakes for you, trading off infrastructure requirements for performance.

**Handshaking is not currently implemented in Aztec.nr.** For now, if you need to receive notes from unknown senders, potential workarounds include:
Aztec.nr's constrained delivery uses the standard handshake registry for this purpose. If you need recipient-discoverable handshakes and constrained message tags, use `MessageDelivery::onchain_constrained()`.

Other potential workarounds include:
- Having senders register themselves in a contract first, allowing recipients to search for note tags from all registered senders
- Using offchain communication to share sender addresses with recipients, who then call `wallet.registerSender(address)` to enable discovery
- Using offchain communication to share sender addresses with recipients, who then call `wallet.registerSender(address)` to enable address-secret discovery
- Implementing a custom discovery mechanism in your contract

See the [Note Delivery](../../../aztec-nr/framework-description/note_delivery.md) documentation for more details on how the sender is used when delivering notes.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/webapp-tutorial/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub contract PodRacing {
.at(game_id)
.at(player)
.insert(GameRoundNote::new(track1, track2, track3, track4, track5, round, player))
.deliver(MessageDelivery::onchain_constrained());
.deliver(MessageDelivery::onchain_unconstrained());

self.enqueue(PodRacing::at(self.context.this_address()).validate_and_play_round(
player,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl<Storage, CallSelf, EnqueueSelf, CallSelfStatic, EnqueueSelfStatic, CallInte
///
/// # Cost
///
/// Private event emission always results in the creation of a nullifer, which acts as a commitment to the event
/// Private event emission always results in the csreation of a nullifer, which acts as a commitment to the event
/// and is used by third parties to verify its authenticity. See [`EventMessage::deliver_to`] for the costs
/// associated to delivery.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use super::tag_secret_derivation::TagSecretDerivation;
/// ## Construction
///
/// The fields are private and there is no public constructor: a `MessageDelivery` can only be produced by a
/// [`MessageDeliveryBuilder`] that enforces valid configurations, so invalid field combinations cannot be
/// represented to the consumer.
/// [`MessageDeliveryBuilder`]. The delivery APIs validate the built configuration before consuming it.

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 know that we are currently allowing to create "invalid" states, but that is only temporary (for a few PRs), right? The end state will only allow devs to build valid states, right?

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.

Yes that will be the end state.

pub struct MessageDelivery {
mode: DeliveryMode,
tag_secret_derivation: TagSecretDerivation,
Expand Down Expand Up @@ -144,10 +143,6 @@ impl MessageDelivery {

/// Delivers the message on-chain, guaranteeing the recipient will receive the correct content.
///
/// >**WARNING**: this delivery mode is [currently NOT fully
/// constrained](https://github.com/AztecProtocol/aztec-packages/issues/14565). The log's tag is unconstrained,
/// meaning a malicious sender could manipulate it to prevent the recipient from finding the message.
///
/// ## Use Cases
///
/// This delivery method is suitable for all use cases, since it always works as expected. It is however the most
Expand Down
Loading
Loading