Skip to content

Commit 625b66d

Browse files
authored
Merge pull request #3011 from 0xMiden/mmagician-release-v0.15
2 parents 21e3bea + c2d4c39 commit 625b66d

39 files changed

Lines changed: 1907 additions & 1251 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## v0.16.0 (TBD)
4+
5+
### Changes
6+
7+
- [BREAKING] Renamed `AccountStorageDelta` to `AccountStoragePatch` ([#3002](https://github.com/0xMiden/protocol/pull/3002)).
8+
- Added `active_note::is_public` and `active_note::is_private` MASM procedures for checking whether the active note is public or private ([#2988](https://github.com/0xMiden/protocol/pull/2988)).
9+
10+
### Fixes
11+
- Fixed `update_ger` to explicitly reject duplicate GER insertions with `ERR_GER_ALREADY_REGISTERED` instead of silently accepting them ([#2983](https://github.com/0xMiden/protocol/pull/2983)).
12+
- AggLayer `bridge_out` now rejects B2AGG notes whose `NoteType` is not `Public`, preventing a recipient-identical private note from desyncing the Local Exit Tree from AggLayer's off-chain mirror ([#2988](https://github.com/0xMiden/protocol/pull/2988)).
13+
14+
## v0.15.1 (TBD)
15+
16+
### Changes
17+
18+
- Reject batches and blocks where an unauthenticated note is consumed before it is created to prevent circular note dependencies ([#2993](https://github.com/0xMiden/protocol/pull/2993)).
19+
320
## v0.15.0 (2026-05-22)
421

522
### Features

Cargo.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ homepage = "https://miden.xyz"
2020
license = "MIT"
2121
repository = "https://github.com/0xMiden/protocol"
2222
rust-version = "1.90"
23-
version = "0.15.0"
23+
version = "0.15.1"
2424

2525
[profile.release]
2626
codegen-units = 1

crates/miden-agglayer/SPEC.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,24 @@ on Miden. The bridge consumes these notes:
113113
1. Asserts the note sender is the designated GER manager.
114114
2. Computes `KEY = poseidon2::merge(GER_LOWER, GER_UPPER)`.
115115
3. Stores `KEY -> [1, 0, 0, 0]` in the `ger_map`, marking the GER as known.
116+
4. Reverts if the GER was already present in the map (duplicate insertions are rejected).
116117

117118
Subsequent CLAIM notes reference a GER that must be present in this map for the claim
118119
to be valid.
119120

121+
> **Note on Solidity divergence:** The Solidity `GlobalExitRootManager` contract treats a
122+
> duplicate GER insertion as an idempotent no-op. Miden intentionally diverges: a duplicate
123+
> `UPDATE_GER` note causes the consuming transaction to revert. Because `UPDATE_GER` is a
124+
> network note (consumed by the note nullifier mechanism), a duplicate would become
125+
> permanently unconsumable rather than silently accepted. Rejecting duplicates makes the
126+
> failure explicit and prevents the GER manager from accidentally creating unconsumed notes.
127+
120128
TODO: GERs cannot be removed once inserted
121129
([#2702](https://github.com/0xMiden/protocol/issues/2702)).
122130

123131
TODO: No hash chain tracks GER insertions for proof generation
124132
([#2707](https://github.com/0xMiden/protocol/issues/2707)).
125133

126-
TODO: Duplicate GER insertions are silently accepted
127-
([#2708](https://github.com/0xMiden/protocol/issues/2708)).
128-
129134
### 2.4 Faucet Registration
130135

131136
![Faucet registration flow](diagrams/faucet-registration.png)
@@ -237,12 +242,14 @@ Asserts the note sender matches the bridge admin stored in
237242
| **Inputs** | `[GER_LOWER(4), GER_UPPER(4), pad(8)]` |
238243
| **Outputs** | `[pad(16)]` |
239244
| **Context** | Consuming an `UPDATE_GER` note on the bridge account |
240-
| **Panics** | Note sender is not the GER manager |
245+
| **Panics** | Note sender is not the GER manager; GER has already been registered in storage |
241246

242247
Asserts the note sender matches the GER manager stored in
243248
`agglayer::bridge::ger_manager_account_id`, then computes
244249
`KEY = poseidon2::merge(GER_LOWER, GER_UPPER)` and stores
245250
`KEY -> [1, 0, 0, 0]` in the `ger_map` map slot. This marks the GER as "known".
251+
Duplicate insertions (same GER value) are explicitly rejected: if the key already exists
252+
in the map the procedure panics with `ERR_GER_ALREADY_REGISTERED`.
246253

247254
#### `bridge_in::claim`
248255

crates/miden-agglayer/asm/agglayer/bridge/bridge_config.masm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use miden::core::crypto::hashes::poseidon2
2+
use miden::core::word
23
use miden::protocol::account_id
34
use miden::protocol::active_account
45
use miden::protocol::active_note
@@ -9,6 +10,7 @@ use agglayer::common::utils
910
# =================================================================================================
1011

1112
const ERR_GER_NOT_FOUND = "GER not found in storage"
13+
const ERR_GER_ALREADY_REGISTERED = "GER is already registered in storage"
1214
const ERR_FAUCET_NOT_REGISTERED = "faucet is not registered in the bridge's faucet registry"
1315
const ERR_TOKEN_NOT_REGISTERED = "(origin token address, origin network) pair is not registered in the bridge's token registry"
1416
const ERR_SENDER_NOT_BRIDGE_ADMIN = "note sender is not the bridge admin"
@@ -60,6 +62,7 @@ const FAUCET_METADATA_SUBKEY_HASH_HI = 3 # METADATA_HASH_HI[4]
6062
#!
6163
#! Panics if:
6264
#! - the note sender is not the global exit root manager.
65+
#! - the GER has already been registered in storage.
6366
#!
6467
#! Invocation: call
6568
pub proc update_ger
@@ -83,8 +86,9 @@ pub proc update_ger
8386

8487
exec.native_account::set_map_item
8588
# => [OLD_VALUE, pad(12)]
86-
87-
dropw
89+
90+
# assert OLD_VALUE is EMPTY_WORD, i.e. the GER was not previously registered
91+
exec.word::eqz assert.err=ERR_GER_ALREADY_REGISTERED
8892
# => [pad(16)]
8993
end
9094

crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use agglayer::common::eth_address::EthereumAddressFormat
2222
# =================================================================================================
2323

2424
const ERR_B2AGG_DESTINATION_NETWORK_IS_MIDEN = "B2AGG note destination network ID must not be Miden's AggLayer network ID"
25+
const ERR_B2AGG_NOTE_MUST_BE_PUBLIC = "B2AGG note must be public"
2526

2627
# CONSTANTS
2728
# =================================================================================================
@@ -106,11 +107,15 @@ const BURN_NOTE_NUM_STORAGE_ITEMS=0
106107
#! - dest_address(5) are 5 u32 values representing a 20-byte Ethereum address.
107108
#!
108109
#! Panics if:
110+
#! - the B2AGG note calling `bridge_out` is not public.
109111
#! - destination network ID is Miden's AggLayer network ID.
110112
#!
111113
#! Invocation: call
112114
@locals(15)
113115
pub proc bridge_out
116+
exec.active_note::is_public assert.err=ERR_B2AGG_NOTE_MUST_BE_PUBLIC
117+
# => [ASSET_KEY, ASSET_VALUE, dest_network_id, dest_address(5), pad(2)]
118+
114119
# Save ASSET to local memory for later BURN note creation
115120
locaddr.BRIDGE_OUT_BURN_ASSET_LOC
116121
exec.asset::store
@@ -560,20 +565,29 @@ end
560565

561566
#! Computes the SERIAL_NUM of the outputted BURN note.
562567
#!
563-
#! The serial number is computed as hash(B2AGG_SERIAL_NUM, ASSET_KEY).
568+
#! The serial number is computed as hash([num_leaves, 0, 0, 0], hash(B2AGG_SERIAL_NUM, ASSET_KEY))).
564569
#!
565570
#! Inputs: [ASSET_KEY]
566571
#! Outputs: [SERIAL_NUM]
567572
#!
568573
#! Where:
569574
#! - ASSET_KEY is the vault key from which to compute the burn note serial number.
570575
#! - SERIAL_NUM is the computed serial number for the BURN note.
576+
#! - num_leaves is the number of leaves in the Local Exit Tree, post-append.
571577
#!
572578
#! Invocation: exec
573579
proc compute_burn_note_serial_num
574580
exec.active_note::get_serial_number
575581
# => [B2AGG_SERIAL_NUM, ASSET_KEY]
576582

583+
exec.poseidon2::merge
584+
# => [SERIAL_AND_ASSET_HASH]
585+
586+
# load num_leaves from its value slot and merge it with the intermediate hash word
587+
push.LET_NUM_LEAVES_SLOT[0..2]
588+
exec.active_account::get_item
589+
# => [num_leaves, 0, 0, 0, SERIAL_AND_ASSET_HASH]
590+
577591
exec.poseidon2::merge
578592
# => [SERIAL_NUM]
579593
end

crates/miden-agglayer/asm/note_scripts/b2agg.masm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const ERR_B2AGG_TARGET_ACCOUNT_MISMATCH="B2AGG note attachment target account do
5050
#! - The note does not contain exactly 6 storage items.
5151
#! - The note does not contain exactly 1 asset.
5252
#! - The note attachment does not target the consuming account.
53+
#! - The note is not public (enforced by `bridge_out`).
5354
#! - The destination network ID equals Miden's AggLayer network ID.
5455
@note_script
5556
pub proc main

crates/miden-agglayer/asm/note_scripts/update_ger.masm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const ERR_UPDATE_GER_TARGET_ACCOUNT_MISMATCH = "UPDATE_GER note attachment targe
3939
#! - account does not expose update_ger procedure.
4040
#! - target account ID does not match the consuming account ID.
4141
#! - number of note storage items is not exactly 8.
42+
#! - the GER has already been registered in storage.
4243
@note_script
4344
pub proc main
4445
dropw

crates/miden-protocol/asm/protocol/active_note.masm

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET
99
use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET
1010
use miden::protocol::note
1111
use miden::protocol::input_note
12+
use miden::protocol::util::note::NOTE_TYPE_PUBLIC
13+
use miden::protocol::util::note::NOTE_TYPE_PRIVATE
1214

1315
# ERRORS
1416
# =================================================================================================
@@ -174,6 +176,52 @@ pub proc get_metadata
174176
# => [METADATA]
175177
end
176178

179+
#! Returns whether the active note is public.
180+
#!
181+
#! Inputs: []
182+
#! Outputs: [is_public]
183+
#!
184+
#! Where:
185+
#! - is_public is 1 if the active note is public, 0 otherwise.
186+
#!
187+
#! Panics if:
188+
#! - no note is currently active.
189+
#!
190+
#! Invocation: exec
191+
pub proc is_public
192+
exec.get_metadata
193+
# => [METADATA]
194+
195+
exec.note::metadata_into_note_type
196+
# => [note_type]
197+
198+
eq.NOTE_TYPE_PUBLIC
199+
# => [is_public]
200+
end
201+
202+
#! Returns whether the active note is private.
203+
#!
204+
#! Inputs: []
205+
#! Outputs: [is_private]
206+
#!
207+
#! Where:
208+
#! - is_private is 1 if the active note is private, 0 otherwise.
209+
#!
210+
#! Panics if:
211+
#! - no note is currently active.
212+
#!
213+
#! Invocation: exec
214+
pub proc is_private
215+
exec.get_metadata
216+
# => [METADATA]
217+
218+
exec.note::metadata_into_note_type
219+
# => [note_type]
220+
221+
eq.NOTE_TYPE_PRIVATE
222+
# => [is_private]
223+
end
224+
177225
#! Returns the sender of the active note.
178226
#!
179227
#! Inputs: []

0 commit comments

Comments
 (0)