SNARKeling Treasure Hunt

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

Unconstrained recipient public input enables proof manipulation and front-running

Author Revealed upon completion

Summary

The claim() function relies on a zk-SNARK proof that includes recipient as a public input. However, the Noir circuit does not use or constrain this value, meaning it is not bound to the validity of the proof. As a result, an attacker can front-run a valid transaction by modifying the recipient field while keeping the same proof, allowing them to redirect the reward.

Vulnerability Details

In the Noir circuit:

fn main(treasure: Field, treasure_hash: pub Field, recipient: pub Field) {
assert(is_allowed(treasure_hash));
assert(std::hash::pedersen_hash([treasure]) == treasure_hash);
}

The recipient public input is declared but never used in any constraint. This means it is not enforced by the zk proof and can be arbitrarily modified without invalidating it.

In zk-SNARK systems, unconstrained public inputs are effectively free values: they are included in the verification process but do not influence proof validity.

Front-Running Scenario

Because recipient is not constrained, an attacker can:

  1. Observe a pending claim() transaction in the mempool

  2. Copy the valid proof and treasureHash

  3. Replace recipient with their own address

  4. Submit the transaction with higher gas to front-run the original

Even though the proof remains valid, the reward is sent to the attacker because the contract trusts the unconstrained public input.

Impact

  • Enables front-running of valid claims

  • Allows redirection of rewards without breaking proof validity

  • Breaks the assumption that all public inputs are cryptographically bound to the proof

  • Creates MEV extraction opportunities at user expense

Severity

High

Justification:

  • Proof remains valid under modified public inputs

  • Enables deterministic front-running

  • Directly impacts reward integrity

Recommended Mitigation

The root issue is that recipient is exposed as a public input but is not constrained in the Noir circuit. The correct fix is to ensure that all public inputs are explicitly bound inside the zk circuit, so they cannot be modified without invalidating the proof.

Ensure recipient is properly constrained in the circuit :

assert(recipient == intended_recipient);

Support

FAQs

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

Give us feedback!