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

A minimal contract with the bug
// 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)).

The same contract, corrected
// 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.

How to scan →

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.