4

I'm generating a pseudorandom number using Solidity. I understand the deterministic nature of the blockchain and the necessity of using an oracle for a seed. That said, I'm selling randomized items that are generated dynamically, which I'm using the following line to generate:

uint random =  uint(keccak256(abi.encodePacked(msg.sender, block.number, newItemId)));

In this randomized number, I've used as seed the caller address, block number, and new Item id, which increases at every buy() function call. When looking at it, it seems fine, as the attacker could manipulate the sender address and guess the block number with a few tries, but getting a predictable newItemId would be difficult because they won't know if someone else is buying at the same time, taking precedence in the block processing order and getting another ID.

A miner could try to mine valid blocks and throw them out until they get one with the prize they want, but the max prize on this would be worth about 300 dollars, which makes it not worth the effort.

Does this work or am I missing something? Is it possible to exploit this in a way I am not aware of?

2 Answers 2

3

You're exploring a classic problem in Solidity: how to generate random numbers -on-chain?

Given there's no entropy, it's hard to get unguessable random numbers. Let's check the sources of randomness you use:

  • msg.sender: not random, for obvious reasons that you mentionned
  • block.number: not random: an attacker could just compute which block would produce a favorable outcome, and send his transaction at this exact block, with enough gas to be sure to be included in it.
  • newItemId: unless there's a lot of transactions on your contract at each block, it's quite easy to guess it as it won't be random. Even if there was a lot of transactions at each block, making it hard to guess which Id was used for the draw, a miner could just manipulate the ordering to get a favorable outcome.

Any other value, such as block.coinbase for instance, would encounter more or less the same problems.

Bear in mind that a max prize of 300 dollars may be enough for many people - actually, hacking is often fun by itself. Do not underestimate this parameter. Additionally, if the hack can be repeated easily, it may be much more overtime.

Safer ways of generating random numbers:

  • Using blockhash(block.number + x): In this case, you use a 2-fold scheme: the first transaction records the future block number whose hash will be used to create the RNG. In a second transaction, you reveal this value and use this RNG.

This solution has the advantage of being quite straightforward and "cheap" as it doesn't need an oracle.

It has the disadvantage of needing a node which monitors the contract and sends a transaction to reveal the RNG. You can ask the user to do it, but beware - it has to be done in the next 256 blocks, as the blockchain only remembers the last 256 block hashes. Moreover, you give this way the miner an opportunity to manipulate the block hash until he gets a favourable outcome.

  • Use a node to bring RNG on-chain.

You mentioned that you know this solution. I won't advertise any services here, but if you find it expensive, i may recommend the following:

  • Either contact their sales team and negociate. I know from experience the prices can get very low, as their marginal cost is near 0 (except the gas).
  • Make one yourself, which is a lot of fun, if you're into this kind of fun.
4
  • yes, I like the commit/reveal approach, but I don't like paying network fees twice in this approach. You said a miner could manipulate the ordering, but for this, they would have to mine a valid block, seems like a lot of trouble for 300 bucks extra reward on that block as it's non-repeatable.
    – sigmaxf
    Aug 13, 2021 at 20:16
  • If you have a volume low enough that most transactions are the sole of their blocks (which is very likely if you're not Uniswap dev), a user can guess the number too.
    – Yakitori
    Aug 13, 2021 at 20:54
  • You mentioned that using block.coinbase would have problems as well. I'm curious, could you elaborate a bit more? @Yakitori
    – sigmaxf
    Sep 25, 2021 at 16:58
  • @sigmawf block.coinbase is a miner-determined value, meaning it can be manipulated.
    – Yakitori
    Sep 28, 2021 at 1:19
0

There is an alternative to using random numbers without risk. Here is how:

  1. Create random number at your private space so nobody knows which is it
  2. Create the keccak hash of this random number
  3. Publish the hash of this random number so everybody knows which is it
  4. Let the users play your game (whatever it is, rolling dice, flipping a coin, etc)
  5. Force all users to settle the transactions of their plays on the blockchain so there is a record of who played what
  6. After everybody published their choices reveal the random number.

This way everybody will be 100% sure you didn't cheat. The only problem with this method is that the random number has to be like 20 bits or bigger so it would be unfeasible to run brute force algorithm and generate hashes in order to guess what was the random number you picked.

Later you can use another function to map this big random number to your own problem domain. I don't know if this is going to work for you, but this is how many people protects values on the blockchain without revealing the actual value. For example the Ethereum Name System (ENS) uses the same hashing principle I described to register domain names (Ethereum domain names) without revealaing the actual name in english text.

1
  • I like this solution but even though it is verifiable on-chain, it is not 100% done on-chain.
    – sigmaxf
    Sep 25, 2021 at 16:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.