The great thing about the blockchain, is that all transactions are open and transparent.
The bad thing about the blockchain, is that all transactions are open and transparent.
For our most recent Ethereum dapp, People's Casino, we had the challenge of creating a random number generator (or RNG) in Solidity which was both fair and unpredictable. As you may know, there is no true randomness in computer programs, since given the same input, the same output should result.
That is why pseudo randomness, as it is called, is generated by the introduction of an outside "unpredictable" influence. This could be a timestamp, or mouse movements for example. In the case of the blockchain, we are out of luck, since any influence in the blockchain is known, and can thereby be calculated.
In our dapp People's Casino, players can buy their own Gambling Machines and let others play them. We needed a way to decide the fate of the spin of the slot machine.
For our first iteration of a RNG we used the following code:
function getRandomPercent() internal view returns (uint) {
return uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % 100;
}
Essentially the function gets a hash of the `block.timestamp` and `block.difficulty` and takes a modulo to make the result between 0 and 99.
We were happy with this at first, since the both the `block.timestamp` and `block.difficulty` are not known to anybody until this transaction is performed.
However we soon got alerted by a helpful friend in our community that this is not a safe solution, and demonstrated that a second contract could be written that started the bet, worked out what the result would be, and then cancelled the transaction if it did not have a satisfactory result.
BACK TO THE DRAWING BOARD!
We then looked into the possibility of using a third party Oracle such as Provable. Provable charges fees for calls to it's api (off chain), which in turn return the data back (to the blockchain). We found that this would not be suitable for our dapp since it's fees were too high and hard to manage for our game play.
In the end we decided to split the spin of the slots into two transactions - firstly, pay your wager, and commit your bet, then secondly, make a free (minus transaction fee) transaction to the contract to get the result of your bet.
In the code, the second call that gets the result, doesn't use the block information from the current transaction, it uses the block information from the block immediately proceeding the transaction that the player made in the first "commit" bet. This way the player cannot influence the result.
There is a problem with this approach however. The contract only has access to the information of the last 256 blocks, (this is an Ethereum limitation) that means that if we try to get the block.timestamp or block.difficulty of any blocks further back that than, we get 0; Solidity's version of null.
As long as this result is never beneficial to the player, then the only downside is that if the player cannot complete their bet in 256 blocks, they will automatically lose. This is approximately 1 hour at 15 seconds a block. We felt that this was a decent amount of time for a player to commit their bet and get a result back.
Although for our needs this solution is fine, we would not recommend using this for any cases where a possible prize could exceed the block reward. There is a small possibility that this could incentivize miners to submit incorrect timestamps that help them. This would be difficult, but not completely infeasible.
For a comprehensive look at our code, check out the full contract here. and for more information on our dapp, checkout out peoplescasino.online.