【Python実践】ブロックチェーン入門!Pythonで実装して基礎を学ぼう

こんにちは!フリーランスエンジニア・ライターの平山です。

皆さんの中にはブロックチェーンについて、こんな思いをお持ちではないでしょうか?

「ブロックチェーンって最近流行ってるらしいけど、ビットコインと何が違うの?」
「ブロックチェーンはインターネット発明以来の革命らしいけど、どうも信じられない。」

たしかに、ブロックチェーンはビットコインを支える基礎理論として誕生しました。

ですが、ブロックチェーンは仮想通貨にとどまらない、インターネットの信用革命ともいえる力を秘めているのです。

この記事では、そんなブロックチェーンをPythonを使って実際に作ってみます。

さっそく、Pythonを使ったブロックチェーンの世界に漕ぎ出しましょう。

ブロックチェーンの基礎

まずはじめに、ブロックチェーンとは何か?というところから始めましょう。

「ブロックチェーン」という言葉は昨今の仮想通貨の盛り上がりに合わせて、よく耳にするようになったのではないでしょうか?

それもそのはずで、ブロックチェーンは最も知られているであろう仮想通貨である、Bitcoinを支える基礎理論のひとつだからです。

bitcoin、およびここから派生した多くの仮想通貨は下にある4つのイノベーションによって支えられています。

  • 分散化されたpeer-to-peerネットワーク(Bitcoinプロトコル)
  • 公開取引元帳(ブロックチェーン)
  • 数学的かつ決定論的な通貨発行(分散マイニング)
  • 分散取引検証システム(トランザクションscript)

出典:Mastering Bitcoin
https://bitcoinbook.info/wp-content/translations/ja/book.pdf

その中でも中核的な技術が、今回扱うブロックチェーンなのです。

じつは、仮想通貨という考え方自体は90年代には登場していました。

ですが、結局実現には至りませんでした。

さまざまな要因が考えられますが、一番の要因はこれらの通貨が中央管理的なものだったからでしょう。

つまり、通貨を一元管理する「中央」が存在するために、そこを攻略してしまえば、容易に通貨を奪うことができてしまったのです。

この問題を解決するためにbitcoinが導入した仕組みがブロックチェーンでした。

ブロックチェーンは分散型台帳技術とも訳されます。

台帳とは取引の記録という意味でつかわれています。

そして、分散とは「すべての情報を全員で共有しよう」、という意味なのです。

ネットワークに参加している全員が取引の記録を保持していた場合、誰かが攻撃されたとしても、全体への影響はあまりありません。

攻撃により起きた変更を検出する仕組みと、データの整合性を保つ仕組みを持っていれば復元が可能だからです。

そのため、通貨を管理するための「中央」が必要なくなったのです。

ここがブロックチェーンの革新的な部分でした。

さらに言えば、ブロックチェーンの用途は、なにも通貨の取引を記録するためだけに限定する必要はありません。

ブロックチェーンは「ネットワーク上の信用を担保するためのシステム」として使うことができます。

そのため、株式、ローン、投票、公証、土地登記、保険、アプリケーションなどなど。

とても幅広い活用が期待されているのがブロックチェーンという技術なんですね。

ちなみにですが、上で出典として紹介しているMastering Bitcoin、入門書として非常に優れた内容です。

日本語翻訳版が無料でダウンロード可能なため、この分野に興味がある方は何を差し置いてもこのpdfは絶対におすすめです。

ブロックチェーンの簡単な実装

ブロックチェーンを実装するには

ブロックチェーンの概要をつかんだところで、より技術的な部分をみていきましょう。

ブロックチェーンは名前の通り、ブロックがチェーン、つまり鎖のように連続しているシステムになります。

このブロックと鎖がどのような働きをしているのか、具体例で見てみましょう。

まずはブロックについてです。

ここでは「太郎くんが花子さんへ仮想の通貨100エンを送る場合を」考えてみます。

ブロックチェーンは分散型「台帳」技術なので、まずは台帳を作りましょう。

つまり、取引の記録を残すのです。

記録に残さなければいけない内容は次の通りです。

  • あて先(だれに送るのか)
  • 差出人(だれが送ったのか)
  • 金額

これらの取引の内容をまとめてトランザクションと呼びます。

トランザクションに次の要素も付け加えて1セットの記録としましょう。

  • インデックス
  • タイムスタンプ
  • プルーフ
  • 前のブロックのハッシュ

新しい用語がたくさん出てきましたね。

順番に見ていきましょう。

インデックスはブロックの通し番号のようなものです。

リストのインデックスのようなイメージですね。

タイムスタンプは取引の時間を記録したものです。

プルーフは長くなるので後でじっくりと説明しましょう。

最後の「前のブロックのハッシュ」がブロックチェーンのキモとなる部分です。

ハッシュとは

ハッシュとは元になるデータから計算された規則性のない値のことです。

例えば、この記事のタイトルが入っている画像のハッシュは次のようになります。

2f167af289855baa6debf39fa07b716a21e333f8583a9bc3d067a0f7f744b03a

ハッシュは次の特徴を持っています。
同じファイルからは同じ値を得られる。
・少しでもファイルのデータが異なると全く違う値になる。
・ハッシュは簡単に生成できるが、ハッシュから元のファイルを復元するのはすごく大変

このような性質を持つために、ハッシュは暗号分野でよく使われます。

ブロックに話を戻すと、ブロックは自分の1つ前の取引でできたブロックのハッシュを持っています。

図にするとこんな感じですね。

このようにブロックは前のブロックのハッシュを持ち、前のブロックは更にその前のハッシュをもち、・・・

と延々とつながっていきます。

ブロック同士がハッシュによってまるで鎖がつながっているみたいなので、ブロックチェーンと呼ばれるわけですね。

このように鎖型にすることで得られるのが、攻撃への耐久性です。

ブロックのハッシュは前のブロックの内容から計算されています。

ということは、悪意ある人が過去のブロックを書き換えようとした場合、書き換え時点から現在までのすべてのハッシュを再計算して書き換える必要があります。

これが攻撃を検出し、攻撃を難しくする仕組みなんですね。

コードの実装

では、ここまでの性質をPythonでコードに起こしてみましょう。

目標は必要な要素をもったブロックを作り、前のブロックからハッシュを計算できるようにすることです。

import hashlib
import json
import datetime

class Block:
    def __init__(self, index, timestamp, transaction, previous_hash):
        self.index = index
        self.timestamp = timestamp
        self.transaction = transaction
        self.previous_hash = previous_hash
        self.property_dict = {str(i): j for i, j in self.__dict__.items()}
        self.now_hash = self.calc_hash()

    def calc_hash(self):
        block_string = json.dumps(self.property_dict, sort_keys=True).encode('ascii')
        return hashlib.sha256(block_string).hexdigest())

まずは、必要なモジュールのインポートからですね。

今回は

  • ハッシュ計算のためにhashlib
  • ハッシュ計算関数に代入するための形を整える用にjson
  • 時刻の取得用にdatetime

をインポートしました。

どれも標準ライブラリなので、インストール作業はとくにありません。

つづいて、ブロックの定義です。

先ほど説明したブロックに必要な要素をコンストラクタで設定しています。

ブロック自身のハッシュを表すnow_hashプロパティ

ハッシュ計算に必要な要素を辞書化したproperty_dictプロパティ

が追加してあります。

続くdef calc_hash(self):でハッシュの計算を定義しました。

ハッシュ計算用の辞書をjsonに変換して、中身の並べ替え、エンコードの変換を行っています。

中身の並べ替えは一貫性のあるハッシュを計算するために必要です。

そして、そのjsonをhashlib.sha256()に代入してハッシュを得ています。

hexdigest()はsha256オブジェクトを16進数文字列で表すためのメソッドです。

関数定義部分の最後はトランザクションを生成する関数です。

def new_transaction(sender, recipient, amount):
    transaction = {
        "差出人": sender,
        "あて先": recipient,
        "金額": amount,
    }
    return transaction

本来はクラスに含めるほうが仕様的に正しいです。

ですが、今回はブロックチェーンの構造理解に重点を置きたいので、重要性が低い部分を分離しました。

やっていることは入力した情報から辞書を作っているだけですね。

ブロックチェーンを動かしてみる

それでは、ブロックチェーンを動かしていきましょう。

block_chain = []

genesis_block = Block(0, 0, 0, "-")
block_chain.append(genesis_block)

最初にブロックチェーンを代入するためのリストを用意します。

このリストに生成されたすべてのブロックが代入されていきます。

そして、ブロックチェーンの起点となる最初のブロックをつくりましょう。

これは慣習的にジェネシスブロックといいます。

ジェネシスブロックはそれ以前の先祖となるブロックを持たないので、previous_hashに"-"を代入してあります。

ここからどんどんチェーンを繋いでいきましょう。

transaction = new_transaction("太郎", "花子", 100)
new_block = Block(1, str(datetime.datetime.now()),transaction , block_chain[0].now_hash)
block_chain.append(new_block)

最初にトランザクションを生成して、これをBlockクラスに代入します。

Blockクラスはインデックス、現在時刻、トランザクション、そして一個前のハッシュを使って新たなブロックをつくります。

最後にblock_chainに新しくできたブロックを追加して一連の流れが終了します。

原理的にはトランザクションの生成からblock_chainに追加するまでを延々と繰り返すだけでブロックチェーンは成長していきます。

それでは確認として、ジェネシスブロックと新しく作ったブロックの中身を見てみましょう。

for key, value in genesis_block.__dict__.items():
  print(key, ':', value)

print("")

for key, value in new_block.__dict__.items():
  print(key, ':', value)
# 実行結果
index : 0
timestamp : 0
transaction : 0
previous_hash : -
property_dict : {'index': 0, 'timestamp': 0, 'transaction': 0, 'previous_hash': '-'}
now_hash : 49f3a23af19229c5a1a12611bdb590f742154a5d11b5018f3f01b740800a5c20

index : 1
timestamp : 2018-10-06 05:50:33.226492
transaction : {'差出人': '太郎', 'あて先': '花子', '金額': 100}
previous_hash : 49f3a23af19229c5a1a12611bdb590f742154a5d11b5018f3f01b740800a5c20
property_dict : {'index': 1, 'timestamp': '2018-10-06 05:50:33.226492', 'transaction': {'差出人': '太郎', 'あて先': '花子', '金額': 100}, 'previous_hash': '49f3a23af19229c5a1a12611bdb590f742154a5d11b5018f3f01b740800a5c20'}
now_hash : 8537bd2621e7f70db6a9902e628f9b80328cb6e65c6974ee578986680600e1c7

こちらの設計意図通りに動きましたね!

簡単なブロックチェーンを改良する

これまでのコードの弱点

さて、無事ブロックチェーンがひとまず完成しましたが、じつはこのシステムには大きな欠陥があります。

どんな欠陥かというと、「特定の時点から現在までを一気に書き換えられるような攻撃者に襲われた場合、どうしようもない」という点です。

たしかにブロックチェーンの性質から、特定の時点「だけ」の改ざんはほぼ不可能です。

だったら現在分まで全部書き換えてしまえ、というのはある意味当然の発想ですね。

これに対処する方法が、先ほど後回しにしたブロックの要素、プルーフなのです。

これはどうやって攻撃に対処するのかというと、「ブロックの計算にめちゃくちゃ時間がかかるようにすればいい」という手段を取ります。

どういうことかというと、まず1つのブロックが新しく作られるまでに10分かかるとします。

攻撃者は5ブロック前の取引から現在分までを改ざんしたいとしましょう。

そうすると、攻撃側は10分以内に5ブロック分の書き換えを終えなければいけないことになります。

10分たってしまうと、新しいブロックが作られるため、またやり直しになるからですね。

そのため、攻撃者はブロック生成側の5倍早いコンピュータを用意する必要があります。

いまのところそこまで早いコンピュータを持っている攻撃者は現れていないので、この方式は問題なく現実に運用されています。

改良版のコードを実装する

では、この機能を先程のコードに実装してみましょう。

ブロックの計算にめちゃくちゃ時間をかけさせる方法はいくつか考案されていますが、ここでは代表的なものを紹介します。

ハッシュの性質にファイルの中身が少しでも違うと全く違う値が出力される、というものがありました。

これを利用しましょう。

ファイルに適当な数値を加えればハッシュはもとの値と全く異なります。

ならば、ブロックに適当な数字を加えて、こちらが指定する条件をもったハッシュ値のみを合格させるようにすれば、ブロックを作るのにかなり時間がかかりそうです。

条件は「ハッシュの頭4桁が全部0」でいきましょう。

こうすれば桁数を下げれば計算が早くなりますし、逆に上げれば時間をかけさせることができます。

難易度の調整が簡単にできるわけですね。

このことから、この条件をdifficultyとよびます。

これを実装したものがこちらになります。

class Block:
    def __init__(self, index, timestamp, transaction, previous_hash):
        self.index = index
        self.timestamp = timestamp
        self.transaction = transaction
        self.previous_hash = previous_hash
        self.difficulty = 4 #難易度を追加
        self.property_dict = {str(i): j for i, j in self.__dict__.items()}
        self.now_hash = self.calc_hash()
        self.proof = None # プルーフを追加
        self.proof_hash = None # プルーフを追加して計算したハッシュ

クラスのコンストラクタにプルーフと難易度が追加されました。

つづいて、プルーフを検証するための関数とプルーフを発見するための関数をクラスに追加します。

# プルーフの検証用関数
    def check_proof(self, nonce):
        proof_string = self.now_hash + str(nonce)
        calced = hashlib.sha256(proof_string.encode("ascii")).hexdigest()
        if calced[:self.difficulty:].count('0') == self.difficulty:
            self.proof_hash = calced
            return True
        else:
            return False

    # プルーフを採掘するための関数
    def mining(self):
        proof = 0
        while True:
            if self.check_proof(proof):
                break
            else:
                proof += 1
        return proof

プルーフを探すことをブロックチェーンでは採掘と呼びます。

金山で金の含まれてる岩を探すイメージですね。

説明の簡略化のために省いていますが、本当はこのプルーフを最初に発見した人には報酬を貰える仕組みがあります。

報酬をもらえるから、みんなが頑張ってプルーフを探索し、ブロックチェーンが健全に回り続ける、という具合です。

まさにbitcoinが盛り上がった2014年頃はゴールドラッシュの様相でした。

そういった意味でもうまいたとえですね。

関数の説明に戻りましょう。

check_proof関数はプルーフとブロックのハッシュの和のハッシュが「ハッシュの頭4桁が全部0」を満たすかどうかを検証しています。

言葉にするとややこしいですが、

proof_string = self.now_hash + str(proof)

で和をとって、

 
calced = hashlib.sha256(proof_string.encode("ascii")).hexdigest()

で和をハッシュに変換している、ということです。

そして、

        if calced[:self.difficulty:].count('0') == self.difficulty:
            self.proof_hash = calced
            return True
        else:
            return False

でハッシュの頭4けたが0かどうか確かめています。

Pythonらしいスライスの使い方ですね。

スライスについて不安な方はこちらでご確認ください。

プルーフを採掘する関数に関しては、特に難しいこともないでしょう。

proofを初期値0からcheck_proof関数に放り込んで、だめなら1を加える、ということを正解が見つかるまでやり続けます。

プルーフは簡単に求める公式がないとされているので、こういった愚直に計算を回すことでしか見つける方法がないんですね。

だから、計算に時間がかかって、ブロックチェーン全体の安全も担保されるわけです。

実際にブロックチェーンを作ってみる

あとは今までとほとんど同じですね。

今回は計算時間を実感するために、ブロックチェーンを5個ほど生成してみました。

block_chain = []

block = Block(0, 0, [], '-') #最初のブロックを作成

block.proof = block.mining()

block_chain.append(block)


for i in range(5):

    block = Block(i+1, str(datetime.datetime.now()), ["適当なトランザクション"], block_chain[i].now_hash)
    block.proof = block.mining()
    block_chain.append(block)

for block in block_chain:
    for key, value in block.__dict__.items():
        print(key, ':', value)
    print("")

#結果
index : 0
timestamp : 0
transaction : []
previous_hash : -
difficulty : 4
property_dict : {'index': 0, 'timestamp': 0, 'transaction': [], 'previous_hash': '-', 'difficulty': 4}
now_hash : d1f0dc2f2526d9873c958cac696cd4f64b230ebcfcc8537a0bf94645cc2760de
proof : 30621
proof_hash : 0000a63ece63ab6bc14791cf8bb4930b57f3e102c97040c1ac94c01c7a7bcb4c

index : 1
timestamp : 2018-10-06 07:58:25.054261
transaction : ['適当なトランザクション']
previous_hash : d1f0dc2f2526d9873c958cac696cd4f64b230ebcfcc8537a0bf94645cc2760de
difficulty : 4
property_dict : {'index': 1, 'timestamp': '2018-10-06 07:58:25.054261', 'transaction': ['適当なトランザクション'], 'previous_hash': 'd1f0dc2f2526d9873c958cac696cd4f64b230ebcfcc8537a0bf94645cc2760de', 'difficulty': 4}
now_hash : bbd19cc1d5239e28f2a628c791bc8c32a478e3d17c1874aa9e0c055c31f2312d
proof : 3847
proof_hash : 00003b32323660ee6e728e2a86f03c81b9ea8d3adf3f4a68cc4ae19d31c19455

index : 2
timestamp : 2018-10-06 07:58:25.065747
transaction : ['適当なトランザクション']
previous_hash : bbd19cc1d5239e28f2a628c791bc8c32a478e3d17c1874aa9e0c055c31f2312d
difficulty : 4
property_dict : {'index': 2, 'timestamp': '2018-10-06 07:58:25.065747', 'transaction': ['適当なトランザクション'], 'previous_hash': 'bbd19cc1d5239e28f2a628c791bc8c32a478e3d17c1874aa9e0c055c31f2312d', 'difficulty': 4}
now_hash : 6e8e56816c5764fdd1bdffb2886f464f03aa5f3c803b342312509b681ce873e3
proof : 67713
proof_hash : 0000d867571c59c00f48937faff37ab2ebadfb88113b9dcc65ab3441bfe8d2a6

index : 3
timestamp : 2018-10-06 07:58:25.261762
transaction : ['適当なトランザクション']
previous_hash : 6e8e56816c5764fdd1bdffb2886f464f03aa5f3c803b342312509b681ce873e3
difficulty : 4
property_dict : {'index': 3, 'timestamp': '2018-10-06 07:58:25.261762', 'transaction': ['適当なトランザクション'], 'previous_hash': '6e8e56816c5764fdd1bdffb2886f464f03aa5f3c803b342312509b681ce873e3', 'difficulty': 4}
now_hash : ed0dddfda2189900c59358c5296412b3ab29cd40d81468846af67ee03297c02f
proof : 1904
proof_hash : 0000600a9a6470ebe97a5afe64fe14d72c41c97d49c4a76ac9a3989e4727e5bd

index : 4
timestamp : 2018-10-06 07:58:25.268177
transaction : ['適当なトランザクション']
previous_hash : ed0dddfda2189900c59358c5296412b3ab29cd40d81468846af67ee03297c02f
difficulty : 4
property_dict : {'index': 4, 'timestamp': '2018-10-06 07:58:25.268177', 'transaction': ['適当なトランザクション'], 'previous_hash': 'ed0dddfda2189900c59358c5296412b3ab29cd40d81468846af67ee03297c02f', 'difficulty': 4}
now_hash : 90147001d30b5536ef1756c26393ccc8d05da8509b52a1b1384b184c47d37aca
proof : 32679
proof_hash : 00006eb7dc8edd30eff912342c3cca92769f9c4ce981e336637a463af3790166

index : 5
timestamp : 2018-10-06 07:58:25.367742
transaction : ['適当なトランザクション']
previous_hash : 90147001d30b5536ef1756c26393ccc8d05da8509b52a1b1384b184c47d37aca
difficulty : 4
property_dict : {'index': 5, 'timestamp': '2018-10-06 07:58:25.367742', 'transaction': ['適当なトランザクション'], 'previous_hash': '90147001d30b5536ef1756c26393ccc8d05da8509b52a1b1384b184c47d37aca', 'difficulty': 4}
now_hash : 961a74b31c4a440cfaffbd9801621cb4e48edb5168249388274f8892da2d1c85
proof : 16278
proof_hash : 0000319ad5cb602530627e723cb8b7c857cbb2fcbe07e660637695f4c4506c0d

実行するパソコンの性能にもよりますが、難易度が4桁くらいなら数秒もかからずにできてしまうのではないでしょうか。

ですが、これが5桁、6桁と桁を上げていくと爆発的に時間が増え始めます。

6桁ぐらいからは実行にかなりの覚悟が必要ですね。

コードをまとめると以下のようになります。

import hashlib
import json
import datetime

class Block:
    def __init__(self, index, timestamp, transaction, previous_hash):
        self.index = index
        self.timestamp = timestamp
        self.transaction = transaction
        self.previous_hash = previous_hash
        self.difficulty = 4 #難易度を追加
        self.property_dict = {str(i): j for i, j in self.__dict__.items()}
        self.now_hash = self.calc_hash()
        self.proof = None # プルーフを追加
        self.proof_hash = None # プルーフを追加して計算したハッシュ

    def calc_hash(self):
        block_string = json.dumps(self.property_dict, sort_keys=True).encode('ascii')
        return hashlib.sha256(block_string).hexdigest()

    # プルーフの検証用関数
    def check_proof(self, proof):
        proof_string = self.now_hash + str(proof)
        calced = hashlib.sha256(proof_string.encode("ascii")).hexdigest()
        if calced[:self.difficulty:].count('0') == self.difficulty:
            self.proof_hash = calced
            return True
        else:
            return False

    # プルーフを採掘するための関数
    def mining(self):
        proof = 0
        while True:
            if self.check_proof(proof):
                break
            else:
                proof += 1
        return proof

# 今回はトランザクションを生成させないのでコメントアウトさせています。
'''
def new_transaction(sender, recipient, amount):
    transaction = {
        "差出人": sender,
        "あて先": recipient,
        "金額": amount,
    }
    return transaction
'''
block_chain = []

block = Block(0, 0, [], '-') #最初のブロックを作成

block.proof = block.mining()

block_chain.append(block)


for i in range(5):

    block = Block(i+1, str(datetime.datetime.now()), ["適当なトランザクション"], block_chain[i].now_hash)
    block.proof = block.mining()
    block_chain.append(block)

for block in block_chain:
    for key, value in block.__dict__.items():
        print(key, ':', value)
    print("")

ブロックチェーンに攻撃を仕掛けるにはものすごくエネルギーが必要なこと、実感できたのではないでしょうか。

ブロックチェーン入門と実装・まとめ

いかがでしたか?

今回は

  • ブロックチェーンの基礎
  • 簡単なブロックチェーンの実装
  • プルーフをつかった計算時間の遅延とマイニング

についてお伝えしました。

ブロックチェーンは仮想通貨の影響か、なにやら怪しげな新技術なんじゃないか、というイメージが先行気味です。

ですが、実際に触ってみたらとても地道でまっとうな技術が組み合わさったものだと実感できたのではないでしょうか?

これから世界を激変させるかもしれないブロックチェーンに手応えを感じていただけたなら何よりも嬉しく思います。

もしこの先ブロックチェーンの基礎を振り返りたくなったなら、またこの記事を見返してください。

プログラミング学習の挫折率は約90%と言われています。学習を成功させるには、モチベーションを維持して成長を実感できる環境が必要です。

侍エンジニアなら「現役エンジニア講師」、「学習コーチ」、「Q&A掲示板」のトリプルサポート体制であなたの学習をサポートするほか、オーダーメイドカリキュラムで必要なことだけを学べるため、さまざまなランキングでNo1という実績を挙げています。

挫折せず最短でプログラミングを習得したいなら侍エンジニアがおすすめです!

詳しくはこちら

Writer

平山 晃

株式会社SAMURAI

フリーのエンジニア・ライター。
プログラミング、ライティング、マーケティングなど、あらゆる手段を駆使して、
ハッピーなフルリモートワーカーを目指し中。

最近興味がある分野は深層強化学習。
積みゲー、積ん読がどんどん増加しているのがここ数年の悩み。
実は侍エンジニア塾の卒業生だったりします。

あなたの目的に合わせた
SAMURAI ENGINEERの運営サービス

SAMURAI ENGINEER Pro

未経験でも挫折しないプログラミングスクール

詳細はこちら

SAMURAI TERAKOYA

日本最大級のサブスク型オンラインITスクール

詳細はこちら

SAMURAI ENGINEER Freelance

「一人で稼げる」スキルを身につける

詳細はこちら
ページ上部へ戻る