Snowman Merkle Airdrop

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

Missing domain separation allows cross-chain replay of delegated claims

Root + Impact

Description

The delegated claim mechanism in SnowmanAirdrop relies on ECDSA signatures to allow third parties to claim NFTs on behalf of users. However, the signed payload does not appear to include proper domain separation parameters such as block.chainid and address(this).

Under normal behavior, signatures should only be valid for:

  1. One specific contract deployment

  2. One specific blockchain network

  3. One specific execution context

The issue occurs because the protocol validates only the recovered signer address without binding the signature to the current chain or contract instance.

As a result, the same valid signature may be replayed:

  • on another chain

  • on another deployment

  • on cloned contract instances

  • on future redeployments using identical logic

This allows delegated claims intended for one deployment to remain valid elsewhere.

// Vulnerable pattern
bytes32 digest = keccak256(
abi.encode(user, amount)
);
address signer = ecrecover(digest, v, r, s);
require(signer == user, "Invalid signature");

The signed message lacks:

  • block.chainid

  • address(this)

  • replay protection domain separation

Without these parameters, signatures become portable across environments.

An attacker can reuse a legitimate signature originally signed for one deployment to mint NFTs again on another deployment or chain where the same contract logic exists.

// Secure pattern
bytes32 digest = keccak256(
abi.encode(
user,
amount,
nonce,
block.chainid,
address(this)
)
);

This ensures signatures are only valid for a single deployment and chain.


Risk

Likelihood:

  • Delegated claim signatures are transferable across environments

  • The protocol does not bind signatures to chain ID or contract address

  • Replay attacks require only one valid user signature

  • Multi-chain deployments and cloned contracts are common in Web3 ecosystems

Impact:

  • Users can unknowingly authorize claims on unintended deployments

  • NFTs may be minted multiple times across chains

  • Signature trust assumptions become invalid

  • Protocol reward accounting becomes inconsistent across deployments

  • Attackers can abuse previously signed payloads indefinitely


Proof of Concept

// User signs delegated claim authorization on Chain A
bytes32 digest = keccak256(
abi.encode(user, amount)
);
signature = sign(userPrivateKey, digest);
// Relayer submits delegated claim on Chain A
claimFor(user, amount, signature);
// NFT is minted successfully on Chain A
// Attacker copies the same signature
// and submits it on Chain B or another deployment
claimFor(user, amount, signature);
// Signature validation succeeds again
// because the signature does not include:
// - block.chainid
// - address(this)
// Additional NFTs are minted again

PoC Explanation

The delegated claim signature is generated without binding it to a specific blockchain network or contract deployment. Because the signed payload does not include block.chainid or address(this), the exact same signature remains valid across multiple environments.

An attacker can reuse a legitimate signature originally intended for one deployment and replay it on another deployment or chain where the same contract logic exists.

Since the contract only validates the recovered signer address and does not verify domain separation parameters, the replayed transaction succeeds and additional NFTs are minted again using the same authorization.


Recommended Mitigation

The protocol should implement full EIP-712 domain separation and replay protection.

The signed payload should include:

  • block.chainid

  • address(this)

  • user nonce

  • expiration timestamp

- bytes32 digest = keccak256(
- abi.encode(user, amount)
- );
+ bytes32 digest = keccak256(
+ abi.encode(
+ user,
+ amount,
+ nonce,
+ block.chainid,
+ address(this)
+ )
+ );

Additionally:

  1. Use EIP-712 typed structured signatures

  2. Consume nonces after execution

  3. Reject expired signatures

  4. Prevent reuse of previously executed digests

These protections ensure delegated claim signatures are valid only for the intended contract deployment and blockchain network.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 6 hours 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!