Rock Paper Scissors

First Flight #38
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

Early move reveal without mutual commitment leading to player disadvantage

Summary

The RockPaperScissors game contract contains a vulnerability in the revealMove() function. The function allows a player to reveal their move without verifying that both players have committed their moves first. This creates a front-running opportunity where an opponent can observe the revealed move and then commit a winning counter-move. Even more concerning, an attacker can fake a move commitment by submitting an empty 32 bytes hash (bytes32(0)) which emits an event, tricking the first player into revealing their move prematurely.

Vulnerability Details

The revealMove() function does not validate that both players have submitted valid commitments before allowing the move revelation. While the function checks that the game state is Committed, it does not ensure that both commitA and commitB contain valid (non-zero) commitments.

A malicious player can exploit this by waiting for the opponent to reveal first, then commit a winning counter-move.

Proof of Concept:

// Test revealing moves
function testRevealMoves() public {
gameId = createAndJoinGame();
bytes32 saltA = keccak256(abi.encodePacked("salt for player A"));
bytes32 commitA = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Rock), saltA));
vm.prank(playerA);
game.commitMove(gameId, commitA);
vm.startPrank(playerB);
// PlayerB fakes a commit to emit an event for playerA to think playerB is committed
game.commitMove(gameId, bytes32(0));
bytes32 saltB = keccak256(abi.encodePacked("salt for player B"));
bytes32 commitB = keccak256(abi.encodePacked(uint8(RockPaperScissors.Move.Paper), saltB));
// This is invoked after reading playerA's revealMove in mempool (frontrunning)
game.commitMove(gameId, commitB);
vm.stopPrank();
// Reveal moves
vm.prank(playerA);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Rock), saltA);
vm.prank(playerB);
game.revealMove(gameId, uint8(RockPaperScissors.Move.Paper), saltB);
}

Impact

  • It undermines the fairness of the game.

  • Impact is limited though by honest player to make sure not to prematurely reveal move even when event is emitted (since it might be fake).

Tools Used

  • Foundry

Recommendations

  • Modify the revealMove() function to check that both commitments are valid (non-zero) before allowing any revealing the moves.

  • If player goes on to reveal their move without the other player committing theirs, the turn should be invalidated.

Updates

Appeal created

m3dython Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
m3dython Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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