SNARKeling Treasure Hunt

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

Invalid Treasure Hash Logic Allows Double Claims / Invariant Break

Author Revealed upon completion

Root + Impact

Description

  • Normal behavior: Each treasure is uniquely identified by its hash and can be claimed only once. The contract should prevent duplicate claims and preserve reward invariants across claims.

The issue is that the contract uses a single immutable _treasureHash for all treasures instead of mapping each valid treasure hash. This causes any valid proof for _treasureHash to permit repeated claims against the same stored key, breaking the uniqueness invariant.

// Root cause in TreasureHunt.sol
// @> Only one immutable treasure hash is stored
bytes32 private immutable _treasureHash;
// @> Claimed mapping only tracks this one hash
mapping(bytes32 => bool) public claimed;

Risk

Likelihood:

  • Occurs whenever multiple valid treasures exist but only one is recognized in state.

Appears during normal claim() flows when proofs correspond to distinct treasure secrets but match the same _treasureHash.

Impact:

  • Attackers may forge repeated claims using different valid proofs but identical public hash binding.

Allows extractor to drain ETH rewards for all MAX_TREASURES or more.

Proof of Concept

// Pseudocode
// All proofs that validate against the same _treasureHash allow repeated claims
for (i in 1..MAX_TREASURES) {
treasureHunt.claim(proofForSomeValidSecret, sameHash, attacker);
}
// claimsCount increments each time without tracking unique treasures

Recommended Mitigation

  • Track each valid treasure hash separately using the mapping and use that key to prevent duplicates.

- remove this code
+ add this code
+mapping(bytes32 => bool) private validTreasures;
-mapping(bytes32 => bool) public claimed;
+function claim(...) external {
+ require(validTreasures[treasureHash], "Unknown treasure");
+ require(!claimed[treasureHash], "Already claimed");
// ...
+claimed[treasureHash] = true;
}

Support

FAQs

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

Give us feedback!