Ethereum Bad Randomness Vulnerability
Randomness is a critical component in many applications, including cryptographic protocols, gaming, and decentralized finance (DeFi). However, generating secure and unbiased randomness in Ethereum smart contracts poses significant challenges. This blog post provides an in-depth exploration of the issues related to bad randomness in Ethereum, its implications, real-world examples, and best practices for achieving secure randomness, aimed at a doctoral thesis level of detail.
Introduction to Randomness in Ethereum
Randomness in Ethereum smart contracts is used for various purposes, such as:
- Lottery and gambling applications
- Random assignment of tokens
- Non-fungible token (NFT) generation
- Decentralized finance protocols
Importance of Secure Randomness
Secure and unbiased randomness is crucial to ensure:
- Fairness in applications like lotteries and gaming
- Security in cryptographic protocols
- Trust in decentralized systems
Challenges of Generating Randomness in Ethereum
Generating true randomness in a deterministic environment like Ethereum is inherently challenging. Ethereum smart contracts run on the Ethereum Virtual Machine (EVM), which is deterministic, meaning that the same input will always produce the same output. This determinism is essential for achieving consensus across the network but complicates the generation of randomness.
Sources of Bad Randomness
- Block Attributes
- User Input
- Combination of Block Attributes and User Input
Block Attributes
Using block attributes such as block hash, block number, or timestamp as sources of randomness is common but problematic due to their predictability and manipulability by miners.
Example of Using Block Attributes
function getRandomNumber() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty)));
}
Here, the block timestamp and difficulty are used to generate a random number. However, miners can manipulate these values to their advantage.
User Input
Using user-provided input as a source of randomness can be exploited by malicious users who can predict and influence the outcome.
Example of Using User Input
function getRandomNumber(address user) public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(user, block.timestamp)));
}
A user can call this function with a carefully chosen address and time the call to manipulate the outcome.
Combination of Block Attributes and User Input
Combining block attributes with user input can provide slightly better randomness but still remains vulnerable to manipulation.
Real-World Examples of Bad Randomness
Fomo3D Game
Fomo3D, a popular Ethereum-based game, suffered from bad randomness issues. The game used block attributes to determine the winner, which allowed miners to exploit their control over block attributes to manipulate the game outcome.
Lottery Contracts
Many lottery contracts on Ethereum have used simple block attributes to generate random numbers. These contracts were exploited by attackers who could predict or influence the block attributes to win the lottery unfairly.
Best Practices for Generating Secure Randomness
Off-Chain Randomness
One effective approach to secure randomness is to generate it off-chain and then deliver it to the smart contract. This can be achieved using oracles.
Chainlink VRF (Verifiable Random Function)
Chainlink VRF is a popular solution that provides cryptographically secure randomness to smart contracts. It generates randomness off-chain and delivers it to the contract in a verifiable manner.
Example of Using Chainlink VRF
import “@chainlink/contracts/src/v0.8/VRFConsumerBase.sol”;
contract RandomNumberConsumer is VRFConsumerBase {
bytes32 internal keyHash;
uint256 internal fee;
uint256 public randomResult;
constructor() VRFConsumerBase(
0x61684817d5c5c14b0f000dd0912b8f4e63e447c1, // VRF Coordinator
0x514910771af9ca656af840dff83e8264ecf986ca // LINK Token
) {
keyHash = 0xAA77729D3466CA35AE8D28C6B162FBC19D2CBF394A0DCEAD0809D59A5FE7A9E6;
fee = 0.1 * 10 ** 18; // 0.1 LINK
}
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;
}
}
Commit-Reveal Schemes
Commit-reveal schemes involve a two-phase process where participants first commit to a value without revealing it and then reveal the value in a later phase. This method can mitigate some manipulation risks.
Example of Commit-Reveal Scheme
contract CommitReveal {
struct Commit {
bytes32 commitment;
bool revealed;
}
mapping(address => Commit) public commits;
uint256 public revealPeriod;
constructor(uint256 _revealPeriod) {
revealPeriod = _revealPeriod;
}
function commit(bytes32 _commitment) public {
commits[msg.sender] = Commit(_commitment, false);
}
function reveal(uint256 _value, bytes32 _nonce) public {
require(keccak256(abi.encodePacked(_value, _nonce)) == commits[msg.sender].commitment, “Invalid commitment”);
commits[msg.sender].revealed = true;
// Handle the revealed value
}
}
RANDAO
RANDAO is a decentralized autonomous organization that provides a source of randomness generated through collective participation. Each participant contributes a random value, and the final randomness is the aggregate of all contributions.
Example of RANDAO
contract RANDAO {
struct Contribution {
uint256 value;
bool revealed;
}
mapping(address => Contribution) public contributions;
uint256 public aggregateRandomness;
uint256 public revealCount;
function contribute(uint256 _value) public {
contributions[msg.sender] = Contribution(_value, false);
}
function reveal(uint256 _value) public {
require(contributions[msg.sender].value == _value, “Invalid value”);
contributions[msg.sender].revealed = true;
aggregateRandomness ^= _value;
revealCount++;
}
function finalize() public {
require(revealCount == expectedContributors, “Not all contributions revealed”);
// Use aggregateRandomness as the final randomness
}
}
Delay and Verification
Adding a delay between the generation of randomness and its use can help mitigate manipulation by giving time for verification and validation of the random value.
Conclusion
Bad randomness in Ethereum smart contracts can lead to significant security vulnerabilities and unfair outcomes in applications that rely on random values. By understanding the challenges and implications of generating randomness in a deterministic environment, developers can implement best practices such as using off-chain randomness, commit-reveal schemes, RANDAO, and delay and verification mechanisms to achieve secure and unbiased randomness.
Source
- ArXiv – Demystifying Random Number in Ethereum Smart Contract: Taxonomy, Vulnerability Identification, and Attack Detection: This study provides a systematic analysis of random number generation in Ethereum smart contracts, highlighting various vulnerabilities associated with bad randomness. It includes detailed explanations of how pseudo-random variables can be exploited and offers insights into vulnerability identification and attack detection. Read more here.
- ImmuneBytes – Bad Randomness in Solidity Smart Contracts: This article discusses the “nothing is secret” attack, a common vulnerability in Solidity smart contracts due to the use of predictable pseudo-random number generators. It outlines specific attack vectors, such as using block hashes, timestamps, and block numbers for randomness, and provides real-world examples and mitigation strategies. Read more here.
- USENIX – Understanding and Discovering Attacks on Ethereum Smart Contracts: This paper explores various attack scenarios on Ethereum smart contracts, including bad randomness. It provides an overview of how attackers can predict or influence random values in smart contracts, particularly in contexts like online games and lotteries. Read more here.