SNARKeling Treasure Hunt

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

Duplicate entry in ALLOWED_TREASURE_HASHES permanently locks 10 ETH

Author Revealed upon completion

Root + Impact

Description

  • The Noir circuit bakes in 10 allowed treasure hashes against which submitted proofs are validated. The deploy script comment documents 10 distinct hash values. However, index [8] and index `[9]inALLOWED\_TREASURE\_HASHES\ are identical — the hash for treasure 9 is used twice and the correct hash for treasure 10 is missing.

// main.nr
global ALLOWED_TREASURE_HASHES: [Field; 10] = [
1505662313093145631275418581390771847921541863527840230091007112166041775502,
-7876059170207639417138377068663245559360606207000570753582208706879316183353,
-5602859741022561807370900516277986970516538128871954257532197637239594541050,
2256689276847399345359792277406644462014723416398290212952821205940959307205,
10311210168613568792124008431580767227982446451742366771285792060556636004770,
-5697637861416433807484703347699404695743570043365849280798663758395067508,
-2009295789879562882359281321158573810642695913475210803991480097462832104806,
8931814952839857299896840311953754931787080333405300398787637512717059406908,
// @> index [8]: correct hash for treasure 9
-961435057317293580094826482786572873533235701183329831124091847635547871092,
// @> index [9]: duplicate of [8] — treasure 10's hash is missing
-961435057317293580094826482786572873533235701183329831124091847635547871092
];
  • Because no valid proof can ever be constructed for the missing tenth hash, claimsCount can never reach MAX_TREASURES (10). The withdraw() function requires claimsCount >= MAX_TREASURES, so the final 10 ETH is permanently locked with no recovery path unless emergencyWithdraw() is used (requires pause).

Risk

Likelihood:

  • The circuit is deployed as-is; no on-chain mechanism exists to update ALLOWED_TREASURE_HASHES without a full redeploy and circuit regeneration.

  • The tenth physical treasure finder will never be able to claim their reward.

Impact:

  • 10 ETH is permanently unclaimable by any legitimate participant.

  • withdraw() can never be called through the normal flow, leaving residual funds inaccessible without an emergency pause-and-withdraw cycle.

Proof of Concept

The is_allowed() function in the circuit iterates over all 10 entries in ALLOWED_TREASURE_HASHES looking for a match. Because index [9] is a copy of index [8], the hash that would correspond to treasure 10 (as listed in Deploy.s.sol) is simply absent from the array. The Noir assert(is_allowed(treasure_hash)) constraint will therefore always fail for any proof built around treasure 10's secret, making it impossible for the Barretenberg backend to generate a valid proof — the claim transaction can never be submitted successfully for that treasure.

function testTenthTreasureUnclaimable() public {
// Claim the first 9 unique treasures successfully
for (uint256 i = 0; i < 9; i++) {
hunt.claim(_proofFor(i), TREASURE_HASHES[i], payable(recipients[i]));
}
assertEq(hunt.getClaimsCount(), 9);
// Attempt to claim treasure 10 — no valid proof exists for its hash
// because the circuit does not contain it
vm.expectRevert(TreasureHunt.InvalidProof.selector);
hunt.claim(_proofFor(9), TREASURE_HASH_10, payable(recipients[9]));
// claimsCount is forever stuck at 9; withdraw() is permanently blocked
vm.expectRevert("HUNT_NOT_OVER");
hunt.withdraw();
// 10 ETH remains locked
assertEq(address(hunt).balance, 10 ether);
}

Recommended Mitigation

Replace the duplicate entry at index [9] with the correct hash for treasure 10 and regenerate all circuit artifacts:

global ALLOWED_TREASURE_HASHES: [Field; 10] = [
1505662313093145631275418581390771847921541863527840230091007112166041775502,
-7876059170207639417138377068663245559360606207000570753582208706879316183353,
-5602859741022561807370900516277986970516538128871954257532197637239594541050,
2256689276847399345359792277406644462014723416398290212952821205940959307205,
10311210168613568792124008431580767227982446451742366771285792060556636004770,
-5697637861416433807484703347699404695743570043365849280798663758395067508,
-2009295789879562882359281321158573810642695913475210803991480097462832104806,
8931814952839857299896840311953754931787080333405300398787637512717059406908,
-961435057317293580094826482786572873533235701183329831124091847635547871092,
- -961435057317293580094826482786572873533235701183329831124091847635547871092
+ <correct_pedersen_hash_for_treasure_10>
];

After fixing, re-run build.sh to regenerate Verifier.sol and all Foundry test fixtures.

Support

FAQs

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

Give us feedback!