Summary
For pseudorandomness you can use something like EIP-4399:
uint256 randomness = uint(keccak256(abi.encodePacked(msg.sender, block.difficulty, block.timestamp)));
(You don't even need to use assembly, difficulty
is directly exposed in solidity)
But for true randomness, you'd need something like Chainlink VRF.
function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
uint256 randomness = _randomWords[0];
}
Why is difficulty
or PREVRANDAO
pseudo-random in a post-merge ETH?
When a validator is due to propose a block, they face a fairly minimal penalty for failing to do so. Given a smart contract that is trying to make itself unpredictable/uncontrollable via PREVRANDAO
, this option allows them to cheaply bias the behavior of the contract in the following ways.
1. You always need a validator to propose two blocks in a row
PREVRANDAO
's value has no entropy given the PREVRANDAO
value from the previous block if no block is proposed. When you propose a block, that's a piece of the math equation that PREVRANDAO
uses to make a random number. So if you don't propose a block, you get a "less" random number.
So users of PREVRANDAO
would need to check that a validator has provided a block since the last time they called PREVRANDAO
. Otherwise, they won't necessarily be drawing statistically independent random outputs on successive calls to PREVRANDAO.
This means that the validator committed to proposing a particular block can effectively set the PREVRANDAO value for the next block to two possible values:
- the one which results from its mandated
PREVRANDAO
input value
- whatever
PREVRANDAO
gets deterministically set to if no block is proposed
This choice alone allows validators to influence the random number, no longer making the number random.
Even if the contract does something cleverer, like taking the PREVRANDAO
output from the first block to be proposed after some height, every validator after that height has the same option. No matter which way the contract tries to access PREVRANDAO
, the last validator to contribute will always have predictable control over the random output which controls the contract.
These issues don't arise for Chainlink VRF, because the output is deterministic, given the blockhash (just infeasible to compute for anyone who doesn't know the secret key.)
2. Validators can choose to not post if a number is unfavorable
Once again, you still run into this issue from pre-merge. The penalty of not posting a block is almost negligible, so a node might be financially incentivized not to propose blocks in a lottery-type smart contract if the PREVRAND
value isn't what they want. Enough nodes do this and you run into issues.
Just taking the current PREVRANDAO
value without looking at recent history gives a validator some control over the output the contract will use, even if it doesn't want to abort, because the validator can decide whether or not the block it proposes contains a transaction which will trigger the use of PREVRANDAO. The only cost involved there is the transaction fee/tip.
3. If all applications use PREVRANDAO
as their seed
If all applications use PREVRANDAO
as their seed, in a way, you could "chain" together wins or hacks based on this.
More information
There is also an interesting thread on this in the Ethereum Magicians forum.