Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Missing Signature Replay Protection (Nonce Mechanism Absent)

Root + Impact

Description

  • The getMessageHash function currently constructs the EIP-712 message hash using only the receiver's address and the amount of tokens.

  • It does not include any form of replay protection parameters such as a nonce, contract address, or expiry time.

  • As a result, a valid signature signed by a user can be reused multiple times by anyone else to claim NFTs on behalf of the user, enabling replay attacks.

// Relevant part showing lack of nonce or other replay protection:
function getMessageHash(address receiver) public view returns (bytes32) {
if (i_snow.balanceOf(receiver) == 0) {
revert SA__ZeroAmount();
}
uint256 amount = i_snow.balanceOf(receiver);
return _hashTypedDataV4(
keccak256(abi.encode(MESSAGE_TYPEHASH, SnowmanClaim({receiver: receiver, amount: amount})))
);
}

Risk

Likelihood:

  • Any attacker observing a valid signature from a user can reuse the signature multiple times to claim NFTs on the user's behalf.

  • This can happen anytime a user signs the message and shares the signature externally or via the transaction itself.

Impact:

  • Unauthorized repeated claims of Snowman NFTs for the same user, leading to token distribution abuse.

  • Users lose control over their claim signatures and cannot revoke or invalidate them.

Recommended Mitigation

  • Add a nonce parameter to the SnowmanClaim struct:

struct SnowmanClaim {
address receiver;
uint256 amount;
uint256 nonce;
}
  • Add a mapping(address => uint256) private s_nonces; to track the nonce for each user.

  • Include the nonce in the message hash calculation:

uint256 currentNonce = s_nonces[receiver];
bytes32 digest = _hashTypedDataV4(
keccak256(abi.encode(MESSAGE_TYPEHASH, receiver, amount, currentNonce))
);
  • After each successful claim, increment the nonce to prevent signature reuse:

s_nonces[receiver] = currentNonce + 1;
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of claim check

The claim function of the Snowman Airdrop contract doesn't check that a recipient has already claimed a Snowman. This poses no significant risk as is as farming period must have been long concluded before snapshot, creation of merkle script, and finally claiming.

Support

FAQs

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