@yuichirominato 2019.01.14更新 393views

【お試し】ブロックチェーンをpythonで実装してみて量子コンピュータソリューションが盛り込めるか考察してみる

PoW ブロックチェーン 暗号

はじめに

Pythonでブロックチェーンの仕組みを学ぶために実装をしてみるという記事がありました。量子コンピュータもブロックチェーンや仮想通貨に対して無関係ではないので、そのあたりを調べてみたいと思います。ただ模倣してるだけではあれなので、作ってみて量子コンピュータソリューションが盛り込めるかどうかの考察を最後に入れてみます。

参考

主にpythonでつくれるブロックチェーンを探してみました。二種類ほどやってみたいと思います。

python3でブロックチェーンを作ってみる
https://qiita.com/weedslayer/items/d1aabe7cf31d182481fb

こちらはこれが元になってるらしいので、

Let’s Build the Tiniest Blockchain In Less Than 50 Lines of Python
https://medium.com/crypto-currently/lets-build-the-tiniest-blockchain-e70965a248b

ブロックチェーンを作ることで学ぶ 〜ブロックチェーンがどのように動いているのか学ぶ最速の方法は作ってみることだ〜
https://qiita.com/hidehiro98/items/841ece65d896aeaa8a2a

こちらもこれが元になってるらしいので、

Learn Blockchains by Building One
https://hackernoon.com/learn-blockchains-by-building-one-117428612f46

まずは簡単なブロックチェーンを作ってみる

50行以下で作れるブロックチェーンを参考にしてみます。

ブロックチェーンとは?

時系列に並んだハッシュ値の塊のようです。

ブロックチェーンの構造

データ構造はシンプルです。下記のようなデータが格納されます。

  1. timestamp
  2. index
  3. data
  4. previous_hash
  5. hash

全体の流れ

  1. ブロックの定義を用意
  2. 1つ目のブロックを作って、全体のリストを用意する
  3. リストの最後のブロックから新しいブロックを作る
  4. 新しく作ったブロックをリストに追加
import hashlib as hasher 
import datetime as date 

class Block: 
    def __init__(self, index, timestamp, data, pre_hash): 
        self.index = index 
        self.timestamp = timestamp 
        self.data = data 
        self.pre_hash = pre_hash 
        self.hash = self.hash_block() 

    def hash_block(self): 
        sha = hasher.sha256() 
        sha.update((str(self.index) + 
                    str(self.timestamp) + 
                    str(self.data) + 
                    str(self.pre_hash)).encode('utf-8')) 
        return sha.hexdigest() 

def next_block(last): 
    data1 = "block" 
    return Block(last.index+1, date.datetime.now(), data1, last.hash) 

#最初のブロックをつくる
chain = [Block(0,date.datetime.now(),"First","0")] 
pre_block = chain[0] 

#繰り返して格納してみる
for i in range(20): 
    blocks_to_add = next_block(pre_block) 
    blockchain.append(blocks_to_add) 
    pre_block = blocks_to_add 
    print("Block",blocks_to_add.index,":",blocks_to_add.hash) 

実行結果

実行した結果できました。

Block 1 : e02f912651eb96de4af88ed2dfbae0983139215db150008b1e5cb0610f598af3
Block 2 : 6814e942dec228327a73531b050f7242a4659073af4a64ce970cb165294d93dc
Block 3 : 261e771a19d1a7084fac613383a3c4e642166bf051b785f6070fe0d8a7302cce
Block 4 : ce27be258217d980e6ece527b44f915ac866bdf66dfdadb8df6452d04fa806cc
Block 5 : f701a8b371e8abccd807eb89e2a2513c71bd915b571ec72366f79a002658892f
Block 6 : 5eaa307133502c3a6762c95a4211c723d9c0f5807eedcf279985af1b1e9db012
Block 7 : bef655f60c5167970c1cda945e57e6ec97e6b704c3c089bd97ec6389b2136ae2
Block 8 : db95e7fcaf51cc0d628020a460a1d4df415113fc359ad5c773ba83321c08a686
Block 9 : 92d8684079a42f812b4f0320b4874008a799b501ba3f7b526d72d5ced554c2f4
Block 10 : 296754918bfa1e926716fa863d84362c65f8c8deb036e011aa9bbbd7ee6902fc
Block 11 : ef27836b886f8c56231d1e7541d00c25312e8004f90668987a3212bbeee53576
Block 12 : 798bdbd7922d9f887cee2f3485020d1a9e993fecf500be9d75909475b344e352
Block 13 : a6f8b4d26a382b5d31ede5989e667194f19ec52b955c79f043c9823fac4a530b
Block 14 : c3caafbd0304813bc405ed24d01b7ee5e7e7a3ffee8a6120792bd9be34d412d9
Block 15 : c647eb7a31a15a9c9e81911b5e3e7c9d064c4e7b24641de4d37193a00dc26157
Block 16 : b985c7341119cff95e7e2355f47829541c47e511ff3d974935ee64e609725604
Block 17 : 6aa6e2af15260154efb1354211890049e5ec4e4b37e0a5c88d212cd1cd1f4503
Block 18 : 419f16eb19b11af42313427ad4b3276e25babccb96011bd2fa209f6b2bd1f942
Block 19 : 499d6771a396d0e94e45daca11da5a154ffcaebc0a39ceb715ab5252da2d8ef3
Block 20 : ae071a188a0190d2a7f8b0f3e0aae5ad266b6302a1306fb662ec8017979cacb2

そして、きちんとブロックができて、データが格納されているのが確認できました。

vars(chain[3])                                                                                                     

{'index': 3,
 'timestamp': datetime.datetime(2019, 1, 14, 15, 48, 58, 255764),
 'data': 'block',
 'pre_hash': '6814e942dec228327a73531b050f7242a4659073af4a64ce970cb165294d93dc',
 'hash': '261e771a19d1a7084fac613383a3c4e642166bf051b785f6070fe0d8a7302cce'}

中間考察

まずは簡単なブロックチェーンの仕組みを見てみました。核となるのは、ハッシュ計算ですが、現状の量子コンピュータではハッシュ計算は苦手なので、あまり応用例は現時点ではなさそうです。逆にハッシュ計算などは量子アニーリングで行うなどイジングに落とし込むのはありかもしれません。

トランザクションとプルーフのついたものを作る

こちらを参考にやってみます。また1からやってみます。また、今回はAPIの導入はせず、トランザクションとプルーフくらいまでを実装してみて考察してみます。
https://qiita.com/hidehiro98/items/841ece65d896aeaa8a2a

全体構成は下記のようになる。先ほどと異なるのはデータにトランザクションあたりが入る。これをさわっていく。

class Blockchain(object):
    def __init__(self):
        self.chain = []
        self.current_transactions = []

    def new_block(self):
        # 新しいブロックを作り、チェーンに加える
        pass

    def new_transaction(self):
        # 新しいトランザクションをリストに加える
        pass

    @staticmethod
    def hash(block):
        # ブロックをハッシュ化する
        pass

    @property
    def last_block(self):
        # チェーンの最後のブロックをリターンする
        pass

データ構造も前におこなったものと同様だが、トランザクション関連とプルーフが追加になっている。

block = {
    'index': 1,
    'timestamp': 1506057125.900785,
    'transactions': [
        {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5,
        }
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

トランザクションをブロックに加える

ブロックチェーンで取引した時の送受信者と取引内容の情報をデータに格納してみる。トランザクション情報をリストに追加してindexを返す。

class Blockchain(object):
    ...

    def new_transaction(self, sender, recipient, amount):
        """
        次に採掘されるブロックに加える新しいトランザクションを作る
        :param sender: <str> 送信者のアドレス
        :param recipient: <str> 受信者のアドレス
        :param amount: <int> 量
        :return: <int> このトランザクションを含むブロックのアドレス
        """

        self.current_transactions.append({
            'sender': sender, #<str>送信者アドレス
            'recipient': recipient, #<str>受信者アドレス
            'amount': amount, #<int>取引量
        })

        return self.last_block['index'] + 1 #ブロックのアドレス

ブロックを生成する

前述と大きな違いはないが、プルーフやトランザクションが考慮されている。

import hashlib
import json
from time import time


class Blockchain(object):
    def __init__(self):
        self.current_transactions = []
        self.chain = []

        # ジェネシスブロックを作る
        self.new_block(previous_hash=1, proof=100)

    def new_block(self, proof, previous_hash=None):
        """
        ブロックチェーンに新しいブロックを作る
        :param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ
        :param previous_hash: (オプション) <str> 前のブロックのハッシュ
        :return: <dict> 新しいブロック
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }

        # 現在のトランザクションリストをリセット
        self.current_transactions = []

        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

    @property
    def last_block(self):
        return self.chain[-1]

    @staticmethod
    def hash(block):
        """
        ブロックの SHA-256 ハッシュを作る
        :param block: <dict> ブロック
        :return: <str>
        """

        # 必ずディクショナリ(辞書型のオブジェクト)がソートされている必要がある。そうでないと、一貫性のないハッシュとなってしまう(?)
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

(息抜き)プルーフ・オブ・ワークの理解

こちらもきちんとまとめてある。見つけるのが難しく、確認が簡単な問題を利用して、採掘の権利を与える。

問題:x=5にy=?をかけることで、ハッシュの最後の数が0になるようなyをさがす。

from hashlib import sha256

x = 5
y = 0 #0から順番に探す

while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
    y += 1

print(f'The solution is y = {y}')

みつかると、

The solution is y = 21

y=21でみつかりました。確認して確認できました。最後が0になってます。

sha256(f'{5*21}'.encode()).hexdigest()                                                                              
'1253e9373e781b7500266caa55150e08e210bc8cd8cc70d89985e3600155e860'

ビットコインなどではこのhashcashを0の桁数を増やしてPoWを実施している。

PoWの実装

ということで、PoWも実装の上まとめてみると、

import hashlib
import json
from time import time
from uuid import uuid4

class Blockchain(object):
    def __init__(self):
        self.current_transactions = []
        self.chain = []

        # ジェネシスブロックを作る
        self.new_block(previous_hash=1, proof=100)

    def new_block(self, proof, previous_hash=None):
        """
        ブロックチェーンに新しいブロックを作る
        :param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ
        :param previous_hash: (オプション) <str> 前のブロックのハッシュ
        :return: <dict> 新しいブロック
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }

        # 現在のトランザクションリストをリセット
        self.current_transactions = []

        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):
        """
        次に採掘されるブロックに加える新しいトランザクションを作る
        :param sender: <str> 送信者のアドレス
        :param recipient: <str> 受信者のアドレス
        :param amount: <int> 量
        :return: <int> このトランザクションを含むブロックのアドレス
        """

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

    @property
    def last_block(self):
        return self.chain[-1]

    @staticmethod
    def hash(block):
        """
        ブロックの SHA-256 ハッシュを作る
        :param block: <dict> ブロック
        :return: <str>
        """

        # 必ずディクショナリ(辞書型のオブジェクト)がソートされている必要がある。そうでないと、一貫性のないハッシュとなってしまう
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()


    def proof_of_work(self, last_proof):
        """
        シンプルなプルーフ・オブ・ワークのアルゴリズム:
         - hash(pp') の最初の4つが0となるような p' を探す
         - p は1つ前のブロックのプルーフ、 p' は新しいブロックのプルーフ
        :param last_proof: <int>
        :return: <int>
        """

        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof

    @staticmethod
    def valid_proof(last_proof, proof):
        """
        プルーフが正しいかを確認する: hash(last_proof, proof)の最初の4つが0となっているか?
        :param last_proof: <int> 前のプルーフ
        :param proof: <int> 現在のプルーフ
        :return: <bool> 正しければ true 、そうでなれけば false
        """

        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()

        return guess_hash[:4] == "0000"

考察

一通り実装を通じてみてみました。PoWの部分の総当たり検索をどうにかするしかなさそうですね。。。重ね合わせなどを使って計算量が減るかどうかという検討はできそうな気はします。ブロックチェーンの実装だけでは暗号がでませんでしたので、その辺りを今後仕組み的にどうなっているかを確認したいと思います。

Recommended


Wikiへ移動