Skip to main content

randomness

Auto-loaded by logic-auditor agent during Phase 2. Provides detection patterns for: block.timestamp/blockhash misuse, predictable randomness, Chainlink VRF integration, commit-reveal schemes, RANDAO on L2. Core artifact: Randomness Source Analysis.

Jump to install

Source facts

Repository
BitterSecurity/Vigilo
Last source activity
February 2, 2026 at 05:07
Detected SKILL.md language
English
Stars
66
Forks
17

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
randomness
description
Auto-loaded by logic-auditor agent during Phase 2. Provides detection patterns for: block.timestamp/blockhash misuse, predictable randomness, Chainlink VRF integration, commit-reveal schemes, RANDAO on L2. Core artifact: Randomness Source Analysis.
user-invocable
false
# Insecure Randomness Vulnerability Analysis **OWASP SC09:2025** - Insecure Randomness allows attackers to predict or manipulate "random" outcomes for profit. **2025-2026 Statistics**: Randomness exploits caused $85M+ in losses, with NFT mint exploits and lottery manipulations being the most common vectors. --- ## Why Randomness Fails (Root Causes) ### Root Cause 1: On-Chain Data is Public Everything on-chain is visible to everyone, including miners/validators. ```solidity // VULNERABLE: Uses public blockchain data function roll() external returns (uint256) { return uint256(keccak256(abi.encodePacked( block.timestamp, // @audit Miner controls this block.difficulty, // @audit Known before execution msg.sender // @audit Attacker controls this ))) % 100; } ``` **Attacker's view**: "I can simulate this off-chain and only submit when I win." ### Root Cause 2: Miner/Validator Control Block proposers control block.timestamp and can reorder transactions. ```solidity // VULNERABLE: Miner can manipulate function lottery() external { require(block.timestamp % 10 == 0); // @audit Miner waits for favorable timestamp winner = msg.sender; } ``` ### Root Cause 3: Predictable Future State Using future blockhash that can be predicted or influenced. ```solidity // VULNERABLE: Blockhash only available for 256 blocks function reveal(uint256 commitBlock) external { require(block.number > commitBlock + 10); uint256 random = uint256(blockhash(commitBlock + 10)); // @audit Returns 0 after 256 blocks! } ``` ### Root Cause 4: Single-Transaction Exploitation Randomness generated and used in same transaction can be front-run or simulated. ```solidity // VULNERABLE: Same-tx randomness function mint() external { uint256 tokenId = uint256(keccak256(abi.encodePacked(block.timestamp, totalSupply))); _mint(msg.sender, tokenId); // @audit Attacker simulates result, only mints if rare } ``` --- ## The Randomness Source Analysis (Core Artifact) For each randomness usage, document: | Function | Source | Controller | Predictability | Attack Vector | |----------|--------|------------|----------------|---------------| | lottery() | block.timestamp | Miner | 100% | Miner manipulation | | roll() | blockhash | Anyone | 100% | Off-chain simulation | | mint() | tx + state | Attacker | 100% | Selective execution | | draw() | VRF | None | 0% | Secure | --- ## Detection Patterns ### Pattern 1: block.timestamp as Randomness **Root Cause**: Miner/Validator Control ```solidity // VULNERABLE: Timestamp is miner-controllable function randomize() public view returns (uint256) { return uint256(keccak256(abi.encodePacked(block.timestamp))) % 100; } function flipCoin() external { if (uint256(block.timestamp) % 2 == 0) { // @audit Binary outcome, easily manipulated _winner(msg.sender); } } ``` **Attack Flow**: 1. Attacker (or colluding validator) sees pending lottery 2. Adjusts block.timestamp within allowed range (~15 seconds) 3. Picks timestamp that gives winning result 4. Profits from "random" outcome **Search Queries**: ``` Grep("block\\.timestamp.*%|block\\.timestamp.*random", glob="**/*.sol") Grep("keccak256.*block\\.timestamp", glob="**/*.sol") ``` ### Pattern 2: blockhash Exploitation **Root Cause**: Predictable Future State ```solidity // VULNERABLE: blockhash is predictable and limited function getRandomNumber(uint256 blockNumber) public view returns (uint256) { return uint256(blockhash(blockNumber)); // @audit blockhash(block.number) = 0 // @audit blockhash(block.number - 257) = 0 (expired) } // VULNERABLE: Future blockhash commit scheme mapping(address => uint256) public commitBlock; function commit() external { commitBlock[msg.sender] = block.number; } function reveal() external { uint256 bn = commitBlock[msg.sender]; require(block.number > bn + 10, "Too early"); uint256 random = uint256(blockhash(bn + 10)); // @audit Returns 0 after 256 blocks! // ... } ``` **Attack Flow**: 1. Attacker commits 2. Waits 257 blocks 3. blockhash returns 0 4. Outcome becomes deterministic **Search Queries**: ``` Grep("blockhash\\(|block\\.prevrandao", glob="**/*.sol") Grep("block\\.number.*\\-.*256|block\\.number.*>.*256", glob="**/*.sol") ``` ### Pattern 3: prevrandao (RANDAO) Misuse **Root Cause**: Validator can bias RANDAO ```solidity // DANGEROUS on L1, CRITICAL on L2 function getRandomFromPrevrandao() public view returns (uint256) { return block.prevrandao; // @audit On L2, sequencer fully controls this! } ``` **L1 vs L2 Differences**: | Chain | prevrandao Behavior | Manipulation Risk | |-------|---------------------|-------------------| | Ethereum L1 | RANDAO mixing | Low (but biasable) | | Arbitrum | L1 block hash | Medium | | Optimism | L1 block hash | Medium | | Base | L1 block hash | Medium | | zkSync | Sequencer controlled | **CRITICAL** | **Search Queries**: ``` Grep("block\\.prevrandao|block\\.difficulty", glob="**/*.sol") Grep("PREVRANDAO|RANDAO", glob="**/*.sol") ``` ### Pattern 4: msg.sender/tx.origin in Randomness **Root Cause**: Attacker Controls Input ```solidity // VULNERABLE: Attacker controls msg.sender function random() public view returns (uint256) { return uint256(keccak256(abi.encodePacked( msg.sender, // @audit Attacker generates addresses until favorable block.number ))); } ``` **Attack Flow**: 1. Attacker generates many addresses 2. Simulates random() for each 3. Uses address that gives winning result 4. Deploys contract from that address or uses that EOA **Search Queries**: ``` Grep("keccak256.*msg\\.sender|msg\\.sender.*random", glob="**/*.sol") Grep("tx\\.origin.*random|random.*tx\\.origin", glob="**/*.sol") ``` ### Pattern 5: Single-Transaction NFT Rarity Exploitation **Root Cause**: Same-tx randomness is simulatable ```solidity // VULNERABLE: Rarity determined at mint time function mint() external payable { uint256 tokenId = totalSupply + 1; uint256 random = uint256(keccak256(abi.encodePacked( tokenId, block.timestamp, msg.sender ))); if (random % 100 < 5) { _mintLegendary(msg.sender, tokenId); // @audit 5% chance, attackers ONLY mint these } else { _mintCommon(msg.sender, tokenId); } } ``` **Attack Flow**: 1. Attacker deploys contract 2. Contract simulates mint result 3. Only executes if legendary 4. Reverts otherwise (wasting only gas) 5. Attacker mints only rare NFTs **Search Queries**: ``` Grep("mint.*random|random.*mint|rarity|legendary", glob="**/*.sol") Grep("keccak256.*totalSupply|keccak256.*tokenId", glob="**/*.sol") ``` --- ## Secure Randomness Patterns ### Pattern 1: Chainlink VRF v2.5 ```solidity import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2Plus.sol"; contract SecureLottery is VRFConsumerBaseV2Plus { uint256 private requestId; mapping(uint256 => address) public requestToPlayer; function enterLottery() external payable { // Request randomness requestId = s_vrfCoordinator.requestRandomWords( VRFV2PlusClient.RandomWordsRequest({ keyHash: keyHash, subId: subscriptionId, requestConfirmations: 3, callbackGasLimit: 100000, numWords: 1, extraArgs: VRFV2PlusClient._argsToBytes( VRFV2PlusClient.ExtraArgsV1({nativePayment: false}) ) }) ); requestToPlayer[requestId] = msg.sender; } function fulfillRandomWords( uint256 _requestId, uint256[] calldata randomWords ) internal override { // Use randomWords[0] for lottery address player = requestToPlayer[_requestId]; uint256 result = randomWords[0] % 100; // ... } } ``` ### Pattern 2: Commit-Reveal Scheme ```solidity contract CommitReveal { mapping(address => bytes32) public commits; mapping(address => uint256) public commitBlock; function commit(bytes32 hash) external { commits[msg.sender] = hash; commitBlock[msg.sender] = block.number; } function reveal(uint256 secret) external { require(block.number > commitBlock[msg.sender] + 10, "Too early"); require(block.number < commitBlock[msg.sender] + 256, "Too late"); require(keccak256(abi.encodePacked(secret)) == commits[msg.sender], "Bad reveal"); // Combine secret with future blockhash uint256 random = uint256(keccak256(abi.encodePacked( secret, blockhash(commitBlock[msg.sender] + 10) ))); delete commits[msg.sender]; // Use random... } } ``` ### Pattern 3: Multiple Party Randomness (RANDAO-style) ```solidity contract MultiPartyRandom { mapping(address => bytes32) public commits; mapping(address => uint256) public reveals; address[] public participants; function commit(bytes32 hash) external { commits[msg.sender] = hash; participants.push(msg.sender); } function reveal(uint256 secret) external { require(keccak256(abi.encodePacked(secret)) == commits[msg.sender]); reveals[msg.sender] = secret; } function finalize() external returns (uint256) { uint256 combined; for (uint i = 0; i < participants.length; i++) { combined ^= reveals[participants[i]]; } return combined; // One honest participant = unbiased randomness } } ``` --- ## Randomness Audit Checklist ### Source Analysis - [ ] No block.timestamp for randomness - [ ] No blockhash for critical randomness - [ ] No msg.sender in random seed - [ ] No same-tx random generation + use ### VRF Integration (if applicable) - [ ] Correct VRF version (v2.5 recommended) - [ ] Adequate confirmations - [ ] Request ID properly tracked - [ ] Fulfillment properly validated ### Commit-Reveal (if applicable) - [ ] Commit before reveal enforced - [ ] Time bounds prevent blockhash expiry - [ ] Secret properly validated - [ ] Cannot reveal early ### L2 Considerations - [ ] prevrandao not used on L2 - [ ] Sequencer manipulation considered - [ ] Alternative randomness for L2 --- ## Search Query Reference ``` # Find randomness sources Grep("block\\.timestamp|block\\.number|blockhash", glob="**/*.sol") Grep("block\\.prevrandao|block\\.difficulty", glob="**/*.sol") Grep("keccak256.*abi\\.encodePacked", glob="**/*.sol") # Find VRF usage Grep("VRF|requestRandomWords|fulfillRandomWords", glob="**/*.sol") Grep("Chainlink|randomness", glob="**/*.sol") # Find commit-reveal Grep("commit|reveal|hash.*secret", glob="**/*.sol") # Find lottery/gaming Grep("lottery|random|dice|flip|roll|mint.*rare", glob="**/*.sol") ``` --- ## Severity Classification ### Critical - Lottery/raffle outcome predictable - NFT rarity manipulation possible - High-value randomness exploitable ### High - Partial randomness bias possible - Commit-reveal timing exploitable - VRF integration flawed ### Medium - Low-value randomness biasable - Minor outcome manipulation - Missing commit-reveal validation --- ## Rationalization Table (Reject These Excuses) | Excuse | Reality |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub