Eggstravaganza

First Flight #37
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Predictable Random Number Generation

Summary

The EggHuntGame.sol contract uses a pseudo-random number generation method that is susceptible to prediction due to reliance on publicly available and manipulatable blockchain data. This vulnerability can be exploited by malicious actors to consistently win the egg-finding game, undermining the fairness and integrity of the gameplay.

Vulnerability Details

The contract attempts to generate a pseudo-random number in the searchForEgg function using the following line:

uint256 random = uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))
) % 100;

Each of these inputs (block.timestamp, block.prevrandao, msg.sender, and eggCounter) is either predictable, manipulable, or publicly known:

  • block.timestamp: Can be influenced slightly by the block miner.

  • block.prevrandao: While an improvement over blockhash, it is still known at the time of execution.

  • msg.sender: Known to the user calling the function.

  • eggCounter: Public state variable, accessible to anyone.

This predictability enables players or bots to simulate or predict outcomes off-chain and only submit transactions when a successful result (e.g., finding an egg) is guaranteed. This was verified through automated testing.

PoC

A test was written to demonstrate that the pseudo-random number generator can yield repeatable values using known inputs:

add this to EggHuntGameTest.t.sol

function testPseudoRandomnessPredictability() public {
game.startGame(300); // Start game for 5 minutes
for (uint256 i = 0; i < 10; i++) {
vm.prank(alice); // Simulate Alice calling the function
game.searchForEgg();
// Extract the pseudo-random number from the EggHuntGame storage
uint256 lastEggCounter = game.eggCounter();
results[i] = uint256(
keccak256(
abi.encodePacked(
block.timestamp,
block.prevrandao,
alice,
lastEggCounter
)
)
) % 100;
// Log the generated pseudo-random number
console.log("Generated random number for iteration", i, ":", results[i]);
}
// Check if at least two generated values are the same, indicating predictability
for (uint256 i = 0; i < results.length; i++) {
for (uint256 j = i + 1; j < results.length; j++) {
if (results[i] == results[j]) {
emit log("Predictable pattern detected in pseudo-random values");
assert(false);
}
}
}
}

This test failed due to duplicate random values being generated, clearly indicating that the randomness is not secure or truly random.

Impact

If left unaddressed, malicious users can:

  • Simulate and find optimal times to call searchForEgg() off-chain.

  • Gain an unfair advantage by calling the function only when an egg will be found.

  • Potentially dominate the game and accumulate more NFTs than honest players.

  • Undermine trust in the game, disincentivizing fair participation.

Tools Used

Foundry

Recommendations

To mitigate this vulnerability, it is recommended to use a more secure source of randomness. Options include:

  1. Chainlink VRF (Verifiable Random Function): A decentralized and verifiable randomness provider.

    • Pros: Secure, fair, and unpredictable.

    • Cons: Requires off-chain integration and LINK tokens.

  2. Commit-reveal scheme: Have players commit a hashed value and later reveal it to determine randomness.

    • Pros: Doesn’t require external dependencies.

    • Cons: Adds complexity and delays.

  3. Post-game randomness: Use randomness at the end of the game to determine egg assignments retroactively.

    • Pros: Reduces the attack surface during gameplay.

    • Cons: Changes gameplay dynamics.

Updates

Lead Judging Commences

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

Insecure Randomness

Insecure methods to generate pseudo-random numbers

Support

FAQs

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