Skip to main content
ClaudeWave
Skill55 repo starsupdated 4mo ago

randomness

>

Install in Claude Code
Copy
git clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/randomness && cp -r /tmp/randomness/packages/opencode/skills/randomness ~/.claude/skills/randomness
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# 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 {