Smart Contract Security

Secure Randomness in Smart Contracts: Why It's So Hard and How to Get It Right

Kennedy OwiroDecember 17, 20258 min read

True randomness on a deterministic blockchain is impossible by definition. Every node must reach the same result, which means every input must be predictable. Yet many smart contracts need randomness — for games, NFT minting, lottery selection, and random assignment. Using block.timestamp, blockhash, or any other on-chain value as randomness is exploitable. Here's why and what to do instead.

Why On-Chain Randomness Fails

// ALL OF THESE ARE EXPLOITABLE:
uint256 random1 = uint256(blockhash(block.number - 1));  // Known to validators
uint256 random2 = uint256(keccak256(abi.encodePacked(block.timestamp)));  // Manipulable
uint256 random3 = uint256(keccak256(abi.encodePacked(block.difficulty)));  // Predictable post-merge
uint256 random4 = uint256(keccak256(abi.encodePacked(
    block.timestamp, block.number, msg.sender  // All known/manipulable
)));

Block proposers know all block-level variables before proposing. They can: choose not to propose unfavorable blocks (block withholding), manipulate timestamp within tolerance, or compute the "random" value in advance and act accordingly.

Solutions for Secure Randomness

1. Chainlink VRF (Recommended)

Chainlink VRF (Verifiable Random Function) generates provably fair random numbers off-chain, with on-chain proofs that the result wasn't manipulated.

import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2Plus.sol";

contract SecureLottery is VRFConsumerBaseV2Plus {
    function requestRandomWinner() external onlyOwner {
        requestRandomWords(VRFV2PlusClient.RandomWordsRequest({
            keyHash: keyHash,
            subId: subscriptionId,
            requestConfirmations: 3,
            callbackGasLimit: 200000,
            numWords: 1,
            extraArgs: ""
        }));
    }

    function fulfillRandomWords(uint256, uint256[] calldata randomWords) internal override {
        uint256 winnerIndex = randomWords[0] % participants.length;
        winner = participants[winnerIndex];
    }
}

2. Commit-Reveal with Multiple Parties

Multiple participants commit secret values, then reveal them. The final random number is the XOR or hash of all reveals. Secure as long as at least one participant is honest.

3. RANDAO (Post-Merge Ethereum)

Ethereum's RANDAO beacon (block.prevrandao) provides better randomness than block hash but is still manipulable by block proposers. Acceptable for low-stakes applications.

When to Use What

Use CaseRecommended Solution
Lottery / high-value gamesChainlink VRF
NFT trait randomnessChainlink VRF
Random assignment (low stakes)RANDAO / prevrandao
On-chain games (turn-based)Commit-reveal
  • ✅ Use Chainlink VRF for any randomness involving value
  • ✅ Never use block.timestamp, blockhash, or block.number as randomness
  • ✅ Handle the async nature of VRF (request → callback pattern)
  • ✅ Set appropriate confirmation blocks for VRF requests

Predictable randomness is the same as no randomness. Audit your RNG implementation before someone predicts your next winning number.

randomnessVRFChainlink VRFRNGsmart contract securitycommit-reveal
Share

Written by

Kennedy Owiro

Founder & CTO, Vultbase

14+ years building security and QA systems at scale. Background in fintech security and Web3 smart contract testing. Built Vultbase's Intelligence Engine with 1,200+ exploit patterns from $40B+ in historical DeFi losses.

Protect your protocol before launch.

Submit your smart contracts for automated security analysis powered by 1,200+ real exploit patterns.

Start Your Audit →