技術メモ

役に立てる技術的な何か、時々自分用の覚書。幅広く色々なことに興味があります。

pythonでコードを書きながらブロックチェーンを理解する

いまや知らない人はいない仮想通貨、別名暗号通貨(cryptocurrency)。
ドル円とは比べ物にならないほどの殺人的なボラティリティを見せているけれど、しばらくすれば落ち着いてくるんだろうか。
はっきりいって今の相場と税率でレバレッジ15倍とか正気の沙汰とは思えない。樹海の中に宝探しに行くようなものだ。

今回の記事の本題はその仮想通貨の仕組みについて。
投資にも興味はあるが、仮想通貨の仕組み自体にも興味がある。
仮想通貨の仕組みを知れば投資先の選択にも参考になるんじゃないかなと思うし、なにより以前から技術的に面白そうだと思っていた。
その仮想通貨の基盤となる技術がブロックチェーンというもので、これを理解しないことには始まらない。ということでファーストステップとしてブロックチェーンを理解するためにコードを写経してみた。

写経するのはid:mizchi氏の記事

コード自体はjavascriptで書かれているものを、アレンジを加えつつpython(python3.5)に書き直してみる。
そっくりなタイトルを見てわかる通りまあただの写経。)
大元のソースはこれnaivechain/main.js at master · lhartikk/naivechain · GitHub

ザックリとした説明

ブロックチェーンとは

  • ブロックチェーンとはその名の通りブロックを鎖状に繋げたもの
  • ブロックとは ①自身のID番号(ハッシュ値) ②前のブロックのID番号 ③その他(取引履歴など)の情報 を持つもの*1

ブロックチェーンは書き換えができない

ID番号は計算によって出てくるものなんだけど、計算するにあたって前のブロックのID番号や自身のブロックの情報を使うことになる。
なので途中のブロックの情報を書き換えるとそれ以降に続いているブロックのID番号が計算と合わなくなってしまう。これがブロックの書き換えができないと言われている大きな理由。

f:id:swdrsker:20180123121137p:plain

コード

blockchain.pyというファイル名で保存

import hashlib
import time
from datetime import datetime

class Block:
    def __init__(self, index, previous_hash, data, timestamp, this_hash):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = timestamp
        self.this_hash = this_hash

    def equal(self, block):
        if not self.index == block.index:
            return False
        elif not self.previous_hash == block.previous_hash:
            return False
        elif not self.data == block.data:
            return False
        elif not self.timestamp == block.timestamp:
            return False
        elif not self.this_hash == block.this_hash:
            return False
        return True


class Blockchain:
    def __init__(self):
        self.blockchain = [self.get_initial_block()]

    def get_initial_block(self):
        return Block(0,
                    "0",
                    "my genesis block!!",
                    1465154705,
                    "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7")

    def get_length(self):
        return len(self.blockchain)

    def get_latest_block(self):
        return self.blockchain[-1]

    def generate_next_block(self, block_data):
        previous_block = self.get_latest_block()
        next_index = previous_block.index + 1
        next_timestamp = int(time.mktime(datetime.now().timetuple()))
        next_hash = calculate_hash(next_index, previous_block.this_hash, block_data, next_timestamp)
        return Block(next_index, previous_block.this_hash, block_data, next_timestamp, next_hash)

    def is_valid_new_block(self, new_block, previous_block):
        if previous_block.index + 1 != new_block.index:
            return False
        elif previous_block.this_hash != new_block.previous_hash:
            return False
        elif calculate_hash_for_block(new_block) != new_block.this_hash:
            return False
        return True

    def is_valid_chain(self):
        if not self.blockchain[0].equal(self.get_initial_block()):
            return False
        else:
            tmp_block = self.blockchain[0]
            for i in range(1,self.get_length()):
                if not self.is_valid_new_block(self.blockchain[i], tmp_block):
                    return False
                else:
                    tmp_block = self.blockchain[i]
        return True

    def add_block(self, new_block):
        if self.is_valid_new_block(new_block, self.get_latest_block()):
            self.blockchain.append(new_block)
        else:
            raise ValueError("add block error!")


def calculate_hash(index, previous_hash, data, timestamp):
    string = str(index) + previous_hash + data + str(timestamp)
    return hashlib.sha256(string.encode('utf-8')).hexdigest()

def calculate_hash_for_block(block):
    return calculate_hash(block.index, block.previous_hash, block.data, block.timestamp)
簡単に動かしてみる

同じフォルダ内で呼び出して使う

from blockchain import *

# ブロックチェーンのインスタンス
blockchain = Blockchain()

# 新しいブロック
new_block = blockchain.generate_next_block("Hello World!")

# ブロックを加える
try:
    blockchain.add_block(new_block)
except ValueError as e:
    print(e)

# もう一つ加える
new_block = blockchain.generate_next_block("hoge")
try:
    blockchain.add_block(new_block)
except ValueError as e:
    print(e)
競合ブロックを作って追加するテスト

先ほどの続きに書く
もし何かの手違いで新しいブロックを生成するタイミングが被ってしまうとどうなるか、その時は同じ「前のブロックチェーンのID番号」を持ったブロックができてしまう。そのようなブロックを追加する場合はエラーが出て弾かれるようにしたい。*2
競合ブロックを作ってみて、2つ目のブロックが追加できないことを確認する

block1 = blockchain.generate_next_block("original block")
block2 = blockchain.generate_next_block("second block")
try:
    blockchain.add_block(block1)
except ValueError as e:
    print(e)
try:
    blockchain.add_block(block2)
    print("競合テストNG")
except ValueError as e:
    print("競合テストOK!") # ここでエラーになれば成功
  • 結果
競合テストOK!
途中のブロックを書き換えるテスト

途中のブロックのデータを書き換えて、ブロックチェーンが不適切だと判定されることを確認する

# 各々のブロックのデータを見てみる
print("-----Block data------")
for i in range(blockchain.get_length()):
    print(blockchain.blockchain[i].data)

# 途中のブロックのデータを書き換える
blockchain.blockchain[2].data = "invalid message!!!!"
print("-----Block data (changed)------")
for i in range(blockchain.get_length()):
    print(blockchain.blockchain[i].data)

# 今のブロックチェーンが妥当かどうか検証
if not blockchain.is_valid_chain():
    print("書き換えテストOK!")
else:
    print("書き換えテストNG")
  • 実行結果
-----Block data------
my genesis block!!
Hello World!
hoge
original block
-----Block data (changed)------
my genesis block!!
Hello World!
invalid message!!!!
original block
書き換えテストOK!

おわり

ブロックチェーンについてはなんとなくわかった(気がする)。でもまだ、マイニングとは具体的になにをしてるんだ、どういう仕組みで仮想通貨の安全性が保たれるんだ、ビットコインと色々なアルトコインは技術的にどこが違うのか、みたいなところまでは全然わかってないのでそれはこれからの課題。

*1:わかりやすいかと思いあえて「ID番号」とするけれど、ハッシュ値のこと。ハッシュ関数ビットコインではSHA256が使われている、コード上でもSHA256にしている。

*2:仮想通貨のマイニングではこの性質がきいてくる