Signature Replay

Security Updated Jul 2026

What is Signature Replay?

Signature replay is a smart-contract vulnerability in which a valid cryptographic signature is accepted more than once (or on a chain it was never intended for) because the verifying contract fails to track that the signature has already been used. Because a digital signature is stateless — it just proves “this private key approved this message” — the contract itself must record that the signature was consumed. If it doesn’t, an attacker can resubmit the same signature to trigger the same action repeatedly: claiming an airdrop ten times, withdrawing funds multiple times, or re-approving a transfer.

This class of bug is one of the most common and expensive in smart-contract security. It is the on-chain cousin of a classic network replay attack, but the “replay” happens inside a single contract’s logic rather than across two chains. Combined with EIP-712 typed signatures and gasless “permit” flows, signature-replay bugs have drained millions of dollars from real protocols.

How Signature Replay Works / Technical Details

Many modern contracts let users authorize an action with a signature instead of an on-chain transaction (cheaper, gasless UX). A typical flow:

  1. User signs an off-chain message: “I allow the contract to transfer 100 USDC from me”
  2. Anyone (often a relayer) submits that signature to the contract
  3. The contract calls ecrecover to verify the signature came from the user
  4. The contract performs the action

The problem: ecrecover only checks authenticity, not freshness. The same signature will verify as “authentic” forever. Without additional guards, step 4 can be repeated.

The Four Guards a Signature Must Carry

A robust signature-based action must bind the signature to four things, or it is replayable:

GuardPreventsHow
NonceSame action twice on this chainContract increments a per-user counter; signature must include it
Chain IDReplay on other EVM chainsInclude block.chainid (or EIP-712 domain chainId) in the signed data
Contract addressReplay on a cloned deploymentInclude the verifying contract address in the signed message
ExpiryReplay far in the futureInclude a deadline timestamp

Drop any one of these and you open a replay window.

Example Vulnerable Pattern

// VULNERABLE: no nonce, no chainId, no expiry
function claim(bytes calldata sig) external {
    address signer = recoverClaimSignature(sig);
    require(signer != address(0), "bad sig");
    // ❌ nothing records that this sig was used
    token.transfer(signer, REWARD);
}

An attacker who obtains one valid sig can call claim in a loop until the contract is empty. The fix is a mapping(bytes32 => bool) used keyed on a hash of the signature (plus nonce/chainId/deadline) that flips to true on first use and rejects duplicates.

Notable Examples and Attack Vectors

The “Missing Nonce” Airdrop Drains

Multiple airdrop-claim contracts have been drained because the claim signature carried no nonce. A single valid claim signature could be replayed thousands of times, minting or transferring far more tokens than intended. The pattern is so common that auditors treat “signature without used-flag” as a default critical finding.

Cross-Chain Replay of Permit Signatures

EIP-2612 permit signatures and EIP-712 typed-data signatures must include the chain ID in their domain separator. Contracts that deployed the same bytecode on multiple chains without setting a chain-specific domain separator allowed a permit signed on mainnet to be replayed on an L2 (or vice versa), granting approvals the user never intended on the second chain.

The 2022 “Authorized Mint” Replay

Several NFT and token contracts let an admin “authorize” minting with an off-chain signature. When the contract failed to bind the signature to a specific token ID or a single-use nonce, a single admin signature authorized unlimited mints, which attackers exploited to mint large supplies.

How to Prevent Signature Replay

1. Always Mark Signatures as Used

Keep a mapping(bytes32 => bool) of consumed signature hashes. On every verification, require it is false, then set it to true. This is the single most important defense.

2. Use EIP-712 Typed Data with a Full Domain

EIP-712’s EIP712Domain should include name, version, chainId, and verifyingContract. This binds each signature to one contract on one chain, killing cross-chain replay.

3. Include a Per-User Nonce and Deadline

Sign each message with a strictly-increasing nonce and an expiry timestamp. The contract checks both, so a signature cannot be reused or submitted after it has expired.

4. Audit the Full Verification Path

Watch for partial-overlap replay: if signatures cover (amount, token) but not (recipient), an attacker might replay with a different recipient. Hash every variable that should be fixed.

5. Reuse Battle-Tested Libraries

OpenZeppelin’s EIP712, Nonces, and SafeERC20 implement these patterns correctly. Don’t hand-roll signature verification.

Frequently Asked Questions

Q: Isn’t ecrecover enough to verify a signature? A: ecrecover only tells you who signed, not when or whether it was already used. You must add your own nonce, expiry, and used-flag logic on top of it.

Q: Does EIP-155 protect smart-contract signatures? A: No. EIP-155 protects raw transactions by binding the chain ID into the transaction signature. Off-chain signatures passed into contracts must include the chain ID separately (via EIP-712 domain) — otherwise they remain cross-chain replayable.

Q: How is signature replay different from reentrancy? A: Reentrancy exploits a contract calling out before updating state; signature replay exploits reusing a valid signature because state was never recorded. Both are state-management bugs, but the triggers differ.