SNARKeling Treasure Hunt

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

Lack of msg.sender proof binding allows Front-Runners to hijack Claimed event log attribution

Author Revealed upon completion

Root + Impact

Description

  • TheClaimedevent is intended to record who discovered and solved each treasure, serving as an on-chain attribution record for leaderboards, NFT rewards, or reputation systems

  • The event incorrectly emitsmsg.senderinstead ofrecipient, allowing front-runners to steal attribution credit since the ZK proof binds therecipientaddress but NOT themsg.sender

// Root cause in the codebase
function claim(bytes calldata proof, bytes32 treasureHash, address payable recipient) external nonReentrant() {
...
// ZK proof verification binds recipient as a public input
bytes32[] memory publicInputs = new bytes32[](2);
publicInputs[0] = treasureHash;
publicInputs[1] = bytes32(uint256(uint160(recipient))); // @> Proof is bound to recipient
bool isValid = verifier.verify(proof, publicInputs);
...
(bool sent, ) = recipient.call{value: REWARD}(""); // @> ETH correctly goes to recipient
...
emit Claimed(treasureHash, msg.sender); // @> Bug: emits msg.sender instead of recipient
}

Risk

Likelihood: HIGH

  • This will occur whenever a valid proof transaction is visible in the mempool before confirmation

  • MEV bots and front-runners actively monitor for valuable transactions to copy

  • The attacker simply copies the exact calldata and submits with higher gas

Impact: MEDIUM

  • Front-runners steal on-chain attribution for all treasure discoveries without solving any puzzles

  • Legitimate solvers receive their ETH rewards but lose all recognition/credit

  • Corrupts leaderboards, NFT distributions, or reputation systems based on

    Claimed

    events

  • In competitive CTF environments, this allows one attacker to claim credit for all 10 treasures

Proof of Concept

The vulnerability allows front-runners to steal attribution credit while the legitimate solver still receives the ETH reward. This attack is particularly damaging in competitive environments where reputation matters.

/ Setup: Alice solves a treasure and generates a valid ZK proof
address alice = address(0xA11CE);
address aliceColdWallet = address(0xC01D);
bytes memory validProof = generateProof(secretPreimage, treasureHashX, aliceColdWallet);
// Step 1: Alice submits her claim transaction to the mempool
// Transaction data: claim(validProof, treasureHashX, aliceColdWallet)
// Alice expects: 10 ETH to aliceColdWallet, Claimed event crediting alice
// Step 2: Front-runner bot (Bob) monitors mempool and sees Alice's transaction
address bob = address(0xB0B);
// Step 3: Bob copies Alice's exact calldata but submits from his address with higher gas
vm.prank(bob);
hunt.claim(validProof, treasureHashX, payable(aliceColdWallet)); // Same proof & recipient
// Result analysis:
assert(aliceColdWallet.balance == 10 ether); // Alice's wallet got the ETH (correct)
// But check the emitted event:
// Event: Claimed(treasureHashX, bob) // Bob gets the credit!
// NOT: Claimed(treasureHashX, alice) or Claimed(treasureHashX, aliceColdWallet)
// Impact on leaderboard/reputation system:
// Leaderboard query: "Who solved treasureHashX?"
// Answer from events: bob (wrong - Bob just front-ran, didn't solve anything)
// Alice solved the puzzle but gets no recognition
// Attack at scale:
contract FrontRunBot {
function stealAllCredit(TreasureHunt hunt) external {
// Monitor mempool for all claim() transactions
// Copy their calldata exactly
// Submit with higher gas from this contract
// Result: All Claimed events show this contract as solver
// Real solvers get ETH but no credit
}
}

Recommended Mitigation

Emit therecipientaddress instead ofmsg.senderin theClaimedevent, since the recipient is cryptographically bound to the proof:

- emit Claimed(treasureHash, msg.sender);
+ emit Claimed(treasureHash, recipient)

Support

FAQs

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

Give us feedback!