SNARKeling Treasure Hunt

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

Treasure Secrets Stored as Plaintext Comments in Deployment Script

Root + Impact

Description

  • The entire security model rests on participants physically finding a treasure and learning its secret. The ZK circuit exists precisely to prove knowledge of this secret without revealing it.

  • The deployment script hardcodes all 10 treasure secrets in a plaintext comment block that will be committed to version control and compiled into bytecode metadata.

// Secret Treasures for the snorkeling hunt (not revealed to the public):
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
@>// Treasures' hashes (revealed to the public, used as public inputs for the proof generation):
// 1505662313093145631275418581390771847921541863527840230091007112166041775502,
// -7876059170207639417138377068663245559360606207000570753582208706879316183353,
// -5602859741022561807370900516277986970516538128871954257532197637239594541050,
// 2256689276847399345359792277406644462014723416398290212952821205940959307205,
// 10311210168613568792124008431580767227982446451742366771285792060556636004770,
// -5697637861416433807484703347699404695743570043365849280798663758395067508,
// -2009295789879562882359281321158573810642695913475210803991480097462832104806,
// 8931814952839857299896840311953754931787080333405300398787637512717059406908,
// -4417726114039171734934559783368726413190541565291523767661452385022043124552,
// -961435057317293580094826482786572873533235701183329831124091847635547871092
// Note: The TreasureHunt contract requires funding at deployment, so we send 100 ETH

Risk

Likelihood:

  • Deployment scripts are routinely pushed to public GitHub repositories.

  • Even in a private repo, compiled contract bytecode embeds the IPFS/Swarm hash of the full source metadata, from which comments are recoverable.

  • Secrets 1 through 10 are trivially guessable by brute force regardless of whether the file is public.

Impact:

  • Any party who reads the script (or brute-forces the small secret space) can generate valid ZK proofs for all 10 treasures without ever visiting the hunt locations, claiming all 100 ETH before any legitimate finder can act.


The physical treasure hunt is rendered meaningless; the protocol's core value proposition is destroyed.

Proof of Concept

Off-chain attacker reads Deploy_s.sol and iterates over secrets 1-10

for (let secret of [1,2,3,4,5,6,7,8,9,10]) {
const proof = await generateProof(secret, recipientAddress); // valid ZK proof
await hunt.claim(proof, treasureHashes[secret-1], recipientAddress);
}
// All 100 ETH drained without physical participatioz.

Recommended Mitigation

Never store secrets in source code, comments, or version-controlled files. Generate secrets from a secure off-chain key management system (HSM, encrypted vault, etc.) and destroy them after their Pedersen hashes are committed to the circuit.

  • Use cryptographically random 32-byte secrets (e.g., cast keccak "$(openssl rand -hex 32)") rather than sequential integers.

  • Add a .gitignore rule that excludes any file containing raw secrets from version control.

  • Audit all metadata compilation outputs to ensure secret material is not embedded in bytecode CBOR/IPFS metadata.

- remove this code
+ add this code
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.

secrets can be brute-forced

The protocol’s “secret” is not drawn from a high-entropy space; instead, the treasure values are just small scalar integers, and the circuit proves validity by checking whether the public `treasure_hash` equals `pedersen_hash([treasure])`. An attacker can offline-enumerate all plausible treasure values, compute their Pedersen hashes, and recover the matching secret with negligible effort.

Support

FAQs

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

Give us feedback!