Skip to content

fix(agglayer): validate decoded AccountId in eth_address::to_account_id#3231

Draft
partylikeits1983 wants to merge 3 commits into
nextfrom
riemann-claude/fix-agglayer-account-id-validation
Draft

fix(agglayer): validate decoded AccountId in eth_address::to_account_id#3231
partylikeits1983 wants to merge 3 commits into
nextfrom
riemann-claude/fix-agglayer-account-id-validation

Conversation

@partylikeits1983

@partylikeits1983 partylikeits1983 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Note: discovered by OZ AI audit tool

The bridge-in claim flow decodes a deposit's destinationAddress into a Miden AccountId via eth_address::to_account_id and uses that id as the AccountId target of the emitted P2ID note.

The conversion only checked the byte layout (top 4 bytes zero, u32 limbs, packed words fit in the field) and never checked the AccountId structural invariants. As a result, an embedded address that decodes into a structurally invalid AccountId (for example, a non-zero suffix least-significant byte) was accepted and emitted as the output target, a note that no valid account could ever consume.

The Rust conversion (EthEmbeddedAccountId::try_from_eth_address) already validates the decoded id, so the on-chain MASM path was more permissive than the reference.

Fix

Validate the decoded id with account_id::validate inside to_account_id, matching the Rust contract and the transaction kernel's own usage. to_account_id has a single caller, so the conversion now always yields a well-formed AccountId.

Tests

Added tests for the non-embedded trap and the invalid-embedded rejection (the latter fails before the fix and passes after). All agglayer tests pass, including the full claim flow.

The bridge-in claim decodes a deposit leaf's destinationAddress into a Miden
AccountId via eth_address::to_account_id and routes bridged value to it (P2ID
for the native faucet, or a MINT note that later produces a P2ID). The
conversion only checked that the most-significant 4 bytes are zero, that the
limbs are u32, and that the packed words fit in the field - it never enforced
the AccountId structural invariants. An embedded destination that decodes into
a structurally invalid AccountId (e.g. a non-zero suffix least-significant
byte) was accepted and emitted as the output target, producing a P2ID/MINT
note that no valid account can consume and thus permanently locking the
bridged assets. The Rust reference conversion
(EthEmbeddedAccountId::try_from_eth_address) already rejects such payloads, so
the on-chain path was strictly more permissive.

Validate the decoded id with account_id::validate inside to_account_id,
matching the Rust contract and the kernel's own usage. Add PoC tests covering
the non-embedded trap and the invalid-embedded rejection, and load the
protocol/standards libraries in the standalone agglayer MASM test host so
agglayer procedures that call into them resolve at runtime.
@partylikeits1983 partylikeits1983 changed the title fix(agglayer): validate decoded AccountId in eth_address::to_account_id fix(agglayer): validate decoded AccountId in eth_address::to_account_id Jul 7, 2026
@partylikeits1983 partylikeits1983 requested a review from Fumuran July 8, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant