Eggstravaganza

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

Weak Randomness + Predictable Outcomes in a Proof-of-Stake Context

Summary

The EggHuntGame::searchForEgg function uses an insecure source of randomness that relies on predictable on-chain values. This makes the game's outcomes potentially manipulable by attackers, compromising the fairness of the egg hunt game.

Vulnerability Details

https://github.com/CodeHawks-Contests/2025-04-eggstravaganza/blob/f83ed7dff700c4319bdfd0dff796f74db5be4538/src/EggHuntGame.sol#L65-L81

The current implementation generates randomness using the following components:

  • block.timestamp - Can be slightly influenced by miners

  • block.prevrandao - More secure but still predictable in certain scenarios

  • msg.sender - Known value

  • eggCounter - On-chain value that can be monitored

An attacker could monitor the blockchain and submit a transaction when block.timestamp and eggCounter align favorably:

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

In Ethereum's Proof-of-Stake context:

  • Validators are selected in advance, making block proposers known approximately 2 epochs (64 blocks) ahead

  • While block.prevrandao provides better randomness than the previous block.difficulty, it's still derived from the RANDAO process which has its own limitations

  • Validators can still manipulate block.timestamp within a small window

  • Sophisticated validators could potentially withhold or reorder transactions to gain advantage

Impact

Even in a PoS environment, the weak randomness implementation allows validators or advanced users to:

  1. Calculate probable outcomes by knowing validator sequences and observing on-chain state

  1. Time transactions to coincide with favorable randomness conditions

  1. For validators themselves, potentially influence transaction ordering or inclusion

  1. Gain an unfair advantage in the egg hunt game

This undermines the fairness of the game and could lead to uneven NFT distribution.

Tools Used

Recommendations

Despite the move to Proof-of-Stake, truly secure randomness still requires external solutions. Chainlink VRF remains the recommended approach:

+ import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
- contract EggHuntGame is Ownable {
+ contract EggHuntGame is Ownable, VRFConsumerBase {
+ bytes32 internal keyHash;
+ uint256 internal fee;
+ mapping(bytes32 => address) public requestIdToSender;
+ constructor(address _eggNFTAddress, address _eggVaultAddress, address _vrfCoordinator, address _link, bytes32 _keyHash, uint256 _fee)
+ VRFConsumerBase(_vrfCoordinator, _link) Ownable(msg.sender) {
+ keyHash = _keyHash;
+ fee = _fee;
// Existing constructor logic
}
- function searchForEgg() external {
+ function searchForEgg() external returns (bytes32 requestId) {
require(gameActive, "Game not active");
require(block.timestamp >= startTime, "Game not started yet");
require(block.timestamp <= endTime, "Game ended");
- uint256 random = uint256(
- keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender, eggCounter))
- ) % 100;
+ requestId = requestRandomness(keyHash, fee);
+ requestIdToSender[requestId] = msg.sender;
+ }
+ function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
+ uint256 random = randomness % 100;
if (random < eggFindThreshold) {
eggCounter++;
eggsFound[requestIdToSender[requestId]] += 1;
eggNFT.mintEgg(requestIdToSender[requestId], eggCounter);
emit EggFound(requestIdToSender[requestId], eggCounter, eggsFound[requestIdToSender[requestId]]);
}
+ delete requestIdToSender[requestId];
}
}
Updates

Lead Judging Commences

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