Generating random numbers has found various applications in the Web3 space. For example, a gaming, gambling/bet DApp finds this useful in determining pool winners. However, doing this can create an in-way for attacks.
Insecure randomness refers to using unreliable sources of entropy or randomness in smart contracts. This can occur when developers rely on predictable variables such as blockhash, block.difficulty, or timestamps to generate random numbers. An attacker can predict the output to manipulate the behavior or outcome of the smart contract.
In the example below, the `InsecureRandomness` contract contains a function random() which calculates randomness using block.timestamp, block.difficulty, and a nonce variable. This randomness calculation is insecure because it can be manipulated.
The AttackContract exploits vulnerabilities in the InsecureRandomness contract by leveraging the predictability of its randomness calculation. It begins by establishing a connection to the InsecureRandomness contract through the constructor, allowing it to access its functions and state variables. Within its attackBet() function, the AttackContract calls the random() function of the InsecureRandomness contract to obtain a supposedly random number. It is important to mention that this method only works if the attacker previously forked the contract and deployed it separately, with the random() function being public and the corresponding nonce.
Now it is clear that this is only a theoretical example but in practice costly bets can be executed with a deterministic number once the attackBet function returns true, the attacker can simply invoke the bet function with guess = 0, you get the idea.
Additionally, it's worth noting that miners can influence these parameters, which is in fact the main reason why this randomness is deemed insecure.
Approach to Secure Randomness
1. Use External Oracles: External oracles provide off-chain data to smart contracts, including random numbers generated from trusted, decentralized sources. These oracles can fetch randomness from various verifiable sources such as Chainlink VRF (Verifiable Random Function), Oraclize, or decentralized randomness protocols like RANDAO. This is the only real method that I will ever recommend.
2. The commit-reveal scheme is a method used to ensure fairness and prevent manipulation in scenarios where participants need to make choices without knowing the choices of others. It works by having participants commit to their choices by hashing them and then revealing the original values later. Randomness can be derived from these revealed values, ensuring that participants cannot change their choices based on the generated randomness.
Here's the logic behind the commit-reveal scheme:
Commitment Phase:
Each participant independently chooses their option (e.g., a number, a move in a game, etc.).
They then hash their chosen option using a cryptographic hash function (e.g., keccak256).
The hashed values are publicly committed to the blockchain.
Reveal Phase:
After all participants have committed to their choices, the reveal phase begins.
Each participant reveals their original choice by providing the plaintext value corresponding to their hashed commitment.
The revealed values are validated against the previously committed hashes to ensure integrity.
Randomness Derivation:
Once all choices have been revealed, randomness can be derived from the revealed values.
For example, the revealed values can be concatenated and hashed together to produce a random seed.
This random seed can then be used to generate random numbers or determine outcomes fairly and transparently. For example the nearest value to the generated number is the winner.