Rock Paper Scissors

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

[M-1] Predictable Commit Due to Salt Reuse (Lack of Fresh Entropy)

Description:
The commitMove mechanism relies on keccak256(abi.encodePacked(move, salt)) to conceal a player's move until the reveal phase. However, the contract does not enforce or validate that the salt is unique per game or per turn. If a player reuses a salt that was revealed in a previous game, an attacker can reconstruct the commit off-chain and predict the move before the reveal.

Impact:

  • A malicious opponent can detect repeated commit hashes by comparing them with previous publicly revealed moves and salts.

  • This breaks the secrecy and fairness of the commit-reveal scheme.

  • Allows front-running and unfair strategic advantage to a player who monitors the chain.

Proof of Concept:

function test_ReusedSaltCanBePredicted() public {
uint8 move = 1; // Rock
bytes32 salt = keccak256("abc123");
bytes32 commit = keccak256(abi.encodePacked(move, salt));
// Simulate reuse
bytes32 newCommit = keccak256(abi.encodePacked(move, salt));
// This confirms salt reuse yields identical commit
assertEq(commit, newCommit, "Commit should match, salt reused!");
console2.logBytes32(commit);
}

➡️ This demonstrates that any previously revealed salt and move combination can be used to predict future plays if reused.

Output (-vvvv)

forge test --mt test_ReusedSaltCanBePredicted -vvvv

Ran 1 test for test/RockPaperScissorsTest.t.sol:RockPaperScissorsTest
[PASS] test_ReusedSaltCanBePredicted() (gas: 7422)
Logs:
0x23404aa228ff393def922dd63f95302a2ceeb06b85dc7497ef67cb37deb3eac9
Traces:
[7422] RockPaperScissorsTest::test_ReusedSaltCanBePredicted()
├─ [0] console::log(0x23404aa228ff393def922dd63f95302a2ceeb06b85dc7497ef67cb37deb3eac9) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::assertEq(0x23404aa228ff393def922dd63f95302a2ceeb06b85dc7497ef67cb37deb3eac9, 0x23404aa228ff393def922dd63f95302a2ceeb06b85dc7497ef67cb37deb3eac9, "Commit should match, salt reused!") [staticcall]
│ └─ ← [Return]
└─ ← [Return]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.41ms (139.56µs CPU time)
Ran 1 test suite in 10.13ms (1.41ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation:

  • Enforce that each commit is unique per game and per turn using a mapping(bytes32 => bool) usedCommits.

  • Reject reused commit hashes:

require(!usedCommits[commit], "Commit already used");
usedCommits[commit] = true;
  • Recommend salts with high entropy (e.g., keccak256(abi.encodePacked(msg.sender, block.timestamp, nonce))) on the frontend.

This ensures that even if a player accidentally reuses their salt, the contract logic prevents it and preserves fairness.

Updates

Appeal created

m3dython Lead Judge about 2 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.