Solidity

Merkle Proof

A Merkle proof is a list of sibling hashes used to prove that a leaf belongs to a Merkle tree with a known root.

A Merkle proof shows that one item was included in a committed list without sending the whole list on-chain.

Merkle Proof Explained in Detail

A Merkle proof proves that a leaf is part of a tree committed by a Merkle root. The proof contains the sibling hashes needed to recompute the root from the leaf.

Protocols use Merkle proofs for airdrops, allowlists, voting lists, reward claims, bridge messages, and other cases where storing every eligible item on-chain would be too expensive.

Smart contract example

The claim below proves inclusion but does not track whether the user already claimed:

function claim(uint256 amount, bytes32[] calldata proof) external {
    bytes32 leaf = keccak256(abi.encode(msg.sender, amount));
    require(MerkleProof.verify(proof, merkleRoot, leaf), "not eligible");

    token.transfer(msg.sender, amount);
}

If there is no claimed bitmap or mapping, the same valid proof can be reused.

Merkle Proof in Auditing

A Merkle proof proves inclusion in one committed root. It does not prove that the root is correct, current, authorized, or unclaimed.

Auditors review whether the same proof can claim twice, claim in another campaign, claim for another token, or remain valid after root rotation.

Red flags in code

  • Leaf built from ambiguous packed values.

  • No claimed mapping, bitmap, nonce, or epoch separation.

  • Root can be changed by an unauthorized or weakly protected account.

  • Leaf omits claimant, amount, token, chain, campaign, pool ID, epoch, or claim index.

  • Proof verification succeeds for a stale root after a campaign should be closed.

How to test or review it

  • Attempt to claim twice with the same proof.

  • If the leaf uses packed encoding, generate adjacent dynamic values and check for encoding ambiguity.

  • Review who can update the root and whether updates need a timelock or multisig.

  • Test wrong amount, wrong claimant, wrong token, and wrong campaign inputs.

  • Confirm failed transfers or non-standard token behavior cannot desynchronize claim state.

Sources