Understanding the Meebits Exploit

Thanks for tagging me.

Hopefully, I can shed some light here on the Meebits exploit, as randomness is a crucial piece of our ecosystem, and has received a lot of abuse with exploits just like this. Chainlink VRF doesn’t work in the way you described, so I’ll highlight the differences there, and explain why the Chainlink VRF was created for exactly these situations. Please note that I’ve only taken a shallow dive into the Meebits contracts, but I think it’s given me more than enough information.

Problem

After looking at their contract, we can see this line:

uint index = uint(keccak256(abi.encodePacked(nonce, msg.sender, block.difficulty, block.timestamp))) % totalSize;

This is in their randomIndex function and is how they generate the random Meebits. We can look at the minting function, and see that’s exactly what they are using to generate the id.

    function _mint(address _to, uint createdVia) internal returns (uint) {
        require(_to != address(0), "Cannot mint to 0x0.");
        require(numTokens < TOKEN_LIMIT, "Token limit reached.");
        uint id = randomIndex();

        numTokens = numTokens + 1;
        _addNFToken(_to, id);

        emit Mint(id, _to, createdVia);
        emit Transfer(address(0), _to, id);
        return id;
    }

There are a few issues with doing this:

  1. As we saw, you can actually just keep “rerolling” with canceled transactions until you get a meebit that you like. This is really easy for anyone to do.
  2. Using hashed things like block.difficulty (or really, anything else on-chain) as RNG actually gives the miners massive influence of the number, as they can do a similar “rerolling” strategy themselves by throwing out winning blocks that would result in them not getting the NFT if the so desired.
  3. Using things like block.timestamp provides 0 randomness, since the timestamp is predictable by anyone.

This has been pretty public knowledge for sometime, and most protocols that use randomness that have some sort of high-worth protocol, need a solution that doesn’t involve these 2 glaring issues.

So what can we do? As @Yoshiko mentions, we could 100% make sure all our collectibles are either:

  1. Not random
  2. Not high value

This doesn’t seem like a proper long-term solution for the industry as a whole, as we are striving for a world with superior digital agreements. A world without randomness would be a major hurdle for adoption, and artificially trying to deflate the value of high-worth assets seems difficult as well.

Solution

The solution here, is we need a way to create randomness that is verifiable and tamper-proof from miners and rerollers. We also have to do this using an oracle. Actual randomness in deterministic systems like a blockchain is nearly impossible without one.

Chainlink VRF is this exact solution. It looks off-chain for a random number, and is checked for it’s randomness on-chain by what’s called the VRF Coordinator. It works like so:

  1. A user requests a random number by calling some function inputting their own RNG seed. This function emits a log that an off-chain chainlink node reads.
  2. The off-chain oracle reads this log, and creates a random number based on the nodes keyhash and the users seed. It then returns the number in a second transaction back on-chain, going through the VRF Coordinator which verifies that the number is actually random.

How does this solve the 2 issues above?

You can’t reroll attack

Since this process is in 2 transactions, and the 2nd transaction is where the random number is created, you can’t see the random number and cancel your transaction.

The miners don’t have influence

Since you’re not using values that the miners have control over like block.difficulty or values that are predictable like block.timestamp, the miners can’t control the random number.

You can read more about it here and see the math and science behind the world-class group of researchers who developed it.

disclaimer: I work on the Chainlink project

3 Likes