SNARKeling Treasure Hunt

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

[L-07] Missing verifier identity pinning allows stale or mismatched verifier acceptance

Author Revealed upon completion

Root + Impact

Description

The protocol relies on a verifier contract generated from specific circuit artifacts. Correctness assumes the deployed verifier corresponds to the intended circuit version.

The issue is that verifier identity is not pinned on-chain. TreasureHunt accepts a verifier address at deployment and during updates without validating code hash / circuit identity. As a result, a stale or mismatched verifier can be accepted, causing verification behavior to diverge from intended circuit semantics.

// contracts/src/TreasureHunt.sol
constructor(address _verifier) payable {
if (_verifier == address(0)) revert InvalidVerifier();
verifier = IVerifier(_verifier); // @> no verifier codehash / circuit-ID check
}
function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
verifier = newVerifier; // @> no verifier codehash / circuit-ID check
}
// contracts/scripts/Deploy.s.sol
verifier = new HonkVerifier();
hunt = new TreasureHunt{value: initialFunding}(address(verifier));
// @> deployment does not assert expected verifier codehash/VK identity

Risk

Likelihood:

  • Verifier rotations are supported by design and operator mistakes are realistic in artifact-based workflows.

  • Generated verifier artifacts can drift across toolchain/circuit versions.

Impact:

  • Claims may unexpectedly fail (DoS) or follow unintended verification rules.

  • The contract does not enforce or attest expected verifier identity, so users must trust off-chain operational discipline for artifact correctness.

Severity rationale:

  • Classified as Low because this is an integrity/operational hardening issue; no direct theft path is introduced by this bug alone.

Proof of Concept

Written reproduction flow:

  1. Generate/deploy TreasureHunt with verifier V1 from circuit artifacts C1.

  2. Later, while paused, update to verifier V2 generated from different artifacts C2 (or stale build).

  3. Unpause and submit proofs expected for C1.

  4. Verification behavior diverges (unexpected reverts/acceptance conditions), while contract provides no on-chain check that V2 matches intended identity.

// Upgrade path accepts any IVerifier-compatible address
vm.prank(owner);
hunt.pause();
vm.prank(owner);
hunt.updateVerifier(IVerifier(address(v2))); // succeeds without identity pinning
vm.prank(owner);
hunt.unpause();

Recommended Mitigation

Pin verifier identity on-chain and enforce it during construction and updates, while preserving controlled verifier upgrades.

+ bytes32 public verifierCodeHash;
+
- constructor(address _verifier) payable {
+ constructor(address _verifier, bytes32 _verifierCodeHash) payable {
if (_verifier == address(0)) revert InvalidVerifier();
+ if (_verifier.codehash != _verifierCodeHash) revert InvalidVerifier();
+ verifierCodeHash = _verifierCodeHash;
verifier = IVerifier(_verifier);
}
function updateVerifier(IVerifier newVerifier, bytes32 newVerifierCodeHash) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
+ if (address(newVerifier) == address(0)) revert InvalidVerifier();
+ if (address(newVerifier).codehash != newVerifierCodeHash) revert InvalidVerifier();
verifier = newVerifier;
+ verifierCodeHash = newVerifierCodeHash;
}

Also enforce the same codehash assertion in deployment tooling before broadcasting.

Support

FAQs

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

Give us feedback!