SNARKeling Treasure Hunt

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

Broken duplicate-claim protection allows repeated claims for the same treasure

Author Revealed upon completion

The contract intends to prevent multiple claims for the same treasureHash, but incorrectly checks a different storage key:

The contract checks whether a treasure has already been claimed using a different key than the one it writes to.

Instead of checking the user-supplied treasureHash, it checks _treasureHash, which is never initialized and defaults to bytes32(0).

// @> Reads wrong key: always bytes32(0)
if (claimed[_treasureHash]) revert AlreadyClaimed(treasureHash);
// ...
// @> Writes correct key: user-supplied treasureHash
claimed[treasureHash] = true;

Risk

Likelihood

  • The issue occurs on every call to claim() because _treasureHash is always bytes32(0)

Impact

  • Multiple rewards can be claimed for the same treasure

Proof of Concept

Replace testClaimDoubleSpendReverts() with:

function testDuplicateClaimAllowed() public {
(
bytes memory proof,
bytes32 treasureHash,
address payable recipient
) = _loadFixture();
vm.startPrank(participant);
// First claim succeeds
hunt.claim(proof, treasureHash, recipient);
uint256 beforeBal = recipient.balance;
// Second claim with same proof and hash SHOULD revert but DOES NOT
hunt.claim(proof, treasureHash, recipient);
// Balance increased again => duplicate claim succeeded
assertEq(recipient.balance, beforeBal + hunt.REWARD());
vm.stopPrank();
}

Recommended Mitigation

- 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!