Rock Paper Scissors

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

Selective Reveal Exploitation in Commit-Reveal Mechanism Allows Second Player Advantage

Description

The RockPaperScissors contract implements a commit-reveal pattern where both players first commit to a hashed move, then reveal their actual moves. However, the revelation process is vulnerable to frontrunning, where the second player can see the first player's move in the transaction mempool before deciding whether to reveal their own move or allow the timeout, creating an unfair advantage.

Summary

When the first player reveals their move by calling the revealMove function, their choice becomes visible in the public mempool before being confirmed in a block. The second player can observe this move and make a strategic decision: if they would lose based on their committed move, they can simply abstain from revealing, forcing a timeout where they'll lose anyway (but without completing the game naturally). If they would win, they proceed with revealing. This selective revelation undermines the fairness the commit-reveal pattern is designed to ensure.

Vulnerability Details

// src/RockPaperScissors.sol (Line ~249-269)
function revealMove(uint256 _gameId, uint8 _move, bytes32 _salt) external {
Game storage game = games[_gameId];
require(msg.sender == game.playerA || msg.sender == game.playerB, "Not a player in this game");
require(game.state == GameState.Committed, "Game not in reveal phase");
require(block.timestamp <= game.revealDeadline, "Reveal phase timed out");
require(_move >= 1 && _move <= 3, "Invalid move");
Move move = Move(_move);
bytes32 commit = keccak256(abi.encodePacked(move, _salt));
if (msg.sender == game.playerA) {
require(commit == game.commitA, "Hash doesn't match commitment");
require(game.moveA == Move.None, "Move already revealed");
game.moveA = move;
} else {
require(commit == game.commitB, "Hash doesn't match commitment");
require(game.moveB == Move.None, "Move already revealed");
game.moveB = move;
}
emit MoveRevealed(_gameId, msg.sender, move, game.currentTurn);
// ...
}

The critical issue is that when a player reveals their move, the actual move value is visible in the transaction parameters before being confirmed on-chain. The emit MoveRevealed event also broadcasts this information publicly.

When one player reveals their move, the second player can:

  1. See the first player's move before it's confirmed

  2. Compare it against their own committed move to determine if they would win

  3. If they would lose, simply not reveal and let the timeout occur

  4. If they would win, proceed with revealing their move

This gives the second player a strategic advantage, as they can guarantee they never lose a game outright (only through timeout), while still collecting wins when possible.

Impact

  1. Fairness compromised: The second player to reveal has a significant advantage.

  2. Game theory undermined: The incentive is to wait for an opponent to reveal first.

  3. Selective completion: Games will likely only complete when favorable to the second revealer.

  4. Timeout abuse: Increased number of timeouts rather than natural game conclusions.

While this doesn't result in direct fund loss, it fundamentally undermines the fairness of the game and the commit-reveal mechanism, which is the core security pattern of the protocol.

Tools Used

Manual code review

Recommendations

  1. Implement a two-phase reveal process:

    // Phase 1: Submit encrypted reveals
    function submitEncryptedReveal(uint256 _gameId, bytes32 encryptedReveal) external {
    // Store encrypted reveal
    }
    // Phase 2: Submit decryption key
    function finalizeReveal(uint256 _gameId, bytes32 decryptionKey) external {
    // Decrypt and process reveal
    }
  2. Use a commit-reveal-with-timeout pattern:

    • Both players must reveal within a fixed timeframe

    • If only one reveals within the timeframe, they win

    • If neither reveals, game is cancelled

    • This ensures no advantage from being the second revealer

Updates

Appeal created

m3dython Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
m3dython Lead Judge about 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.