ブロックチェーンを実装してみた & 改ざんを試みた

# はじめに
最近、Web3に関するツイートやニュースの中でも、Non-Fungible Token (NFT)を目にすることが多い。
NFTはブロックチェーンに基づいた技術で、その改ざん不可能性が肝となっているらしい。
以前からブロックチェーンの改ざん不可能性については知っていたものの、
 - なぜ改ざんが不可能なのか
 - 改ざんを試みると、どうなるのか

といった疑問が残っていた。
今回は、これらの疑問を解決すべく、自分でブロックチェーンを実装し、送金データの投入を行う。更に、送金データの改ざんを試みることで、さらなる理解を目指す。

# ブロックチェーン
ブロックチェーンは、hash値、data、nonceで構成される「ブロック」がつながったものである。
データを読み取る時は、各ブロックが持っているdataを読み取るのみでよい。
競合する複数のブロックチェーンが存在した時、つながっているブロック数が最大のもののみを信用する。

### hash値
なんらかのデータをhash化することによって得られた値。
```math
h = H(d)
```
ここで$h$,$H$,$d$はそれぞれhash値、hash関数、データである。hash関数に期待される性質として、
 - $H(d)$の計算が簡単
 - $h$から$d$の推定が困難
 
の２つが挙げられる。ビットコインで用いられているハッシュ関数「SHA256」の入出力は下記サイトで確認できるため、興味があれば遊んでみてほしい。
https://www.milk-island.net/javascript/hashgenerator/sha2_256.html
またpythonにおいては、下記コードで辞書```data```のhash値を得られる。
```python
import hashlib
import json
print(hashlib.sha256(json.dumps(data).encode()).hexdigest())
```

### data
今回は主に送金データを扱う。そのため、次のような辞書をブロックに持たせる。
```python
data = {
    'from': 'Aさん',
    'to': 'Bさん',
    'amount': 7000
}
```
このデータを持ったブロックが存在すると、AさんからBさんへ7000の送金があったことを意味する。

### nonce
ブロックの有効性の検証に使用される値である。
ブロックチェーンでは、「あるブロック$B$が有効である」ことと、「そのブロック自身のhash値$H(B)$がある条件$C$を満たすこと」とを等価とする。よく用いられる$C$には、「$H(B)$を2進数で表記したときに、最初の$D$桁が0であること」がある。またこの$D$を採掘難易度と呼ぶ。$H(B)$は$B$に依存し、$B$はnonceに依存するため、$H(B)$はnonceに依存する。

### ブロックが「つながる」とは
ブロック$B_n$と$B_{n-1}$が存在し、$B_n$の持つhash値が$B_{n-1}$のhash値$H(B_{n-1})$と等しいとき、$B_n$は$B_{n-1}$の後ろにつながっていると言う。

# 実装
上記を踏まえ、ブロック、ブロックチェーン、ブロックチェーンを用いた送金データ投入をそれぞれ実装してゆく。

### ブロック
Blockクラスを実装する。
コンストラクタで採掘難易度と前ブロックのhash値を設定し、```set_data```でデータの投入を、```get_data```でデータの取り出しを行う。なお、```set_data```のwhile文の中で、そのブロック自身が有効となるようなnonceを探している。これは「マイニング」と呼ばれる作業である。
```block.py
import hashlib
import json
import typing as t


class Block:
    _diff: int
    _data: t.Dict
    _hash: str
    _nonce: int

    @staticmethod
    def _get_hash(data: t.Dict) -> str:
        return hashlib.sha256(json.dumps(data).encode()).hexdigest()

    def __init__(self, difficulty: int, prev_hash: str = None) -> None:
        self._diff = difficulty
        self._nonce = 0
        self._hash = '0' * 64 if prev_hash is None else prev_hash

    def _is_valid_hash(self, hash_: str) -> bool:
        bin_ = str(bin(int(hash_, 16)))[2:].zfill(256)
        return bin_.startswith('0' * self._diff)

    def set_data(self, data: t.Dict) -> None:
        while True:
            hash_ = self._get_hash({'data': data, 'nonce': self._nonce, 'hash': self._hash})
            if self._is_valid_hash(hash_):
                self._data = data
                break
            else:
                self._nonce += 1

    def get_data(self) -> t.Dict:
        return self._data

    def get_hash(self) -> str:
        return self._hash

    def get_next_hash(self) -> str:
        return self._get_hash({'data': self._data, 'nonce': self._nonce, 'hash': self._hash})

    def get_is_valid_block(self) -> bool:
        return self._is_valid_hash(self._get_hash({'data': self._data, 'nonce': self._nonce, 'hash': self._hash}))
```

### ブロックチェーン
ブロックチェーンの全ブロックから、各ユーザの所持金を簡単に取得するために、BlockChainクラスを実装する。
```block_chain.py
from block import Block
import typing as t


class BlockChain:
    def __init__(self):
        self.blocks: t.List[Block] = []

    def append(self, block: Block):
        self.blocks.append(block)

    def _get_all_data(self) -> t.List[t.Dict]:
        out: t.List[t.Dict] = []
        for b in self.blocks:
            out.append(b.get_data())
        return out

    def get_balances(self) -> t.Dict:
        balances: t.Dict = dict()
        people_names: t.List[str] = []
        blocks = self._get_all_data()
        for b in blocks:
            from_ = b['from']
            to = b['to']
            if from_ not in people_names:
                people_names.append(from_)
            if to not in people_names:
                people_names.append(to)

        for name in people_names:
            balances[name] = 0

        for b in blocks:
            from_ = b['from']
            to = b['to']
            amount = b['amount']
            balances[from_] -= amount
            balances[to] += amount
        return balances

    def is_valid_chain(self) -> bool:
        prev_hash = '0' * 64
        for b in self.blocks:
            if not b.get_is_valid_block():
                print('INVALID BLOCK!')
                return False
            if prev_hash != b.get_hash():
                print('INVALID CONNECTION!')
                return False

            prev_hash = b.get_next_hash()
        return True
```

### メイン部
上で実装したブロックチェーンに、実際に送金データを投入してみる。辞書```data_list```が示すように、AさんからBさんへの100の送金、BさんからCさんへの50の送金、AさんからDさんへの50の送金の3つのデータを投入する。送金前は全ユーザの所持金を0としているため、```BlockChain#get_balances()```により示される各ユーザの所持金が
Aさん: -150
Bさん: 50
Cさん: 50
Dさん: 50
となれば正しく投入できていることが確認できる。

```main.py
from block import Block
from block_chain import BlockChain


def main():
    data_list = [
        {
            'from': 'Aさん',
            'to': 'Bさん',
            'amount': 100
        },
        {
            'from': 'Bさん',
            'to': 'Cさん',
            'amount': 50
        },
        {
            'from': 'Aさん',
            'to': 'Dさん',
            'amount': 50
        },
    ]
    difficulty = 10
    prev_hash = None
    block_chain = BlockChain()

    for data in data_list:
        block = Block(difficulty=difficulty, prev_hash=prev_hash)

        print('mining...')
        block.set_data(data)
        print('done.')

        prev_hash = block.get_next_hash()
        block_chain.append(block)

    print('balances:', block_chain.get_balances())
    print('valid:', block_chain.is_valid_chain())


if __name__ == '__main__':
    main()
```
### 実行結果
```
mining...
done.
mining...
done.
balances: {'Aさん': -150, 'Bさん': 50, 'Cさん': 50, 'Dさん': 50}
valid: True
```
期待通りの動作が確認できた。

# 改ざんを試みる
データをブロックチェーンに保存することで得られる最大の恩恵は、改ざんが不可能になることであろう。実際に改ざんを試みると、どんなことが起こるのだろうか。

### 改ざんしやすいブロックを実装
Blockクラスを継承し、dataのみのセット、nonceのみのセットのためのインスタンスメソッドをそれぞれ実装した。
```vulnerable_block.py
from block import Block
import typing as t


class VulnerableBlock(Block):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def set_data_without_mining(self, data: t.Dict) -> None:
        self._data = data

    def set_nonce(self, nonce: int) -> None:
        self._nonce = nonce

```
```data_list[1]```は、BさんからCさんへ50の送金があったことを示すが、Aさんからハッカーへ10000000の送金があったことを示すデータへの改ざんを試みる。
```hack.py
from vulnerable_block import VulnerableBlock
from block_chain import BlockChain


def main():
    data_list = [
        {
            'from': 'Aさん',
            'to': 'Bさん',
            'amount': 100
        },
        {
            'from': 'Bさん',
            'to': 'Cさん',
            'amount': 50
        },
        {
            'from': 'Aさん',
            'to': 'Dさん',
            'amount': 50
        },
    ]
    difficulty = 10
    prev_hash = None
    block_chain = BlockChain()

    for data in data_list:
        block = VulnerableBlock(difficulty=difficulty, prev_hash=prev_hash)

        print('mining...')
        block.set_data(data)
        print('done.')

        prev_hash = block.get_next_hash()
        block_chain.append(block)

    print('balances:', block_chain.get_balances())
    print('valid:', block_chain.is_valid_chain())

    # ここまで、BlockがVulnerableBlockに変わった事以外は、main.pyと同じ。
    # hacked_blockに、Aさんからハッカーへ10000000の送金があったデータを投入する。
    print('改ざんスタート')
    hacked_block = VulnerableBlock(difficulty=difficulty)
    # BさんからCさんへの50の送金のかわりに、Aさんからハッカーへ10000000の送金があったことにする。
    hacked_block.set_data({
            'from': 'Aさん',
            'to': 'ハッカー',
            'amount': 10000000
        })
    block_chain.blocks[1] = hacked_block
    print('balances:', block_chain.get_balances())
    print('valid:', block_chain.is_valid_chain())


if __name__ == '__main__':
    main()
```

### 実行結果
```
mining...
done.
mining...
done.
mining...
done.
balances: {'Aさん': -150, 'Bさん': 50, 'Cさん': 50, 'Dさん': 50}
valid: True
改ざんスタート
balances: {'Aさん': -10000150, 'Bさん': 100, 'ハッカー': 10000000, 'Dさん': 50}
INVALID CONNECTION!
valid: False
```
INVALID CONNECTION!
1番目のブロックを書き換えたことで、そのhash値も変わり、2番目のブロックとのつながりが解除されてしまった。改ざん後のブロックチェーンの長さは2、改ざん前の長さが3であるため、改ざん前のブロックチェーンの方が信用される。つまり、改ざんに失敗したと言える。

# まとめ
今回はブロックチェーンを実装し、実際にデータを投入した。これにより、近年の強力な技術の基礎を理解することができた。また、実際に改ざんを試み、失敗したことで、ブロックチェーンの長さに基づく信頼度の堅牢性も確認することができた。

# 参考にしたサイト
 - Aicia Solid Project, 【あなただけに…】ブロックチェーンの仕組みだよ！【教えます…】, https://www.youtube.com/watch?v=bclE3gLeyzg
 - 秋山, Pythonでブロックチェーンを実装して採掘までやってみたので解説する, https://paiza.hatenablog.com/entry/2018/05/11/Python%E3%81%A7%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%83%81%E3%82%A7%E3%83%BC%E3%83%B3%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%A6%E6%8E%A1%E6%8E%98%E3%81%BE%E3%81%A7%E3%82%84%E3%81%A3%E3%81%A6