SNARKeling Treasure Hunt

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

`updateVerifier` allows zero address; constructor does not

Author Revealed upon completion

Root + Impact

Description

  • Normal behavior: The verifier address should always be non-zero, matching constructor rules, including after admin updates.

  • Problem: The constructor reverts when the verifier is address(0). updateVerifier assigns verifier = newVerifier with no zero check. An owner mistake while paused bricks all future claim calls until a valid verifier is set again.

// @> constructor rejects zero
if (_verifier == address(0)) revert InvalidVerifier();
// @> update path allows zero — inconsistent with constructor
function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
verifier = newVerifier;
emit VerifierUpdated(address(newVerifier));
}

Risk

Likelihood:

  • Owner calls updateVerifier with zero while paused during a botched rotation or compromised key scenario.

  • Unlikely in steady state; operator error is the main path.

Impact:

  • Claims revert until a non-zero verifier is configured; no user payouts during the outage.

Proof of Concept

Explanation: After the owner sets verifier to address(0) via updateVerifier, claim cannot complete verification. The Foundry test below encodes that sequence; it requires fixture files from circuits/scripts/build.sh (for _fixture()).

Supporting code — run:

cd circuits && ./scripts/build.sh
cd ..
forge test --match-test testPoC_UpdateVerifierZeroBricksClaims -vv

Supporting code — test:

// contracts/test/TreasureHuntPoC.t.sol — testPoC_UpdateVerifierZeroBricksClaims
function testPoC_UpdateVerifierZeroBricksClaims() public {
(bytes memory proof, bytes32 treasureHash, address payable recipient) = _fixture();
vm.startPrank(OWNER);
hunt.pause();
hunt.updateVerifier(IVerifier(address(0)));
hunt.unpause();
vm.stopPrank();
vm.startPrank(PARTICIPANT);
vm.expectRevert();
hunt.claim(proof, treasureHash, recipient);
vm.stopPrank();
}

Recommended Mitigation

Explanation: Mirror the constructor: reject address(0) so a paused rotation cannot brick claim by pointing verifier at an address with no verify implementation. Reuse the existing InvalidVerifier() error for consistency.

function updateVerifier(IVerifier newVerifier) external {
require(paused, "THE_CONTRACT_MUST_BE_PAUSED");
require(msg.sender == owner, "ONLY_OWNER_CAN_UPDATE_VERIFIER");
+ if (address(newVerifier) == address(0)) revert InvalidVerifier();
verifier = newVerifier;

Support

FAQs

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

Give us feedback!