The normal behavior is to have 10 distinct secrets mapped to 10 distinct allowed Pedersen hashes.
The issue is that ALLOWED_TREASURE_HASHES contains the same value at index 8 and 9. This collapses two intended treasure slots into one cryptographic identity. Under intended one-claim-per-hash semantics, only one of those two slots can be redeemed independently.
Likelihood:
The duplicate constant is deterministic and always active for all proofs.
At least one legitimate participant eventually hits the colliding slot in normal operation.
Impact:
One treasure reward becomes permanently uncollectable once per-hash replay protection is enforced as intended.
Protocol promise of 10 unique claimable treasures is violated.
Standalone reproduction in Noir:
Compute pedersen_hash([9]) which equals -4417726114039171734934559783368726413190541565291523767661452385022043124552.
Check membership against current ALLOWED_TREASURE_HASHES.
Observe membership is false because the array duplicates hash #10 instead of including hash #9.
A proof for treasure 9 cannot satisfy is_allowed(treasure_hash).
Written exploit flow:
The duplicated hash allows only one of the two intended physical treasure slots to be represented as a unique on-chain claim key.
Once that shared hash is claimed once (with a correct claimed[treasureHash] guard), the second participant mapped to the same hash cannot claim independently.
Use the global constant ALLOWED_TREASURE_HASHES in circuits/src/main.nr.
After updating the circuit constant, regenerate proving artifacts and contracts/src/Verifier.sol from the new circuit.
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.