Domain Separator Immutability
Let me break down the Domain Separator Immutability issue in detail.
The problem stems from how the domain separator is handled in the current implementation. Here's why it's problematic
This code sets the domain separator once during construction and never updates it. Here's why this is dangerous:
Chain Fork Scenario:
Imagine you deploy this contract on Polygon mainnet
The chain undergoes a hard fork (like Polygon2 fork)
Now your contract exists on both chains with the SAME domain separator
This means signatures become valid on both chains!
// On Polygon Mainnet (chainId: 1)
Contract deployed -> domainSeparator = hash(..."chainId: 1"...)
// Chain fork happens -> Polygon2 (chainId: 10001)
Same contract exists -> domainSeparator is still hash(..."chainId: 1"...)
// Result: A signature valid on mainnet is also valid on the fork!
// User signs a message on mainnet
signature = sign("Transfer 1 ETH", domainSeparator)
// Attacker can replay this on the forked chain
// Because domainSeparator is the same on both chains!
verifySignature("Transfer 1 ETH", signature, domainSeparator) // Returns true on both chains!
During the ETH PoW fork, some protocols that didn't handle domain separators correctly had their signatures replayed across chains
During the BSC fork in August 2023, contracts with static domain separators were potentially vulnerable to signature replay attacks
Signature replay attacks against honest users
Manual Review
contract SafeEIP712 {
bytes32 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
}
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.