Skip to main content
Sigvex

AA Signature Audit Remediation

Bind smart-account signatures to the EntryPoint-provided userOpHash so they cannot be replayed across chains or accounts.

AA Signature Audit Remediation

Overview

ERC-4337 smart accounts validate user operations with signatures. Unlike EOA transactions, the protocol does not enforce chain ID and account binding for you, so a signature scheme that re-derives its own hash can be replayed across chains or across accounts that share an owner. The fix is to verify against the userOpHash the EntryPoint already provides, which commits to the EntryPoint address, chain ID, and every operation field.

Related Detector: AA Signature Audit

Before (Vulnerable)

function validateUserOp(PackedUserOperation calldata op, bytes32 userOpHash, uint256 funds)
    external returns (uint256)
{
    bytes32 hash = keccak256(abi.encode(op.sender, op.nonce, op.callData));
    address signer = ECDSA.recover(hash, op.signature); // Missing chain ID!
    return signer == owner ? 0 : 1;
}

After (Fixed)

function validateUserOp(PackedUserOperation calldata op, bytes32 userOpHash, uint256 funds)
    external returns (uint256)
{
    // userOpHash already includes EntryPoint address, chain ID, and all op fields
    address signer = ECDSA.recover(
        MessageHashUtils.toEthSignedMessageHash(userOpHash),
        op.signature
    );
    return signer == owner ? 0 : 1;
}

The userOpHash is computed by the EntryPoint as a commitment over the operation, the EntryPoint address, and the chain ID. Recovering against it ties each signature to one operation on one account on one chain, so it cannot be replayed elsewhere.

Alternative Mitigations

  • Use EIP-712 typed data with a domain separator that includes chainId and verifyingContract (the account address) when a custom signing scheme is required.
  • Recover with a library that rejects malleable signatures (rejecting s values in the upper half of the curve order), as OpenZeppelin’s ECDSA does.
  • For deliberately multi-chain accounts, document the cross-chain replay surface and require an explicit per-chain nonce in the signed payload.

Common Mistakes

  • Re-deriving the hash from a subset of operation fields, dropping the chain ID and account binding.
  • Using raw ecrecover without checking the return value for address(0) or guarding against malleability.
  • Assuming the EntryPoint-managed nonce protects a custom validation path that never reads it.

References