Skip to content

fix: F-2026-18829 | [Dual Defense] UEA_SVM Hardcodes Ed25519 Verifier 0x…00ca After Runtime Moved to 0xEC…01 - #320

Open
0xNilesh wants to merge 3 commits into
audit-fixesfrom
F-2026-18829
Open

fix: F-2026-18829 | [Dual Defense] UEA_SVM Hardcodes Ed25519 Verifier 0x…00ca After Runtime Moved to 0xEC…01#320
0xNilesh wants to merge 3 commits into
audit-fixesfrom
F-2026-18829

Conversation

@0xNilesh

Copy link
Copy Markdown
Member

Finding

The Ed25519 signature-verifier precompile moved from the legacy 0x00000000000000000000000000000000000000ca to 0xEC00000000000000000000000000000000000001. The node registers only the new address:

  • app/app.go instantiates usigverifierprecompile.NewPrecompile() (precompiles/usigverifier, USigVerifierPrecompileAddress = 0xEC…01) and puts it into the static-precompile map handed to EVMKeeper.WithStaticPrecompiles.
  • app/upgrades/ai-audit-fixes-2's registerPrecompileV2 appends 0xEC…01 to ActiveStaticPrecompiles on chains that took that upgrade.

Nothing is bound to 0x…00ca anywhere in the node. But every genesis setup script still declared 0x…00ca active and omitted 0xEC…01 entirely. ActiveStaticPrecompiles is what gates callability (Keeper.IsAvailableStaticPrecompile), so on a fresh genesis this produced two distinct failures:

  1. 0x…00ca was declared but unimplemented. Keeper.GetStaticPrecompileInstance treats an address that is active in params but absent from the in-memory precompile map as memory corruption and panics with precompiled contract not stored in memory: 0x…00ca. baseapp's recover contains it — the node survives and the tx fails — but every call to 0x…00ca aborts.
  2. 0xEC…01 was not active at all, so the real verifier was unreachable and Ed25519 verification could not work on a fresh chain.

Per the decision on this finding, 0x…00ca is removed, not re-registered.

Changes

Genesis lists — dropped 0x…00ca, appended 0xEC…01. The address is appended (rather than substituted in place) because x/vm's ValidatePrecompiles requires the list to be sorted and 0xEC… sorts after every 0x00… entry; the rest of each list is byte-identical.

  • scripts/test_node.sh
  • local-native/scripts/setup-genesis-auto.sh
  • local-multi-validator/scripts/setup-genesis-auto.sh
  • testnet/core/setup/setup_genesis_validator.sh

Upgrade handlerapp/upgrades/usigverifier-precompile-fix, registered in app/upgrades.go. The scripts only fix new genesis; an already-running chain keeps 0x…00ca in its EVM params. The handler follows app/upgrades/remove-utxverifier's deregisterUtxHashVerifierPrecompile precedent: read evmParams.ActiveStaticPrecompiles, filter the legacy address out, add 0xEC…01 if missing (mirroring ai-audit-fixes-2's registerPrecompileV2), SetParams, log, and no-op when already in sync. Idempotent, and the current address is taken from precompiles/usigverifier rather than re-typed.

Needs scheduling. The handler has a name (usigverifier-precompile-fix) but no upgrade height yet. If the team prefers, the syncActiveStaticPrecompiles step can be folded into an existing pending upgrade instead of shipping as its own — it is a single self-contained call.

Docsx/uexecutor/README.md said the Ed25519 precompile lives at 0x00…00ca; corrected to 0xEC…01.

Dead fixturetest/utils/bytecode.go's UEA_SVM_BYTECODE was referenced by nothing in the repo and still embedded the old target (60ca5afaPUSH1 0xca; STATICCALL — and selector bbdd8207). Removed; nothing else in the tree referenced it.

Tests

app/upgrades/usigverifier-precompile-fix/upgrade_test.go:

  • legacy present / current absent → legacy removed, current added, every other entry preserved, list still sorted
  • idempotent: a second run reports nothing removed and nothing added, and returns an identical list
  • both missing → current added only
  • legacy present and current present → legacy removed, nothing added
  • legacy matched case-insensitively
  • a guard over the four genesis scripts asserting each active_static_precompiles line contains 0xEC…01 and does not contain 0x…00ca

Follow-ups (deliberately out of scope here)

  1. 0x00000000000000000000000000000000000000CB (utxhashverifier) is the same defect. app/upgrades/remove-utxverifier deregisters it from live chains and the utxverifier module store is deleted, but all four genesis lists still declare it active — and there is no utxhashverifier implementation anywhere in precompiles/. A fresh genesis therefore re-introduces a declared-but-unimplemented address on exactly the panic path described above. Left in place so this PR stays scoped to the Ed25519 verifier.

  2. 0x0000000000000000000000000000000000000803 (vesting) — confirmed, same defect. It is declared active in all four genesis lists and is part of the evm fork's types.AvailableStaticPrecompiles, but there is no vesting precompile package in the evm fork (precompiles/ has bank, bech32, callbacks, distribution, erc20, gov, ics20, p256, slashing, staking, werc20 — no vesting) and app/precompiles.go's NewAvailableStaticPrecompiles never registers it. So 0x803 is active-but-unimplemented → precompiled contract not stored in memory on any call. This matches Hacken #438 / RC-09.

  3. Bonus, opposite direction: 0x0000000000000000000000000000000000000806 (slashing) is registered but never activated. app/precompiles.go builds and registers slashingPrecompile, but 0x806 appears in none of the four genesis lists, so the slashing precompile is unreachable on a fresh chain. Not a panic — just a silently dead feature.

Contract half (tracked separately)

The durable fix is in push-chain-core-contracts: src/uea/UEA_SVM.sol:42 still has

address public constant VERIFIER_PRECOMPILE =
    0x00000000000000000000000000000000000000ca;

which must become 0xEC00000000000000000000000000000000000001, followed by a registerUEA for the new implementation. That change is not in this PR.

Note that this is not self-healing for existing accounts: UEAProxy snapshots its implementation address into UEA_LOGIC_SLOT at initializeUEA time and _implementation() reads that slot on every delegatecall. Already-deployed SVM UEA proxies therefore keep delegating to the old UEA_SVM implementation even after registerUEA points the factory at a new one, and need migrating through the module-sponsored migration path.

… 0x..00ca

Nothing is registered at 0x..00ca, so declaring it active panics the EVM
precompile lookup, and the real verifier was never activated.
Drops the legacy ed25519 verifier address from EVM ActiveStaticPrecompiles
and adds 0xEC..01 if missing, for chains already past genesis.
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