Medium severity
ecrecover zero-address (unchecked signature recovery)
The address returned by ecrecover is used without checking that it is non-zero. ecrecover returns address(0) for an invalid signature instead of reverting. If the recovered address is compared with a signer, owner or mapping entry that is unset or can be zero (an uninitialised signer, a deleted role, an unregistered user), an arbitrary invalid signature passes verification. The raw precompile also accepts malleable (high-s) signatures.
Vulnerable pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Voucher {
address public signer; // may be left unset (address(0))
mapping(address => uint256) public nonces;
mapping(address => uint256) public credits;
function setSigner(address s) external {
require(signer == address(0), "set");
signer = s;
}
function redeem(uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
bytes32 digest = keccak256(abi.encode(msg.sender, amount, nonces[msg.sender]++, block.chainid, address(this)));
address recovered = ecrecover(digest, v, r, s);
require(recovered == signer, "bad sig");
credits[msg.sender] += amount;
}
}The fix
Use OpenZeppelin ECDSA.recover (which rejects zero addresses and malleable signatures), or explicitly require(recovered != address(0)).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Voucher {
address public signer;
mapping(address => uint256) public nonces;
mapping(address => uint256) public credits;
function setSigner(address s) external {
require(signer == address(0), "set");
signer = s;
}
function redeem(uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
bytes32 digest = keccak256(abi.encode(msg.sender, amount, nonces[msg.sender]++, block.chainid, address(this)));
address recovered = ecrecover(digest, v, r, s);
require(recovered != address(0), "invalid sig");
require(recovered == signer, "bad sig");
credits[msg.sender] += amount;
}
}Scan your contract for this
pactlint flags ecrecover-zero-address and other recurring DeFi bug classes in Solidity
source or a verified contract on Ethereum or Base. Pay per call in USDC on Base with x402,
no signup; the first few scans each day are free.
This detector is open source (MIT): see ecrecover-zero-address in tanod-labs/slither-detectors, or run it in CI with the pactlint GitHub Action. Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.