PoWを使った簡易ブロックチェーンをpythonで実装
« 一覧に戻る

PoWを使った簡易ブロックチェーンをpythonで実装

Programming

前回は非常にシンプルな、2つのブロックをハッシュ値でつなぐブロックチェーンを作りました。
Pythonのクラスやインスタンス(オブジェクト指向)の復習にもなるので、ぜひ見てみてください。

(リンク)

最近ふと、ブロックチェーンへの興味がより一層深まってきて、自分でコードを書きたくなりました。 ブロックチェーンは何かを、文章としての説明では理解していたつもりで…pathlog.jp

ですが、さすがにこれだけではブロックチェーンとは呼べませんので、今回は Proof of Work(PoW)を実装しなぜブロックの改ざんが困難になるのかをコードレベルで確認していきます。

また、それに伴って、マイニングやタイムスタンプ、その他もろもろ(ハッシュの難易度調整、個数調整)を追加しました。

完成品コード

以下が、今回の実装をすべて含んだ完成形のコードです。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

だいぶ「ブロックチェーン」になってきたのではないでしょうか!

解説

このコードは、ざっくり見ると以下のような構成になっています。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

それぞれ見ていきましょう。

とその前に、まずはハッシュ関数と、時間のライブラリをインポートします。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

Blockクラス

このクラスで、一つのブロックのインスタンスを作成できるようにします。

基本的には、やっていることはシンプルで、情報の保存だけです。

インスタンス作成時に、引数としてデータや一つ前のブロックのハッシュ値、ナンス、自らのハッシュ値、タイムスタンプを受け取り、それらを保存します。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

それに加えて、後で楽になるので、こいつ自身にもハッシュする関数を入れておきます。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

これだけになります。

メインはすべてBlockchainクラスに入っています。

Blockchainクラス

まず、それぞれがどのような役割を分担しているのか確認しましょう。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

このようになっております。

__init__とハッシュ関数

まず、Blockchainインスタンスが作成されたら、self.chainというリストを作ります。ここにブロックを入れていきます。

それから難易度(ハッシュ値の最初に0が何個並ぶか)と生成するブロックの個数をinputで取得します。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

そのあとは、create_genesis_blockを呼び出して、genesis_blockを作成します。それを先程のリストに入れます。

これが最初のブロックです。

そのあとは、最初のinputの値に応じて、forループでブロックを追加していきます。

二回目以降は、データとしてf”block {i}:を使用しています。つまり3つめのブロックなら、”block 3″ですね。

hash_block関数は、データ、一つ前のハッシュ値、ナンス、タイムスタンプ(ブロック生成時のもの)を引数として受け取ります。

それらをstringとして足し合わせて、SHA-256でハッシュした値を返します。

ジェネシスブロック(create_genesis_block)

次に、ジェネシスブロックの生成の関数があります。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

これは、まず呼び出されたときにデータを”genesis”、previous_hashを0としてself.mine_block関数に渡して、マイニングしてもらいます。

見つかった結果をgenesisとして保存します。

これは以下のような辞書形式で返ってきます。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

上から見つかったナンス(調整値)、ハッシュ値、タイムスタンプ(エポック秒)、経過時間、試行回数です。

Blockインスタンスの作成にはdata, previous_hash, nonce, hash, timestampがそれぞれ必要ですから、

Block("genesis", "0", genesis["nonce"], genesis["hash"], genesis["timestamp"])

のようにして、ジェネシスブロックを作成します。

データは”genesis”、前のブロックのハッシュ値は0、ナンス、ハッシュ値、タイムスタンプを渡します。
そうして得られたBlockを返します。

こうして得られた値を、__init__で、リストに追加します。

self.chain.append(genesis_block)

これで、ジェネシスブロックの完成です!

マイニング(mine_block)

次に、今回追加した、Proof of Work(PoW)を使ったマイニングを行う関数です。

ここはとても楽しいところです。

そもそもPoWとは、データにナンスと呼ばれる任意の数字を加えて、それをハッシュした値の先頭に指定された個数の0が並べば、それが認められるというものです。

この関数は、その条件に合ったときのナンスやハッシュ値、時間などを辞書形式にし、返します。

ナンスはnonce、指定する先頭の0の個数がdifficultyです。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

ナンスを0、試行回数を0に初期化します。

targetは、0をdifficulty個かけたものです。difficultyが5ならば”00000″です。
ナンスを加えてハッシュした結果が、targetで始まれば、そのナンスで決定です。

経過時間計測用のstart_timeと、timestampも定義します。

ここから、マイニングの作業を定義していきます。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

whileループを用いて、ハッシュを何度も何度も試行します。

ナンス=0から始めて、だめなら+1して再挑戦、だめなら+1して再挑戦を繰り返します。(attemptsも、この回数を記録します)

そして、多くの試行を経て条件に合うものが見つかったら、breakします。

その後は以下のように処理されます。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

終了時間を記録し、終了時間から開始時間を引くことで、処理にかかった時間を算出します。

resultという辞書にまとめてこれらの結果を入れ、それを返します。

今回は実際のBitcoinのブロックチェーンに基づいて、時間もハッシュの対象に含めています
これにより(取引)時間も改ざん不可能となりました。

各ブロックは timestamp を含む完全再計算可能な hash を持つのです。

以上が簡易的ですが、マイニングの実装になります。

ブロックの追加(add_block)

ここでは、ジェネシスブロックを除いた、ブロックの追加作業を行っていきます。

具体的には、まずマイニングの指令を先程のマイニング関数に出して、必要な情報をかき集めて、それを使ってBlockのインスタンスを作成します。

完成形は以下のようになります。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

マイニングに必要なのはデータと一つ前のブロックのハッシュ値ですから、

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

のようにすることで、マイニングの結果(辞書)を取得することができます。

それらをわかりやすくするために変数に代入し、↓

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

ブロックを作成してチェーンリストに追加します。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

あとは結果をわかりやすく表示すれば完成です。
このような形式でターミナルに表示されます。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

これで肝心な動く部分は完成です!

チェーン検証(is_chain_valid)

ここでは以下3つの観点から、ブロックチェーンの正しさをチェックしています。

  • ブロック同士のハッシュの繋がり
  • タイムスタンプの関係
  • PoWにおけるナンスも含めたハッシュ値の整合

この3点です。

これを一段階具体的なロジックにすると、(4段階)

現在のブロックに保存されている「一つ前のブロックのハッシュ値」と、一つ前のブロックのハッシュ値が同値である

現在のブロックの作成された時間が、一つ前のブロックの作成された時間よりも遅い
かつ
現在のブロックの作成された時間が、一つ前のブロックが作成されてから〇〇秒以内になっている

保存された現在のブロックのハッシュ値が、計算した結果と同値である

現在のブロックのハッシュ値を計算し、それが指定された数の0で始まる

ということになります。

本実装では簡略化のため、ブロック生成間隔の上限を100秒とハードコードします。

擬似コードにすると、

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

となります。

これをブロックごとにループで回しますので、最終的にコードは以下のようになります。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

ここで、Blockクラス自身のハッシュ関数を使っています。

チェーンがうまくいっていれば、Trueを返します。

実行

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

bcというBlockchainクラスのインスタンスを作成します。
is_chain_validの結果を表示するようにします。

実行結果は以下のようになります。

難易度6のブロック個数5と入力しました。

[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

以上で完成です!お疲れ様でした。

おわりに

自分でブロックチェーンを書いて、着々と仕組みを深く理解していくのが、本当に楽しくなってきました。

ここまで自力で書ける自分にも驚いています。
時間を忘れてやってしまいます笑

次はこの愛着のある自作ブロックチェーンを使って、数値をいじくって観察したいと思います。

今後はこの実装を使って、以下のような実験を行う予定です。

difficulty と attempts の関係の統計分析

ブロック生成時間の分布の可視化

nonce をランダム初期化した場合の偏り検証

difficulty を途中で変更した場合の fork 実験

以上です!

Ryo

Ryo

CS好きの高校一年生。現在は米大学のラボでエネルギー制約下の分散コンピューティング経済学の研究をしています。