Snowman Merkle Airdrop

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

Typo in EIP-712 TypeHash

Author Revealed upon completion

Root + Impact

Description

  • Describe the normal behavior ; The EIP-712 signature verification flow should allow users to sign a structured message off-chain and submit the signature on-chain, where the contract re-generates the same hash and recovers the signer’s address using ECDSA.recover. The MESSAGE_TYPEHASH must exactly match the off-chain typed data schema for this to work.

  • The struct definition in the type hash incorrectly uses "addres" instead of "address"

  • The struct name uses "addres" (missing one “s”). Off‑chain signatures generated against the correct definition (address) will fail to verify.

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

Risk

Likelihood:

  • Reason 1 // **Always triggered ** This is a deterministic failure — every signature verification will fail unless the same typo is duplicated off-chain (which is highly unlikely). Even a correct signature from MetaMask or a backend using the correct EIP-712 schema will mismatch.


  • Reason 2// The string "SnowmanClaim(addres receiver, uint256 amount)" contains a typo in the word "address""addres".This causes the keccak256 hash to be different from the expected struct type hash.


  • Impact 1 Off-chain signatures will be invalid and verification will fail. All valid claim signatures will be rejected, blocking every user from claiming.

Proof of Concept

1// Off-chain signer (e.g., frontend/backend):
const domain = {
name: "Snowman Airdrop",
version: "1",
chainId: 1,
verifyingContract: contractAddress,
};
const types = {
SnowmanClaim: [
{ name: "receiver", type: "address" },
{ name: "amount", type: "uint256" },
],
};
const value = {
receiver: user.address,
amount: 100,
};
const signature = await signer._signTypedData(domain, types, value);
2// On-chain signature check:
// Fails because of wrong hash
function getMessageHash(address receiver) public view returns (bytes32) {
uint256 amount = i_snow.balanceOf(receiver);
return _hashTypedDataV4(
keccak256(abi.encode(
// TYPO! Should be "address"
keccak256("SnowmanClaim(addres receiver, uint256 amount)"),
receiver,
amount
))
);
}
3//. Result:
_recover(digest, signature) → returns incorrect address
→ claim reverts with SA__InvalidSignature

Recommended Mitigation

- remove this code
keccak256("SnowmanClaim(addres receiver, uint256 amount)");
+ add this code
bytes32 private constant MESSAGE_TYPEHASH =
keccak256("SnowmanClaim(address receiver, uint256 amount)");
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 10 hours 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.