SNARKeling Treasure Hunt

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

Deployment script exposes private treasure secrets, allowing anyone to generate valid proofs

Author Revealed upon completion

Root + Impact

Description

  • The deployment script publicly lists the supposedly private treasure secrets

  • Since the circuit only requires knowledge of one valid treasure secret, anyone reading the script can generate valid proofs and claim rewards without finding the physical treasures.

// Secret Treasures for the snorkeling hunt (not revealed to the public):
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

Risk

Likelihood:

  • Deploy.s.sol is included in the repository and is explicitly in scope.

  • Any participant or attacker can read the file and learn all valid treasure secrets before the hunt begins.

Impact:

  • The core security assumption of the protocol is broken.

  • Attackers do not need to physically find a treasure; they can use the exposed secrets to generate valid ZK proofs and claim the ETH rewards. This can drain the contract’s funded balance.

Proof of Concept

The PoC is conceptual because the exploit follows directly from the disclosed secrets. The deployment script reveals all private treasure preimages, and the circuit only proves knowledge of one of those preimages. Therefore, any user with repository access can generate valid proofs for the listed secrets and claim rewards without finding the real-world treasure.

The script reveals the private treasure values:

// Secret Treasures for the snorkeling hunt (not revealed to the public):

// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

The Noir circuit accepts a private treasure input and checks that its Pedersen hash matches an allowed public hash

An attacker can choose any disclosed value, such as 1, and generate a valid proof for its corresponding public hash. The attacker can then submit the proof to TreasureHunt.claim() and receive the reward:

hunt.claim(proof, treasureHash, attackerRecipient);

fn main(treasure: Field, treasure_hash: pub Field, recipient: pub Field) {
assert(is_allowed(treasure_hash));
assert(std::hash::pedersen_hash([treasure]) == treasure_hash);
}

Recommended Mitigation

Remove all private treasure secrets from public source code, deployment scripts, comments, tests, documentation, and committed artifacts.

Store treasure secrets offline and only disclose each secret physically through the real-world treasure hunt. Public code should include only commitments or hashes, never the preimage secrets.

- // Secret Treasures for the snorkeling hunt (not revealed to the public):
- // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

Support

FAQs

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

Give us feedback!