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 10 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.

Give us feedback!