feat(aztec-nr): wire constrained message delivery#23866
Draft
vezenovm wants to merge 48 commits into
Draft
Conversation
This was referenced Jun 4, 2026
b9f12b8 to
befb4ea
Compare
69abd69 to
e2a43cd
Compare
vezenovm
added a commit
that referenced
this pull request
Jun 10, 2026
) Fixes [F-697](https://linear.app/aztec-labs/issue/F-697/aztec-nr-extend-the-onchaindelivery-builder-for-secret-origin) Fixes [F-649](https://linear.app/aztec-labs/issue/F-649/fixaztec-nr-skip-note-log-linkage-for-onchain-constrained-delivery-to) ## Summary - Extends `MessageDelivery` with separate typed builders for unconstrained and constrained on-chain delivery. - Adds internal tag-secret derivation config: - offchain: none - onchain unconstrained: address-pair by default, with optional non-interactive handshake selection - onchain constrained: non-interactive handshake - Introduces `OnchainDeliveryMode` as the typed on-chain mode used by tagging-index helpers and the HandshakeRegistry ABI instead of raw `u8` values. - Keeps invalid delivery/tag-derivation combinations rejected at compile time where possible. - Stops linking constrained-tagged note logs to note-hash squashing, so removing a squashed note does not break the recipient tag index chain. - Re-pins the HandshakeRegistry standard contract and updates the generated standard-contract metadata. Constrained tagging is still not fully wired up: the delivery config is validated, but tag emission remains mocked through the existing wallet-derived unconstrained path behind TODO(#14565). Stack: #23875, then this PR, then #23866, then #23867. --------- Co-authored-by: AztecBot <tech@aztec-labs.com> Co-authored-by: Nicolas Chamo <nicolas@chamo.com.ar>
Base automatically changed from
mv/f-697-general-delivery-builder
to
merge-train/fairies-v5
June 10, 2026 15:51
6337393 to
c552943
Compare
…et_and_index Also corrects the safety comment on the registry utility call: a duplicate handshake replaces the stored note rather than failing, so the actual argument is that every branch constrains the secret it returns.
…lpers' into mv/f-669-constrained-delivery-helpers
CircuitRecorder.finish() and finishWithError() dereferenced this.recording without a guard, so finalizing with no active recording threw "Cannot read properties of undefined (reading 'parent')". Because SimulatorRecorderWrapper calls finishWithError on the error path before re-throwing, that TypeError replaced the real failure (e.g. schnorr_initializerless: capsule load failed). Guard both methods (and the FileCircuitRecorder overrides) to resolve to undefined when there is no recording, so the original error propagates.
recordCall was the remaining unguarded this.recording! deref (companion to the finish/finishWithError guards): this.recording!.oracleCalls.push(entry). Under concurrent reuse of the shared recorder, this.recording can be reset mid-flight, so recordCall threw "Cannot read properties of undefined (reading 'oracleCalls')" AFTER the real oracle had already returned, fabricating a failure in an otherwise-successful execution (then surfaced via the wrapper's catch). Record best-effort and never throw: skip the push when there is no active recording. Also guards FileCircuitRecorder.recordCall and start (the file-recording-on path). The recorder is debug/profiling-only; making recordings concurrency-correct (no misparenting/dropping) is a separate follow-up.
…-constrained-delivery-helpers
…f-669-constrained-delivery-helpers # Conflicts: # yarn-project/simulator/src/private/circuit_recording/circuit_recorder.test.ts # yarn-project/simulator/src/private/circuit_recording/circuit_recorder.ts # yarn-project/simulator/src/private/circuit_recording/file_circuit_recorder.ts # yarn-project/simulator/src/private/circuit_recording/simulator_recorder_wrapper.ts
StatefulTest is a generic test contract used across e2e/PXE unit tests that don't exercise constrained-delivery semantics. Routing its `create_note`/`create_note_no_init_check`/`destroy_and_create_no_init_check` inserts through `onchain_constrained()` ties those tests to the constrained-msg nullifier chain — that breaks parallel-deploy block tests (chain collision on shared recipient), PXE unit tests that don't set up handshake state, and the e2e_2_pxes "no account secret key" scenario. Dedicated constrained-delivery coverage lives in ConstrainedDeliveryTest + e2e_constrained_delivery; same precedent as the earlier state_vars test switch.
7b401f0 to
7628eab
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes F-669
Fixes F-670
The change
0against the standard HandshakeRegistry, and constrainindex > 0through the previous chain nullifier.(secret, index)pair and emit the current constrained-message nullifier.Large portion of the diff is tests.
Concurrency / batching constraint (worth a look)
Pinning the e2e tests surfaced a sequencing constraint in constrained delivery that reviewers should sanity-check:
(sender, recipient, secret)chain collide. Each send is keyed on an incrementing index, so two sends fired as separate parallel txs read the same index and one tx is rejected. Distinct recipients are distinct chains and parallelize fine. Pinned asit.failing(documents the limit; flips green if parallel sends ever become supported).emit_two_events) and client-sideBatchCall.get_or_create_app_siloed_handshake_secretis a utility call reading committed state, so a bootstrap earlier in the same tx is invisible and the later send mints a fresh secret on a separate chain (next index lands at 1, not 2). A new recipient therefore needs one landed tx to establish the chain before sends can be batched onto it. This is why the batched tests seed the handshake first.All four behaviors are pinned in
e2e_constrained_delivery.test.ts; the rationale lives in the module doc and onget_or_create_app_siloed_handshake_secret. Follow-up: allowing utility functions to execute against combined committed + pending transaction state is tracked in F-238.