SNARKeling Treasure Hunt

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

Unconstrained recipient public input enables proof manipulation and front-running

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);
Updates

Lead Judging Commences

s3mvl4d Lead Judge 18 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

unused "recipient" in circuit

The claim that the proof system is broken because the recipient is not explicitly constrained in the Noir circuit reflects a misunderstanding of how zero-knowledge proofs bind public inputs. Although the circuit does not impose algebraic constraints on recipient, the value is still included in the public input vector, which is cryptographically committed to during proof generation. As a result, the proof is only valid for the exact tuple of public inputs it was created with. Any attempt by an attacker to front-run and substitute a different recipient would alter this tuple, causing the verifier’s check to fail because the proof no longer matches the provided public inputs. Therefore, while unconstrained public inputs do not enforce logical relationships within the circuit, they remain inseparably bound to the proof itself, and this binding is sufficient to prevent tampering or replay with modified values. Run the unit tests 'testClaimInvalidProofFails', 'testFrontRunningClaimFails'.

Support

FAQs

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

Give us feedback!