SNARKeling Treasure Hunt

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

Total System Collapse: Plaintext Secret Exposure

Root + Impact

Description

A Zero-Knowledge proof is only useful if the "witness" (the private input) is difficult to guess.

  1. Hardcoded Hashes: The ALLOWED_TREASURE_HASHES are public. Anyone can see them by looking at the Noir file or the contract deployment data.

  2. Deterministic Hashing: The circuit uses a standard Pedersen hash of a single Field element: pedersen_hash([treasure]).

  3. The Exploit: If the "treasures" are human-readable strings, small numbers, or even 256-bit values that haven't been salted, an attacker can run a dictionary attack or a rainbow table against the 10 public hashes.

// The "Map" is public
global ALLOWED_TREASURE_HASHES: [Field; 10] = [
1505662313093145631275418581390771847921541863527840230091007112166041775502,
// ... an attacker just needs to find what 'x' results in this hash
];
fn main(treasure: Field, treasure_hash: pub Field, recipient: pub Field) {
// @> If I know the treasure, I can generate the proof for ANY recipient.
assert(std::hash::pedersen_hash([treasure]) == treasure_hash);
}

Risk

Likelihood: Critical

  • The hashes are visible in the source code.

  • If the treasure values were generated with low entropy (e.g., "treasure1", "gold_key"), they can be cracked in milliseconds.

Impact: High

  • The "Hunt" is over immediately. One person (or bot) will claim all 10 rewards (100 ETH) before anyone else can even start.

Proof of Concept

Recommended Mitigation

Salting: The treasure should be a combination of a secret AND a unique identifier (like a nullifier) to prevent dictionary attacks.

  • Off-chain Commitment: Don't bake the hashes into the circuit. Instead, store a Merkle Root of the allowed treasure hashes in the Smart Contract. This allows you to add treasures without recompiling the circuit and hides the individual hashes until they are claimed.

  • High Entropy: Ensure the treasure secrets are cryptographically strong (at least 256 bits of randomness).

Updates

Lead Judging Commences

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

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!