AA Signature Audit
Detects signature vulnerabilities in account abstraction implementations including missing chain ID, replay across accounts, and malleability.
AA Signature Audit
Overview
The AA signature audit detector examines signature verification in ERC-4337 validateUserOp implementations for vulnerabilities specific to account abstraction contexts. These include missing chain ID binding, cross-account replay (where a signature valid for one smart account works on another with the same owner), and improper handling of the userOpHash provided by the EntryPoint.
Why This Is an Issue
Smart accounts validate user operations via signatures. Unlike EOA transactions where the protocol enforces chain ID and nonce, smart accounts must explicitly include these in their signature scheme. Common issues:
- Re-deriving the hash: Computing a new hash from UserOp fields instead of using the EntryPoint-provided
userOpHash(which includes EntryPoint address and chain ID). - Cross-account replay: Signature valid for account A also valid for account B if both share an owner and the signature does not bind to the account address.
- Missing nonce validation: The EntryPoint manages nonces, but custom validation may bypass this.
How to Resolve
// Before: Re-derives hash without chain ID
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: Use EntryPoint-provided hash
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;
}
Examples
Sample Sigvex Output
{
"detector_id": "aa-signature-audit",
"severity": "medium",
"confidence": 0.74,
"description": "validateUserOp() computes its own hash at offset 0x3c instead of using the provided userOpHash parameter. The custom hash may lack chain ID binding, enabling cross-chain signature replay.",
"location": { "function": "validateUserOp()", "offset": 60 }
}
Detection Methodology
- Hash usage tracking: Checks whether the
userOpHashparameter (second argument) is passed to the signature recovery function. - Custom hash detection: Identifies keccak256 operations within validation that re-derive the hash from UserOp fields.
- Chain ID inclusion: Verifies that chain ID is included in any custom hash derivation.
- Malleability check: Ensures signature recovery uses a library that rejects malleable signatures (s-value in upper half).
Limitations
- Cannot determine whether a custom hash scheme is intentionally different (e.g., multi-chain accounts that deliberately omit chain ID).
- EIP-712 typed data signatures with proper domain separator are recognized but complex nested structures may not be fully parsed.
Related Detectors
- Account Abstraction — general ERC-4337 issues
- AA Storage Access — validation phase violations
- Signature Replay — general signature replay
- Signature Malleability — ECDSA malleability