crates/lint/docs/ecrecover.md
Severity: Med
ID: ecrecover
Flags direct calls to Solidity's ecrecover builtin when the signature's s value is not proven
to be in the canonical lower half of the secp256k1 curve order.
The lint follows simple local aliases and recognizes low-s bounds established by require,
assert, conditionals, and early-return or revert branches. It accepts the canonical maximum
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, stricter bounds, reversed
comparisons, and the equivalent strict comparison against that value plus one. Facts are
invalidated by reassignment, loop-carried writes, and calls that may change mutable state. They are
retained only when they hold on every path reaching the call.
Checks on v, the recovered address, nonces, domain separators, or the top bit of an EIP-2098
signature do not prove that s is canonical and do not suppress the warning.
The analysis is intentionally local. A low-s guard may be established before the call or after
assigning the recovered address to a local, provided it dominates the address's first observable
use. The analysis does not summarize internal helpers or modifiers, recognize bound signature
schemes that fix one recovery ID, or inspect Yul and low-level calls to precompile address 0x01.
For each valid high-s ECDSA signature, an attacker can derive a second signature for the same
message and signer by replacing s with its complement in the curve order and flipping v.
Contracts that use the signature bytes as a unique identifier can therefore have replay or
double-use protections bypassed. The ecrecover precompile does not enforce the low-s rule that
Ethereum transactions enforce.
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) pure returns (address) {
return ecrecover(hash, v, r, s);
}
uint256 constant HALF_ORDER =
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) pure returns (address) {
require(uint256(s) <= HALF_ORDER, "invalid signature s");
return ecrecover(hash, v, r, s);
}