How to Use Random in Smart Contracts the Correct Way
A couple of months ago we were doing a huge giveaway on Twitter and I wanted the result to be transparent and verifiable. And nothing suits “transparent and verifiable” much better than the blockchain itself. However, the concept of “random” is always tricky and it is especially tricky while developing smart contracts. So one Sunday night I went to Oáza (one of my favorite pubs in Prague) and started researching & working.
Here is my method of using random in smart contracts the proper way.
The Problem With Random
Although the concept of randomness feels natural to us, it is nonexistent for machines. In traditional programming, a piece of software usually generates a pseudo-random output using a seed. Simply, the random function takes an input (the seed), hashes it, and returns an output. And usually, the machine time is used for this seed so you can get pretty random outputs.
Imagine that you are running an online gambling website (for some reason gambling is the first thing that comes up to mind regarding random, even in academia). In that case, as you own the server and your software is closed (nobody except you knows how you generate your seed and your machine time), it is practically safe to use the above pseudo-random approach. However, it gets a little tricky on blockchain as it is too transparent, everybody knows all the parameters that can go into the random function. Thus, in case, you don’t want to run a casino that always loses, a different approach should be taken.
The Existing Tutorials
If you Google how to generate random numbers in Solidity, you will probably come across a code piece similar to this one:
Let’s dissect what this function does. Firstly abi.encodePacked method, packs a bunch of parameters together (Current blocks timestamp, its difficulty, and the address of the function caller in this case). Then keccak256 calculates a hash out of that data and it is cast to an unsigned integer(unit). Finally, the output is put into modulo operation to get an output between 0 and number-1. The “number” is a function call parameter and it sets the max number you can get as an output.
The problem is that all the parameters used in this function are public information. Anybody can read the block timestamp and difficulty at a given time and I assume everybody knows their wallet address. Encoding this information, hashing it, and casting it to an integer is hard for a human being (although not impossible) but it is piece of cake for a machine. This means if we use this function in our production software anyone can write a script to precalculate the output and only call our smart contract when it is favorable for them.
When you dive deeper into tutorials you will probably see something called the block hash. The block hash is basically a hash generated out of the information inside a block and it is an essential concept for blockchains to operate. Here is an example of a random function using the block hash:
For example, the hash of block number 15623764 on Ethereum looks like this:
0x66fb653992b2f2a152c7421b66d9cb321efa49e638f4a2ba42a1bfa5e96fe4fa .
It is unreadable and pretty “random” so it makes perfect sense to use it for our random function. However, there is one problem, contracts don’t know the current block hash as the current block hasn’t been finalized yet. They can only access the hash of the previous block and the previous block hash is also public information which causes the precomputing problem mentioned above.
The Solution
The solution is pretty simple actually: separating the random function into two parts to prevent precomputation. Let’s examine some code before understanding why this works.
The first function we need is a form of “prepare random” when it is called the variable preparationBlockNumber is set to the block number of the transaction that gets recorded to the blockchain. Also, note that there is another variable named canCallRandom which is a boolean that prevents the preparation method from getting called more than one time. In addition, the user should commit to the random in this function. For example, in the case your smart contract is a simple gambling application that gives the user double the money 50% of the time, the user should send the money in this function.
The second function we need to implement is the actual function where the random number is generated, the roll() function in the example above. When it is called, it executes the random() function which does the same integer casting, and modulo operation found in the other examples above. The key detail here is that the blockhash of the preparation block is used and although it is known now it wasn’t calculated yet when prepareRandom() was called.
This way although roll()’s outcome can be precalculated it is too late for the user to exploit it as they were already committed and roll()’s output is independent of when it is called. Also, canCallRandom variable is altered so that the prepare random can be called again. According to the coin flip casino example above, this function is also the place where you send the user double their money if the roll is successful.
A complete smart contract that can be used to produce an array of random numbers would look similar to the one below. (This is actually the contract we used for our giveaway.)
Closing Notes
There are many applications of random and how the smart contract would look will depend on your actual needs. Nevertheless, if you want your random function to be unexploitable it should follow these key principles:
1- The random should be split into two
2- The user should commit to the action in the first part (in the example of gambling they should send the money)
3- The data from the first part (which was non-existent before it was executed) should be used for the actually random number generation in the second part
4- The two functions should always be called one after another (if the preparation function can be called multiple times, the same precomputation exploit would still exist)
Hope you enjoyed reading, if you did I would be glad if you can show your love by clapping.e
I am also building up a public repository of essential smart contract implementations. If you are interested you can take a look at GitHub. (The final version of the random number generator is also included there.)
To stay up to date with me you can follow me on Twitter and please always feel free to ask your questions. Also, if you are interested in what we are developing at EasyBlock make sure to give us a follow and join our Discord.