Digital signatures are the backbone of blockchain authentication. But when signature verification is implemented incorrectly, attackers can replay valid signatures — reusing them on different chains, different contracts, or for different actions than intended. Wintermute lost $160M partly due to a vanity address generation flaw. Signature replay has affected countless airdrop contracts, bridges, and meta-transaction systems.
How Signature Replay Works
A valid signature proves that a specific private key approved a specific message. Replay attacks exploit situations where the contract doesn't bind the signature to a specific context — allowing the same signature to be used multiple times or in different contexts.
// VULNERABLE: No nonce, no chain binding
function claimAirdrop(uint256 amount, bytes memory signature) external {
bytes32 hash = keccak256(abi.encodePacked(msg.sender, amount));
address signer = ECDSA.recover(hash, signature);
require(signer == admin, "Invalid signature");
token.transfer(msg.sender, amount);
// Attacker can replay this signature indefinitely!
}
Replay Attack Variants
1. Same-Contract Replay
The signature lacks a nonce, so it can be submitted multiple times to the same contract.
2. Cross-Chain Replay
After Ethereum's merge or hard forks, signatures valid on one chain may work on the other if chainId isn't included in the signed message.
3. Cross-Contract Replay
A signature meant for Contract A works on Contract B because the contract address isn't part of the signed message.
4. Meta-Transaction Replay
In gasless transaction systems (ERC-2771), improper nonce management allows replaying relayed transactions.
Prevention: EIP-712 Typed Structured Data
// SECURE: Full domain separation with EIP-712
bytes32 constant DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
bytes32 constant CLAIM_TYPEHASH = keccak256(
"Claim(address recipient,uint256 amount,uint256 nonce,uint256 deadline)"
);
mapping(address => uint256) public nonces;
function claimAirdrop(
uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s
) external {
require(block.timestamp <= deadline, "Expired");
uint256 nonce = nonces[msg.sender]++;
bytes32 structHash = keccak256(abi.encode(
CLAIM_TYPEHASH, msg.sender, amount, nonce, deadline
));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash));
address signer = ecrecover(digest, v, r, s);
require(signer == admin, "Invalid");
token.transfer(msg.sender, amount);
}
Solana: Ed25519 Signature Verification
On Solana, use the Ed25519 precompile with proper instruction introspection. Always include the program ID and a nonce in signed messages to prevent cross-program replay.
- ✅ Use EIP-712 typed structured data signing
- ✅ Include
chainIdand contract address in domain separator - ✅ Implement incrementing nonces per signer
- ✅ Add expiration deadlines to signatures
- ✅ On Solana, verify program ID in signed message
How Vultbase Detects Signature Vulnerabilities
- Pattern DB — 15 signature replay patterns including cross-chain, nonce-less, and EIP-712 misimplementation
- Static Analysis — Flags missing nonce checks, absent chain ID binding, and raw ecrecover without domain separation
A single replayed signature can drain an entire contract. Get your signature verification audited before deployment.