技術メモ

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

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:仮想通貨のマイニングではこの性質がきいてくる

三角関数の公式を覚えるのに全く苦労しなかった話

三角関数の公式を覚えるのが大変だとよく言われるけれど、高校生の頃は実は全く苦労した記憶がないという話。というのも、単に回転行列を覚えるだけだったからだ。


\color{red}{
\left(
\begin{array}{cc}
\mathrm{cos}\theta & -\mathrm{sin}\theta \\
\mathrm{sin}\theta & \mathrm{cos}\theta \\
\end{array}
\right)
}


\color{red}{
\left\{
\begin{array}{1}
 \mathrm{sin}(-\theta) = -\mathrm{sin}\theta\\
 \mathrm{cos}(-\theta) = \mathrm{cos}\theta 
\end{array}
\right.
}

これさえ覚えておけばいい*1


これさえ覚えておけばちょっと計算するだけで加法定理や和積の定理は作れるからだ*2

\displaystyle
\begin{eqnarray}
\left(
\begin{array}{cc}
\mathrm{cos}(\alpha+\beta) & -\mathrm{sin}(\alpha+\beta) \\
\mathrm{sin}(\alpha+\beta) & \mathrm{cos}(\alpha+\beta) \\
\end{array}
\right)
&=&
\left(
\begin{array}{cc}
\mathrm{cos}\alpha & -\mathrm{sin}\alpha \\
\mathrm{sin}\alpha & \mathrm{cos}\alpha \\
\end{array}
\right)
\left(
\begin{array}{cc}
\mathrm{cos}\beta & -\mathrm{sin}\beta \\
\mathrm{sin}\beta & \mathrm{cos}\beta \\
\end{array}
\right)
\\\\&=&
\left(
\begin{array}{cc}
\mathrm{cos}\alpha\mathrm{cos}\beta - \mathrm{sin}\alpha\mathrm{sin}\beta & -\mathrm{sin}\alpha\mathrm{cos}\beta -  \mathrm{sin}\beta\mathrm{cos}\alpha \\
 \mathrm{sin}\alpha\mathrm{cos}\beta +  \mathrm{sin}\beta\mathrm{cos}\alpha &\mathrm{cos}\alpha\mathrm{cos}\beta -  \mathrm{sin}\alpha\mathrm{sin}\beta \\
\end{array}
\right)
\end{eqnarray}



\displaystyle
\begin{eqnarray}
\left(
\begin{array}{cc}
\mathrm{cos}(\alpha-\beta) & -\mathrm{sin}(\alpha-\beta) \\
\mathrm{sin}(\alpha-\beta) & \mathrm{cos}(\alpha-\beta) \\
\end{array}
\right)
&=&
\left(
\begin{array}{cc}
\mathrm{cos}\alpha & -\mathrm{sin}\alpha \\
\mathrm{sin}\alpha & \mathrm{cos}\alpha \\
\end{array}
\right)
\left(
\begin{array}{cc}
\mathrm{cos}\beta & \mathrm{sin}\beta \\
{-\mathrm{sin}}\beta & \mathrm{cos}\beta \\
\end{array}
\right)
\\\\&=&
\left(
\begin{array}{cc}
\mathrm{cos}\alpha\mathrm{cos}\beta + \mathrm{sin}\alpha\mathrm{sin}\beta& -\mathrm{sin}\alpha\mathrm{cos}\beta +  \mathrm{sin}\beta\mathrm{cos}\alpha \\
 \mathrm{sin}\alpha\mathrm{cos}\beta -  \mathrm{sin}\beta\mathrm{cos}\alpha&\mathrm{cos}\alpha\mathrm{cos}\beta +  \mathrm{sin}\alpha\mathrm{sin}\beta \\
\end{array}
\right)
\end{eqnarray}


あとは和積の公式は加法定理を使えばごく自然に求まる。
割とすぐ計算できてしまうので試験中でもとりあえず回転行列を書いて加法定理を確認するようにしていたくらいで、「覚えよう!」と思って加法定理を覚えたことがない。

当たり前にみんな使っている考え方だと思っていたけれど、公式を覚えるのに苦労するという話があったので思い出した。

*1:この3つも図で書けば簡単に思い出せる

*2:数学的な成り立ちから言うと最初に加法定理があって回転行列が導き出せるのだけれど、覚えるという点で見れば回転行列を覚える方が手っ取り早い

置換可能素数

以前の「Nが現れる素数」に続いて面白い素数が紹介されていた。*1

このような素数を求めてみた。

ルール

<ルール>
素数Pに対して下の条件を満たすn(1,2,...9)が存在する。
1. Pの各桁の中にnを含まない。
2. Pの各桁をnで置換した数も全て素数になる。

(タイトルでは置換可能素数と呼んでいるけれど、そういう言葉はないです。)

コード

python3.5.3で動きます

from sympy import isprime
from math import log10, floor

def replace(num, replace_num):
    digits = len(str(num))
    replaced_nums = [str(num) for i in range(digits)]
    for i, replaced_num in enumerate(replaced_nums):
        replaced_num = replaced_num[:i] + str(replace_num) + replaced_num[i+1:]
        replaced_nums[i] = replaced_num
    return replaced_nums

replace_list = [1,3,7,9]
for prime in range(10,100000):
    if not isprime(prime):
        continue
    for replace_num in replace_list:
        if str(replace_num) in str(prime):
            continue
        replaced_nums = replace(prime, replace_num)
        for replaced_num in replaced_nums:
            if not isprime(replaced_num):
                break
        else:
            print("%8d :(%d) "%(prime, replace_num))

結果

11 :(3)
11 :(7)
13 :(7)
17 :(3)
17 :(9)
19 :(7)
31 :(7)
37 :(1)
41 :(3)
41 :(7)
43 :(1)
43 :(7)
47 :(1)
47 :(3)
61 :(7)
67 :(1)
71 :(3)
73 :(1)
79 :(1)
107 :(3)
107 :(9)
109 :(7)
137 :(9)
139 :(7)
167 :(3)
179 :(3)
197 :(3)
251 :(7)
337 :(1)
347 :(9)
409 :(1)
439 :(1)
449 :(3)
499 :(1)
607 :(1)
643 :(7)
709 :(1)
859 :(3)
1013 :(9)
1019 :(3)
1061 :(3)
1433 :(9)
1499 :(3)
1613 :(9)
2089 :(3)
3637 :(1)
3709 :(1)
3767 :(9)
4337 :(9)
4877 :(1)
4933 :(7)
6997 :(1)
7487 :(1)
8623 :(9)
9433 :(1)
9433 :(7)
16901 :(3)
25633 :(9)
36493 :(7)
47717 :(3)
56269 :(3)
76963 :(1)
86269 :(3)