Solidity Pitfalls: Random Number Generation for Ethereum

    Michiel Mulders
    Share

    This article was created in partnership with iOlite. Thank you for supporting the partners who make SitePoint possible.

    Solidity is a fairly new language and as no code is perfect, it contains issues related to the code and what you want to accomplish with it. This article will guide you through the best practices and pitfalls when using a random number as input for your Ethereum smart contract.

    Solidity Random Number Generation

    Solidity is not capable of creating random numbers. Actually, every algorithm for creating random numbers is pseudorandom — no language is capable of creating completely random numbers. The problem with Solidity is that complex algorithms cost too much, so more basic solutions are used. Besides that, Solidity code should be deterministic, as it will run on multiple nodes. We need an algorithm that is able to generate a random number once, and use it on multiple nodes. Things like a clock time are not available for generating random numbers, so we have to look at other options. As a developer, you should be aware of this problem because an attacker is able to predict the result in some specific cases.

    One of the most commonly used algorithms is ‘linear congruential generator’ (LCG). It’s one of the oldest algorithms, fast, and easy to understand. LCG is a good option for embedded systems as they have a limited amount of memory. However, it is not well-suited for cryptographically secure applications. Although, this is still being used in smart contracts as a fast algorithm is much cheaper to implement in terms of gas costs.

    The algorithm itself performs these steps:

    • Accept input
    • Execute algorithm on the input
    • Take modulus of output (divide by max number in the range you require)
    • Output between 0 and max number in the range you require

    Let’s explore the different ways of creating random numbers using a Lottery smart contract example. Users are able to join the Lottery by sending 0.1 Ether to the contract together with an integer between 0 and 250.

    1. Block.timestamp & Block.difficulty

    A block.timestamp is assigned by the miner whenever he confirms the transaction. No player of our Lottery contract is able to control it. Let’s take a look at this piece of code for creating a random number.

    function random() private view returns (uint8) {
      return uint8(uint256(keccak256(block.timestamp, block.difficulty))%251);
    }
    

    Find the Gist here.

    This piece of code first hashes a block timestamp and difficulty. Next, we convert the hash to an integer and divide it by 251 to get an integer between 0 and 250. However, the problem with this piece of code is that we shouldn’t trust a miner for picking a winner.

    2. Lottery Input – Arbitrary Data

    We need more arbitrary data for picking our winner. We can use the addresses of the players that have entered our lottery smart contract, but we have to hide it from other players as they can abuse it. Hiding this information is not possible as it’s all recorded on the blockchain.

    The numbers submitted to our lottery smart contract can be used. Users have to hash the number they pick together with their Ethereum address. This gives us a pretty random number.

    3. Chainlink VRF

    This all being said, randomness is achievable, but you just have to use an oracle to get a random number from outside the blockchain. The issue with working with external data is that it’ll be really difficult to prove that the number is actually random, and make sure the entity off-chain isn’t manipulating the random number in any way. This is where Chainlink VRF comes into play. Chainlink VRF (Verifiably Random Function) is how we can get provably random numbers in solidity.

    Chainlink VRF adds an event to the blockchain that the Chainlink node reads from, and returns with a random number. There is an on-chain randomness check that happens through what’s called the VRF Coordinator. This uses a specific key hash from the oracle and a seed phrase from the user mixed with some cryptography to make sure the number is truly random. This way, we can have an unbiased random number.

    4. Other Mechanisms

    4.1 Ethereum Alarm Clock

    Developers need to think about when to pick a winner. Things like clock time are not available in the Ethereum Virtual Machine, as the code will run on multiple nodes, on a different time. This makes it even harder to pick a winner. One way to do it is by implementing a function in your smart contract that will close the lottery and pick a winner. This is not as decentralized as we want it to be. The owner of the contract can close the lottery when he is sure a friend of theirs will win. We want to avoid this kind of cheating.

    A better option is to use the Ethereum Alarm Clock. It is a service that allows scheduling transactions to be executed at a later time on the Ethereum blockchain. This service is completely trustless, meaning that the entire service operates as a smart contract. Basically, the Ethereum Alarm Clock is using the block number to schedule transactions. Watch out, it doesn’t mean that the contract wakes up on its own. It relies on users having an interest (Ether reward) for calling the ‘pick winner’ function. Of course, if nobody calls your function, your lottery will fail.

    4.2 Random Data Input

    Random.org provides an API which gives you a random source of data through JSON. An Ethereum smart contract can use this source of data to feed an algorithm that picks a random number. As security is important, it is possible to use digital signing. The random data will be signed by Random.org. You can verify the integrity of the data so you can prove it really came from Random.org and the data hasn’t been tampered with.

    RANDAO is a new project in the blockchain space that is solely focused on providing random numbers. They use a combination of oracles and smart contracts to provide you with random numbers. However, the RANDAO service is pretty slow at the moment. This is not ideal if you have an application that is often used.

    4.3 Blocknumber Watcher

    You can also use a watcher in your code, which checks the block number until it matches the target number you have set.

    function f( blocknumber, to_address, value_) { 
      var filter = web3.eth.filter('latest').watch(
        function(err, blockHash) { 
    
          var target=blocknumber; 
    
          if(web3.eth.blockNumber==target) { 
            filter.stopWatching(); // your function here 
            web3.eth.sendTransaction({to:to_address, from:web3.eth.coinbase, value: web3.toWei(value_,"ether")});
            filter = null; 
    
            console.warn('Block reached'); 
    
            if (callback) return callback(false);
            else return false;
    
          } else { 
            console.log('Waiting the block'); 
          } 
      }); 
    };
    

    Source. Gist.

    4.4 iOlite Smart Contract Creation

    iOlite is creating a product that accepts natural language to create smart contracts. It is using a Stanford Natural Language Processing (NLP) engine called Fast Adaptation Engine (FAE). iOlite relies on community training by Solidity experts. Solidity experts (contributors) can define structures containing one or more sentences and attach it to the corresponding smart contract code.

    The Stanford NLP engine has been created to understand complex language. The level of language complexity depends on the amount of machine training. After appropriate training, the engine will be capable of creating complex smart contracts. The FAE is capable of creating such contracts, as a complex contract is actually not that complex. Experts can split the request into multiple smaller pieces of code and attach it to one sentence.

    When someone inputs multiple sentences, it will look for corresponding structures/sentences to build the ‘complex’ contract. Contributors will be rewarded with iOlite tokens via the mining process of new structures.

    The benefit of using iOlite is that smart contract experts can solve difficult problems for you like the generation of random numbers. You can find more information at iOlite.io.

    Conclusion

    As you can see, it isn’t an easy task to generate true random input. Do not rely on block.timestamp, now and block.blockhash as a source of randomness. A good solution includes a combination of several pseudorandom data inputs and the use of oracles or smart contracts to make it more reliable. You need to be 100% sure nobody can tamper with the data that’s being inputted into your smart contract.

    Be careful and think twice before implementing random number generation logic.