Skip to main content
Sigvex

Signature Verification Remediation

Verify ECDSA signatures with EIP-712 typed data, a domain separator, nonce, and a strict signer check to prevent forgery and replay.

Signature Verification Remediation

Overview

Signatures authorize meta-transactions, permits, relays, and governance votes. Verification fails when the contract does not compare the recovered signer to the expected address, builds the hash with abi.encodePacked over dynamic types (which collide), or omits chain and contract binding so a signature replays elsewhere. The fix is EIP-712 typed-data hashing with a domain separator, a nonce, and an explicit signer comparison.

Related Detector: Signature Verification

Before (Vulnerable)

function execute(address to, uint256 amount, bytes memory sig) external {
    bytes32 hash = keccak256(abi.encodePacked(to, amount));
    address signer = ECDSA.recover(hash, sig);
    require(signer == owner, "Not owner");
    _transfer(to, amount);
}

After (Fixed)

bytes32 constant EXECUTE_TYPEHASH =
    keccak256("Execute(address to,uint256 amount,uint256 nonce)");

function execute(address to, uint256 amount, uint256 nonce, bytes memory sig)
    external
{
    require(!usedNonces[nonce], "Nonce used");
    usedNonces[nonce] = true;
    bytes32 structHash = keccak256(abi.encode(EXECUTE_TYPEHASH, to, amount, nonce));
    bytes32 digest = _hashTypedDataV4(structHash);
    address signer = ECDSA.recover(digest, sig);
    require(signer == owner, "Not owner");
    _transfer(to, amount);
}

abi.encode with a typehash gives each field a fixed slot, removing the collision risk of packed encoding. _hashTypedDataV4 binds the digest to the contract’s domain separator (chain ID and contract address), and the nonce makes each signature single-use.

Alternative Mitigations

  • Verify smart-contract signers with EIP-1271 isValidSignature so account-abstraction wallets are supported alongside EOAs.
  • Add an explicit deadline field to the signed struct so authorizations expire.
  • Reject malleable signatures by enforcing the low-half s value and a canonical v, which a vetted ECDSA library does for you.

Common Mistakes

  • Using the recovered address without comparing it to an expected signer; ecrecover returns address(0) for malformed input, so a missing check can accept the zero address.
  • Reusing the same struct hash across functions, letting a signature for one action authorize another.
  • Setting the nonce after the external call, opening a reentrancy window before it is marked used.
  • Computing the domain separator once in the constructor and never accounting for a chain fork, where the cached chain ID goes stale.

References