Normal behavior: The Noir circuit at circuits/src/main.nr embeds a 10-element ALLOWED_TREASURE_HASHES array, one hash per treasure. Each entry must be a distinct Pedersen hash so that 10 separate treasure preimages can produce distinct valid proofs, and the Solidity withdraw() function (gated on claimsCount >= MAX_TREASURES = 10) becomes reachable on the honest happy path after all treasures are claimed.
Specific issue: The ALLOWED_TREASURE_HASHES array at circuits/src/main.nr:55-66 contains only 9 unique values — indices 8 and 9 hold the same hash (-961435057317293580094826482786572873533235701183329831124091847635547871092). Cross-referencing the Deploy script's documented intended 10-hash set at contracts/scripts/Deploy.s.sol:17-26 confirms that the 9th intended hash (-4417726114039171734934559783368726413190541565291523767661452385022043124552) is absent from the circuit. The 10th hash appears twice.
The accompanying test circuits/src/tests.nr::test_treasure_hunt_all_treasures_success passes only because it uses treasures = [1, 2, 3, 4, 5, 6, 7, 8, 10, 10] — treasure #9 is silently replaced by a second instance of #10, which is exactly the bug.
Likelihood: HIGH
Reason 1: The bug is present in the current code; no conditional trigger required. Any deployment that uses the current circuit artifacts has this issue.
Reason 2: The intended 9th treasure finder cannot generate a valid proof — their treasure's hash is not in ALLOWED_TREASURE_HASHES, so is_allowed(treasure_hash) returns false and the circuit's first assertion fails.
Impact: HIGH
Impact 1: The intended 9th-treasure finder is permanently deprived of their 10 ETH reward. The on-chain contract has no mechanism to accept a new circuit or to refund an unclaimable secret — they receive nothing.
Impact 2: withdraw() at contracts/src/TreasureHunt.sol:223-232 is gated on claimsCount >= MAX_TREASURES = 10, but honest claims can reach at most 9 (one hash is unclaimable). Therefore withdraw() is permanently unreachable on the happy path. The remaining 10 ETH of contract funding becomes stuck unless the owner falls back to pause() + emergencyWithdraw() (which is documented only as an emergency escape, not a business-as-usual fund-recovery path, and forbids recipient == owner forcing a relay through a secondary address).
Combined: ~10 ETH (one reward's worth) at risk plus a broken post-hunt settlement flow + the protocol's "10 treasures / 100 ETH" user-facing promise in the README is violated.
This is a setup / circuit-data correctness finding; the evidence is in-scope static inspection + a passing existing test that silently relies on the bug.
Array inspection: circuits/src/main.nr:55-66 (shown above) shows indices 8 and 9 are literally the same number.
Cross-reference with Deploy: contracts/scripts/Deploy.s.sol:17-26 (the doc-comment list of intended hashes) includes -4417726114...3124552 as the 9th hash, which is absent from the circuit.
Cryptographic confirmation via the contest's own test:
This test passes iff pedersen_hash([treasures[i]]) == ALLOWED_TREASURE_HASHES[i] for all i — and it passes with treasure 9 == 10 (the test substitutes treasure #10 for treasure #9's slot because no integer hashes to ALLOWED_TREASURE_HASHES[8] except 10).
Withdraw-gating check (Solidity, in-scope):
A Foundry test demonstrating the happy-path reachability failure:
Replace circuits/src/main.nr:64 (or the index-8 slot) with the missing 9th hash from the Deploy script's documented set, then regenerate contracts/src/Verifier.sol via circuits/scripts/build.sh:
Also fix circuits/src/tests.nr::test_treasure_hunt_all_treasures_success to use treasures = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] — the test should catch the exact class of bug that's present today.
Add a uniqueness invariant test:
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.
The contest is complete and the rewards are being distributed.