SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Typo in 'claimed' check allows infinite replay of a single valid proof to drain the entire contract balance

Root + Impact

Description

Normally, after a treasure is claimed using a valid ZK proof, the claimed mapping is updated to prevent the same treasureHash from being reused, protecting the contract from draining attacks.

However, in TreasureHunt.sol at line 88, there is a critical typo. The claim function checks claimed[_treasureHash] instead of checking the parameter treasureHash. Because _treasureHash is an uninitialized immutable private state variable, it strictly evaluates to bytes32(0). Thus, the replay check always evaluates claimed[bytes32(0)], which is always false.

This completely bypasses the replay protection. A single legitimate user or attacker with ONE valid proof can repeatedly call claim() to systematically drain the entire contract's ETH (e.g., 100 ETH) until claimsCount >= MAX_TREASURES is hit.

// Vulnerable code in contracts/src/TreasureHunt.sol line 88
if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

broken double-claim protection

In `claim()`, the guard uses `claimed[_treasureHash]`, where `_treasureHash` is an immutable state variable that is never initialized to the caller-supplied treasure identifier, while the contract later marks `claimed[treasureHash] = true` using the function argument instead. As a result, the duplicate-claim check and the state update are performed against different keys, which means a previously claimed treasure is not actually blocked from being claimed again with the same valid proof and `treasureHash`. This breaks a core invariant of the protocol described in the README, namely, that each treasure can only be redeemed once, and allows one valid treasure/proof pair to be reused to drain rewards repeatedly until either the `MAX_TREASURES` cap or the contract balance is exhausted.

Support

FAQs

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

Give us feedback!