Summary
EIP-712 digest should comprise of following.
keccak256("Distribute(uint256 contestId,byte32 data,uint256 nonce,uint256 deadline)");
(contestId, data, nonce[organizer], deadline);
Vulnerability Details
function deployProxyAndDistributeBySignature(
address organizer,
bytes32 contestId,
address implementation,
bytes calldata signature,
bytes calldata data
) public returns (address) {
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));
if (ECDSA.recover(digest, signature) != organizer) revert ProxyFactory__InvalidSignature();
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
address proxy = _deployProxy(organizer, contestId, implementation);
_distribute(proxy, data);
return proxy;
}
Impact
Signature replay attack possible.
Tools Used
Manual Review
Recommendations
+ byte32 DISTRIBUTE_TYPEHASH = keccak256("Distribute(uint256 contest,byte32 data,uint256 nonce)");
+ mapping (bytes32 => uint256) nonce;
function deployProxyAndDistributeBySignature(
address organizer,
bytes32 contestId,
address implementation,
bytes calldata signature,
bytes calldata data,
+ uint256 deadline
) public returns (address) {
- bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(contestId, data)));
+ bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(DISTRIBUTE_TYPEHASH, contestId, data, nonce[contestId]++, deadline)));
+ if (block.timestamp >= deadline) revert ProxyFactory__OldSignature();
if (ECDSA.recover(digest, signature) != organizer) revert ProxyFactory__InvalidSignature();
bytes32 salt = _calculateSalt(organizer, contestId, implementation);
if (saltToCloseTime[salt] == 0) revert ProxyFactory__ContestIsNotRegistered();
if (saltToCloseTime[salt] > block.timestamp) revert ProxyFactory__ContestIsNotClosed();
address proxy = _deployProxy(organizer, contestId, implementation);
_distribute(proxy, data);
return proxy;
}