The 10 treasure secrets are sequential integers (1 through 10). The entire keyspace can be brute-forced in milliseconds by computing pedersen_hash([n]) for small values of n and comparing against the public ALLOWED_TREASURE_HASHES. An attacker generates 10 valid proofs and claims all 100 ETH without ever participating in the physical treasure hunt.
The ZK proof system is meant to guarantee that only someone who physically discovered the treasure (and therefore knows the secret) can generate a valid proof. The hashes are public; the security argument rests entirely on preimage secrecy.
The treasure secrets are the integers 1 through 10. Pedersen hash is preimage-resistant for cryptographically random inputs, but with a keyspace of 10 values, exhaustive search is instant. Deploy.s.sol:14-15 also leaks all secrets in plaintext, though brute-force alone is already sufficient.
Why the ZK model collapses: the security argument requires the secret space to have high min-entropy (≥ 128 bits for cryptographic security). A keyspace of 10 values has log₂(10) ≈ 3.3 bits — less than a single decimal digit. Preimage-resistance of Pedersen hash does not help, because preimage-resistance only protects against attackers who don't know the distribution of the secret; here the distribution (small integers 1–10) is public.
Likelihood: Any observer with a laptop recovers all 10 secrets in under 1 second via brute-force against the public hashes. No physical presence, no credentials, no compromised keys required.
Impact: An attacker generates 10 valid proofs and calls claim() 10 times, draining all 100 ETH before any honest snorkeler reaches the water. The ZK privacy guarantee is completely nullified.
A remote attacker reads the public hashes from main.nr:55, brute-forces all 10 secrets against small integers (complete in <1 second), then runs nargo prove with each recovered (secret, hash, attacker_recipient) tuple. The circuit's two constraints (is_allowed(treasure_hash) + pedersen_hash([treasure]) == treasure_hash) both pass because the attacker genuinely knows the secret. 10 valid proofs are produced off-chain. The attacker then submits 10 claim() transactions — all guards in TreasureHunt.sol (balance check, recipient check, claimsCount, per-hash claimed flag, verifier.verify) pass for each distinct hash. Contract balance goes from 100 ETH to 0.
Use cryptographically strong random secrets (256-bit field elements), not sequential integers. Manage secrets via secure off-chain key management and never commit them to source code.
Also remove the plaintext secret disclosure at Deploy.s.sol:14-15.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.