Snowman Merkle Airdrop

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

ECDSA TypeHash Typo Breaks Off‑Chain Signature Verification

Root + Impact

Description:
SnowmanAirdrop.sol uses EIP‑712 typed data for off‑chain signature verification in claimSnowman(), but the constant MESSAGE_TYPEHASH is defined with a spelling mistake:

bytes32 public constant MESSAGE_TYPEHASH =
keccak256("SnowmanClaim(addres receiver, uint256 amount)");

The word “addres” is missing the “d”. Off‑chain signing tools build the digest for
"SnowmanClaim(address receiver, uint256 amount)" and will never match the on‑chain hash. As a result, valid signatures generated by standard libraries are rejected, blocking genuine claims and frustrating users.

Risk

Likelihood:

  • Users integrating with ethers.js, web3.js or similar will generate signatures for the correct struct spelling.

  • Every off‑chain signature will fail on‑chain verification.

Impact:

  • All legitimate claim attempts revert with “Invalid signature.”

  • Users will waste gas and submit support tickets.

  • Protocol reputation suffers due to poor UX and broken claim flow.

Proof of Concept

// off‑chain using ethers.js
const domain = {
name: "SnowmanAirdrop",
version: "1",
chainId: 1,
verifyingContract: AIRDROP_ADDRESS
};
const types = {
SnowmanClaim: [
{ name: "receiver", type: "address" },
{ name: "amount", type: "uint256" }
]
};
const signature = await signer._signTypedData(
domain,
types,
{ receiver: userAddress, amount: allocatedAmount }
);
// on‑chain recovery in claimSnowman()
await snowmanAirdrop.claimSnowman(
userAddress,
proof,
v, r, s
);
// reverts with “Invalid signature”

Explanation:
The digest computed off‑chain uses the correct type string, so ECDSA.recover fails against the typo’d MESSAGE_TYPEHASH.

Recommended Mitigation

- bytes32 public constant MESSAGE_TYPEHASH =
- keccak256("SnowmanClaim(addres receiver, uint256 amount)");
+ bytes32 public constant MESSAGE_TYPEHASH =
+ keccak256("SnowmanClaim(address receiver, uint256 amount)");

After redeployment, update all signing scripts to use the corrected type string. Add a unit test:

function testTypeHashConsistency() public {
bytes32 expected = keccak256("SnowmanClaim(address receiver, uint256 amount)");
assertEq(MESSAGE_TYPEHASH, expected, "typehash mismatch");
}

This ensures on‑chain and off‑chain hashes align, restoring valid signature verification and eliminating blocked claims.

Updates

Lead Judging Commences

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

Inconsistent MESSAGE_TYPEHASH with standard EIP-712 declaration

A typo in the `MESSAGE_TYPEHASH` variable of the `SnowmanAirdrop` contract will prevent signature verification claims. Used `addres` instead of `address`

Support

FAQs

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