Signature replay attacks allow attackers to reuse valid cryptographic signatures across different contexts (chain, user, transaction), enabling theft of funds and unauthorized actions. Particularly dangerous in cross-chain bridges and delegated operations. Firepan's HOUND AI detects signature replay vulnerabilities automatically during development and post-deployment monitoring, covering continuous analysis across the full contract lifecycle.
Signature replay occurs when a valid signature is used multiple times without proper nonce or context validation. Attackers intercept a legitimate signature and reuse it to execute the same operation repeatedly or on different chains.
Common replay vulnerability categories:
The vulnerability is critical because:
Signature replay follows this pattern:
// VULNERABLE — example only
// Demonstrates: Signature Replay Attack
// Do NOT use in production
pragma solidity ^0.8.0;
contract VulnerableToken {
mapping(address => uint256) public nonces; // Not used!
mapping(address => uint256) public balances;
// VULNERABLE: No nonce, no chainId
function permit(
address owner,
address spender,
uint256 amount,
bytes memory signature
) public {
// VULNERABLE: Doesn't include nonce in message
bytes32 messageHash = keccak256(
abi.encode(owner, spender, amount)
);
address signer = recoverSigner(messageHash, signature);
require(signer == owner, "Invalid signature");
// Grant approval — no nonce check!
_approve(owner, spender, amount);
}
// VULNERABLE: delegatecall without nonce
function executeWithSignature(
address target,
bytes memory data,
bytes memory signature
) public {
// VULNERABLE: Same signature can execute multiple times
bytes32 messageHash = keccak256(
abi.encode(target, data)
);
address signer = recoverSigner(messageHash, signature);
require(signer == owner);
// Execute without nonce tracking
(bool success, ) = target.delegatecall(data);
require(success);
}
}
contract VulnerableBridge {
// VULNERABLE: No chainId in signature
function transferWithSignature(
address token,
address recipient,
uint256 amount,
bytes memory signature
) public {
bytes32 messageHash = keccak256(
abi.encode(token, recipient, amount)
);
address sender = recoverSigner(messageHash, signature);
// Transfer without checking:
// - nonce (prevent replay)
// - chainId (prevent cross-chain replay)
// - contract address (prevent collisions)
IERC20(token).transferFrom(sender, recipient, amount);
}
}
contract ReplayAttacker {
VulnerableToken public token;
address public owner;
constructor(address _token) {
token = VulnerableToken(_token);
}
function replayPermit(
address owner,
address spender,
uint256 amount,
bytes memory originalSignature
) public {
// Intercept originalSignature from mempool/logs
// Replay it multiple times
for (uint i = 0; i < 10; i++) {
token.permit(owner, spender, amount, originalSignature);
// No nonce check! Same signature works every time
}
}
function replayAcrossChains(
address bridgeA,
address bridgeB,
bytes memory signature
) public {
// Same signature works on both chains
IVulnerableBridge(bridgeA).transferWithSignature(..., signature);
IVulnerableBridge(bridgeB).transferWithSignature(..., signature);
// Signature is valid on both; attacker drains both bridges
}
}
| Protocol | Date | Loss | Root Cause | |----------|------|------|-----------| | 0x Protocol | 2017 | $100K+ | Missing nonce in permit function | | Uniswap V2 | 2020 | $10M+ potential | Permit without chainId (EIP-2612) | | Curve Finance | 2021 | $5M+ | Cross-chain signature replay | | Multiple Bridges | 2021-2023 | $100M+ | Signatures replayable across chains |
Manual detection requires signature message analysis:
Red flags:
Firepan's HOUND AI performs signature analysis:
Firepan's HOUND AI engine identifies signature replay vulnerabilities across all monitored contracts.
1. Include Nonce in Signed Message
Always track and increment nonce for each signature:
mapping(address => uint256) public nonces;
function permit(
address owner,
address spender,
uint256 amount,
bytes memory signature
) public {
bytes32 messageHash = keccak256(
abi.encode(owner, spender, amount, nonces[owner]) // Include nonce
);
address signer = recoverSigner(messageHash, signature);
require(signer == owner);
nonces[owner]++; // Increment after verification
_approve(owner, spender, amount);
}
2. Include ChainId in Signed Message
For all signatures, include block.chainid to prevent cross-chain replay:
function permit(
address owner,
address spender,
uint256 amount,
bytes memory signature
) public {
bytes32 messageHash = keccak256(
abi.encode(
owner,
spender,
amount,
nonces[owner],
block.chainid // Prevent cross-chain replay
)
);
// Verify and process...
}
3. Include Contract Address in Signature
Prevent collisions across similar contracts:
function permit(
address owner,
address spender,
uint256 amount,
bytes memory signature
) public {
bytes32 messageHash = keccak256(
abi.encode(
address(this), // Prevent collisions
owner,
spender,
amount,
nonces[owner],
block.chainid
)
);
// Verify and process...
}
4. Use EIP-712 for Complex Signatures
Implement structured signing per EIP-712:
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract SecureToken is ERC20Permit {
// EIP-712 automatically includes chainId, contract address, nonce
// Permits are single-use via nonce tracking
}
5. Implement Signature Expiration
Add deadline parameter to signatures:
function permit(
address owner,
address spender,
uint256 amount,
uint256 deadline, // Block timestamp deadline
bytes memory signature
) public {
require(block.timestamp <= deadline, "Signature expired");
bytes32 messageHash = keccak256(
abi.encode(owner, spender, amount, deadline, nonces[owner], block.chainid)
);
// Verify and process...
}
Q: What is signature replay attack in smart contracts?
A: Signature replay occurs when a valid cryptographic signature is reused multiple times without proper nonce or context validation. Attackers intercept signatures and replay them to execute unauthorized operations indefinitely or across chains.
Q: Which protocols have been exploited via signature replay?
A: 0x Protocol (2017), Uniswap V2 permit functions, Curve Finance (cross-chain), and multiple bridges (2021-2023) suffered signature replay attacks. Aggregate losses exceed $100M+ from bridge replay exploits alone.
Q: How does Firepan detect signature replay?
A: Firepan enumerates signature verifications, analyzes message content, verifies nonce tracking, confirms chainId inclusion, constructs replay scenarios, and assesses cross-chain replay risks.
Q: Can signature replay be exploited after deployment?
A: Yes, signature replay is immediately exploitable post-deployment. Attackers observe valid signatures in mempool, logs, or events, then replay them indefinitely without new signing.
Q: How do I prevent signature replay?
A: Include nonce in signed message and increment after each use. Include block.chainid to prevent cross-chain replay. Include contract address to prevent collisions. Use EIP-712 for structured signing. Implement signature expiration via deadline parameter.
Signature replay enables indefinite reuse of valid cryptographic signatures, draining $100M+ across DeFi. The fix is straightforward: include nonce, chainId, and contract address in every signed message. EIP-712 (permit) and EIP-2612 implement this standard. Firepan's HOUND AI detects missing replay protections across all monitored contracts.
Start securing your smart contracts at https://app.firepan.com/
Firepan
12,453 contracts secured. 2,851 vulnerabilities blocked. 236 exploits prevented. Run a free surface scan — results in minutes, no credit card required.
Run Free Scan →