---
# System prepended metadata

title: 2020 程安 Crypto筆記4 (區塊鏈安全)
tags: [crypto, 程式安全]

---

# 2020 程安 Crypto筆記4 (區塊鏈安全)
###### tags: `程式安全` `crypto`

## 區塊鏈
1. 去中心化的記帳本
- A紀錄一筆交易,並broadcast給其他人
    - B可以偽造交易,broadcast給其他人
     $\Rightarrow$ 因此每筆交易都需要簽章
- A用私鑰加密交易,形成簽章附在交易紀錄中,每個人都可以拿A的公鑰來verify交易紀錄
    - B還是可以複製已經存在的交易來偽造交易
    $\Rightarrow$ 因此簽章需要加上`time stamp`或`id (hash value)`
- 先算出`nonce`的人擁有記帳權
    - `nonce` : 會包含在block中,必須使整個block的`hash`值開頭為`0x00`
    - `block` : 一個block裡面會有好幾筆交易
    - 前一個block的`hash`值會包含在下一個block中
    - 如果同時有兩個人算出`nonce`,由`blockchain`較長的一方當作主鏈
- 其他決定記帳權的方式
    - PoW `Proof of Work`
        - 解決一個複雜的問題 (ex. 上面算nonce的方式)
        - Bitcoin, Ethereum
    - PoS `Proof of Stake`
        - 持幣數量越多越有機會
    - DPoS `Delegated Proof of Stake`
        - 每個節點投票(抵押代幣)選出記帳見證人
        - 每一輪隨機決定一個順序,在所有見證人都出過塊後在進行下一輪
### 51%攻擊
- 如果有個人擁有全網`51%`的算力,則他挖的通常會是最長的鏈
    $\Rightarrow$ 他高機率比所有人都還要快算出`nonce`
- 因此他可以竄改交易紀錄
    > 攻擊者將自己錢包中的錢到交易所兌換成法幣 
    > 接著攻擊者開始算一個假的chain
    > 當這個chain比其他chain都還要長時
    > 攻擊者就將假的chain廣播出去
    > 這時假chain就會被視為主鏈(因為最長鏈共識)
    > 而原本的chain上的交易都會無效,錢會物歸原主
    > 因此攻擊者的錢又回到錢包
    > 但4攻擊者還是實際領到錢,錢直接翻倍

### Ethereum (以太坊)
- 錢包
    - 冷錢包(硬體錢包) : 類似USB (`Ledger`, `Trezor`)
    - 熱錢包(軟體錢包) : browser插件 ([`MetaMask`](https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn), `Trust Wallet`)
- Ethereum Address    
    - Ex. my address (`MetaMask`)`0x3A60aFa681630A722948295970cB17fB02fAd846`
    - public key跟address是1-1 mapping
        - public key做`keccak256` hash之後取**後20bytes**就是address
        `Note: keccak256是SHA3的前身,差別在參數有點不同`

- EVM (`Ethereum Virtual Machine`)
    - 去中心化的虛擬機
    - `Turing Complete`
    - 以太坊區塊鏈上除了存放交易以外也可以存放程式碼,作為**驗證交易**使用(**智能合約**)
        - `EVM`用以執行這些程式碼

### Smart Contract (智能合約)
- User創建智能合約(一個class),存放在以太坊上,並呼叫其API來跟他互動
    ![](https://i.imgur.com/P2tRZzH.png)
    
- 使用`Solidity`(或`Vyper`)撰寫
    - `Solidity`語法類似`javascript`
    - `Vyper`語法類似`python`
    
- 但存放在以太坊上的其實是bytecode(類似assembly code)
    - Compiler將`Solidity`轉成bytecode
    - [EVM opcode](https://ethervm.io/)
        ```asm
        % example
        PUSH1 0x80    % 60 80
        PUSH1 0x40    % 60 40
        MSTORE        % 52
        CALLVALUE     % 34
        DUP1          % 80
        ISZERO        % 15
        PUSH2 0x0010  % 61 00 10
        JUMPI         % 57
        PUSH1 0x00    % 60 00
        ...

        % 0x6080604052348015610010576000...
        ```
- 在`EVM`上執行bytecode是要錢的,依`opcode`計算費用
    - 每個`opcode`值不同的`gas fee`
        - Example: `ADD`花費`3 gas`
        - `gas price`由**交易發起人**決定
            - `gas price`是給礦工的小費,給得越高交易越快被verify
            - Example: 若`1 gas = 10 gwei`, 則`ADD`需花費`30 gwei`
            - `wei`為以太坊中的最小單位,`gwei` = $10^9$`wei`, `kwei` = $10^3$`wei`

- 要跟合約互動,使用`web3.js`
    - `EVM`可以看成後端,`web3.js`可以看成前端
    - `web3.js`是一個函式庫,將以太坊的`JSON-RPC`(一個遠端程式呼叫協定)重新封裝並增加實用的函式庫
        - User可以透過`web3.js`的API跟`JSON-RPC Server`溝通,進一步跟以太坊溝通
        - Example : 
            - `web3.eth.Contract(abi, contract addr)`可以與智能合約互動
                - 互動前需要先取得該合約的`ABI (Application Binary Interface)`: 類似`.h`檔的東西,說明有哪些函式以及使用方式

                ```javascript
                const abi = require('./abi.json');
                const contract_address = 'CONTRACT_ADDRESS';
                contract_instance = new web3.eth.Contract(abi, contract_address);
                ```
    - `web3`也可以用來查看區塊鏈上的資訊
        - `web3.eth.getBalance()`用以取得帳戶餘額,default單位為`wei`
        - `web3.eth.sendTransaction()`用來傳送交易
    - `web3.py` $\Rightarrow$ python版本的
    - `ethers` $\Rightarrow$ 比`web3.js`還好用(?

- [Remix](https://remix.ethereum.org)
    - 以太坊提供的線上IDE
    - 用來建立智慧合約

#### Solidity
- [`Solidity`語法教學](https://cryptozombies.io)
- 因為智慧合約也式程式碼,因此也會有漏洞
    - [DSAP TOP 10](https://dasp.co) : 智慧合約的10大漏洞
    - [SWC Registry](https://secregistry) : 分類了所有智慧合約的漏洞

- `View` vs `Pure`
    - `Solidity 0.4.17`後開始用`view`和`pure`取代`constant`
    - 
        |      | 保證不寫入storage | 保證不讀取storage |
        | ---- |:-----------------:|:-----------------:|
        | view |       **Y**       |       **N**       |
        | pure |       **Y**       |       **Y**       |
        
    - 因為去中心化,所以對智慧合約的操作都必須要連網, 且盡可能避免錯誤操作
        - 因此若該function完全不需要讀取或寫入智慧合約的state,就會宣告function為`view`或`pure`
        - 這樣可以直接用compiler檔下可能的意外操作
            - Example: 宣告`pure`卻讀取contract address owner
    
    ```solidity
    pragma solidity ^0.4.0;
    
    contract Ownable {
        address private owner; // owner為storage上的值
        
        // 宣告view: 不會對storage上的值做修改
        // 宣告returns(...): return type為address
        function getOwner() view returns(address) 
        {
            return owner; //這邊如果對owner做修改,compiler編譯時就會報錯
        }
    }
    ```

- Storage vs Memory
    - Storage為智慧合約存放state的地方,直接反映到區塊鏈上的值
        - Global變數就會放在Storage中
    - function的回傳值或參數通常則會放在Memory中,或是有些compiler會用stack來存(因為不花`gas`, `EVM`就是用stack來存)
        - 如果回傳值或參數是來自global變數,就會複製一份到memory中進行操作
        - 但也可以在宣告時加上`storage`,這時就會直接對storage中的值做修改,直接反應到區塊鏈上
    - function的local變數則是預設用`storage`處理,但也可以宣告`memory`
        - but 如果是memory陣列,會比較困難(無法使用`push`)

- function modifier 函數修改器
    ```solidity
    pragma solidity ^0.4.0;

    contract Ownable {
      address public owner = msg.sender;

      /*自定義一個函數修改器*/
      modifier onlyOwner {
        require(msg.sender == owner); //require() = assert()
        _; /*若有funtion宣告此modifier,這邊會被改成該function的程式碼
             以下面function為例,這邊就會執行
                if (_newOwner == 0x0) throw;
                owner = _newOwner; */
      }

      /*修改合約的owner*/
      function changeOwner(address _newOwner)
      onlyOwner         //宣告modifier
      {
        if(_newOwner == 0x0) throw;
        owner = _newOwner;
      }
    }
    ```

## 區塊鏈常見漏洞
### Underflow/Overflow
- `uint`往下減會underflow
    - `uint = uint256` $\rightarrow$ `256bit = 32byte`
    - Example `(uint)`$0 - 1 = 2^{256}-1$
- 可以使用`Safemath`函式庫來處理`uint`的運算
    - 在合約一開始宣告`using SafeMath for uint256;`

### Random Number
- 區塊鏈本身很難產生安全的亂數
- 隨機的來源只有block.timestamp, block.difficulty, block.blockhash
    - 但其實還是可以預測,比如說deploy另一個合約做,在呼叫原本的合約之前先行取得資訊(因為時間接近,通常會在同一個block),就可以預測到亂數的值
- 因此最好的方法還是呼叫外面的中心化server來取得隨機數


## 攻擊: Reentrancy Attack
1. 呼叫合約API : 會發送一個transaction給目標合約
    - transaction的`data`欄位填目標API的`function signature`
        - `function signature` = `keccak256('fun(type)')`的前4byte
        - [回查function](https://www.4byte.directory)
            - 紀錄存在的function signature及其對應函式的DB
            ![](https://i.imgur.com/BhNIeZp.png)
            
2. `fallback` & `receive`
- 合約要接收錢就一定要定義`receive`及`fallback`函式,否則會return異常並返還錢
    - 若有人呼叫合約但是`data`欄位是空的 $\Rightarrow$ 執行`receive()`
        - 如果合約中沒有`receive()`,則執行`fallback()`
    - 若有人呼叫合約但是`data`欄位中的`function signatur`沒有match $\Rightarrow$ 執行`fallback()`

- `receive`
    - 一個合約最多只有一個`receive`
    - 必須標示為`external`
    - 若要接收錢就必須標記`payable`屬性 (適用於所有函式)
    - 宣告方式為
        ```solidity
        contract my_cont
        {
            receive() external payable {
            
            //what should do in receive function
            
            }
        }
        ```
        
- `fallback`
    - 一個合約最多只有一個`fallback`
    - 沒有名字,不能有參數,也沒有return value,必須標記為`external`
    ```solidity
    contract my_cont
    {
        function () external [payable] {
        
        //what should do in fallback function
        
        }
    }
    ```

2. Reentrancy Attack
- Example
    ```solidity=
    contract A{
        function withdraw(uint _amount) public payable {
            require(balances[msg.sender] >= _amount); 
            msg.sender.call{value: _amount}(""); //trigger fallback
            balances[msg.sender] -= _amount; //vul
        }
    }
    
    contract B{ //malicious contract
        A target;
        
        constructor() {
        target = A(my_addr);
        }
        
        function() externel payable { //fallback
            target.withdraw()
        }
        
        function pwn() {
            target.withdraw() //觸發最開始的交易
        }
    }
    ```
- 因為合約跟錢包在同一個空間中,因此若交易是某地址送錢給另一個地址,其實有可能是**合約送錢給合約**
    - 而對合約而言,送錢給合約相當於呼叫合約的`fallback`
    - 若A合約有個API `withdraw`,B是攻擊者的惡意合約,他將`fallback`的內容都寫成再執行一次`withdraw`
        - 此時因為balance的更新寫在`fallback`被觸發之後,因此在A合約錢被掏空之前都不會執行到


### `Delegatecall`