Static signatures bound to caller revert under ERC-4337, causing DoS
| Title | Static signatures bound to caller revert under ERC-4337, causing DoS |
|---|---|
| Reward | $1416, 3 Dups |
| Contest | Sequence - 7 October 2025 on Code4rena |
| Author | Agontuk |
| Context | ERC4337 |
Sequence wallets support ERC-4337 by validating signatures inside validateUserOp() in ERC4337v07. The function enforces that msg.sender is the entry point, then verifies the signature through an external self-call: this.isValidSignature(userOpHash, userOp.signature).
Because of that external call, the msg.sender seen inside isValidSignature(), implemented in BaseAuth, is the wallet itself rather than the entry point. The static signature validation in BaseAuth.signatureValidation(), which enforces a caller binding, therefore fails whenever a non-zero expected caller is set to the entry point.
// ERC4337v07
function validateUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external returns (uint256 validationData) {
if (entrypoint == address(0)) {
revert ERC4337Disabled();
}
if (msg.sender != entrypoint) {
revert InvalidEntryPoint(msg.sender);
}
// userOp.nonce is validated by the entrypoint
if (missingAccountFunds != 0) {
IEntryPoint(entrypoint).depositTo{ value: missingAccountFunds }(address(this));
}
if (this.isValidSignature(userOpHash, userOp.signature) != IERC1271_MAGIC_VALUE_HASH) { //@audit this will cause the msg.sender in isValidSignature to be ERC4337v07
return SIG_VALIDATION_FAILED;
}
return 0;
}
// BaseAuth.sol
function isValidSignature(bytes32 _hash, bytes calldata _signature) external view returns (bytes4) {
Payload.Decoded memory payload = Payload.fromDigest(_hash);
(bool isValid,) = signatureValidation(payload, _signature);
if (!isValid) {
return bytes4(0);
}
return IERC1271_MAGIC_VALUE_HASH;
}
function signatureValidation(
Payload.Decoded memory _payload,
bytes calldata _signature
) internal view virtual returns (bool isValid, bytes32 opHash) {
// Read first bit to determine if static signature is used
bytes1 signatureFlag = _signature[0];
if (signatureFlag & 0x80 == 0x80) {
opHash = _payload.hash();
(address addr, uint256 timestamp) = _getStaticSignature(opHash);
if (timestamp <= block.timestamp) {
revert InvalidStaticSignatureExpired(opHash, timestamp);
}
if (addr != address(0) && addr != msg.sender) {
revert InvalidStaticSignatureWrongCaller(opHash, msg.sender, addr);
}
return (true, opHash);
}
Alpha: calling through this makes msg.sender become address(this) — check that this is intended.
Conclusion
This finding would earn you $1416. Verify that msg.sender is the one the code expects.