SNARKeling Treasure Hunt

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

H-01: Duplicate treasure hash in the Noir circuit means only 9 unique treasures exist and one finder can claim two reward slots

Author Revealed upon completion

Root + Impact

Description

  • The circuit's is_allowed() function checks whether a submitted treasure_hash belongs to a hardcoded array of 10 allowed hashes. This array is intended to represent 10 distinct physical treasures.

  • The 9th and 10th entries of ALLOWED_TREASURE_HASHES are identical. There are therefore only 9 distinct hashes, making treasure #9 (secret value 9) impossible to prove, while the finder of the treasure associated with the duplicated hash can satisfy is_allowed() for two different claim slots.

global ALLOWED_TREASURE_HASHES: [Field; 10] = [
1505662313093145631275418581390771847921541863527840230091007112166041775502,
-7876059170207639417138377068663245559360606207000570753582208706879316183353,
-5602859741022561807370900516277986970516538128871954257532197637239594541050,
2256689276847399345359792277406644462014723416398290212952821205940959307205,
10311210168613568792124008431580767227982446451742366771285792060556636004770,
-5697637861416433807484703347699404695743570043365849280798663758395067508,
-2009295789879562882359281321158573810642695913475210803991480097462832104806,
8931814952839857299896840311953754931787080333405300398787637512717059406908,
// @> index 8 and index 9 are identical
-961435057317293580094826482786572873533235701183329831124091847635547871092,
-961435057317293580094826482786572873533235701183329831124091847635547871092
];

The circuit test confirms that treasure 9 is replaced by a second 10:

// circuits/src/tests.nr line 30
// @> treasure 9 missing; 10 appears twice
let treasures: [Field; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 10, 10];

Risk

Likelihood:

  • The duplicate is present in the deployed circuit artifacts; any participant inspecting the public hash list will notice two identical entries.

  • The finder of the duplicated treasure can exploit this without any additional tooling.

Impact:

  • Treasure #9 is unclaimable. 10 ETH is permanently locked for that slot.

  • The finder of the duplicated hash can claim two 10 ETH rewards (20 ETH total) with a single physical treasure find, at the expense of other participants.

  • The hunt cannot be completed by 10 unique participants as designed.

Proof of Concept

The circuit's own test suite exposes the duplication: test_treasure_hunt_all_treasures_success is supposed to prove all ten unique treasures, but it silently skips secret 9 and passes secret 10 twice (once for index 8, once for index 9). Both iterations hash to the same ALLOWED_TREASURE_HASHES[8] / ALLOWED_TREASURE_HASHES[9] value, so the test passes without ever verifying that treasure #9 is provable.

// circuits/src/tests.nr
#[test]
fn test_treasure_hunt_all_treasures_success() {
// treasure 9 is skipped; index 8 and 9 both use treasure 10
let treasures: [Field; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 10, 10];
for i in 0..10 {
// Both i=8 and i=9 use the same treasure (10) and the same hash
main(treasures[i], ALLOWED_TREASURE_HASHES[i], 2);
}

Recommended Mitigation

  • The last entry in ALLOWED_TREASURE_HASHES must be replaced with the Pedersen hash of secret 9. The correct value is already recorded in the deploy script (index 8), confirming it was computed correctly there. After patching main.nr, the circuit must be recompiled and Verifier.sol regenerated so the on-chain verifier enforces the corrected hash list. The corresponding test should also be updated to use [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] to cover all unique secrets.

  • then recompile the circuit and regenerate Verifier.sol after the fix.

- -961435057317293580094826482786572873533235701183329831124091847635547871092
+ -4417726114039171734934559783368726413190541565291523767661452385022043124552

Support

FAQs

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

Give us feedback!