Off-chain signatures are the backbone of modern Web3 UX, enabling gasless meta-transactions, permit-style token approvals, and centralized authority verification.
However, implementing cryptography in Solidity is notoriously difficult. "Rolling your own" signature verification using the raw ecrecover precompile is a common path to critical vulnerabilities.
1. The "Zero Address" Bug
The most insidious failure mode of the built-in ecrecover function is how it handles invalid signatures. If the signature is malformed or incorrect, ecrecover does not revert. Instead, it silently returns address(0).
A naive implementation looks like this:
// VULNERABLE CODE
function claim(bytes memory signature) public {
address signer = ecrecover(hash, v, r, s);
require(signer == authorizedSigner, "Invalid signature");
// ... grant reward
}If the developer accidentally left the authorizedSigner state variable uninitialized (meaning its value is address(0)), then anyinvalid signature will result in ecrecover returning address(0), satisfying the condition signer == authorizedSigner. The attack succeeds.
Solution: Always check that the result of ecrecover is not the zero address before comparing it to the target signer.
2. Signature Replay Attacks
A valid signature is just a mathematical proof of intent. Without context, that proof is valid forever and anywhere.
If Alice signs a message "Pay Bob 1 ETH" for Contract A, Bob might take that same signature and submit it to Contract B, or submit it to Contract A a second time.
Solution: Every signed message must include:
- A Nonce: A unique number used only once per user to prevent re-submission.
- The Chain ID: To prevent replay across different networks (e.g., Ethereum Mainnet vs. L2s).
- Contract Address: To tie the signature to a specific application context.
3. Signature Malleability
Elliptic Curve cryptography has a quirk: for any valid signature (v, r, s), there exists another mathematically valid signature (v', r, s') that resolves to the exact same public key.
An attacker can take a valid signature sent by a user, modify the s value mathematically, and submit a "different" looking signature that the contract accepts as valid. If your contract uses the signature hash itself as a unique identifier (e.g., to prevent replays), this malleability breaks your logic.
Conclusion: Never use raw ecrecover. Use battle-tested libraries like OpenZeppelin's ECDSA.sol, which handle malleability checks and zero-address failures automatically.