Snowman Merkle Airdrop

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

[M-1] Typo in EIP-712 `MESSAGE_TYPEHASH` Prevents Valid Signature Verification

[M-1] Typo in EIP-712 MESSAGE_TYPEHASH Prevents Valid Signature Verification

Description

  • Normal Protocol Behavior: The SnowmanAirdrop.sol contract uses EIP-712 signatures to authorize claims, allowing a third party (like "satoshi" in the tests) to submit a claim on behalf of a receiver if they provide a valid signature from the receiver. The signature is generated over a hash of a SnowmanClaim struct, which includes the receiver's address and their amount of Snow tokens.

  • Specific Issue: The MESSAGE_TYPEHASH constant, used to construct the EIP-712 typed data hash, contains a typographical error. The receiver field is misspelled as "addres" instead of "address".

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

    This means that the hash computed by the contract for signature verification will be different from the hash computed by any standard EIP-712 compliant client or library that uses the correct struct definition: SnowmanClaim(address receiver, uint256 amount). As a result, valid signatures generated by users/clients will not match the contract's expectation, causing all such claimSnowman calls to fail with SA__InvalidSignature.

Risk

Likelihood: High

  • Any attempt to use the claimSnowman function with a signature generated by a standard EIP-712 compliant method (which would use the correct spelling) will fail.

Impact: Medium

  • Functionality Breakdown: The primary mechanism for delegated claims (claimSnowman when msg.sender != receiver) is broken. Users who intend to have a third party submit their claim transaction will be unable to do so.

  • User Frustration: Users attempting to use this feature will encounter unexpected failures.

  • Deviation from Standard: The contract does not correctly implement the EIP-712 standard for the specified struct due to the typo, undermining interoperability and trust in the signature scheme.
    (Note: If claimSnowman is also intended to be called directly by the receiver themselves providing their own signature, that path would also be broken for the same reason.)

Proof of Concept

Add testTypoInMessageTypehash_PreventsCorrectClientSignatureValidation function in test/SnowmanAirdrop.t.sol :

  1. Mints 1 Snow token to "alice".

  2. Alice approves the SnowmanAirdrop contract.

  3. It then manually constructs an EIP-712 digest (correctDigestToSign) using the correct type string: "SnowmanClaim(address receiver,uint256 amount)".

  4. Alice signs this correctDigestToSign.

  5. "satoshi" attempts to call claimSnowman on behalf of Alice using this signature and Alice's valid Merkle proof.

  6. The call is expected to, and does, revert with SA__InvalidSignature. This is because the SnowmanAirdrop contract, due to the typo in its internal MESSAGE_TYPEHASH, calculates a different expected digest.

// test/SnowmanAirdrop.t.sol
function testTypoInMessageTypehash_PreventsCorrectClientSignatureValidation()
public
{
// Alice is one of the default users set up by Helper.s.sol with 1 Snow token.
assertEq(snow.balanceOf(alice), 1);
// Alice approves the airdrop contract for her 1 Snow token
vm.prank(alice);
snow.approve(address(airdrop), 1);
uint256 amountForSignature = 1; // This is i_snow.balanceOf(alice) for the test
// 1. Manually construct the EIP-712 struct hash with the CORRECT type string
bytes32 CORRECT_MESSAGE_TYPEHASH = keccak256(
bytes("SnowmanClaim(address receiver,uint256 amount)")
);
bytes32 correctStructHash = keccak256(
abi.encode(
CORRECT_MESSAGE_TYPEHASH,
alice, // receiver
amountForSignature // amount
)
);
// 2. Manually construct the full EIP-712 digest
bytes32 EIP712DOMAIN_TYPEHASH = keccak256(
bytes(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
)
);
bytes32 calculatedDomainSeparator = keccak256(
abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes("Snowman Airdrop")),
keccak256(bytes("1")),
block.chainid,
address(airdrop)
)
);
bytes32 correctDigestToSign = keccak256(
abi.encodePacked(
"\x19\x01", // EIP-191 prefix
calculatedDomainSeparator,
correctStructHash
)
);
// 3. Alice signs this CORRECTLY computed digest
(uint8 alV_correct, bytes32 alR_correct, bytes32 alS_correct) = vm.sign(
alKey,
correctDigestToSign
);
// 4. Satoshi attempts to claim for Alice using this "correct client" signature
console2.log(
"Attempting claim with signature based on CORRECT type string..."
);
vm.prank(satoshi);
vm.expectRevert(SnowmanAirdrop.SA__InvalidSignature.selector);
airdrop.claimSnowman(
alice,
AL_PROOF, // Assumes AL_PROOF is a valid Merkle proof for Alice
alV_correct,
alR_correct,
alS_correct
);
// 5. For comparison, get the digest the contract *would* compute
bytes32 digestFromContractViaGetter = airdrop.getMessageHash(alice);
assertNotEq(
correctDigestToSign,
digestFromContractViaGetter,
"Digest from correct type string should differ from contract's typo-based digest."
);
//if we reached this, that means the test passed.
console2.log(
"TEST PASSED: Claim with a signature based on the *correct* EIP-712 type string was REJECTED."
);
}

The test confirms that a signature generated based on the correct EIP-712 struct definition fails verification due to the contract's internal typo.

Recommended Mitigation

Correct the typographical error in the MESSAGE_TYPEHASH constant within the SnowmanAirdrop.sol contract.

Change:

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

This will ensure that the contract computes the EIP-712 hash consistent with standard client implementations, allowing valid signatures to be correctly verified.

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 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.