Signature Malleability
Detects ECDSA signature verification that fails to check s-value bounds, v-value validity, or zero-address returns from ecrecover.
Signature Malleability
Overview
The signature malleability detector identifies insecure use of the ecrecover precompile. Every ECDSA signature has two valid s values (one in the lower half and one in the upper half of the secp256k1 curve order). Without enforcing that s falls in the lower half, an attacker can take any valid signature and produce a second valid signature for the same message — without knowing the private key.
This detector checks for four common issues: missing zero-address check on ecrecover return, missing s-value upper bound validation, missing v parameter validation (must be 27 or 28), and missing chain ID in EIP-712 domain separators.
Why This Is an Issue
If a contract uses signatures for authorization (permit, meta-transactions, gasless relays) and the same signature can be replayed in a modified form, an attacker can bypass replay protection. Nonce-based schemes that mark a signature hash as “used” can be circumvented because the malleable signature has a different hash but recovers to the same signer.
How to Resolve
// Before: Vulnerable — no s-value or zero-address check
function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address) {
return ecrecover(hash, v, r, s);
}
// After: Fixed — validate all signature components
function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal view returns (address) {
require(v == 27 || v == 28, "Invalid v value");
require(
uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
"Invalid s value"
);
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "Invalid signature");
return signer;
}
The simplest approach is to use OpenZeppelin’s ECDSA.recover(), which handles all these checks.
Detection Methodology
- Ecrecover call detection: Identifies calls to the ecrecover precompile (address 0x01) via STATICCALL or CALL.
- Zero-address check: Verifies that the return value is compared against
address(0)before use. - S-value validation: Checks for comparison of the
sparameter againstsecp256k1n/2(the curve half-order constant0x7FFF...20A0). - V-value validation: Checks that
vis validated to be 27 or 28.
Limitations
False positives: Contracts that use OpenZeppelin’s ECDSA library may still show raw ecrecover calls in bytecode if the library was inlined, but the checks are present in surrounding code. False negatives: Custom signature schemes that do not use the ecrecover precompile directly are not analyzed.
Related Detectors
- Signature Replay — detects missing nonce/deadline replay protection
- Access Control — detects missing authorization checks