Reproduced Exploit
DynamicAccount accepts address(0) from ecrecover and pays out
ecrecover of a signature with r = 0, s = 0, v = 27 returns address(0). agents(0) is unset, so it is also address(0). _validateSignature returns success, and execute transfers 1000 USDC to the caller. The fixed account returns before the transfer when the recovered signer is address(0), and the 1000…
Loss
1000 USDC moved on a zero signature
Chain
Ethereum
Category
auth
Date
Jan 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 53328-h-10-validatesignature-does-not-properly-handle-address0-pa. Standalone Foundry PoC and full write-up: 53328-h-10-validatesignature-does-not-properly-handle-address0-pa_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/auth/signature-validation · vuln/access-control/missing-auth
Key info#
| Field | Value |
|---|---|
| Loss | 1000 USDC moved on a zero signature |
| Vulnerable contract | DynamicAccount._validateSignature |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2025-01 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | ecrecover zero address compared to an unset agent |
TL;DR#
ecrecover of a signature with r = 0, s = 0, v = 27 returns address(0). agents(0) is unset, so it is also address(0). _validateSignature returns success, and execute transfers 1000 USDC to the caller. The fixed account returns before the transfer when the recovered signer is address(0), and the 1000 stays in the account.
Background#
Pashov Audit Group reviewed Omo on 2025-01-25 (H-10, high). DynamicAccount._validateSignature recovers a signer with ECDSA.recover. The audited helper returns the precompile's address(0) on an invalid signature. The next check treats that zero address as the primary agent when no agent has been set.
The vulnerable code#
address signer = ECDSA.recover(hash, signature);
if (isAdmin(signer)) return 0;
// @> VULN: address(0) from ecrecover compares equal to the unset agents(0)
if (this.agents(0) == signer) {
return 0;
}
The report's fix rejects the zero address before the agent check:
address signer = ECDSA.recover(hash, userOp.signature);
+ if (signer == address(0)) return SIG_VALIDATION_FAILED;
if (isAdmin(signer)) return 0;
Root cause#
ecrecover returns address(0) when the signature is not a valid point. An unset agents(0) is the same value. Comparing them with == accepts every invalid signature whenever the account has not registered an agent. isAdmin(address(0)) is false, so the zero signer falls through to that comparison.
Preconditions#
- The account holds 1000 USDC.
agents(0)isaddress(0).- The caller supplies a 65-byte signature whose
randsare zero and whosevis 27.
Attack walkthrough#
- 1000 USDC is minted to the account.
- The caller invokes
executewith the zero signature, naming themselves as the recipient. ECDSA.recovercallsecrecoverand getsaddress(0).this.agents(0)isaddress(0), so the comparison succeeds and validation returns 0.executetransfers the 1000 USDC to the caller.
Diagrams#
Impact#
The caller receives 1000 USDC and the account balance is 0. No private key was used. On the fixed account the same signature is recognized as address(0) and the transfer is skipped, so the account still holds 1000 and the caller holds 0.
PoC#
cd 53328-h-10-validatesignature-does-not-properly-handle-address0-pa_exp
forge test -vv
test_exploit_zero_signature_moves_funds asserts the recovered signer is address(0), the caller received 1000, and the account is empty. test_control_rejects_address_zero asserts the fixed account pays nothing and keeps the 1000.
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/53328-h-10-validatesignature-does-not-properly-handle-address0-pa.md
- Pashov report (Omo, 2025-01-25): https://github.com/pashov/audits/blob/master/team/md/Omo-security-review_2025-01-25.md
- Quoted source:
DynamicAccount._validateSignatureas printed in that report. The client repository is not linked from the report; this file is the reduced synthetic.
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 53328-h-10-validatesignature-does-not-properly-handle-address0-pa_exp (evm-hack-registry mirror).
- AuditVault finding: 53328-h-10-validatesignature-does-not-properly-handle-address0-pa.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.