If I am writing a smart contract for some kind of (gambling) game, how can I generate a random number securely? What are the best practises and security trade-offs to be aware of?
[Ethereum] How to securely generate a random number in the smart contract
contract-designgamblingSecurity
Related Solutions
Just to clarify, the "trivial solution" referred to is about how to produce a series of random numbers from a single random seed.
As a general rule, BLOCKHASH can only be safely used for a random number if the total amount of value resting on the quality of that randomness is lower than what a miner earns by mining a single block.
To see why this is the case, we can imagine the opposite situation, where a value of perhaps millions rests on the randomness obtained from a BLOCKHASH operation (for example, to select a lottery winner who will win that amount). Due to the large amount of money at stake, a well-funded attacker would have a financial incentive to buy a ticket and then generate many different alternative blocks (perhaps using millions of AWS instances for a short time period but at a substantial cost) for the block height from which the drawing is to be computed. When a block is found with a hash that will result in the miner's ticket winning, the miner then immediately mines more blocks on top of this one (to ensure it succeeds) and then submits it to the network, allowing them to guarantee themselves the prize. This operation might be very expensive, but as long as the prize is big enough it will still be a profitable attack to perform.
This is an extreme example, but the boring version of this attack where a miner just so happens to mine that one block, checks if they win, and then throws away the answer if they don't, would still be able to double their overall odds of winning and is still an "unfair" statistical manipulation, albeit a weak one. However, if throwing the block away costs more than anyone can hope to gain, it is highly unlikely that anyone will perform the attack, thus our general rule.
Responding to comments...
Although you say it does not require heavy security, I feel it is worthwhile to mention that a simple hash function would be predictable. For example, keccak256(now)
can be calculated, trivially, by another contract that calls into your contract. By working it out ahead of time, it will know, in advance, what your hash function will produce. This works because the time is "now" (same) in both contexts.
Setting the predictability concern aside, you would convert a hash function to a uint, e.g. uint(keccak256(now))
and this will yield a number in the range of 0-2^256-1. You would scale that number to the desired range and precision with divisors and offsets.
Hope it helps.
UPDATE
Scaling is a general programming concern not unique to Solidity. We're just going to take a number with known range and adjust it for another range.
pragma solidity 0.5.1;
contract Randomish {
uint public constant MAX = uint(0) - uint(1); // using underflow to generate the maximum possible value
uint public constant SCALE = 500;
uint public constant SCALIFIER = MAX / SCALE;
uint public constant OFFSET = 100;
// generate a randomish number between 100 and 600.
// Warning: It is trivial to know the number this function returns BEFORE calling it.
function randomish() public view returns(uint) {
uint seed = uint(keccak256(abi.encodePacked(now)));
uint scaled = seed / SCALIFIER;
uint adjusted = scaled + OFFSET;
return adjusted;
}
}
Best Answer
There are a few trade-offs and key points to keep in mind in this area.
Any decision that a user makes which affects the outcome gives that user an unfair advantage. Examples include:
Everything that the contract sees, the public sees.
The EVM will not outrace a physical computer.
Now for the technique:
Perfectly Decentralized Lottery-Style Non-Malleable Commitment:
N
N
with their address:bytes32 hash = sha3(N,msg.sender)
1Once the submission round has ended, the reveal round begins.
N
to the contractsha3(N,msg.sender)
matches the original submissionIf the user does not submit a valid
N
in time, his deposit is forfeit.N
s are allXOR
'd together to generate the resulting random number.N % numUsers
)3Notes and alternatives:
1. The users must concatenate their address to their
N
before hashing. Otherwise, another user could simply submit an identical hash, then wait forN
to be revealed, then submit that.N XOR N
equals 0, so this could be used to cancel out all submissions except the attacker's.2. This is where the trade-offs come in. The last person to reveal their
N
has a choice whether to reveal or to not reveal. This essentially gives them a double chance at winning. Enter enough times, and you get a new choice for each entry. Hint: Miners chose the order of transactions in a block. In order to discourage this, users must put up a large security deposit, equal to the value they would gain by manipulating the random number. This could be a problem for many users, especially for large jackpots, even with game-theoretic optimizations.3. A reward is necessary in order to foster competition among participants. It causes a classic tragedy-of-the-commons/prisoner's dilemma situation. Collusion between participants would allow them to win a large pot and split it among themselves, but if everyone knows what everyone else will submit, they know what they themselves should submit to win the reward. Thus, if the reward is larger than the value of the random number divided by the number of players, then everyone is incentivised to keep their own number a secret in order to have a better shot at the reward. Note that only one of the participants needs to submit a good random number, and the result will be unpredictable.
Examples: Chainlink VRF, RANDAO, The thread where I first thought through this