Reproduced Exploit
Etherspot ResourceLockValidator — replayable signature proof
The validator checks a ResourceLock Merkle proof but never consumes it. EntryPoint can therefore submit the same call data with a different nonce, and the wallet executes the signed action repeatedly.
Chain
Other
Category
bridge
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: 61409-c-07-in-resourcelockvalidator-the-validateuserop-function-is. Standalone Foundry PoC and full write-up: 61409-c-07-resource-lock-validator-signature-proof-replay_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/bridge/replay · vuln/auth/signature-replay
Reproduction: local synthetic Foundry reduction; the complete passing trace is in output.txt.
Key info#
| Field | Value |
|---|---|
| Loss | The same resource-lock call executes twice; no live funds are moved in this synthetic. |
| Vulnerable contract | ResourceLockValidator.validate (reduced in test/61409-c-07-resource-lock-validator-signature-proof-replay.sol) |
| Attacker EOA | 0x1111111111111111111111111111111111111111 (configured runner caller) |
| Attack contract | Exploit |
| Attack tx | Local Foundry Exploit.run() |
| Chain · block · date | Ethereum model · block 0 · synthetic |
| Compiler | Solidity ^0.8.24 |
| Bug class | Replayable proof / missing nonce or consumed-proof state |
TL;DR#
The validator checks a ResourceLock Merkle proof but never consumes it. EntryPoint can therefore submit the same call data with a different nonce, and the wallet executes the signed action repeatedly.
Background#
ResourceLock authorization is intentionally narrower than ordinary account nonce validation. It must bind a proof to a one-time call (or maintain a nonce/consumed set) before returning validation success.
The vulnerable code#
The synthetic preserves the audited operation with an @> VULN marker:
function validate(bytes32 proof, uint256 /*nonce*/) external returns (bool) {
validations++;
// @> VULN: proof is accepted without recording it as consumed.
return proof != bytes32(0);
}
Full source: test/61409-c-07-resource-lock-validator-signature-proof-replay.sol.
Root cause#
The validator treats the EntryPoint nonce as sufficient even though the signed hash is reconstructed from ResourceLock call data only. No proof hash is stored as consumed, so a fresh nonce does not create a fresh authorization.
Preconditions#
- A wallet has installed the ResourceLock validator and a valid signed proof exists.
- The EntryPoint accepts a subsequent nonce for the same call data.
- The validator is reached without a one-time proof/nonce check.
Attack walkthrough#
Exploit.run()validatesPROOFwith nonce0and executes the wallet.- It submits the identical
PROOFwith nonce1;validatereturns true again. - The wallet execution counter reaches
2, proving replay. The passing assertion is recorded at output.txt:355.
Diagrams#
Remediation#
Hash the nonce into the signed ResourceLock authorization and reject a proof hash already present in a consumed mapping. Mark it consumed atomically before executing the call.
How to reproduce#
cd evm-hack-registry/61409-c-07-resource-lock-validator-signature-proof-replay_exp
forge test -vvvvv
Sources#
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 61409-c-07-resource-lock-validator-signature-proof-replay_exp (evm-hack-registry mirror).
- AuditVault finding: 61409-c-07-in-resourcelockvalidator-the-validateuserop-function-is.
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.