withdraw()Duplicate hash in circuit array + permanent fund lock for owner
The ALLOWED_TREASURE_HASHES array in the Noir circuit should contain 10 unique hashes, one for each of the 10 physical treasures. This allows any participant who finds a physical treasure to generate a valid ZK proof and claim their reward.
Indices 8 and 9 of the ALLOWED_TREASURE_HASHES array are both set to the same value -961435057317293580094826482786572873533235701183329831124091847635547871092 (the hash of treasure 10). The hash for treasure 9 (-4417726114039171734934559783368726413190541565291523767661452385022043124552, as listed in Prover.toml.example) is missing from the circuit. The is_allowed() function will therefore return false for treasure 9's hash, making it impossible to generate a valid proof for that treasure.
The test file tests.nr also confirms awareness of this issue — treasure 9 is omitted and replaced with a second 10:
Likelihood:
The circuit always rejects a proof for treasure 9 since its hash is not in the allowed set — this occurs for every participant who finds treasure 9
claimsCount reaches at most 9 (from the remaining 9 unique valid hashes), never the required MAX_TREASURES (10), so the withdraw() condition is never satisfied
Impact:
The participant who physically finds treasure 9 cannot claim their 10 ETH reward — they are permanently disadvantaged despite valid real-world effort
The owner's withdraw() function requires claimsCount >= MAX_TREASURES (line 224), which can never be satisfied, so the owner's leftover funds are permanently locked in the contract (only recoverable via emergencyWithdraw while paused, which is a degraded fallback)
The duplicate hash means there are only 9 unique valid hashes, violating the stated invariant of 10 treasures
Replace the duplicate entry at index 8 with the correct hash for treasure 9:
Also fix the test to use the correct treasure:
The issue stems from a mismatch between the circuit and the contract’s economic assumptions: the Solidity contract is configured for `MAX_TREASURES = 10` and only allows the owner to call `withdraw()` once `claimsCount >= MAX_TREASURES`, while the Noir circuit’s baked-in `ALLOWED_TREASURE_HASHES` array does not actually contain ten distinct treasures because one hash is duplicated and another expected hash is missing. As a result, under the intended one-claim-per-treasure design described in the README, there are only nine uniquely claimable treasures even though the system is funded and accounted as if ten rewards can be legitimately redeemed. That creates two linked consequences from the same root cause: first, one treasure is effectively unclaimable because no valid proof can ever be generated for the missing allowed hash, and second, the normal “hunt over” withdrawal path becomes bricked because honest participants can never reach ten legitimate unique claims, leaving the post-hunt fund recovery logic via `withdraw` function permanently unreachable. The owner can still intervene through the emergency path.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.