Back

SC09: Insecure Randomness - Overview

What is Insecure Randomness?

Insecure Randomness occurs when a smart contract derives a "random" number from on-chain values that are predictable or manipulable. The contract treats data such as block.timestamp or a block hash as if it were secret and unpredictable, when in reality every one of those values is either public before the transaction executes, computable within the same transaction, or influenceable by the party producing the block. An attacker who can predict or bias the outcome wins whatever the randomness was meant to guard: a lottery draw, a game result, a rare NFT mint, or a reward allocation.

The root problem is fundamental to the platform. A blockchain is deterministic and fully public. Every node must be able to re-execute a transaction and arrive at exactly the same state, so nothing in the EVM is genuinely random. Every input a contract can read—block metadata, its own storage, the calldata, the caller—is visible to all participants and reproducible by all nodes. There is no native random() that is both on-chain and secret. Any randomness that looks unpredictable is only unpredictable to someone who has not bothered to compute it.

Core Concept

Insecure (predictable / manipulable) sources:
  block.timestamp            -> set by the block proposer within a tolerance
  blockhash(block.number-1)  -> known before you call; 0 for old/future blocks
  block.number               -> fully public and monotonic
  block.prevrandao           -> known to the proposer, biasable within limits
  block.difficulty           -> legacy alias of prevrandao post-Merge
  block.coinbase             -> chosen by the proposer
  gasleft()                  -> attacker controls gas sent
  msg.sender / nonce         -> attacker controls the address and account nonce
  keccak256(any of the above)-> hashing public inputs yields a public output

Secure (verifiable / commitment-based) sources:
  Chainlink VRF              -> off-chain randomness WITH an on-chain proof
  Commit-reveal + future data-> secret committed first, revealed later
  Threshold / drand beacons  -> externally produced, verifiable randomness

The pattern that makes these sources dangerous is not that the numbers "look non-random." A keccak256 hash of the timestamp looks perfectly scrambled. The problem is that the attacker can compute the identical value. If the contract computes winner = keccak256(block.timestamp, block.prevrandao) % players, then a contract calling into it in the same transaction can compute that exact expression first and only proceed when the answer is favourable.

Why It's Critical for Smart Contracts

Randomness on-chain concentrates several conditions that make weakness especially damaging:

Why Does This Matter?

Business Impact

Technical Impact

Technical Context

Why On-Chain Values Are Not Random

Each of the commonly abused sources fails for a specific, concrete reason. Understanding the exact failure mode is the point—"don't use block variables" is the rule, but knowing why tells you what a real fix must provide.

1. block.timestamp

uint256 winner = uint256(keccak256(abi.encodePacked(block.timestamp))) % total;

Why it fails: The block proposer chooses the timestamp within a network-accepted tolerance. A proposer who is also a player can try candidate timestamps until the draw favours them. Even without a malicious proposer, the timestamp is visible to any contract executing in that block, so the value is known at compute time.

2. blockhash(block.number - 1)

uint256 rand = uint256(blockhash(block.number - 1)) % total;

Why it fails: The hash of the previous block is already fixed and public by the time your transaction runs—any attacker contract can read the exact same value and branch on it. Worse, blockhash returns 0 for blocks older than 256 or for the current/future block, so contracts that reach for a "future" block hash silently get a constant.

3. block.prevrandao / block.difficulty

uint256 rand = uint256(keccak256(abi.encodePacked(block.prevrandao))) % total;

Why it fails: Post-Merge, block.difficulty is an alias for block.prevrandao, the beacon-chain RANDAO value. It is known to the proposer of the block in advance and it can be biased at the margin: a proposer can choose to skip its slot (forgoing the block reward) to influence the RANDAO mix when the prize outweighs the reward. It is a source of entropy for the consensus layer, not a secure randomness oracle for high-value application logic.

4. block.number, block.coinbase, gasleft()

uint256 rand = uint256(keccak256(abi.encodePacked(
    block.number, block.coinbase, gasleft()))) % total;

Why it fails: block.number is fully public and predictable. block.coinbase is chosen by the proposer. gasleft() depends on how much gas the caller sends and where in execution it is measured—the attacker controls both. Combining several attacker-known or attacker-controlled values does not create secrecy; the combination is just another value the attacker can compute.

5. msg.sender, tx.origin, and account nonce

uint256 rand = uint256(keccak256(abi.encodePacked(msg.sender, nonce))) % total;

Why it fails: The attacker chooses which address calls the contract (including deploying fresh contracts at addresses it can grind toward) and knows its own nonce. Anything seeded from the caller identity is under the attacker's control, not the contract's.

The Atomic "Compute-Then-Decide" Attack

The most important mental model is that an attacker rarely needs to predict the future. Because the EVM is composable and transactions are atomic, the attacker computes the outcome in the same transaction and only commits when it wins:

Attacker contract, single transaction:
  1. Compute the exact "random" value the target will use
     (same block variables, same math the target uses)
  2. IF the computed result is a WIN:
        call target.play()   // enter the lottery / mint / flip
     ELSE:
        revert()             // abort; pay only gas, leave no trace
  3. Repeat next block until a winning result appears

This defeats every source that is readable at execution time—which is all of the block and message variables. It does not require a malicious validator; any ordinary user can do it from a contract.

Sources of Randomness Compared

SourceWho knows / controls itSafe for value-bearing randomness?
block.timestampProposer sets it; public at executionNo
blockhash(n-1)Fixed and public before your tx runsNo
block.prevrandaoKnown to proposer; biasable by slot-skippingNo (not for high value)
block.number / coinbasePublic / proposer-chosenNo
gasleft(), msg.sender, nonceAttacker-controlledNo
Naive commit-revealReveal can be withheld or front-runOnly with penalties/timeouts
Chainlink VRF (or equivalent)Off-chain, delivered with a verifiable proofYes

Real-World Impact

The specifics below are described as classes of incident that have recurred across the ecosystem, not as claims about any one named victim. Predictable-RNG bugs are among the most repeatedly exploited categories in on-chain gaming and NFTs.

Case Class 1: Predictable Lottery / Raffle Draws

Weakness:

Impact:

Root Cause: Treating a public, execution-time value as a secret. The fix is externally verifiable randomness or a properly penalised commit-reveal.

Case Class 2: NFT Rarity / Mint-Order Sniping

Weakness:

Impact:

Root Cause: Assigning value-bearing rarity from data the minter can see or influence. Robust designs commit to the metadata/order independently of the mint transaction, or use a verifiable randomness beacon to shuffle after the mint closes.

Case Class 3: On-Chain Games of Chance (Dice / Coin-Flip / Slots)

Weakness:

Impact:

Root Cause: Resolving the outcome atomically from data available at bet time. Safe designs split the bet and the resolution across a randomness request and a later, proof-backed fulfilment.

Prevalence and Statistics

Insecure Randomness is a recognised entry in the OWASP Smart Contract Top 10 (2025) as SC09. It is a persistent finding in audits of any protocol that needs to pick winners, shuffle, or assign rarity.

Rather than cite precise loss figures (which vary by source and incident), the defensible picture is:

Note: exact loss totals differ between reports and years, and many exploited contracts are never publicly attributed. Treat any single figure as illustrative; the durable takeaway is that on-chain randomness derived from block or message data is predictable, cheap to exploit, and repeatedly abused.

Common Misunderstandings

Myth 1: "Hashing the block variables makes them random"

Reality: keccak256 is a deterministic, public function. Hashing public inputs produces a public output that anyone can recompute. The scramble hides nothing from an attacker who runs the same hash.

Myth 2: "prevrandao is the official randomness, so it's safe"

Reality: block.prevrandao is entropy for the consensus layer. It is known to the block proposer in advance and can be biased by a proposer willing to skip a slot. It is not designed to secure high-value application payouts.

Myth 3: "The attacker can't know the future block hash"

Reality: They don't need to. Contracts almost always read a hash that is already fixed at execution time, and attackers compute the outcome in the same transaction, reverting when they lose. "Future" block hashes, meanwhile, return 0 once out of range.

Myth 4: "Combining several block variables adds security"

Reality: Combining values that are each public or attacker-controlled yields a value that is still public or attacker-controlled. Ten predictable inputs do not make one unpredictable output.

Myth 5: "Only miners/validators can exploit this"

Reality: Validators can bias some values, but the far more common attack needs no special privilege at all: any user can deploy a contract that computes the result and reverts on a loss.

Myth 6: "Commit-reveal is automatically safe"

Reality: A commit-reveal scheme is only as strong as its handling of reveal withholding and reveal front-running. Without deposits, penalties, and timeouts, a participant who dislikes the pending outcome simply never reveals, or front-runs the reveal to change the result.

How Insecure Randomness Differs from Related Issues

AspectInsecure Randomness (SC09)Price Oracle Manipulation (SC02)Logic Errors (SC03)
Root cause"Random" value is predictable/biasableTrusted price feed is manipulableFlawed business rules
What the attacker gainsControls a supposedly-chance outcomeMispriced trades/liquidationsUnintended state transitions
Typical fixVerifiable randomness / commit-revealRobust, manipulation-resistant oraclesCorrect and test the logic
DetectionTrace RNG seed to block/msg dataTrace price to a spot sourceSpec review, invariant testing

Key Takeaways

  1. The chain has no native secret randomness—it is deterministic and public by design, so any on-chain-only seed is knowable.
  2. Block and message variables are all predictable or biasable—timestamp, block hash, prevrandao, coinbase, gasleft, sender, and nonce must never gate value.
  3. The killer attack is atomic compute-then-revert—an attacker computes the result and only plays when it wins, needing no privilege.
  4. Verifiable randomness is the robust answer—an oracle like Chainlink VRF supplies randomness with a cryptographic proof that the contract verifies.
  5. Commit-reveal works only with care—it needs deposits, penalties, and timeouts to resist reveal withholding and front-running.

How to Identify if You're Vulnerable

Ask these questions about your contract:

If you answered "yes" to the block-variable questions or "no" to the verifiable-randomness question, you likely have an exploitable randomness flaw today.

Next Steps