# 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來跟他互動

- 使用`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

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`