Project

One World
NFTDeFi
15,000 USDC
View results
Submission Details
Severity: low
Valid

Domain separator is set on deployment and cannot be updated

Summary

domainSeperator in EIP712Base is only set during contract deployment and cannot be updated. Since it includes the chain ID, this means signatures remain valid across chain forks, enabling cross-chain replay attacks.

Vulnerability Details

The current implementation sets domainSeperator once during contract deployment and stores it as an immutable value. The chain ID is embedded within the domain separator at deployment time, but remains static even if the contract is deployed on a forked chain.

This creates a scenario where signatures generated on the original chain remain valid on any fork of that chain, as the domain separator doesn't reflect the new chain's identity. The issue is made worse by the fact that there's no mechanism to update or refresh the domain separator after deployment.

Impact

Signatures created on the original chain can be replayed on forked chains and any meta-transaction using this signature verification can be replayed. This affects all contracts inheriting from EIP712Base (OWPIdentity, NativeMetaTransaction), which could lead to unauthorized actions being executed on forked chains, particularly dangerous for high-value operations like profit claiming or membership transfers.

Proof of Concept

EIP712Base.sol#L100-L103

contract EIP712Base {
// existing code...
bytes32 internal domainSeperator; // no way to update this after deployment
constructor(
string memory name,
string memory version
){
@> _setDomainSeperator(name, version);
}
function _setDomainSeperator(string memory name, string memory version) internal {
domainSeperator = keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes(name)),
keccak256(bytes(version)),
address(this),
bytes32(getChainId())
)
);
}
// existing code...
}

Tools Used

Manual Review

Recommended Mitigation Steps

The domain separator should be computed dynamically based on the current chain ID:

- bytes32 internal domainSeperator;
+ string internal name;
+ string internal version;
constructor(string memory _name, string memory _version) {
- _setDomainSeperator(_name, _version);
+ name = _name;
+ version = _version;
}
function getDomainSeperator() public view returns (bytes32) {
- return domainSeperator;
+ return keccak256(
+ abi.encode(
+ EIP712_DOMAIN_TYPEHASH,
+ keccak256(bytes(name)),
+ keccak256(bytes(version)),
+ address(this),
+ bytes32(getChainId())
+ )
+ );
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
0xbrivan2 Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Appeal created

irondevx Submitter
12 months ago
0xbrivan2 Lead Judge
11 months ago
0xbrivan2 Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

can't update domainSeparator in case of hard fork

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.