SNARKeling Treasure Hunt

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

Low entropy secrets allow brute force

Author Revealed upon completion

Low entropy secrets allow brute force

Description

The treasure secrets used as preimages for the Pedersen hashes are sequential integers from 1 to 10, as seen in Deploy.s.sol:15.

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

The security of the treasure hunt relies on the assumption that an attacker cannot derive the secret preimage from its hash. However, the input space is so small that any participant can brute-force all preimages by computing pedersen_hash(i) for a small range of integers and matching the results against the ALLOWED_TREASURE_HASHES baked into the public circuit (circuits/src/main.nr:55-67).

The ALLOWED_TREASURE_HASHES are hardcoded in the circuit (circuits/src/main.nr:55-67) and embedded in the compiled artifact that must be distributed to players for proof generation. Additionally, each treasureHash is passed as a public input to claim(), making claimed hashes visible in on-chain calldata. Combined with the trivially small secret space, any player can recover all 10 secrets, generate valid proofs, and drain the entire 100 ETH contract balance.

Risk

Likelihood: High

The circuit is public, the hashes are visible, and brute-forcing 10 sequential integers is instantaneous. Additionally, the deploy script contains a comment with the plaintext secrets.

Impact: High

All 100 ETH can be drained by a single attacker before legitimate players have a chance to claim.

Proof of Concept

  1. Read the ALLOWED_TREASURE_HASHES array from the public circuit.

  2. Loop through a small integer range and compute pedersen_hash(i) for each value.

  3. Match each result against the allowed hashes to recover all 10 secrets.

  4. For each recovered secret, generate a valid ZK proof bound to the attacker's recipient address.

  5. Submit all 10 claim() transactions and drain the contract.

Test test_treasure_hunt_all_treasures_success already generates the proofs.

Recommended Mitigation

Use high-entropy random field elements (e.g., 254-bit random values) as treasure secrets instead of sequential integers. This makes brute-forcing computationally infeasible.

// Example: secrets should look like this
// 0x1a2b3c...(random 32 bytes) instead of 1, 2, 3, ...

Additionally, remove the plaintext secrets comment from Deploy.s.sol.

Support

FAQs

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

Give us feedback!