SNARKeling Treasure Hunt

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

`ALLOWED_TREASURE_HASHES` has a duplicated entry and a missing hash, leaving the 9th treasure un-provable and permanently locking its 10 ETH reward

Author Revealed upon completion

Scope: circuits/src/main.nr

Root + Impact

Description

The Noir circuit bakes in the set of allowed Pedersen hashes of the
treasures. It is declared as a 10-element array, but the last two entries
are identical, and the hash for the 9th treasure (secret 9) is
missing:

// circuits/src/main.nr (lines 5566)
global ALLOWED_TREASURE_HASHES: [Field; 10] = [
1505662313093145631275418581390771847921541863527840230091007112166041775502, // secret 1
-7876059170207639417138377068663245559360606207000570753582208706879316183353, // secret 2
-5602859741022561807370900516277986970516538128871954257532197637239594541050, // secret 3
2256689276847399345359792277406644462014723416398290212952821205940959307205, // secret 4
10311210168613568792124008431580767227982446451742366771285792060556636004770, // secret 5
-5697637861416433807484703347699404695743570043365849280798663758395067508, // secret 6
-2009295789879562882359281321158573810642695913475210803991480097462832104806, // secret 7
8931814952839857299896840311953754931787080333405300398787637512717059406908, // secret 8
@>-961435057317293580094826482786572873533235701183329831124091847635547871092, // DUPLICATED (was intended to be hash of secret 9)
@>-961435057317293580094826482786572873533235701183329831124091847635547871092 // hash of secret 10
];

Cross-checking against circuits/Prover.toml.example (lines 18–29),
which the build.sh script treats as the authoritative witness source,
the intended 9th hash is:

-4417726114039171734934559783368726413190541565291523767661452385022043124552

That value is absent from the circuit's allowed set. Deploy.s.sol's
public-treasure-hashes block (lines 17–26) also enumerates the same 10
hashes and confirms -4417726... is the 9th one.

Proof: circuits/src/tests.nr::test_treasure_hunt_all_treasures_success
uses treasures = [1,2,3,4,5,6,7,8,10,10] — i.e. the authors wrote the
test around the bug (secret 9 never tested, secret 10 duplicated),
because is_allowed(pedersen_hash([9])) would return false.

Risk

Likelihood: HIGH

  • The bug manifests immediately on a legitimate claim attempt for
    treasure 9. No special adversarial setup is required — an honest
    snorkeler who finds the 9th physical treasure simply cannot produce a
    valid proof.

  • The issue exists on every deployment of this circuit.

Impact: HIGH

  • Only 9 unique treasure secrets are provable, yet MAX_TREASURES
    and the 100 ETH initial funding are sized for 10. The 10 ETH
    reserved for the 9th treasure is unreachable via normal gameplay —
    leaking the protocol's promised payout by 10%.

  • withdraw() requires claimsCount >= MAX_TREASURES, which is
    unreachable through honest play because only 9 unique treasures can be
    claimed. The owner therefore cannot reclaim the residual 10 ETH after
    the hunt concludes. (emergencyWithdraw requires a pause and exists
    for other reasons; it is a workaround, not the intended end-of-hunt
    flow.)

  • The 10th physical treasure's finder and the owner both lose the
    10 ETH: the finder gets nothing on-chain, and the owner cannot
    reclaim via the documented post-hunt flow.

Proof of Concept

  1. Using the values in circuits/Prover.toml.example, try to prove
    treasure #9 with secret = 9:

    # Simulated via Noir CLI — same result can be observed by running
    # nargo check / nargo prove on the intended inputs.
    treasure: 9
    treasure_hash: -4417726114039171734934559783368726413190541565291523767661452385022043124552
    recipient: (any)

    The circuit's is_allowed(hash) check (main.nr line 30, which loops
    over ALLOWED_TREASURE_HASHES) returns false because
    -4417726... is not in the baked set. assert(is_allowed(...))
    fails, so proof generation aborts. Secret 9 is unprovable.

  2. Conversely, the circuit's own test file acknowledges this by using
    [1, 2, 3, 4, 5, 6, 7, 8, 10, 10]:

    // circuits/src/tests.nr::test_treasure_hunt_all_treasures_success
    let treasures: [Field; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 10, 10];
    for i in 0..10 {
    let treasure = treasures[i];
    let treasure_hash = ALLOWED_TREASURE_HASHES[i];
    ...
    main(treasure, treasure_hash, recipient); // passes only because index 8 and 9 both accept secret 10
    }
  3. Forward propagation:

    • With only 9 provable treasures, honest play caps claimsCount
      at 9 (less the duplicates that the double-claim bug would
      allow — but that's a separate finding).

    • withdraw() requires claimsCount >= MAX_TREASURES (10)
      unreachable via honest play.

Recommended Mitigation

Replace the duplicated entry with the intended hash of secret 9. Using
the value already in Prover.toml.example and Deploy.s.sol:

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

Additionally in tests.nr, restore the intended
let treasures: [Field; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; so the
circuit's own unit test covers all 10 treasures. Regenerate the verifier
artifacts (circuits/scripts/build.sh) and Foundry fixtures after the
fix.

Disclosure

This finding was identified and written up with the assistance of an
autonomous AI auditor (Anthropic Claude).

Support

FAQs

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

Give us feedback!