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
Open
fix: F-2026-18829 | [Dual Defense] UEA_SVM Hardcodes Ed25519 Verifier 0x…00ca After Runtime Moved to 0xEC…01#3200xNilesh wants to merge 3 commits into
0xNilesh wants to merge 3 commits into
Conversation
… 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.
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.
Finding
The Ed25519 signature-verifier precompile moved from the legacy
0x00000000000000000000000000000000000000cato0xEC00000000000000000000000000000000000001. The node registers only the new address:app/app.goinstantiatesusigverifierprecompile.NewPrecompile()(precompiles/usigverifier,USigVerifierPrecompileAddress = 0xEC…01) and puts it into the static-precompile map handed toEVMKeeper.WithStaticPrecompiles.app/upgrades/ai-audit-fixes-2'sregisterPrecompileV2appends0xEC…01toActiveStaticPrecompileson chains that took that upgrade.Nothing is bound to
0x…00caanywhere in the node. But every genesis setup script still declared0x…00caactive and omitted0xEC…01entirely.ActiveStaticPrecompilesis what gates callability (Keeper.IsAvailableStaticPrecompile), so on a fresh genesis this produced two distinct failures:0x…00cawas declared but unimplemented.Keeper.GetStaticPrecompileInstancetreats an address that is active in params but absent from the in-memory precompile map as memory corruption andpanics withprecompiled contract not stored in memory: 0x…00ca. baseapp's recover contains it — the node survives and the tx fails — but every call to0x…00caaborts.0xEC…01was 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…00cais removed, not re-registered.Changes
Genesis lists — dropped
0x…00ca, appended0xEC…01. The address is appended (rather than substituted in place) becausex/vm'sValidatePrecompilesrequires the list to be sorted and0xEC…sorts after every0x00…entry; the rest of each list is byte-identical.scripts/test_node.shlocal-native/scripts/setup-genesis-auto.shlocal-multi-validator/scripts/setup-genesis-auto.shtestnet/core/setup/setup_genesis_validator.shUpgrade handler —
app/upgrades/usigverifier-precompile-fix, registered inapp/upgrades.go. The scripts only fix new genesis; an already-running chain keeps0x…00cain its EVM params. The handler followsapp/upgrades/remove-utxverifier'sderegisterUtxHashVerifierPrecompileprecedent: readevmParams.ActiveStaticPrecompiles, filter the legacy address out, add0xEC…01if missing (mirroringai-audit-fixes-2'sregisterPrecompileV2),SetParams, log, and no-op when already in sync. Idempotent, and the current address is taken fromprecompiles/usigverifierrather than re-typed.Docs —
x/uexecutor/README.mdsaid the Ed25519 precompile lives at0x00…00ca; corrected to0xEC…01.Dead fixture —
test/utils/bytecode.go'sUEA_SVM_BYTECODEwas referenced by nothing in the repo and still embedded the old target (60ca5afa—PUSH1 0xca; STATICCALL— and selectorbbdd8207). Removed; nothing else in the tree referenced it.Tests
app/upgrades/usigverifier-precompile-fix/upgrade_test.go:active_static_precompilesline contains0xEC…01and does not contain0x…00caFollow-ups (deliberately out of scope here)
0x00000000000000000000000000000000000000CB(utxhashverifier) is the same defect.app/upgrades/remove-utxverifierderegisters it from live chains and theutxverifiermodule store is deleted, but all four genesis lists still declare it active — and there is noutxhashverifierimplementation anywhere inprecompiles/. 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.0x0000000000000000000000000000000000000803(vesting) — confirmed, same defect. It is declared active in all four genesis lists and is part of the evm fork'stypes.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) andapp/precompiles.go'sNewAvailableStaticPrecompilesnever registers it. So0x803is active-but-unimplemented →precompiled contract not stored in memoryon any call. This matches Hacken #438 / RC-09.Bonus, opposite direction:
0x0000000000000000000000000000000000000806(slashing) is registered but never activated.app/precompiles.gobuilds and registersslashingPrecompile, but0x806appears 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:42still haswhich must become
0xEC00000000000000000000000000000000000001, followed by aregisterUEAfor the new implementation. That change is not in this PR.Note that this is not self-healing for existing accounts:
UEAProxysnapshots its implementation address intoUEA_LOGIC_SLOTatinitializeUEAtime and_implementation()reads that slot on every delegatecall. Already-deployed SVM UEA proxies therefore keep delegating to the oldUEA_SVMimplementation even afterregisterUEApoints the factory at a new one, and need migrating through the module-sponsored migration path.