Pythonでブロックチェーンの改ざん検知

# はじめに
以前[こちらの記事](https://qiita.com/sssssssiiiiinnn/items/e1022667026b942b2d7d)で書いたPythonのブロックチェーンプログラムに改ざん検知のテストを追加してみました。

# 改ざん検知の仕組み
ブロックチェーンでは各ブロックが一つ前のブロックのハッシュ値を保持しており、改ざんが行われるとハッシュ値の整合性が取れなくなります。早速実装していきます。
動作としては単純で、改ざんが行われているか知りたいブロックのハッシュ値と次のブロックのprevious_hashを比較するだけです。if文のところでis_same()のような関数を使いたかったですが良い変数名が思い浮かばずやめました。
以下の関数をプログラムの最後に実行します。

```Python
    def check_falsification(self):
      print('=' * 30 + 'Check falsification')
      for chain_index, block in enumerate(self.chain):
        if chain_index == 0:
          continue
        else:
          if block['previous_hash'] != self.make_hash(self.chain[chain_index-1]):
            print(f'[Block {chain_index-1}]Falsification is detected!!')
            return
      print('There is no falsification')
```

### ソースコード
上の関数を実装すると以下のようになります。

```Python
import hashlib
import time
import json


class BlockChain(object):

    def __init__(self):
        self.chain = []
        self.transaction_pool = []
        self.create_block(previous_hash='Initialize')

    def make_hash(self, block):
        json_block = json.dumps(block)
        return hashlib.sha256(json_block.encode()).hexdigest()
    
    def create_block(self, previous_hash=None, nonce=0, transaction=None, timestamp=time.time()):
        block = {
            'previous_hash': previous_hash,
            'nonce': nonce,
            'transaction': transaction,
            'timestamp': timestamp
          }
        self.add_block_to_chain(block)
    
    def add_block_to_chain(self, block):
        self.chain.append(block)

    def mining(self):
        previous_hash = self.make_hash(self.chain[-1])
        nonce = 0
        transaction = self.transaction_pool
        self.transaction_pool = []
        while True:
            if self.proof_of_work(previous_hash, nonce, transaction):
                break
            else:
                nonce += 1
        timestamp = time.time()
        self.create_block(previous_hash, nonce, transaction, timestamp)

    def proof_of_work(self, previous_hash, nonce, transaction):
        guess_block = {
          'previous_hash': previous_hash,
          'nonce': nonce,
          'transaction': transaction
        }
        guess_hash = self.make_hash(guess_block)
        return guess_hash.startswith('0'*2)

    def add_transaction(self, sender_name, reciever_name, value):
        transaction = {
            'sender_name': sender_name,
            'reciever_name': reciever_name,
            'value': value
        }
        self.transaction_pool.append(transaction)

    def print_chain(self):
        for chain_index, block in enumerate(self.chain):
            print(f'{"="*40}Block {chain_index:3}')
            if block['transaction'] is None:
                print('Initialize')
                continue
            else:
                for key, value in block.items():
                    if key == 'transaction':
                        for transaction in value:
                            print(f'{"transaction":15}:')
                            for kk, vv in transaction.items():
                                print(f'\t{kk:15}:{vv}')
                    else:
                        print(f'{key:15}:{value}')

    def check_falsification(self):
      print('=' * 30 + 'Check falsification')
      for chain_index, block in enumerate(self.chain):
        if chain_index == 0:
          continue
        else:
          if block['previous_hash'] != self.make_hash(self.chain[chain_index-1]):
            print(f'[Block {chain_index-1}]Falsification is detected!!')
            return
      print('There is no falsification')


if __name__ == '__main__':
    # print('='*30 + 'Start' + '='*30)
    blockchain = BlockChain()
    blockchain.add_transaction(sender_name='Alice', reciever_name='Bob', value=100)
    blockchain.add_transaction(sender_name='Alice', reciever_name='Chris', value=1)
    blockchain.mining()
    blockchain.add_transaction(sender_name='Bob', reciever_name='Dave', value=100)
    blockchain.mining()
    blockchain.add_transaction(sender_name='Chris', reciever_name='Dave', value=100)
    blockchain.mining()
    blockchain.print_chain()
    blockchain.check_falsification()
```

### 実行結果
```
========================================Block   0
Initialize
========================================Block   1
previous_hash  :f904f9ea034f3e38ca26d52b5274b1d1159472ac12063194ee5c8b7f692e2adc
nonce          :33
transaction    :
        sender_name    :Alice
        reciever_name  :Bob
        value          :100
transaction    :
        sender_name    :Alice
        reciever_name  :Chris
        value          :1
timestamp      :1579064814.8078277
========================================Block   2
previous_hash  :75fd5621af68c8f2d8700f4585c6a05bf4f0ff781a9bef0924ad31c7ac3066b0
nonce          :220
transaction    :
        sender_name    :Bob
        reciever_name  :Dave
        value          :100
timestamp      :1579064814.815833
========================================Block   3
previous_hash  :5aa74c98450873ce6485a92a932cecbd72f542d961cb1cc3c2fd9129f15eebf2
nonce          :243
transaction    :
        sender_name    :Chris
        reciever_name  :Dave
        value          :100
timestamp      :1579064814.8278437
==============================Check falsification
There is no falsification ←改ざんなし
```

# 改ざんと検知
では次に改ざんを行ってみます。Daveさんが自分の資産を増やそうとBlock 2の取引のvalueを改ざんしたとします。blockchain.print_chainの直前に以下を追加します。

```Python
    blockchain.chain[2]['transaction'][0]['value'] = 10000
```

この状態でcheck_falsificationを実行するとどうなるでしょうか。

### 実行結果
```
==============================Make falsification
========================================Block   0
Initialize
========================================Block   1
previous_hash  :3697444272d45bb529d559f0ac13cd287a310303cae0db0e27c644d32d371c27
nonce          :105
transaction    :
        sender_name    :Alice
        reciever_name  :Bob
        value          :100
transaction    :
        sender_name    :Alice
        reciever_name  :Chris
        value          :1
timestamp      :1579064861.3724322
========================================Block   2
previous_hash  :e6cdee32c283131e191a97113887172fba2f61c49eae031d271162545c345bec
nonce          :146
transaction    :
        sender_name    :Bob
        reciever_name  :Dave
        value          :10000 ←改ざん
timestamp      :1579064861.3824363
========================================Block   3
previous_hash  :7810a72e38652124bff69c1436669e873a01d7d4b392107d210e33c3c841ca0c
nonce          :162
transaction    :
        sender_name    :Chris
        reciever_name  :Dave
        value          :100
timestamp      :1579064861.391444
==============================Check falsification
[Block 2]Falsification is detected!! ←改ざん検知
```
[Block 2]において改ざんが検知されました。

# まとめ
今回は改ざんから検知までを実装してみました。本来であればblockchainの中身の変数はカプセル化して操作不可にすべきですね。