SNARKeling Treasure Hunt

First Flight #59
Beginner FriendlyGameFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

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

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
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Validated
Assigned finding tags:

secrets stored in plain text

In `Deploy.s.sol`, the comments explicitly list the “Secret Treasures for the snorkeling hunt” as 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, and `circuits/Prover.toml.example` likewise stores the full treasure array in plaintext alongside the corresponding `treasure_hash` values. Since the Noir circuit proves knowledge of one of these treasure secrets by checking that `pedersen_hash([treasure]) == treasure_hash`, publishing the raw treasure inputs defeats the intended secrecy assumption behind the treasure-hunt design: anyone with repository access can recover valid witnesses and generate proofs without actually discovering the treasure in the real world.

Support

FAQs

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

Give us feedback!