Weak Randomness
Detects use of predictable on-chain values — blockhash, block.timestamp, block.prevrandao — as sources of randomness, which miners or validators can manipulate.
Weak Randomness
Overview
Remediation Guide: How to Fix Weak Randomness
The weak randomness detector flags functions that use predictable block properties — block.timestamp, blockhash, block.difficulty, or (post-Merge) block.prevrandao — as the sole or primary source of entropy for outcomes that have economic value. Miners (pre-Merge) and validators (post-Merge) have influence over these values and can withhold unfavorable blocks to bias outcomes.
Sigvex detects this by scanning for the relevant EVM opcodes (TIMESTAMP, BLOCKHASH, DIFFICULTY, PREVRANDAO) in functions that also contain conditional branching or value transfers whose outcome depends on a comparison against a hashed or manipulated form of those values. The detector also recognizes patterns where keccak256 is applied to block properties in an attempt to create randomness that is still predictable.
Why This Is an Issue
On-chain randomness has no truly unpredictable source without a commit-reveal scheme or a verifiable random function (VRF). A validator producing a block knows block.timestamp and block.prevrandao before committing. If the gamble result is unfavorable, they can simply withhold the block and try again with a different timestamp. The cost is one missed block reward; the gain can be much larger in high-stakes contracts.
Notable exploits include: FOMO3D (2018, miners selectively withheld blocks to win the jackpot), Lottery.sol (multiple incidents), and various NFT minting contracts where blockhash determined mint rarity.
How to Resolve
// Before: Vulnerable — randomness from block properties
function rollDice() external returns (uint256) {
uint256 randomNumber = uint256(
keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender))
) % 6; // PREDICTABLE: validator knows this before block is finalized
return randomNumber;
}
// After: Use Chainlink VRF for verifiable randomness
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
contract SecureDice is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface private vrfCoordinator;
bytes32 private keyHash;
uint64 private subscriptionId;
mapping(uint256 => address) private requestToPlayer;
function rollDice() external returns (uint256 requestId) {
requestId = vrfCoordinator.requestRandomWords(
keyHash, subscriptionId, 3, 100000, 1
);
requestToPlayer[requestId] = msg.sender;
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
internal override
{
uint256 result = (randomWords[0] % 6) + 1;
// Use result for the player stored in requestToPlayer[requestId]
}
}
Examples
Vulnerable Code
contract VulnerableLottery {
address[] public participants;
uint256 public jackpot;
function enterLottery() external payable {
require(msg.value == 0.1 ether);
participants.push(msg.sender);
jackpot += msg.value;
}
function pickWinner() external {
require(participants.length > 0);
// VULNERABLE: block.timestamp and blockhash are known to validators
uint256 seed = uint256(
keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp))
);
uint256 winnerIdx = seed % participants.length;
address winner = participants[winnerIdx];
delete participants;
payable(winner).transfer(jackpot);
jackpot = 0;
}
}
Fixed Code
// Off-chain VRF or commit-reveal scheme
contract SecureLottery {
// Commit-reveal pattern
mapping(address => bytes32) public commitments;
uint256 public revealDeadline;
function commit(bytes32 commitment) external payable {
require(msg.value == 0.1 ether);
commitments[msg.sender] = commitment;
}
function reveal(uint256 secret) external {
require(block.timestamp <= revealDeadline);
bytes32 commitment = keccak256(abi.encodePacked(secret, msg.sender));
require(commitments[msg.sender] == commitment, "Invalid reveal");
// Combine all revealed secrets for final randomness
}
}
Sample Sigvex Output
{
"detector_id": "weak-randomness",
"severity": "high",
"confidence": 0.91,
"description": "Function pickWinner() uses block.timestamp and blockhash as randomness sources. Validators can manipulate these values to bias lottery outcomes.",
"location": { "function": "pickWinner()", "offset": 156 }
}
Detection Methodology
- Entropy source detection: Identifies uses of
TIMESTAMP,BLOCKHASH,DIFFICULTY,PREVRANDAOopcodes. - Randomness derivation tracking: Checks whether these values flow through
keccak256or other hash operations intended to produce randomness. - Economic outcome detection: Determines whether the derived value influences a
MODULOoperation,JUMPIbranching, or token/ether transfer that has financial impact. - VRF recognition: Identifies calls to Chainlink VRF coordinator contracts and suppresses findings for those functions.
Limitations
False positives:
- Timestamp usage for non-randomness purposes (deadlines, cooldowns) may sometimes be caught if the pattern resembles randomness derivation.
blockhashused for linking blocks or merkle proofs — not as randomness — can trigger false positives.
False negatives:
- Commit-reveal schemes that are not sufficiently random (e.g., low-entropy user inputs) are not detected.
- Multi-party computation schemes that appear as simple hash operations may be missed.
Related Detectors
- Timestamp Dependence — detects reliance on block.timestamp for time logic