Skip to content
Request an audit

Static signatures bound to caller revert under ERC-4337, causing DoS

TitleStatic signatures bound to caller revert under ERC-4337, causing DoS
Reward$1416, 3 Dups
ContestSequence - 7 October 2025 on Code4rena
AuthorAgontuk
ContextERC4337

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.

solidity
// 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.

Full Report
Codebase

Message on Telegram All 62 posts