Please note, this content is no longer actively maintained.
The content of the SWC registry has not been thoroughly updated since 2020. It is known to be incomplete and may contain errors as well as crucial omissions.
For currently maintained guidance on known Smart Contract vulnerabilities written primarily as guidance for security reviewers, please see the EEA EthTrust Security Levels specification. As well as the latest release version, an Editor's draft is available, that represents the latest work of the group developing the specification.
General guidance for developers on what to consider to ensure security, that is currently maintained, is also available through the Smart Contract Security Verification Standard (SCSVS).
Title
Weak Sources of Randomness from Chain Attributes
Relationships
- CWE-330: Use of Insufficiently Random Values
- EthTrust Security Levels:
- [M] Sources of Randomness
- [M] Document Special Code Use
Description
Ability to generate random numbers is very helpful in all kinds of applications. One obvious example is gambling DApps, where pseudo-random number generator is used to pick the winner. However, creating a strong enough source of randomness in Ethereum is very challenging. For example, use of block.timestamp
is insecure, as a miner can choose to provide any timestamp within a few seconds and still get his block accepted by others. Use of blockhash
, block.difficulty
and other fields is also insecure, as they're controlled by the miner. If the stakes are high, the miner can mine lots of blocks in a short time by renting hardware, pick the block that has required block hash for him to win, and drop all others.
Remediation
- Using external sources of randomness via oracles, and cryptographically checking the outcome of the oracle on-chain. e.g. Chainlink VRF. This approach does not rely on trusting the oracle, as a falsly generated random number will be rejected by the on-chain portion of the system.
- Using commitment scheme, e.g. RANDAO.
- Using external sources of randomness via oracles, e.g. Oraclize. Note that this approach requires trusting in oracle, thus it may be reasonable to use multiple oracles.
- Using Bitcoin block hashes, as they are more expensive to mine.
References
- How can I securely generate a random number in my smart contract?
- When can BLOCKHASH be safely used for a random number? When would it be unsafe?
- The Run smart contract
Samples
guess_the_random_number.sol
/*
* @source: https://capturetheether.com/challenges/lotteries/guess-the-random-number/
* @author: Steve Marx
*/
pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
uint8 answer;
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}
guess_the_random_number_fixed.sol
/*
* @source: https://capturetheether.com/challenges/lotteries/guess-the-random-number/
* @author: Steve Marx
*/
pragma solidity ^0.4.25;
contract GuessTheRandomNumberChallenge {
uint8 answer;
uint8 commitedGuess;
uint commitBlock;
address guesser;
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
//Guess the modulo of the blockhash 20 blocks from your guess
function guess(uint8 _guess) public payable {
require(msg.value == 1 ether);
commitedGuess = _guess;
commitBlock = block.number;
guesser = msg.sender;
}
function recover() public {
//This must be called after the guessed block and before commitBlock+20's blockhash is unrecoverable
require(block.number > commitBlock + 20 && commitBlock+20 > block.number - 256);
require(guesser == msg.sender);
if(uint(blockhash(commitBlock+20)) == commitedGuess){
msg.sender.transfer(2 ether);
}
}
}
old_blockhash.sol
pragma solidity ^0.4.24;
//Based on the the Capture the Ether challange at https://capturetheether.com/challenges/lotteries/predict-the-block-hash/
//Note that while it seems to have a 1/2^256 chance you guess the right hash, actually blockhash returns zero for blocks numbers that are more than 256 blocks ago so you can guess zero and wait.
contract PredictTheBlockHashChallenge {
struct guess{
uint block;
bytes32 guess;
}
mapping(address => guess) guesses;
constructor() public payable {
require(msg.value == 1 ether);
}
function lockInGuess(bytes32 hash) public payable {
require(guesses[msg.sender].block == 0);
require(msg.value == 1 ether);
guesses[msg.sender].guess = hash;
guesses[msg.sender].block = block.number + 1;
}
function settle() public {
require(block.number > guesses[msg.sender].block);
bytes32 answer = blockhash(guesses[msg.sender].block);
guesses[msg.sender].block = 0;
if (guesses[msg.sender].guess == answer) {
msg.sender.transfer(2 ether);
}
}
}
old_blockhash_fixed.sol
pragma solidity ^0.4.24;
//Based on the the Capture the Ether challange at https://capturetheether.com/challenges/lotteries/predict-the-block-hash/
//Note that while it seems to have a 1/2^256 chance you guess the right hash, actually blockhash returns zero for blocks numbers that are more than 256 blocks ago so you can guess zero and wait.
contract PredictTheBlockHashChallenge {
struct guess{
uint block;
bytes32 guess;
}
mapping(address => guess) guesses;
constructor() public payable {
require(msg.value == 1 ether);
}
function lockInGuess(bytes32 hash) public payable {
require(guesses[msg.sender].block == 0);
require(msg.value == 1 ether);
guesses[msg.sender].guess = hash;
guesses[msg.sender].block = block.number + 1;
}
function settle() public {
require(block.number > guesses[msg.sender].block +10);
//Note that this solution prevents the attack where blockhash(guesses[msg.sender].block) is zero
//Also we add ten block cooldown period so that a minner cannot use foreknowlege of next blockhashes
if(guesses[msg.sender].block - block.number < 256){
bytes32 answer = blockhash(guesses[msg.sender].block);
guesses[msg.sender].block = 0;
if (guesses[msg.sender].guess == answer) {
msg.sender.transfer(2 ether);
}
}
else{
revert("Sorry your lottery ticket has expired");
}
}
}
random_number_generator.sol
pragma solidity ^0.4.25;
// Based on TheRun contract deployed at 0xcac337492149bDB66b088bf5914beDfBf78cCC18.
contract RandomNumberGenerator {
uint256 private salt = block.timestamp;
function random(uint max) view private returns (uint256 result) {
// Get the best seed for randomness
uint256 x = salt * 100 / max;
uint256 y = salt * block.number / (salt % 5);
uint256 seed = block.number / 3 + (salt % 300) + y;
uint256 h = uint256(blockhash(seed));
// Random number between 1 and max
return uint256((h / x)) % max + 1;
}
}