Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Signature Not Bound to Contract Address Enables Cross-Deployment Replay

Root + Impact

Description

Normal Behavior

EIP-712 signatures should be domain-separated, meaning a signature created for one contract instance should not be valid for another deployment.

Issue

The signed SnowmanClaim message does not explicitly bind the signature to the specific contract address or a unique deployment identifier.

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

Although EIP-712 provides domain separation, relying solely on the domain without explicitly binding claim data to the contract address makes signatures reusable across contracts that share the same name and version.

As a result, the same signature can be replayed in:

  • Contract redeployments

  • Testnet / mainnet mirrors

  • Forked or cloned airdrop contracts

Risk

Likelihood:

  • Reason 1: The contract name and version are static.

  • Reason 2: Redeployments or forks commonly reuse the same EIP-712 domain.

  • Reason 3: No contract-specific data is included in the signed payload.

Impact:

  • Impact 1: Signatures may be unintentionally valid across multiple contract instances.

  • Impact 2: Claim authorization can leak beyond its intended scope.

  • Impact 3: Weakens trust assumptions around signature-based access control.

Proof of Concept

This issue arises from insufficient domain binding in the signed message.

1. A user signs a valid SnowmanClaim message for a deployed SnowmanAirdrop contract.
2. The same contract is redeployed (or forked) with the same name and version.
3. The user submits the same signature to the new deployment.
4. The signature validates successfully, allowing an unintended claim.

Recommended Mitigation

Bind the signature explicitly to the contract address by including it in the signed data.

struct SnowmanClaim {
address receiver;
uint256 amount;
+ address airdrop;
}

Then enforce:

require(claim.airdrop == address(this), "Invalid airdrop");

This ensures signatures are only valid for a single, specific deployment

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!