SNARKeling Treasure Hunt

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

H-02: 9th treasure hash differs between the Noir circuit and the deploy script making that treasure permanently unclaimable

Author Revealed upon completion

Root + Impact

Description

  • The deploy script publishes a list of 10 treasure hashes that participants use as public inputs when generating their ZK proofs off-chain. The Noir circuit bakes in its own list of allowed hashes that the on-chain verifier enforces. These two lists must be identical for every hash but the 9th entry (index 8) differs between them.

// Deploy.s.sol line 25 — hash published to participants for treasure #9
// @> -4417726114039171734934559783368726413190541565291523767661452385022043124552
// main.nr line 63 — hash enforced by the circuit for index 8
// @> -961435057317293580094826482786572873533235701183329831124091847635547871092

A participant who finds treasure #9 is given the deploy-script hash as their treasure_hash public input. The circuit's is_allowed() does not contain that value, so the proof fails verification and claim() reverts with InvalidProof.

Risk

Likelihood:

  • The mismatch is present in the committed code; it affects every attempt to claim treasure #9 from the moment of deployment.

  • No special attacker action is needed the ordinary happy-path claim fails automatically.

Impact:

  • Treasure #9 is permanently unclaimable regardless of whether the participant legitimately found it.

  • 10 ETH is locked in the contract and is recoverable only via emergencyWithdraw (which requires pausing the entire hunt).

  • The organizer faces reputational and potential legal liability for offering an uncollectable reward.

Proof of Concept

The failure path follows the normal participant workflow with no attacker tooling required. The participant generates a valid local proof using the hash advertised in the deploy script, but the on-chain verifier rejects it because the circuit enforces a different value for that index. The mismatch is deterministic and reproducible on every attempt.

1. Participant finds physical treasure #9.
2. Off-chain: participant uses hash from Deploy.s.sol index 8:
-4417726114039171734934559783368726413190541565291523767661452385022043124552
3. Proof generation succeeds locally (nargo execute passes).
4. On-chain: verifier checks is_allowed(treasure_hash).
The circuit's ALLOWED_TREASURE_HASHES[8] = -9614350... ≠ -4417726...
5. is_allowed() returns false → proof rejected → claim() reverts InvalidProof.

Recommended Mitigation

  • The deploy script values should be treated as the source of truth since they were publicly distributed to participants. Both main.nr index 8 (currently pointing to the wrong hash) and index 9 (currently a duplicate, as identified in H-01) must be corrected in a single coordinated update. After patching the circuit, regenerating Verifier.sol ensures the deployed verifier enforces the corrected list. No contract state changes are required only the circuit artifact and its derived verifier need to be updated before deployment.

  • Recompile the circuit and regenerate Verifier.sol after the fix.

- -961435057317293580094826482786572873533235701183329831124091847635547871092, // index 8 (wrong)
- -961435057317293580094826482786572873533235701183329831124091847635547871092 // index 9 (duplicate)
+ -4417726114039171734934559783368726413190541565291523767661452385022043124552, // index 8 (treasure 9 hash)
+ <correct pedersen hash of secret 9> // index 9 (treasure 10 hash)

Support

FAQs

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

Give us feedback!