SNARKeling Treasure Hunt

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

M-01 Mismatch Between `MAX_TREASURES` and Unique Circuit Hashes

Author Revealed upon completion

Description

The Solidity contract hardcodes MAX_TREASURES = 10, and the normal post-hunt withdrawal path requires claimsCount >= MAX_TREASURES. However, the Noir circuit currently contains only 9 unique allowed treasure hashes because the final entry in ALLOWED_TREASURE_HASHES is duplicated.

This creates a cross-module inconsistency between the contract’s terminal condition and the circuit’s effective set of uniquely claimable treasures.

Risk

If duplicate-claim protection is fixed as intended, the system may allow only 9 unique successful treasure claims while still requiring 10 claims before withdraw() is permitted.

This can lead to:

  • an unreachable normal terminal condition

  • residual ETH remaining locked unless an emergency owner path is used

  • inconsistent assumptions between contract logic and circuit logic

Proof of Concept

The inconsistency can be reasoned about directly from the code. If the circuit exposes only 9 unique hashes, then the contract’s 10-claim terminal condition may never be satisfied.

In Solidity:

uint256 public constant MAX_TREASURES = 10;

and:

function withdraw() external {
require(claimsCount >= MAX_TREASURES, "HUNT_NOT_OVER");

In Noir:

global ALLOWED_TREASURE_HASHES: [Field; 10] = [
...,
-961435057317293580094826482786572873533235701183329831124091847635547871092,
-961435057317293580094826482786572873533235701183329831124091847635547871092
];

Recommended Mitigation

Bring the contract and circuit back into agreement. The preferred fix is to restore the intended unique allowlist in the circuit, then regenerate all dependent artifacts.

diff --git a/circuits/src/main.nr b/circuits/src/main.nr
--- a/circuits/src/main.nr
+++ b/circuits/src/main.nr
@@ -61,6 +61,6 @@ global ALLOWED_TREASURE_HASHES: [Field; 10] = [
-5697637861416433807484703347699404695743570043365849280798663758395067508,
-2009295789879562882359281321158573810642695913475210803991480097462832104806,
8931814952839857299896840311953754931787080333405300398787637512717059406908,
- -961435057317293580094826482786572873533235701183329831124091847635547871092,
+ -4417726114039171734934559783368726413190541565291523767661452385022043124552,
-961435057317293580094826482786572873533235701183329831124091847635547871092
];

Or, if the intended number of distinct treasures is actually 9, then the contract should instead be updated to match that design decision:

diff --git a/contracts/src/TreasureHunt.sol b/contracts/src/TreasureHunt.sol
--- a/contracts/src/TreasureHunt.sol
+++ b/contracts/src/TreasureHunt.sol
@@ -26,7 +26,7 @@ contract TreasureHunt {
// ----- constants -----
uint256 public constant REWARD = 10 ether;
- uint256 public constant MAX_TREASURES = 10;
+ uint256 public constant MAX_TREASURES = 9;

Support

FAQs

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

Give us feedback!