Photo by Stock Catalog
秋山です。
皆さんは暗号通貨で遊んでいますか?
エンジニアの中には、ブロックチェーンなど暗号通貨で使われている技術に興味がある…という人も多いのではないでしょうか。最近は、ブロックチェーンを活用した新しいモノもどんどん増えていますね。
というわけで今回は、ブロックチェーンや採掘(≒Proof of Work)について、Pythonでコードを書きながら説明してみたいと思います。
■ブロックチェーンをPythonで実装してみる
最も単純なブロックチェーンの場合、ブロック単位のデータにハッシュ値があり、そのハッシュ値は一つ前のブロックのハッシュ値を含んで計算されています。そのため、すべてのデータはチェーン上に繋がって前後関係のもとにある…という状態です。
ハッシュ値が何かわからない人はググったりして調べていただければと思いますが(ハッシュ値(ダイジェスト値)とは - IT用語辞典)、ざっくり言うと、各データを要約した数列…のようなものです。
プログラミング的に表現すると、例えば配列に要素を追加するときは、必ず1つ前の要素をハッシュ値として含む…みたいなことです。
文章だけで説明するのは大変なので、とりあえず実際にコードで書いてみましょう。(実用するには脆弱なコードだとは思いますが、とりあえずこの記事で説明に使うためのコードとして大目に見てください…)
最後の行で順に出力した結果を見ると、最初のブロックを除いて、すべてのブロックが一つ前のブロックのハッシュ値を含んでいる鎖(チェーン)のような構造ができていますよね。
図にすると以下のような感じです。Bitcoinなどと比べてもかなり簡略化されたブロック構造ですが、ブロックチェーン的な構造になっています。
このチェーン構造は、途中でデータの書き換えが発生すると、当然ハッシュ値も変わってしまいます。そのため、どこかで書き換えが発生すると、それ以降のハッシュ値は全て再計算しなければなりません。
下図のように、一つ前のブロックのハッシュ値も含まれるため、2番目以前の書き換えが起こっても矛盾が発生してしまいます。
このような形で、ブロックチェーンは改ざんを検出できるようになっています。
ただ、どのブロックチェーンが真であるかは多数決なりで決まるため、この場合、たとえば悪意のある人が大多数で都合のいいように書き換えて、すべてのチェーンのハッシュ値を別のものへ置き換えてしまったりすると、取引データの改ざんが可能となってしまいます。
前述の簡単なコードだと、たとえば5ブロック分の取引を改ざんした上でハッシュ値を再計算し、複数人で「こっちが本物です」と言えば改ざんも可能となってしまいます。
■ルールを追加してみる
改ざんを防ぐために、新しいブロックを追加する際のルールを追加導入します。
- 新しいブロックのハッシュ値には任意の値を加算し、ハッシュ化した際にn桁目までは0で埋めなければならない。
- 1を満たす値を計算する際、取引データには好きな値を一つだけ加えてよい。
1.はブロックを作る時間をかけさせるために、任意の値(これ以降 nonce と呼びます。number used onceの略称で「一度だけ使われる値」という意味です)を探させます。(また「n桁目までは0で埋めなければならない」のn桁目は、これ以降difficulty(難易度)の頭をとってdiffと以降呼びます)
2.は、その計算をしてくれた人へのご褒美ではないですが、好きな値を一つだけ加えられる権利を与えます。このルールがあることによって、Bitcoinで言う採掘報酬、現在は12.5btcを好きなアドレスにもらえる権利…みたいなものが発生します。
で、このルールが追加されることによってどんないいことがあるかというと、新しいブロックを生成するのに時間がかかるようになります。ブロックチェーンでは、あるブロックを改ざんしようと思ったら、それ以降の連なるブロックも、すべて正しいハッシュ値に調整し直す必要がありますから、改ざんする場合も時間がかかってしまうのです。
今回のコードでは、paiza.IOでも実行できるようにブロックの生成時間は短くなるようにしていますが、これを10分、20分、30分…と延ばしていくことも簡単にできます。たとえば5ブロック分を改ざんしようとして5ブロック×10分の時間をかけていると、10分後には新しいブロックが追加されているため、永久に追いつけない…つまり改ざんができない!となるわけです。(もちろん5倍の計算力があるスーパーコンピューターを持っている人がいたりすると話は別ですが…)
それでは前述のコードにルールに沿った処理を追加していきましょう。
import hashlib, json class Block: def __init__(self, index, timestamp, prev_hash, transaction): self.index = index self.timestamp = timestamp self.prev_hash = prev_hash self.transaction = transaction self.diff = 4 #難易度 diff を追加 self.now_hash = self.calc_hash() self.nonce = None #採掘時に計算する対象 nonce を追加 def calc_hash(self): joined_data = { 'index' : self.index,\ 'timestamp' : self.timestamp,\ 'prev_hash' : self.prev_hash,\ 'transaction' : self.transaction,\ 'diff' : self.diff } json_text = json.dumps(joined_data, sort_keys=True) return hashlib.sha256(json_text.encode('ascii')).hexdigest()
ここで、nonceが正しいかをチェックする関数を作ります。
nonceが正しいかどうかを判断するのは、「sha256( 現在のブロックのハッシュ値 + nonce ) の上 diff 桁目までが 0 で埋まっているか?」という条件です。
この条件を満たすならTrue、満たさないならFalseを返す関数を作りましょう。
def check(self, nonce): nonce_joined = self.now_hash+str(nonce) calced = hashlib.sha256(nonce_joined.encode('ascii')).hexdigest() if calced[:self.diff:].count('0') == self.diff: return True else: return False
こんな感じですね。
ただ、チェックする関数だけがあっても、nonceを手で計算しまくって探すわけにもいかないので、今度は探す関数を作りましょう。と言っても、愚直にnonceを1ずつ増やしてハッシュ値を計算していくだけの関数です。
def mining(self, append_transaction): nonce = 0 self.transaction.append(append_transaction) self.now_hash = self.calc_hash() #報酬の好きな取引を一つ入れた後にハッシュ値を再計算、このハッシュ値に nonce を足して上diff桁まで0が続くものを探していきます。 while True: nonce_joined = self.now_hash+str(nonce) calced = hashlib.sha256(nonce_joined.encode('ascii')).hexdigest() if calced[:self.diff:].count('0') == self.diff: #見つかった場合は処理を抜ける。 break nonce += 1 return nonce
sha256はハッシュ値から任意の元のデータを作る(ここでは対応するnonceを作りたい…)のが現実的な計算時間では困難なので、愚直に計算するしかありません。
関数の引数の append_transaction は、採掘報酬として好きな取引を追加するための引数です。
■ルールを追加した結果を見てみる
関数もできたので、これらがちゃんと動作するか試してみましょう。
先ほどはブロックをただ繋げただけでしたが、今回は繋げる前に採掘をして、nonceを得るという作業があります。nonceは何も考えずに加算しているので、その数が採掘にかかった時間と考えればいいですね(時間と言っても今回は1秒以下ですが)。
また、今回はdiffを4としていますが、5以上にするとpaiza.IOではタイムアウトしてしまうので注意してください。
この結果を簡単に図にしてみました。最初とは違って、nonceという要素が加わっています。
さらにnonceとブロックのハッシュ値を使って、ブロックが難易度通りにルールを守っているか?を検証する流れは、以下の図のような感じです。
こんな感じで、nonceが真であるか否かのチェックは一度の計算でできます。
一方でnonceを探すには、今回のブロックで言うと 48192 回の計算を行っています。採掘している人が複数いる場合を考えると、採掘報酬の書き込みをしてハッシュ値を計算…という流れが前段に入ります。そのためブロックのハッシュ値は人によって変わるので、nonceの値も採掘している人たちそれぞれが別のモノを探すことになります。
それぞれのブロックはこのようにしてnonceを採掘し、正しいnonceかどうかを周りが確認して、正しいnonce・正しいデータであると皆が認めたものだけが繋がっていく…というわけです。
■まとめ
上記の例ではdiff = 難易度 を固定値として4を使いましたが、これを調整することで採掘を困難にさせたり、平均10分程度で採掘が終わるように調整させたり、Bitcoinみたいなこともできるようになります。
また、複数人で採掘を行う場合、nonceを見つけたブロックが枝分かれのように複数になってしまう場合もあり得ます。その場合は、それぞれのプログラムで採掘された結果に対して、最も長いブロックチェーンが優先されるというルールが導入されます。
たとえば、1番目のブロックが生まれ、Aさんが 2番目、3番目のブロックを作った時に、 Bさんが遅れて2番目のブロックを採掘してしまったとします。このとき、Aさんの作った2番目のブロックの nonce とBさんの作った2番目のブロックの nonce は異なっています。ここで最も長いブロックチェーンを優先するというルールに基づいて、Bさんは 2番目のブロックを破棄し、Aさんがすでに採掘したブロックを自分のブロックに取り込み、 4番目のブロックの採掘に取り掛かる…ということになります。(Bさんのブロックに取引を書き込んでしまった人の取引は巻き戻ることになります)
Bitcoinを使ったことがある人は、送金しても取引所にすぐ反映されない…といった経験があるかと思います。これは、上記のような巻き戻りを避けるために、一定のブロック数が進むまで待機状態になっているような場合が多いです。Bitcoinではよく6ブロック待ったりします。
機会があれば、そのへんの話も実際のコードと突き合わせながら書けたらと思います。
ほかにも暗号通貨やPythonや機械学習などに関する記事をいろいろ書いているので、興味のある方は見てみてください。
paiza.hatenablog.com
paiza.hatenablog.com
「プログラミング自体が初心者なので、まずはPythonを使えるようになりたい!」という方は、プログラミングが動画で学べる「paizaラーニング」の「Python入門編」(今年から全編無料になりました)から始めてみてください。
途中でブログパーツとして使ったオンライン実行環境サービス「paiza.IO (パイザ・アイオー)」はこちら
「PaizaCloud」では、環境構築に悩まされることなく、ブラウザだけで簡単にWebサービスやサーバアプリケーションなどの開発・公開ができます。
「paizaラーニング」では、未経験者でもブラウザさえあれば、今すぐプログラミングの基礎が動画で学べるレッスンを多数公開しております。
詳しくはこちら
そしてpaizaでは、Webサービス開発企業などで求められるコーディング力や、テストケースを想定する力などが問われるプログラミングスキルチェック問題も提供しています。
スキルチェックに挑戦した人は、その結果によってS・A・B・C・D・Eの6段階のランクを取得できます。必要なスキルランクを取得すれば、書類選考なしで企業の求人に応募することも可能です。「自分のプログラミングスキルを客観的に知りたい」「スキルを使って転職したい」という方は、ぜひチャレンジしてみてください。
詳しくはこちら