Yaamlabs
Case study 23

Forged eligibility in an airdrop proof

Web3

A token distribution proved who was eligible with a compact cryptographic proof against a published root. We were asked whether the proof could be satisfied by someone who was never on the list.

Smart contract audit, Token distribution, severity HIGH, reported 2025

At a glance

  • Challenge. The proof tree did not separate leaves from internal nodes, so a joining node could pose as a leaf and prove eligibility for someone never listed.
  • Approach. We examined how leaves and internal nodes were distinguished, then built a proof that passed without a real entry, in a test deployment.
  • Result. Leaves and internal nodes are now domain separated before hashing, proof shape is constrained, claims are single use, and the claim path was retested.
  • Impact. Someone who was never on the eligibility list could have claimed from the token distribution with a proof the contract accepted as genuine.
  • Classification. CWE-351 Insufficient Type Distinction, OWASP Top 10 (2021): A08 Software and Data Integrity Failures. Component: Airdrop Merkle proof verification and claim path.

The challenge

A proof of membership against a single published root is only as strong as the way the tree is built. If the structure does not clearly separate the leaves that represent real entries from the internal nodes that hold the tree together, then a value can be presented in the role of a leaf when it was really a joining node, and the proof still checks out.

The elegance of the scheme is what hides the flaw. The maths is correct, the root is genuine, the verification passes. The gap is that two different inputs can be made to satisfy the same check, and only one of them was ever meant to.

In a distribution, that means eligibility can be fabricated rather than earned. The contract does exactly what it was told, against a proof that should never have been accepted.

Our approach

We examined how the tree distinguished leaves from internal nodes and whether the verification enforced that distinction, then constructed a proof that satisfied the check without corresponding to a real entry, in a test deployment.

Around that we checked the surface that shared the assumption:

  • Whether leaves and internal nodes were domain separated before hashing
  • Whether the proof length and shape were constrained
  • Whether a claim could be replayed or double claimed
  • Whether the same tree pattern was reused elsewhere in the system

Delivered with the constructed proof and the test deployment, so the finding could be reproduced without taking our word for it.

From finding to fix

  1. Studied how the proof tree was built
  2. Presented a node in a leaf's place
  3. Satisfied the proof without being listed
  4. Domain separated the tree, retested

The outcome

Leaves and internal nodes are now domain separated before hashing and the proof shape is constrained, so a joining node can no longer stand in for a leaf. Claims were made single use to close replay.

We retested the claim path and the tree construction after the fix. Reported as HIGH in 2025. Retest included in the engagement.

Check your own system

  • Hash leaves and internal nodes with distinct prefixes or constructions so one can never be read as the other.
  • Reject any proof whose length or shape does not match the published tree.
  • Mark each claim as used on first redemption, then try the same valid proof a second time in a test deployment.
  • Search the codebase for other contracts that reuse the same tree construction and apply the same fix to each.

Read more case studies, see Smart Contract Audits, or write to hello@yaamlabs.com.