EIP-4399: Supplant DIFFICULTY opcode with RANDOM

I think keeping DIFFICULTY is a reasonable option, though I do worry that people will colloquially name it RANDOM if we don’t give them an alternative.

Renaming and clarifying behaviours is at best effort basis and the broader ecosystem can start calling it as they like despite all the efforts put in there.

Perhaps having an EIP in CFI for the “real” RANDOM instruction @mkalinin describes would be a good deterrent from others starting to call this one RANDOM?

While I agree that we don’t have any direct power to control the colloquial naming here, I do think that we have quite a bit of indirect power. If we name an opcode X in the specification, it is almost certain that Solidity will use that name, and various EVM documentation sources will use that name. This will result in people seeing X in all of the places they look, and that becomes a defacto shelling point for communication.

If we continue to call this DIFFICULTY, there is a chance that people pick a new more appropriate name colloquially because DIFFICULTY is definitely wrong. For example, Solidity may decide to name it RANDOM or EVM docs may name it DIFFICULTY (RANDOM) or something. This has potential to lead to people using it incorrectly compared to if we call it NOT_RANDOM or whatever in the specification (and that then propagates out).

How about the following:

  • Introduce BEACONSLOT for querying the current slot number
  • Introduce RANDAO(n) for querying the nth slot
  • Name DIFFICULTY as LASTRANDAO, and make it an alias of RANDAO(BEACONSLOT)
2 Likes

LASTRANDAO would technically be RANDAO(BEACONSLOT-1), and if that was an empty slot then it would be RANDAO(BEACONSLOT-2), etc.

Yes, I took the liberty to not duplicate the older explanation :grimacing:

I assumed that LASTRANDAO will do the “find latest valid randao” lookup, but RANDAO(n) returns 0 on missing slot.

On ACD#132 the PREVRANDAO name was agreed on.

1 Like

@mkalinin Could you articulate the security difference between PREVRANDAO in eth2 and BLOCKHASH in eth1? It seems like they are both biasable in the same way: an attack gets the option to throw away a number of non-advantageous blocks the more capital (bribing or increasing stake/hashpower) they have.

This is largely true. There is an additional new attack vector as well though in that someone who produces multiple blocks in a row can know what a future RANDAO will be, and the more consecutive blocks in a row they produce (or collude with), the more foresight they have. Missing blocks can break this chain. It also means that someone who produces multiple blocks in a row can, in advance, try out different combinations of missing blocks to see what works out best for them in terms of future RANDAO generation.

On the flip side, there are no longer uncle blocks which means block producers who entirely miss their slot don’t get just a 1/16+fees decrease in block reward, they miss the whole block reward plus fees.

additional new attack vector as well though in that someone who produces multiple blocks in a row can know what a future RANDAO will be, and the more consecutive blocks in a row they produce (or collude with), the more foresight they have

I’m not sure how that is different from a miner in eth1 producing a chain of mined blocks? That miner knows the “future” blockhash.

It also means that someone who produces multiple blocks in a row can, in advance, try out different combinations of missing blocks

This does indeed seem like a unique PoS attack vector. In PoW a miner controlling majority hash power for some period of time has to commit to a particular string of blocks

Under PoW, the miner would have to shadow mine a long chain and then throw it away (or at least uncle it) if it doesn’t pan out, and they cannot pragmatically gain that information faster than the chain would advance to that point naturally. Under PoS, the validator can just spend 100ms to see what the future RANDAO would be. It costs them nothing to read future random numbers (limited to the number of blocks in a row that they have access to the proposer of).

1 Like

Under PoW, the miner would have to shadow mine a long chain and then throw it away (or at least uncle it) if it doesn’t pan out

A miner may get a chance to mine 2-3 canonical blocks in a row, it depends on its hashpower, how lucky it is, and a network delay. It’s not necessarily needed to be thrown or uncled in the end. Likewise, a staker may get the same chance and if there is no desirable payout in any combination of empty slots it may induce, it may just propose blocks in a regular fashion.

In general, assuming the chance of a party to produce n consecutive canonical blocks in a row is equal in PoW and PoS networks, the party gains n bits of influence power disregarding the network, i.e. from this perspective an attack looks pretty similar in both networks.

Though, the size of a capital required to reach a certain probability to produce n blocks in a row and factors influencing this size depend a lot on the type of the network.

2 Likes

If we really want a proper name why just not use PSEUDO_RANDOM, it tells us exactly what we need to know about the behavior of OPCODE. I would agree that it would be hard to believe anybody using opcodes and doesn’t really understand what it does and how it works, so just RANDOM would also be fine. Could imagine people using it on solidty/vyper should have more descriptive naming, then again even there it’s hard to imagine someone doing proper work and not learning what it really is.

My conclusion is this looks a lot like bikeshedding :slight_smile:

1 Like

We have decided on the PREVRANDAO name which is exactly what this operation does return. This also should incentivise users to look a bit under the hood of what the RANDAO is and what implications of using prev RANDAO are.

3 Likes

Pseudorandom has been overused (IMO) as it basically means, “anything where humans don’t know the generation algorithm” or “humans don’t know the sequence”. The problem is that this means most engineers treat pseudorandom and random the same, because pragmatically they are the same in most use cases that anyone cares about.

The problem here is that we want to make it abundantly clear to developers that they should not be naively using this to generate random numbers that are secure against attack. By naming it prevrandom, it hopefully will make it more clear that this is a previously known value.

You clearly haven’t spent the last 5 years auditing contract code. :joy:

Naming things is hard :man_shrugging:
When I read prevrandao it was last thing on my mind it was meant as previous, I found it hard to read, basically it’s combo of previous, random and dao, I give it 0 chance new ppl will know what it means or could mean just by first time seeing it. Most recognizable part is DAO and it gives the least meaning to what this op code is. Just my 2 cents on it, hope it works out, at least I learned what it is, hopefully, others have smoother DX.

I find Tips for application developers confusing:

In section:

“Make your applications rely on the future randomness with a reasonably high lookahead. For example, an application stops accepting bids at the end of epoch K and uses a RANDAO mix produced in slot K + N + ε to roll the dice, where N is a lookahead in epochs and ε is a few slots into epoch N + 1.”

You are adding elements with different units, epochs (K and N) and slots (ε), in “K + N + ε”.

Should not it be?:

“Make your applications rely on the future randomness with a reasonably high lookahead. For example, an application stops accepting bids at the end of epoch K and uses a RANDAO mix produced in slot (K+1)*32 + N*32 + ε to roll the dice, where N is a lookahead in epochs and ε is a few slots into epoch K + N + 1.”

From my understanding PREVRANDAO @ slot n = (PREVRANDAO @ slot n -1 )XOR( RANDAO Reveal of the slot n-1 proposer ) (Correct me if I was wrong @mkalinin ).

Normally developers use the method below as the random source of their dapps
blockhash(uint blockNumber) returns (bytes32)?

Hence, adding a similar function RANDAO(n) in EIP-4399 to support retrieving the RANDAO value is indispensable. Only the current slot PREVRANDAO value is not working for solidity developers.

Btw, the slot number n is available in evm now?

The formula would be PREVRANDAO[n] = PREVRANDAO[n-1] XOR (SHA256(RANDAO_REVEAL(n-1)), source. But I would not recommend to rely on this formula in smart contracts as it may be changed in the future.

The slot number is not available now. EIP-4399 introduced as few changes as it was possible and RANDAO(n) is considered to be implemented at some point (yet undefined) it time

1 Like

Thank you for the explanation. So, the PREVRANDAO value is the first param (get_randao_mix(state, epoch)) in the following formula, source
mix = xor(get_randao_mix(state, epoch), hash(body.randao_reveal))
But why not just return the mix value as the randomness in evm?