SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
Submission Details
Impact: high
Likelihood: high

Wrong Variable Used in Duplicate-Claim Check, User Can claim multiple times with same `treasureHash` and drain the contract's balance

Author Revealed upon completion

Root + Impact

Description

  • In the claim() function, the duplicate claim check uses _treasureHash (an immutable private variable that is NEVER initialized in the constructor, making it permanently bytes32(0)) instead of the caller-supplied treasureHash parameter. The check if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash); will always readclaimed[bytes32(0)], which is false. Meanwhile, _markClaimed(treasureHash) correctly marks the caller-supplied hash, but the guard never checks it. This means any valid ZK proof for a given treasure can be replayed up to MAX_TREASURES (10) times, limited only by the claimsCount cap, allowing a single treasure finder to drain the entire 100 ETH contract balance.

function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
...
if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash); // BUG: uses _treasureHash (always bytes32(0))
...
_markClaimed(treasureHash); // marks correct hash, but check above never reads it
...
}

Risk

Impact:

  • A single treasure finder can replay their valid ZK proof up to 10 times (limited by MAX_TREASURES), draining the entire 100 ETH contract balance. Each replay uses a different recipient address (since recipient != msg.sender is enforced), but the attacker controls all recipient addresses. This completely breaks the core game mechanic and results in total loss of funds.


POC

no need

Recommended Mitigation

- bytes32 private immutable _treasureHash;
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
...
- if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
+ if (claimed[treasureHash]) revert AlreadyClaimed(treasureHash);
...
}

Support

FAQs

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

Give us feedback!