Skip to content

feat(agglayer): add faucet deregistration#2838

Open
partylikeits1983 wants to merge 2 commits into
nextfrom
ajl-deregister-faucet
Open

feat(agglayer): add faucet deregistration#2838
partylikeits1983 wants to merge 2 commits into
nextfrom
ajl-deregister-faucet

Conversation

@partylikeits1983

@partylikeits1983 partylikeits1983 commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Closes #2705. Rebased onto next (the agglayer crate now lives there); base changed from agglayer to next.

Adds a bridge-admin-gated deregister_faucet MASM procedure that fully revokes a faucet, clearing every entry registration wrote for it: faucet_registry_map, token_registry_map, and all four faucet_metadata_map sub-keys. Wired through a new DEREGISTER_AGG_BRIDGE note script and a DeregisterAggBridgeNote Rust builder.

The token registry is pair-keyed on (origin_token_address, origin_network). Rather than trusting note-supplied values, deregister_faucet reads the faucet's registered address and network back from its own faucet_metadata_map (via get_faucet_conversion_info) and recomputes the key, so the cleared entry is provably the one register_faucet wrote and the registries cannot desynchronize. The note therefore carries only the faucet id (2 felts).

bridge_in::claim now also re-checks assert_faucet_registered after the token lookup. Together with bridge_out's existing check, this makes deregistration an unconditional revocation: a deregistered faucet can never mint or unlock, even via a stale token_registry key that a re-registration under a different token identity might leave behind (register_faucet does not clear the prior key).

Changes

  • bridge_config.masm: new deregister_faucet proc reusing assert_sender_is_bridge_admin, assert_faucet_registered, get_faucet_conversion_info, and hash_token_address.
  • components/bridge.masm: re-export deregister_faucet.
  • note_scripts/deregister_agg_bridge.masm: new note script (carries only the faucet id).
  • src/deregister_note.rs: new DeregisterAggBridgeNote builder.
  • bridge.rs: add the DEREGISTER script root to the network-account note allowlist.
  • bridge_in.masm: re-check assert_faucet_registered in claim.
  • Tests: round-trip clearing all three maps, an end-to-end revocation test, a stale-key-claim rejection test, and negative tests for ERR_FAUCET_NOT_REGISTERED / ERR_SENDER_NOT_BRIDGE_ADMIN.
  • SPEC.md, CHANGELOG.md: documentation.

Follow-up (out of scope)

register_faucet does not clear a faucet's prior token_registry key when re-registered under a different (origin_token_address, origin_network). The claim-side assert_faucet_registered check above neutralizes any security impact, but clearing the stale key in register_faucet would be a tidy follow-up.

@partylikeits1983 partylikeits1983 changed the title feat(agglayer): add faucet deregistration (#2705) feat(agglayer): add faucet deregistration Apr 27, 2026
@partylikeits1983 partylikeits1983 marked this pull request as ready for review April 28, 2026 21:55
@partylikeits1983 partylikeits1983 marked this pull request as draft April 28, 2026 21:56
@partylikeits1983 partylikeits1983 self-assigned this Apr 30, 2026
@partylikeits1983 partylikeits1983 added agglayer PRs or issues related to AggLayer bridging integration pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority labels Apr 30, 2026
@partylikeits1983 partylikeits1983 marked this pull request as ready for review May 1, 2026 18:32
@partylikeits1983 partylikeits1983 marked this pull request as draft May 11, 2026 22:05
@partylikeits1983

Copy link
Copy Markdown
Contributor Author

Putting into draft mode until v0.15.0

@partylikeits1983 partylikeits1983 changed the base branch from agglayer to next June 10, 2026 03:39
@partylikeits1983 partylikeits1983 marked this pull request as ready for review June 10, 2026 03:40
Adds a bridge-admin-gated `deregister_faucet` MASM procedure that revokes a
faucet, clearing every entry registration wrote for it: the `faucet_registry_map`,
`token_registry_map`, and all four `faucet_metadata_map` sub-keys. Wired through a
new `DEREGISTER_AGG_BRIDGE` note script and a `DeregisterAggBridgeNote` Rust
builder. After deregistration, in-flight B2AGG and CLAIM notes referencing the
cleared faucet fail their existing registration checks.

The token registry is pair-keyed on (origin_token_address, origin_network). Rather
than trusting note-supplied values, `deregister_faucet` reads the faucet's
registered address and network back from its own `faucet_metadata_map` (via
`get_faucet_conversion_info`) and recomputes the key, so the cleared token-registry
entry is provably the one `register_faucet` wrote and the two registries cannot
desynchronize. The note therefore carries only the faucet id (2 felts).

- bridge_config.masm: new `deregister_faucet` proc reusing
  `assert_sender_is_bridge_admin`, `assert_faucet_registered`,
  `get_faucet_conversion_info`, and `hash_token_address`.
- components/bridge.masm: re-export `deregister_faucet`.
- note_scripts/deregister_agg_bridge.masm: new note script.
- src/deregister_note.rs: new `DeregisterAggBridgeNote` builder.
- bridge.rs: add the DEREGISTER script root to the network-account note allowlist.
- tests/agglayer/config_bridge.rs: round-trip test verifying all three maps are
  cleared, an end-to-end test proving the faucet is revoked, and negative tests for
  `ERR_FAUCET_NOT_REGISTERED` and `ERR_SENDER_NOT_BRIDGE_ADMIN`.
- SPEC.md, CHANGELOG.md: documentation updates.

@mmagician mmagician left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo:

  • verbosity
  • naming of the note & related variables
  • stack comments need fixing
  • (optional) small refactors around code de-dup

#! `assert_faucet_registered` and `lookup_faucet_by_token_address` calls panic, so any in-flight
#! notes (B2AGG / CLAIM) targeting the deregistered faucet fail their existing registration checks.
#!
#! The origin token address and origin network are NOT taken from the note: they are read back

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don;t think we necessarily care where token address is NOT taken from.

Comment on lines +310 to +314
#! 1. Writes `[0, 0, faucet_id_suffix, faucet_id_prefix] -> [0, 0, 0, 0]` into `faucet_registry`.
#! 2. Writes `hash(tokenAddress[5] || origin_network) -> [0, 0, 0, 0]` into `token_registry`, where
#! the address/network come from `faucet_metadata` and origin_network is byte-swapped before
#! hashing (matching `register_faucet` Step 4 and `lookup_faucet_by_token_address`).
#! 3. Clears all four `faucet_metadata` sub-keys (addr-lo, addr-hi/network/scale, hash-lo, hash-hi).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is explaining again what the above section already explain, just using concrete values / slots

Comment on lines +340 to +341
# Read the faucet's registered (origin_token_addr, origin_network) from faucet_metadata so the
# cleared key is provably the one register_faucet wrote. Drop the scale we don't need.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Read the faucet's registered (origin_token_addr, origin_network) from faucet_metadata so the
# cleared key is provably the one register_faucet wrote. Drop the scale we don't need.
# Read the faucet's registered (origin_token_addr, origin_network) from faucet_metadata. Drop the scale we don't need.

# cleared key is provably the one register_faucet wrote. Drop the scale we don't need.
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC
exec.get_faucet_conversion_info
# => [origin_token_addr(5), origin_network, scale, pad(8)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack layout comments are off by 1, they add up to 15 not 16

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this spills over to the following stack layout comments too

Comment on lines +375 to +395
# Each sub-key KEY = [sub_key, 0, suffix, prefix] is cleared to VALUE = [0, 0, 0, 0].
push.0.0.0.0
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_ADDR_LO
push.FAUCET_METADATA_MAP_SLOT[0..2]
exec.native_account::set_map_item
dropw
# => [pad(16)]

push.0.0.0.0
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_ADDR_HI
push.FAUCET_METADATA_MAP_SLOT[0..2]
exec.native_account::set_map_item
dropw
# => [pad(16)]

push.0.0.0.0
loc_load.DEREG_FAUCET_ID_PREFIX_LOC loc_load.DEREG_FAUCET_ID_SUFFIX_LOC push.0.FAUCET_METADATA_SUBKEY_HASH_LO
push.FAUCET_METADATA_MAP_SLOT[0..2]
exec.native_account::set_map_item
dropw
# => [pad(16)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be cool if we could write a loop for clearing the four subkeys, it would simplify the code quite a bit

call.bridge_config::deregister_faucet
# => [pad(18)]

# Drop the 2 overflow pad felts to bring sdepth back to the 16-minimum.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Drop the 2 overflow pad felts to bring sdepth back to the 16-minimum.

/// and faucet metadata. It carries only the faucet account ID; the bridge reads the registered
/// origin token address and origin network back from its own faucet metadata, so the token
/// registry key it clears matches the faucet's current registration rather than trusting
/// note-supplied values. The note is always public.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The note is always public.

I don't think this is asserted in MASM.

Is there an attack vector if the note is private?

Comment on lines +1546 to +1554
/// Tests that deregistration is an unconditional revocation even when a stale `token_registry` key
/// survives.
///
/// `register_faucet` does not clear a faucet's previous token key when the faucet is re-registered
/// under a different `(origin_token_address, origin_network)`, so after deregistration the prior
/// token key can still resolve to the (now-unregistered) faucet via
/// `lookup_faucet_by_token_address`. Because `claim` re-checks `assert_faucet_registered` after the
/// token lookup, such a CLAIM is rejected with `ERR_FAUCET_NOT_REGISTERED` instead of minting
/// through the revoked faucet.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be complicated to also clear the stale key? This seems to cause a bit of overhead, in terms of documentation at the very least.

Ok(())
}

/// Tests that DEREGISTER_AGG_BRIDGE panics with `ERR_SENDER_NOT_BRIDGE_ADMIN` when the note

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a few failure scenarios, could we bundle them with rstest?

Comment thread CHANGELOG.md
- [BREAKING] Removed `AccountStorageMode::Network`; network accounts are now identified via `NetworkAccountNoteAllowlist` ([#2900](https://github.com/0xMiden/protocol/pull/2900)).
- Added `PswapAttachment` scheme and `PswapNote::payback_note` / `remainder_note` discovery helpers so creators can reconstruct private paybacks from on-chain commitments ([#2909](https://github.com/0xMiden/protocol/pull/2909)).
- Added benchmark for ECDSA signed transaction ([#2967](https://github.com/0xMiden/protocol/pull/2967)).
- [agglayer] Added `bridge_config::deregister_faucet` MASM procedure, `DEREGISTER_AGG_BRIDGE` note script, and `DeregisterAggBridgeNote` Rust builder, enabling the bridge admin to revoke a faucet's authorization by clearing its `faucet_registry_map`, `token_registry_map`, and `faucet_metadata_map` entries. The token-registry key is recomputed from the faucet's stored metadata so the cleared key always matches what registration wrote, rather than trusting note-supplied values. `bridge_in::claim` now also re-checks `assert_faucet_registered` after the token lookup, making deregistration an unconditional revocation even if a stale token-registry key survives a re-registration ([#2838](https://github.com/0xMiden/protocol/pull/2838)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too verbose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agglayer PRs or issues related to AggLayer bridging integration pr-from-maintainers PRs that come from internal contributors or integration partners. They should be given priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AggLayer: Add faucet deregistration

3 participants