Blockhash Dependence
id: LS06H
title: Blockhash Dependence for Randomness
baseSeverity: H
category: randomness
language: solidity
blockchain: [ethereum]
impact: Predictable or manipulable outcomes due to limited blockhash access
status: draft
complexity: medium
attack_vector: external
mitigation_difficulty: medium
versions: [">=0.4.0", "<0.8.21"]
cwe: CWE-330
swc: SWC-120
📝 Description
- Blockhash dependence occurs when contracts use
blockhash(block.number - N)to derive randomness or decision logic. - While this may appear secure,
blockhash()is only reliable for the most recent 256 blocks** and is ultimately manipulable by miners, especially for the current or very recent blocks. - This makes it insecure for randomness, lottery draws, or other critical conditions.
🚨 Vulnerable Code
contract BlockhashLottery {
function getRandomNumber() public view returns (uint256) {
return uint256(blockhash(block.number - 1)) % 100; // ❌ insecure randomness
}
function drawWinner() external {
uint256 result = getRandomNumber();
if (result == 42) {
// payout logic
}
}
}
🧪 Exploit Scenario
Step-by-step exploit process:
- Attacker calls drawWinner() using a transaction at the current block.
- As a miner or via Flashbots, attacker can manipulate blockhash(block.number - 1) by reordering or withholding blocks.
- They simulate and submit only blocks where result == 42, ensuring they win.
- This undermines fairness and predictability of the draw.
Assumptions:
- The contract relies on recent blockhashes for randomness or security decisions.
- The attacker can simulate and choose when to broadcast transactions or blocks.
✅ Fixed Code
// Use verifiable randomness via Chainlink VRF
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
contract SecureLottery is VRFConsumerBase {
bytes32 internal keyHash;
uint256 internal fee;
uint256 public randomResult;
constructor() VRFConsumerBase(<vrfCoordinator>, <linkToken>) {
keyHash = <your_key_hash>;
fee = <your_fee>;
}
function getRandomNumber() public returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK");
return requestRandomness(keyHash, fee);
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
}
}
🧭 Contextual Severity
- context: "Lottery or game using blockhash for prize logic"
severity: H
reasoning: "Predictable randomness allows rigged wins."
- context: "Blockhash used for NFT trait randomization"
severity: M
reasoning: "User experience degraded but no financial loss."
- context: "Blockhash used for timestamps or logging only"
severity: I
reasoning: "No exploitability—informational only."
🛡️ Prevention
Primary Defenses
- Never use blockhash() for randomness or sensitive logic.
- Use verifiable randomness providers like Chainlink VRF, Witnet, or Redstone.
Additional Safeguards
- If blockhash is used, delay execution across multiple blocks to reduce predictability.
- Use commit-reveal schemes with trusted participants for low-cost alternatives.
Detection Methods
- Slither: weak-prng, blockhash-dependence detectors.
- Manual inspection of randomness logic relying on blockhash, especially recent ones.
- Symbolic analysis to check miner-controlled inputs.
🕰️ Historical Exploits
- Name: Roulette Smart Contract Exploit
- Date: 2018
- Loss: Potential manipulation of game outcomes
- Post-mortem: Link to post-mortem
📚 Further Reading
✅ Vulnerability Report
id: LS06H
title: Blockhash Dependence for Randomness
severity: H
score:
impact: 4
exploitability: 4
reachability: 4
complexity: 2
detectability: 4
finalScore: 4.0
📄 Justifications & Analysis
- Impact: Affects fairness and trust; attacker can rig outcomes in randomness logic.
- Exploitability: Easily done if attacker controls block production or uses private mempools.
- Reachability: Affects lotteries, NFT traits, game draws, and oracle fallback logic.
- Complexity: Requires block manipulation; not high but achievable via miner collusion.
- Detectability: Very detectable in code audits and static analysis.