# Unsafe ecrecover

**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.

## What it does

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`.

## Why is this bad?

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.

## Example

### Bad

```solidity
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) pure returns (address) {
    return ecrecover(hash, v, r, s);
}
```

### Good

```solidity
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);
}
```
