├── .gitignore ├── 02 ├── 00 │ ├── README.md │ ├── client.py │ └── server.py ├── 01 │ ├── README.md │ ├── SampleServer1.py │ ├── SampleServer2.py │ ├── core │ │ ├── __init__.py │ │ └── server_core.py │ └── p2p │ │ ├── __init__.py │ │ ├── connection_manager.py │ │ ├── core_node_list.py │ │ └── message_manager.py ├── 02 │ ├── README.md │ ├── SampleClient.py │ ├── SampleServer1.py │ ├── SampleServer2.py │ ├── core │ │ ├── client_core.py │ │ └── server_core.py │ └── p2p │ │ ├── __init__.py │ │ ├── connection_manager.py │ │ ├── connection_manager_4edge.py │ │ ├── connection_manager_old.py │ │ ├── core_node_list.py │ │ ├── edge_node_list.py │ │ ├── message_manager.py │ │ └── my_protocol_message_handler.py └── 03 │ ├── README.md │ ├── SampleClient.py │ ├── SampleClient2.py │ ├── SampleServer1.py │ ├── SampleServer2.py │ ├── core │ ├── client_core.py │ └── server_core.py │ └── p2p │ ├── __init__.py │ ├── connection_manager.py │ ├── connection_manager_4edge.py │ ├── core_node_list.py │ ├── edge_node_list.py │ ├── message_manager.py │ └── my_protocol_message_handler.py ├── 03 ├── 01 │ ├── SampleBlockchain1.py │ ├── blockchain │ │ ├── __init__.py │ │ ├── block.py │ │ ├── block_builder.py │ │ └── blockchain_manager.py │ └── sample.py ├── 02 │ ├── SampleBlockchain2.py │ ├── SampleBlockchain3.py │ ├── blockchain │ │ ├── __init__.py │ │ ├── block.py │ │ ├── block_builder.py │ │ └── blockchain_manager.py │ └── transaction │ │ ├── __init__.py │ │ ├── transaction_pool.py │ │ └── transaction_pool_old.py ├── 03 │ ├── SampleClient.py │ ├── SampleClient2.py │ ├── SampleServer1.py │ ├── SampleServer2.py │ ├── blockchain │ │ ├── __init__.py │ │ ├── block.py │ │ ├── block_builder.py │ │ └── blockchain_manager.py │ ├── core │ │ ├── client_core.py │ │ └── server_core.py │ ├── p2p │ │ ├── __init__.py │ │ ├── connection_manager.py │ │ ├── connection_manager_4edge.py │ │ ├── core_node_list.py │ │ ├── edge_node_list.py │ │ ├── message_manager.py │ │ └── my_protocol_message_handler.py │ └── transaction │ │ ├── __init__.py │ │ └── transaction_pool.py └── 04 │ ├── SampleBlockchain4.py │ ├── SampleClient.py │ ├── SampleClient2.py │ ├── SampleClient3.py │ ├── SampleServer1.py │ ├── SampleServer2.py │ ├── SampleServer3.py │ ├── blockchain │ ├── Block.py │ ├── __init__.py │ ├── block_builder.py │ └── blockchain_manager.py │ ├── core │ ├── client_core.py │ └── server_core.py │ ├── p2p │ ├── __init__.py │ ├── connection_manager.py │ ├── connection_manager_4edge.py │ ├── core_node_list.py │ ├── edge_node_list.py │ ├── message_manager.py │ └── my_protocol_message_handler.py │ └── transaction │ ├── __init__.py │ └── transaction_pool.py ├── 04 ├── key_manager.py ├── pubkey_test.py ├── pubkey_test2.py ├── test_trx.py ├── transactions.py ├── tsx.py ├── utxo_manager.py ├── utxo_test.py ├── wallet_app.py └── wallet_gui.py ├── 05 ├── blockchain │ ├── __init__.py │ ├── block.py │ ├── block_builder.py │ └── blockchain_manager.py ├── core │ ├── __init__.py │ ├── client_core.py │ └── server_core.py ├── p2p │ ├── __init__.py │ ├── connection_manager.py │ ├── connection_manager_4edge.py │ ├── core_node_list.py │ ├── edge_node_list.py │ ├── message_manager.py │ ├── my_protocol_message_handler.py │ └── my_protocol_message_store.py ├── sample_server1.py ├── sample_server2.py ├── transaction │ ├── __init__.py │ ├── transaction_pool.py │ ├── transactions.py │ └── utxo_manager.py ├── utils │ ├── __init__.py │ ├── key_manager.py │ └── rsa_util.py └── wallet_app.py ├── 06 ├── blockchain │ ├── __init__.py │ ├── block.py │ ├── block_builder.py │ └── blockchain_manager.py ├── core │ ├── __init__.py │ ├── client_core.py │ └── server_core.py ├── p2p │ ├── __init__.py │ ├── connection_manager.py │ ├── connection_manager_4edge.py │ ├── core_node_list.py │ ├── edge_node_list.py │ ├── message_manager.py │ ├── my_protocol_message_handler.py │ └── my_protocol_message_store.py ├── sample_server1.py ├── sample_server2.py ├── transaction │ ├── __init__.py │ ├── transaction_pool.py │ ├── transactions.py │ └── utxo_manager.py ├── utils │ ├── __init__.py │ ├── key_manager.py │ └── rsa_util.py └── wallet_app.py ├── 07 ├── aes_test.py ├── blockchain │ ├── __init__.py │ ├── block.py │ ├── block_builder.py │ └── blockchain_manager.py ├── core │ ├── __init__.py │ ├── client_core.py │ └── server_core.py ├── p2p │ ├── __init__.py │ ├── connection_manager.py │ ├── connection_manager_4edge.py │ ├── core_node_list.py │ ├── edge_node_list.py │ ├── message_manager.py │ ├── my_protocol_message_handler.py │ └── my_protocol_message_store.py ├── sample_server1.py ├── sample_server2.py ├── transaction │ ├── __init__.py │ ├── transaction_pool.py │ ├── transactions.py │ └── utxo_manager.py ├── utils │ ├── __init__.py │ ├── aes_util.py │ ├── key_manager.py │ └── rsa_util.py └── wallet_app.py ├── LICENSE ├── README.md └── doc └── img └── cover.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /02/00/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## このサンプルコードが対象としているセクション  3 | 4 | 2-3-2 : Coreノードの動作 5 | 6 | 7 | ### Tested System: 8 | * OSX 10.12.6 9 | * python 3.6.2 10 | 11 | 12 | ### サーバーの起動 13 | 14 | ```bash: 15 | python server.py 16 | ``` 17 | 18 | ### クライアントの起動 19 | 20 | ```bash: 21 | python client.py 22 | ``` 23 | -------------------------------------------------------------------------------- /02/00/client.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 | my_socket.connect(('10.1.1.27', 50030)) 5 | my_text = 'Hello! This is test message from my sample client!' 6 | my_socket.sendall(my_text.encode('utf-8')) 7 | -------------------------------------------------------------------------------- /02/00/server.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | import socket 3 | import os 4 | 5 | 6 | def __handle_message(args_tuple): 7 | 8 | conn, addr, data_sum = args_tuple 9 | while True: 10 | data = conn.recv(1024) 11 | data_sum = data_sum + data.decode('utf-8') 12 | 13 | if not data: 14 | break 15 | 16 | if data_sum != '': 17 | print(data_sum) 18 | 19 | 20 | def __get_myip(): 21 | # 環境によって socket.gethostbyname(socket.gethostname())ではうまくIPアドレスが取れないためこちらを使った 22 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 23 | s.connect(('8.8.8.8', 80)) 24 | return s.getsockname()[0] 25 | 26 | 27 | def main(): 28 | 29 | # AF_INET : IPv4 ベースのアドレス体系を使うということ 30 | # SOCK_STREAM : TCP/IPを使うということ 31 | my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | 33 | # 多重接続になってもいいようにスレッドで処理するようにする 34 | executor = ThreadPoolExecutor(max_workers=10) 35 | 36 | # 開くポート番号は適当に選んだだけ。 37 | myhost = __get_myip() 38 | print('my ip address is now ...', myhost) 39 | my_socket.bind((myhost, 50030)) 40 | # 同時に接続してくる相手の数。今回はテストなのでとりあえず1 41 | my_socket.listen(1) 42 | 43 | while True: 44 | # 接続があるまで待機 45 | print('Waiting for the connection ...') 46 | conn, addr = my_socket.accept() 47 | print('Connected by .. ', addr) 48 | data_sum = '' 49 | executor.submit(__handle_message, (conn, addr, data_sum)) 50 | 51 | 52 | if __name__ == '__main__': 53 | main() 54 | -------------------------------------------------------------------------------- /02/01/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## このサンプルコードが対象としているセクション  3 | 4 | 2.3.3 メッセージを定義しよう 5 | 〜 6 | 2.4.2 Coreノード同士を接続する 7 | 8 | 9 | ### Tested System: 10 | * OSX 10.12.6 11 | * python 3.6.2 12 | 13 | 14 | ### サーバー1の起動 15 | 16 | ```bash: 17 | python SampleServer1.py 18 | ``` 19 | 20 | ### サーバー2の起動 21 | 22 | ```bash: 23 | python SampleServer2.py 24 | ``` 25 | -------------------------------------------------------------------------------- /02/01/SampleServer1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | 13 | def shutdown_server(): 14 | global my_p2p_server 15 | my_p2p_server.shutdown() 16 | 17 | 18 | def main(): 19 | signal.signal(signal.SIGINT, signal_handler) 20 | global my_p2p_server 21 | # 始原のCoreノードとして起動する 22 | my_p2p_server = ServerCore(50082) 23 | my_p2p_server.start() 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /02/01/SampleServer2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | my_p2p_server = None 6 | 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | def main(): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_server 20 | my_p2p_server = ServerCore(50090, '10.1.1.126', 50082) 21 | my_p2p_server.start() 22 | my_p2p_server.join_network() 23 | 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /02/01/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/02/01/core/__init__.py -------------------------------------------------------------------------------- /02/01/core/server_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager import ConnectionManager 4 | 5 | 6 | STATE_INIT = 0 7 | STATE_STANDBY = 1 8 | STATE_CONNECTED_TO_CENTRAL = 2 9 | STATE_SHUTTING_DOWN = 3 10 | 11 | 12 | class ServerCore: 13 | 14 | def __init__(self, my_port=50082, core_node_host=None, core_node_port=None): 15 | self.server_state = STATE_INIT 16 | print('Initializing server...') 17 | self.my_ip = self.__get_myip() 18 | print('Server IP address is set to ... ', self.my_ip) 19 | self.my_port = my_port 20 | self.cm = ConnectionManager(self.my_ip, self.my_port) 21 | self.core_node_host = core_node_host 22 | self.core_node_port = core_node_port 23 | 24 | def start(self): 25 | self.server_state = STATE_STANDBY 26 | self.cm.start() 27 | 28 | def join_network(self): 29 | if self.core_node_host is not None: 30 | self.server_state = STATE_CONNECTED_TO_CENTRAL 31 | self.cm.join_network(self.core_node_host, self.core_node_port) 32 | else: 33 | print('This server is runnning as Genesis Core Node...') 34 | 35 | def shutdown(self): 36 | self.server_state = STATE_SHUTTING_DOWN 37 | print('Shutdown server...') 38 | self.cm.connection_close() 39 | 40 | def get_my_current_state(self): 41 | return self.server_state 42 | 43 | def __get_myip(self): 44 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 45 | s.connect(('8.8.8.8', 80)) 46 | return s.getsockname()[0] 47 | -------------------------------------------------------------------------------- /02/01/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/02/01/p2p/__init__.py -------------------------------------------------------------------------------- /02/01/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | 11 | def add(self, peer): 12 | """ 13 | Coreノードをリストに追加する。 14 | """ 15 | with self.lock: 16 | print('Adding peer: ', peer) 17 | self.list.add((peer)) 18 | print('Current Core List: ', self.list) 19 | 20 | 21 | def remove(self, peer): 22 | """ 23 | 離脱したと判断されるCoreノードをリストから削除する。 24 | """ 25 | with self.lock: 26 | if peer in self.list: 27 | print('Removing peer: ', peer) 28 | self.list.remove(peer) 29 | print('Current Core list: ', self.list) 30 | 31 | 32 | def overwrite(self, new_list): 33 | """ 34 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 35 | """ 36 | with self.lock: 37 | print('core node list will be going to overwrite') 38 | self.list = new_list 39 | print('Current Core list: ', self.list) 40 | 41 | 42 | def get_list(self): 43 | """ 44 | 現在接続状態にあるPeerの一覧を返却する 45 | """ 46 | return self.list 47 | 48 | 49 | def get_length(self): 50 | return len(self.list) 51 | -------------------------------------------------------------------------------- /02/01/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | 16 | ERR_PROTOCOL_UNMATCH = 0 17 | ERR_VERSION_UNMATCH = 1 18 | OK_WITH_PAYLOAD = 2 19 | OK_WITHOUT_PAYLOAD = 3 20 | 21 | 22 | class MessageManager: 23 | 24 | def __init__(self): 25 | print('Initializing MessageManager...') 26 | 27 | def build(self, msg_type, my_port=50082, payload=None): 28 | 29 | message = { 30 | 'protocol': PROTOCOL_NAME, 31 | 'version': MY_VERSION, 32 | 'msg_type': msg_type, 33 | 'my_port': my_port 34 | } 35 | 36 | if payload is not None: 37 | message['payload'] = payload 38 | 39 | return json.dumps(message) 40 | 41 | def parse(self, msg): 42 | 43 | msg = json.loads(msg) 44 | msg_ver = StrictVersion(msg['version']) 45 | 46 | cmd = msg.get('msg_type') 47 | my_port = msg.get('my_port') 48 | payload = msg.get('payload') 49 | 50 | if msg['protocol'] != PROTOCOL_NAME: 51 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 52 | elif msg_ver > StrictVersion(MY_VERSION): 53 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 54 | elif cmd == MSG_CORE_LIST: 55 | return ('ok', OK_WITH_PAYLOAD, cmd, my_port, payload) 56 | else: 57 | return ('ok', OK_WITHOUT_PAYLOAD, cmd, my_port, None) 58 | -------------------------------------------------------------------------------- /02/02/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## このサンプルコードが対象としているセクション  3 | 4 | 2.4.3 Edgeノードを作る 5 | 〜 6 | 2.4.4 CoreノードとEdgeノードを接続してみる 7 | 8 | 9 | ### Tested System: 10 | * OSX 10.12.6 11 | * python 3.6.2 12 | 13 | 14 | ### サーバー1の起動 15 | 16 | ```bash: 17 | python SampleServer1.py 18 | ``` 19 | 20 | ### サーバー2の起動 21 | 22 | ```bash: 23 | python SampleServer2.py 24 | ``` 25 | 26 | ### クライアント(Edgeノード)の起動 27 | 28 | ```bash: 29 | python SampleClient.py 30 | ``` -------------------------------------------------------------------------------- /02/02/SampleClient.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.client_core import ClientCore 4 | 5 | my_p2p_client = None 6 | 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_client() 10 | 11 | 12 | def shutdown_client(): 13 | global my_p2p_client 14 | my_p2p_client.shutdown() 15 | 16 | 17 | def main(): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_client 20 | my_p2p_client = ClientCore(50095, '10.1.1.126', 50082) 21 | my_p2p_client.start() 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /02/02/SampleServer1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | 4 | from core.server_core import ServerCore 5 | 6 | 7 | my_p2p_server = None 8 | 9 | 10 | def signal_handler(signal, frame): 11 | shutdown_server() 12 | 13 | 14 | def shutdown_server(): 15 | global my_p2p_server 16 | my_p2p_server.shutdown() 17 | 18 | 19 | def main(): 20 | signal.signal(signal.SIGINT, signal_handler) 21 | global my_p2p_server 22 | # 始原のCoreノードとして起動する 23 | my_p2p_server = ServerCore(50082) 24 | my_p2p_server.start() 25 | 26 | 27 | if __name__ == '__main__': 28 | main() 29 | -------------------------------------------------------------------------------- /02/02/SampleServer2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | 13 | def shutdown_server(): 14 | global my_p2p_server 15 | my_p2p_server.shutdown() 16 | 17 | 18 | def main(): 19 | signal.signal(signal.SIGINT, signal_handler) 20 | global my_p2p_server 21 | my_p2p_server = ServerCore(50090, '10.1.1.126', 50082) 22 | my_p2p_server.start() 23 | my_p2p_server.join_network() 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | -------------------------------------------------------------------------------- /02/02/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager_4edge import ConnectionManager4Edge 4 | 5 | STATE_INIT = 0 6 | STATE_ACTIVE = 1 7 | STATE_SHUTTING_DOWN = 2 8 | 9 | 10 | class ClientCore: 11 | 12 | def __init__(self, my_port=50082, core_host=None, core_port=None): 13 | self.client_state = STATE_INIT 14 | print('Initializing ClientCore...') 15 | self.my_ip = self.__get_myip() 16 | print('Server IP address is set to ... ', self.my_ip) 17 | self.my_port = my_port 18 | self.cm = ConnectionManager4Edge(self.my_ip, my_port, core_host, core_port) 19 | 20 | def start(self): 21 | self.client_state = STATE_ACTIVE 22 | self.cm.start() 23 | self.cm.connect_to_core_node() 24 | 25 | def shutdown(self): 26 | self.client_state = STATE_SHUTTING_DOWN 27 | print('Shutdown edge node ...') 28 | self.cm.connection_close() 29 | 30 | def get_my_current_state(self): 31 | return self.client_state 32 | 33 | def __get_myip(self): 34 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 35 | s.connect(('8.8.8.8', 80)) 36 | return s.getsockname()[0] 37 | -------------------------------------------------------------------------------- /02/02/core/server_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager import ConnectionManager 4 | 5 | STATE_INIT = 0 6 | STATE_STANDBY = 1 7 | STATE_CONNECTED_TO_NETWORK = 2 8 | STATE_SHUTTING_DOWN = 3 9 | 10 | 11 | class ServerCore: 12 | 13 | def __init__(self, my_port=50082, core_node_host=None, core_node_port=None): 14 | self.server_state = STATE_INIT 15 | print('Initializing server...') 16 | self.my_ip = self.__get_myip() 17 | print('Server IP address is set to ... ', self.my_ip) 18 | self.my_port = my_port 19 | self.cm = ConnectionManager(self.my_ip, self.my_port) 20 | self.core_node_host = core_node_host 21 | self.core_node_port = core_node_port 22 | 23 | def start(self): 24 | self.server_state = STATE_STANDBY 25 | self.cm.start() 26 | 27 | def join_network(self): 28 | if self.core_node_host is not None: 29 | self.server_state = STATE_CONNECTED_TO_NETWORK 30 | self.cm.join_network(self.core_node_host, self.core_node_port) 31 | else: 32 | print('This server is runnning as Genesis Core Node...') 33 | 34 | def shutdown(self): 35 | self.server_state = STATE_SHUTTING_DOWN 36 | print('Shutdown server...') 37 | self.cm.connection_close() 38 | 39 | def get_my_current_state(self): 40 | return self.server_state 41 | 42 | def __get_myip(self): 43 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 44 | s.connect(('8.8.8.8', 80)) 45 | return s.getsockname()[0] 46 | -------------------------------------------------------------------------------- /02/02/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/02/02/p2p/__init__.py -------------------------------------------------------------------------------- /02/02/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, peer): 11 | """ 12 | Coreノードをリストに追加する。 13 | 14 | param: 15 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding peer: ', peer) 19 | self.list.add((peer)) 20 | print('Current Core List: ', self.list) 21 | 22 | def remove(self, peer): 23 | """ 24 | 離脱したと判断されるCoreノードをリストから削除する。 25 | 26 | param: 27 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if peer in self.list: 31 | print('Removing peer: ', peer) 32 | self.list.remove(peer) 33 | print('Current Core list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('core node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Core list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるPeerの一覧を返却する 47 | """ 48 | return self.list 49 | 50 | def get_length(self): 51 | return len(self.list) 52 | 53 | 54 | def get_c_node_info(self): 55 | """ 56 | リストのトップにあるPeerを返却する 57 | """ 58 | return list(self.list)[0] 59 | -------------------------------------------------------------------------------- /02/02/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, edge): 11 | """ 12 | Edgeノードをリストに追加する。 13 | 14 | param: 15 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding edge: ', edge) 19 | self.list.add((edge)) 20 | print('Current Edge List: ', self.list) 21 | 22 | def remove(self, edge): 23 | """ 24 | 離脱したと判断されるEdgeノードをリストから削除する。 25 | 26 | param: 27 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if edge in self.list: 31 | print('Removing edge: ', edge) 32 | self.list.remove(edge) 33 | print('Current Edge list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('edge node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Edge list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるEdgeノードの一覧を返却する 47 | """ 48 | return self.list 49 | -------------------------------------------------------------------------------- /02/02/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | 16 | ERR_PROTOCOL_UNMATCH = 0 17 | ERR_VERSION_UNMATCH = 1 18 | OK_WITH_PAYLOAD = 2 19 | OK_WITHOUT_PAYLOAD = 3 20 | 21 | 22 | class MessageManager: 23 | 24 | def __init__(self): 25 | print('Initializing MessageManager...') 26 | 27 | def build(self, msg_type, my_port=50082, payload=None): 28 | 29 | message = { 30 | 'protocol': PROTOCOL_NAME, 31 | 'version': MY_VERSION, 32 | 'msg_type': msg_type, 33 | 'my_port': my_port 34 | } 35 | 36 | if payload is not None: 37 | message['payload'] = payload 38 | 39 | return json.dumps(message) 40 | 41 | def parse(self, msg): 42 | 43 | msg = json.loads(msg) 44 | msg_ver = StrictVersion(msg['version']) 45 | 46 | cmd = msg.get('msg_type') 47 | my_port = msg.get('my_port') 48 | payload = msg.get('payload') 49 | 50 | if msg['protocol'] != PROTOCOL_NAME: 51 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 52 | elif msg_ver > StrictVersion(MY_VERSION): 53 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 54 | elif cmd == MSG_CORE_LIST: 55 | return ('ok', OK_WITH_PAYLOAD, cmd, my_port, payload) 56 | else: 57 | return ('ok', OK_WITHOUT_PAYLOAD, cmd, my_port, None) 58 | 59 | -------------------------------------------------------------------------------- /02/02/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | 4 | # 独自に拡張したGENERALメッセージの処理や生成を担当する 5 | class MyProtocolMessageHandler(object): 6 | 7 | def __init__(self): 8 | print('Initializing MyProtocolMessageHandler...') 9 | 10 | def handle_message(self, msg): 11 | msg = json.loads(msg) 12 | print(msg) 13 | return 14 | 15 | -------------------------------------------------------------------------------- /02/03/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## このサンプルコードが対象としているセクション  3 | 4 | 2.5 P2Pネットワークのプロトコルを拡張しよう 5 | 6 | 7 | ### Tested System: 8 | * OSX 10.12.6 9 | * python 3.6.2 10 | 11 | 12 | ### サーバー1の起動 13 | 14 | ```bash: 15 | python SampleServer1.py 16 | ``` 17 | 18 | ### サーバー2の起動 19 | 20 | ```bash: 21 | python SampleServer2.py 22 | ``` 23 | 24 | ### クライアント(Edgeノード)の起動 25 | 26 | ```bash: 27 | python SampleClient.py 28 | ``` 29 | 30 | ### クライアント(Edgeノード)2の起動 31 | 32 | ```bash: 33 | python SampleClient2.py 34 | ``` -------------------------------------------------------------------------------- /02/03/SampleClient.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.client_core import ClientCore 4 | 5 | my_p2p_client = None 6 | 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_client() 10 | 11 | 12 | def shutdown_client(): 13 | global my_p2p_client 14 | my_p2p_client.shutdown() 15 | 16 | 17 | def main(): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_client 20 | my_p2p_client = ClientCore(50100, '10.1.1.126', 50082) 21 | my_p2p_client.start() 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /02/03/SampleClient2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_ENHANCED 7 | 8 | my_p2p_client = None 9 | 10 | 11 | def signal_handler(signal, frame): 12 | shutdown_client() 13 | 14 | 15 | def shutdown_client(): 16 | global my_p2p_client 17 | my_p2p_client.shutdown() 18 | 19 | 20 | def main(): 21 | signal.signal(signal.SIGINT, signal_handler) 22 | global my_p2p_client 23 | my_p2p_client = ClientCore(50098, '10.1.1.126', 50090) 24 | my_p2p_client.start() 25 | 26 | sleep(10) 27 | 28 | message = { 29 | 'from':'hoge', 30 | 'to':'fuga', 31 | 'message' :'test' 32 | } 33 | 34 | my_p2p_client.send_message_to_my_core_node(MSG_ENHANCED,json.dumps(message)) 35 | 36 | 37 | if __name__ == '__main__': 38 | main() -------------------------------------------------------------------------------- /02/03/SampleServer1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | 13 | def shutdown_server(): 14 | global my_p2p_server 15 | my_p2p_server.shutdown() 16 | 17 | 18 | def main(): 19 | signal.signal(signal.SIGINT, signal_handler) 20 | global my_p2p_server 21 | # 始原のCoreノードとして起動する 22 | my_p2p_server = ServerCore(50082) 23 | my_p2p_server.start() 24 | 25 | 26 | if __name__ == '__main__': 27 | main() -------------------------------------------------------------------------------- /02/03/SampleServer2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | my_p2p_server = None 6 | 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | 18 | def main(): 19 | signal.signal(signal.SIGINT, signal_handler) 20 | global my_p2p_server 21 | my_p2p_server = ServerCore(50090, '10.1.1.126', 50082) 22 | my_p2p_server.start() 23 | my_p2p_server.join_network() 24 | 25 | 26 | if __name__ == '__main__': 27 | main() -------------------------------------------------------------------------------- /02/03/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager_4edge import ConnectionManager4Edge 4 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 5 | from p2p.message_manager import ( 6 | MessageManager, 7 | RSP_FULL_CHAIN, 8 | MSG_ENHANCED, 9 | ) 10 | 11 | 12 | STATE_INIT = 0 13 | STATE_ACTIVE = 1 14 | STATE_SHUTTING_DOWN = 2 15 | 16 | 17 | class ClientCore: 18 | 19 | def __init__(self, my_port=50082, c_host=None, c_port=None): 20 | self.client_state = STATE_INIT 21 | print('Initializing ClientCore...') 22 | self.my_ip = self.__get_myip() 23 | print('Server IP address is set to ... ', self.my_ip) 24 | self.my_port = my_port 25 | self.my_core_host = c_host 26 | self.my_core_port = c_port 27 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, c_host, c_port, self.__handle_message) 28 | self.mpm = MyProtocolMessageHandler() 29 | self.my_protocol_message_store = [] 30 | 31 | def start(self): 32 | """ 33 | Edgeノードとしての待受を開始する(上位UI層向け 34 | """ 35 | self.client_state = STATE_ACTIVE 36 | self.cm.start() 37 | self.cm.connect_to_core_node() 38 | 39 | def shutdown(self): 40 | """ 41 | 待ち受け状態のServer Socketを閉じて終了する(上位UI層向け 42 | """ 43 | self.client_state = STATE_SHUTTING_DOWN 44 | print('Shutdown edge node ...') 45 | self.cm.connection_close() 46 | 47 | def get_my_current_state(self): 48 | """ 49 | 現在のEdgeノードの状態を取得する(上位UI層向け 50 | """ 51 | return self.client_state 52 | 53 | def send_message_to_my_core_node(self, msg_type, msg): 54 | """ 55 | 接続中のCoreノードに対してメッセージを送付する(上位UI層向け 56 | 57 | Params: 58 | msg_type : MessageManagerで規定のメッセージ種別を指定する 59 | msg : メッセージ本文。文字列化されたJSONを想定 60 | """ 61 | msg_txt = self.cm.get_message_text(msg_type, msg) 62 | print(msg_txt) 63 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 64 | 65 | def get_my_protocol_messages(self): 66 | """ 67 | 拡張メッセージとして送信されてきたメッセージを格納しているリストを取得する 68 | (現状未整備で特に意図した利用用途なし) 69 | """ 70 | 71 | if self.my_protocol_message_store != []: 72 | return self.my_protocol_message_store 73 | else: 74 | return None 75 | 76 | def __client_api(self, request, message): 77 | """ 78 | MyProtocolMessageHandlerで呼び出すための拡張関数群(現状未整備) 79 | 80 | params: 81 | request : MyProtocolMessageHandlerから呼び出されるコマンドの種別 82 | message : コマンド実行時に利用するために引き渡されるメッセージ 83 | """ 84 | if request == 'pass_message_to_client_application': 85 | self.my_protocol_message_store.append(message) 86 | elif request == 'api_type': 87 | return 'client_core_api' 88 | else: 89 | print('not implemented api was used') 90 | 91 | def __handle_message(self, msg): 92 | """ 93 | ConnectionManager4Edgeに引き渡すコールバックの中身。 94 | """ 95 | print(msg) 96 | if msg[2] == RSP_FULL_CHAIN: 97 | # TODO: ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証する処理を呼び出す 98 | pass 99 | elif msg[2] == MSG_ENHANCED: 100 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。 101 | # SimpleBitcoin としてはこの種別は使わない 102 | self.mpm.handle_message(msg[4], self.__client_api) 103 | 104 | def __get_myip(self): 105 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 106 | s.connect(('8.8.8.8', 80)) 107 | return s.getsockname()[0] 108 | -------------------------------------------------------------------------------- /02/03/core/server_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager import ConnectionManager 4 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 5 | from p2p.message_manager import ( 6 | MessageManager, 7 | MSG_NEW_TRANSACTION, 8 | MSG_NEW_BLOCK, 9 | RSP_FULL_CHAIN, 10 | MSG_ENHANCED, 11 | ) 12 | 13 | STATE_INIT = 0 14 | STATE_STANDBY = 1 15 | STATE_CONNECTED_TO_NETWORK = 2 16 | STATE_SHUTTING_DOWN = 3 17 | 18 | 19 | class ServerCore: 20 | 21 | def __init__(self, my_port=50082, core_node_host=None, core_node_port=None): 22 | self.server_state = STATE_INIT 23 | print('Initializing server...') 24 | self.my_ip = self.__get_myip() 25 | print('Server IP address is set to ... ', self.my_ip) 26 | self.my_port = my_port 27 | self.cm = ConnectionManager(self.my_ip, self.my_port, self.__handle_message) 28 | self.mpm = MyProtocolMessageHandler() 29 | self.core_node_host = core_node_host 30 | self.core_node_port = core_node_port 31 | self.my_protocol_message_store = [] 32 | 33 | def start(self): 34 | """ 35 | Coreノードとしての待受を開始する(上位UI層向け 36 | """ 37 | self.server_state = STATE_STANDBY 38 | self.cm.start() 39 | 40 | def join_network(self): 41 | """ 42 | 事前に取得した情報に従い拠り所となる他のCoreノードに接続する(上位UI層向け 43 | """ 44 | if self.core_node_host is not None: 45 | self.server_state = STATE_CONNECTED_TO_NETWORK 46 | self.cm.join_network(self.core_node_host, self.core_node_port) 47 | else: 48 | print('This server is runnning as Genesis Core Node...') 49 | 50 | def shutdown(self): 51 | """ 52 | 待ち受け状態のServer Socketを閉じて終了する(上位UI層向け 53 | """ 54 | self.server_state = STATE_SHUTTING_DOWN 55 | print('Shutdown server...') 56 | self.cm.connection_close() 57 | 58 | def get_my_current_state(self): 59 | """ 60 | 現在のCoreノードの状態を取得する(上位UI層向け。多分使う人いない 61 | """ 62 | return self.server_state 63 | 64 | def __core_api(self, request, message): 65 | """ 66 | MyProtocolMessageHandlerで呼び出すための拡張関数群(現状未整備) 67 | 68 | params: 69 | request : MyProtocolMessageHandlerから呼び出されるコマンドの種別 70 | message : コマンド実行時に利用するために引き渡されるメッセージ 71 | """ 72 | 73 | msg_type = MSG_ENHANCED 74 | 75 | if request == 'send_message_to_all_peer': 76 | new_message = self.cm.get_message_text(msg_type, message) 77 | self.cm.send_msg_to_all_peer(new_message) 78 | return 'ok' 79 | elif request == 'send_message_to_all_edge': 80 | new_message = self.cm.get_message_text(msg_type, message) 81 | self.cm.send_msg_to_all_edge(new_message) 82 | return 'ok' 83 | elif request == 'api_type': 84 | return 'server_core_api' 85 | 86 | def __handle_message(self, msg, peer=None): 87 | """ 88 | ConnectionManagerに引き渡すコールバックの中身。 89 | """ 90 | if peer is not None: 91 | # TODO: 現状はMSG_REQUEST_FULL_CHAINの時にしかこの処理に入らないけど、 92 | # まだブロックチェーンを作るところまで行ってないのでとりあえず口だけ作っておく 93 | print('Send our latest blockchain for reply to : ', peer) 94 | else: 95 | if msg[2] == MSG_NEW_TRANSACTION: 96 | # TODO: 新規transactionを登録する処理を呼び出す 97 | pass 98 | elif msg[2] == MSG_NEW_BLOCK: 99 | # TODO: 新規ブロックを検証する処理を呼び出す 100 | pass 101 | elif msg[2] == RSP_FULL_CHAIN: 102 | # TODO: ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証する処理を呼び出す 103 | pass 104 | elif msg[2] == MSG_ENHANCED: 105 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。 106 | # SimpleBitcoin としてはこの種別は使わない 107 | 108 | # あらかじめ重複チェック(ポリシーによる。別にこの処理しなくてもいいかも 109 | print('received enhanced message', msg[4]) 110 | current_messages = self.my_protocol_message_store 111 | has_same = False 112 | if not msg[4] in current_messages: 113 | self.my_protocol_message_store.append(msg[4]) 114 | self.mpm.handle_message(msg[4], self.__core_api) 115 | 116 | def __get_myip(self): 117 | """ 118 | Google先生から自分のIPアドレスを取得する。内部利用のみを想定 119 | """ 120 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 121 | s.connect(('8.8.8.8', 80)) 122 | return s.getsockname()[0] 123 | -------------------------------------------------------------------------------- /02/03/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/02/03/p2p/__init__.py -------------------------------------------------------------------------------- /02/03/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, peer): 11 | """ 12 | Coreノードをリストに追加する。 13 | 14 | param: 15 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding peer: ', peer) 19 | self.list.add((peer)) 20 | print('Current Core List: ', self.list) 21 | 22 | 23 | def remove(self, peer): 24 | """ 25 | 離脱したと判断されるCoreノードをリストから削除する。 26 | 27 | param: 28 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 29 | """ 30 | with self.lock: 31 | if peer in self.list: 32 | print('Removing peer: ', peer) 33 | self.list.remove(peer) 34 | print('Current Core list: ', self.list) 35 | 36 | def overwrite(self, new_list): 37 | """ 38 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 39 | """ 40 | with self.lock: 41 | print('core node list will be going to overwrite') 42 | self.list = new_list 43 | print('Current Core list: ', self.list) 44 | 45 | 46 | def get_list(self): 47 | """ 48 | 現在接続状態にあるPeerの一覧を返却する 49 | """ 50 | return self.list 51 | 52 | def get_length(self): 53 | return len(self.list) 54 | 55 | 56 | def get_c_node_info(self): 57 | """ 58 | リストのトップにあるPeerを返却する 59 | """ 60 | return list(self.list)[0] 61 | -------------------------------------------------------------------------------- /02/03/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, edge): 11 | """ 12 | Edgeノードをリストに追加する。 13 | 14 | param: 15 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding edge: ', edge) 19 | self.list.add((edge)) 20 | print('Current Edge List: ', self.list) 21 | 22 | def remove(self, edge): 23 | """ 24 | 離脱したと判断されるEdgeノードをリストから削除する。 25 | 26 | param: 27 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if edge in self.list: 31 | print('Removing edge: ', edge) 32 | self.list.remove(edge) 33 | print('Current Edge list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('edge node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Edge list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるEdgeノードの一覧を返却する 47 | """ 48 | return self.list 49 | -------------------------------------------------------------------------------- /02/03/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /02/03/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 5 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | 10 | class MyProtocolMessageHandler: 11 | """ 12 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 13 | """ 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | """ 27 | msg = json.loads(msg) 28 | 29 | my_api = api('api_type', None) 30 | print('my_api: ', my_api) 31 | if my_api == 'server_core_api': 32 | print('Bloadcasting ...', json.dumps(msg)) 33 | # TODO: よく考えるとEdgeから受信した場合にしか他のCoreにブロードキャストしないようにすれば重複チェックもいらない… 34 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 35 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 36 | else: 37 | print('MyProtocolMessageHandler received ', msg) 38 | api(PASS_TO_CLIENT_APP, msg) 39 | 40 | return 41 | -------------------------------------------------------------------------------- /03/01/SampleBlockchain1.py: -------------------------------------------------------------------------------- 1 | from blockchain.blockchain_manager import BlockchainManager 2 | from blockchain.block_builder import BlockBuilder 3 | 4 | 5 | def main(): 6 | bb = BlockBuilder() 7 | my_genesis_block = bb.generate_genesis_block() 8 | bm = BlockchainManager(my_genesis_block.to_dict()) 9 | 10 | prev_block_hash = bm.get_hash(my_genesis_block.to_dict()) 11 | print('genesis_block_hash :' , prev_block_hash) 12 | 13 | transaction = { 14 | 'sender': 'test1', 15 | 'recipient': 'test2', 16 | 'value' : 3 17 | } 18 | 19 | new_block = bb.generate_new_block(transaction, prev_block_hash) 20 | bm.set_new_block(new_block.to_dict()) 21 | 22 | new_block_hash = bm.get_hash(new_block.to_dict()) 23 | print('1st_block_hash :' , new_block_hash) 24 | 25 | transaction2 = { 26 | 'sender': 'test1', 27 | 'recipient': 'test3', 28 | 'value' : 2 29 | } 30 | new_block2 = bb.generate_new_block(transaction2, new_block_hash) 31 | bm.set_new_block(new_block2.to_dict()) 32 | 33 | print(bm.chain) 34 | chain = bm.chain 35 | print(bm.is_valid(chain)) 36 | 37 | if __name__ == '__main__': 38 | main() -------------------------------------------------------------------------------- /03/01/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/01/blockchain/__init__.py -------------------------------------------------------------------------------- /03/01/blockchain/block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | 4 | class Block: 5 | def __init__(self, transaction, previous_block_hash): 6 | """ 7 | Args: 8 | transaction: ブロック内にセットされるトランザクション 9 | previous_block_hash: 直前のブロックのハッシュ値 10 | """ 11 | self.timestamp = time() 12 | self.transaction = transaction 13 | self.previous_block = previous_block_hash 14 | 15 | def to_dict(self): 16 | d = { 17 | 'timestamp': self.timestamp, 18 | 'transaction': json.dumps(self.transaction), 19 | 'previous_block': self.previous_block, 20 | } 21 | return d 22 | 23 | 24 | class GenesisBlock(Block): 25 | """ 26 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 27 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 28 | """ 29 | def __init__(self): 30 | super().__init__(transaction='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 31 | 32 | def to_dict(self): 33 | d = { 34 | 'transaction': self.transaction, 35 | 'genesis_block': True, 36 | } 37 | return d -------------------------------------------------------------------------------- /03/01/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | from .block import Block 2 | from .block import GenesisBlock 3 | 4 | 5 | class BlockBuilder: 6 | 7 | def __init__(self): 8 | print('Initializing BlockBuilder...') 9 | pass 10 | 11 | def generate_genesis_block(self): 12 | genesis_block = GenesisBlock() 13 | return genesis_block 14 | 15 | def generate_new_block(self, transaction, previous_block_hash): 16 | new_block = Block(transaction, previous_block_hash) 17 | return new_block 18 | 19 | 20 | -------------------------------------------------------------------------------- /03/01/blockchain/blockchain_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import hashlib 3 | import binascii 4 | import threading 5 | 6 | class BlockchainManager: 7 | 8 | def __init__(self, genesis_block): 9 | print('Initializing BlockchainManager...') 10 | self.chain = [] 11 | self.lock = threading.Lock() 12 | self.__set_my_genesis_block(genesis_block) 13 | 14 | def __set_my_genesis_block(self, block): 15 | self.genesis_block = block 16 | self.chain.append(block) 17 | 18 | def set_new_block(self, block): 19 | with self.lock: 20 | self.chain.append(block) 21 | 22 | def is_valid(self, chain): 23 | 24 | last_block = chain[0] 25 | current_index = 1 26 | 27 | while current_index < len(chain): 28 | block = self.chain[current_index] 29 | if block['previous_block'] != self.get_hash(last_block): 30 | return False 31 | 32 | last_block = block 33 | current_index += 1 34 | 35 | return True 36 | 37 | def _get_double_sha256(self, message): 38 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 39 | 40 | def get_hash(self, block): 41 | """ 42 | 正当性確認に使うためブロックのハッシュ値を取る 43 | :param block: Block 44 | """ 45 | block_string = json.dumps(block, sort_keys=True) 46 | return binascii.hexlify(self._get_double_sha256((block_string).encode('utf-8'))).decode('ascii') 47 | -------------------------------------------------------------------------------- /03/01/sample.py: -------------------------------------------------------------------------------- 1 | from blockchain.blockchain_manager import BlockchainManager 2 | from blockchain.block_builder import BlockBuilder 3 | 4 | 5 | def main(): 6 | bb = BlockBuilder() 7 | my_genesis_block = bb.generate_new_block('this_is_simple_bitcoin_genesis_block',None) 8 | bm = BlockchainManager(my_genesis_block.to_dict()) 9 | 10 | prev_block_hash = bm.get_hash(my_genesis_block.to_dict()) 11 | print('genesis_block_hash :' , prev_block_hash) 12 | 13 | transaction = { 14 | 'sender': 'test1', 15 | 'recipient': 'test2', 16 | 'value' : 3 17 | } 18 | 19 | new_block = bb.generate_new_block(transaction,prev_block_hash) 20 | bm.set_new_block(new_block.to_dict()) 21 | 22 | new_block_hash = bm.get_hash(new_block.to_dict()) 23 | print('1st_block_hash :' , new_block_hash) 24 | 25 | transaction2 = { 26 | 'sender': 'test1', 27 | 'recipient': 'test3', 28 | 'value' : 2 29 | } 30 | new_block2 = bb.generate_new_block(transaction2,new_block_hash) 31 | bm.set_new_block(new_block2.to_dict()) 32 | 33 | print(bm.chain) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() -------------------------------------------------------------------------------- /03/02/SampleBlockchain2.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from blockchain.blockchain_manager import BlockchainManager 5 | from blockchain.block_builder import BlockBuilder 6 | from transaction.transaction_pool import TransactionPool 7 | 8 | 9 | # TransactionPoolの確認頻度 10 | CHECK_INTERVAL = 10 11 | block_timer = None 12 | 13 | def generate_block_with_tp(tp, bb, bm, prev_block_hash): 14 | 15 | result = tp.get_stored_transactions() 16 | if len(result) == 0: 17 | print('Transaction Pool is empty ...') 18 | 19 | new_block = bb.generate_new_block(result, prev_block_hash) 20 | bm.set_new_block(new_block.to_dict()) 21 | prev_block_hash = bm.get_hash(new_block.to_dict()) 22 | # ブロック生成に成功したらTransaction Poolはクリアする 23 | tp.clear_my_transactions() 24 | 25 | print('Current Blockchain is ... ', bm.chain) 26 | print('Current prev_block_hash is ... ', prev_block_hash) 27 | 28 | block_timer = threading.Timer(CHECK_INTERVAL, generate_block_with_tp, args=(tp, bb, bm, prev_block_hash)) 29 | block_timer.start() 30 | 31 | def main(): 32 | 33 | bb = BlockBuilder() 34 | my_genesis_block = bb.generate_genesis_block() 35 | bm = BlockchainManager(my_genesis_block.to_dict()) 36 | 37 | tp = TransactionPool() 38 | 39 | prev_block_hash = bm.get_hash(my_genesis_block.to_dict()) 40 | print('genesis_block_hash :' , prev_block_hash) 41 | 42 | transaction = { 43 | 'sender': 'test1', 44 | 'recipient': 'test2', 45 | 'value' : 3 46 | } 47 | 48 | tp.set_new_transaction(transaction) 49 | 50 | transaction2 = { 51 | 'sender': 'test1', 52 | 'recipient': 'test3', 53 | 'value' : 2 54 | } 55 | 56 | tp.set_new_transaction(transaction2) 57 | 58 | block_timer = threading.Timer(CHECK_INTERVAL, generate_block_with_tp, args=(tp, bb, bm, prev_block_hash)) 59 | block_timer.start() 60 | 61 | 62 | if __name__ == '__main__': 63 | main() -------------------------------------------------------------------------------- /03/02/SampleBlockchain3.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from time import sleep 4 | 5 | from blockchain.blockchain_manager import BlockchainManager 6 | from blockchain.block_builder import BlockBuilder 7 | from transaction.transaction_pool import TransactionPool 8 | 9 | 10 | # TransactionPoolの確認頻度 11 | CHECK_INTERVAL = 10 12 | block_timer = None 13 | 14 | def generate_block_with_tp(tp, bb, bm, prev_block_hash): 15 | 16 | result = tp.get_stored_transactions() 17 | if result != None: 18 | new_block = bb.generate_new_block(result, prev_block_hash) 19 | bm.set_new_block(new_block.to_dict()) 20 | prev_block_hash = bm.get_hash(new_block.to_dict()) 21 | # ブロック生成に成功したらTransaction Poolはクリアする 22 | index = len(result) 23 | tp.clear_my_transactions(index) 24 | else: 25 | print('Transaction Pool is empty ...') 26 | 27 | print('Current Blockchain is ... ', bm.chain) 28 | print('Current prev_block_hash is ... ', prev_block_hash) 29 | 30 | block_timer = threading.Timer(CHECK_INTERVAL, generate_block_with_tp, args=(tp, bb, bm, prev_block_hash)) 31 | block_timer.start() 32 | 33 | def main(): 34 | 35 | bb = BlockBuilder() 36 | my_genesis_block = bb.generate_genesis_block() 37 | bm = BlockchainManager(my_genesis_block.to_dict()) 38 | 39 | tp = TransactionPool() 40 | 41 | prev_block_hash = bm.get_hash(my_genesis_block.to_dict()) 42 | print('genesis_block_hash :' , prev_block_hash) 43 | 44 | transaction = { 45 | 'sender': 'test1', 46 | 'recipient': 'test2', 47 | 'value' : 3 48 | } 49 | 50 | tp.set_new_transaction(transaction) 51 | 52 | transaction2 = { 53 | 'sender': 'test1', 54 | 'recipient': 'test3', 55 | 'value' : 2 56 | } 57 | 58 | tp.set_new_transaction(transaction2) 59 | 60 | block_timer = threading.Timer(CHECK_INTERVAL, generate_block_with_tp, args=(tp, bb, bm, prev_block_hash)) 61 | block_timer.start() 62 | 63 | sleep(15) 64 | 65 | transaction3 = { 66 | 'sender': 'test5', 67 | 'recipient': 'test6', 68 | 'value' : 10 69 | } 70 | 71 | tp.set_new_transaction(transaction3) 72 | 73 | 74 | if __name__ == '__main__': 75 | main() -------------------------------------------------------------------------------- /03/02/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/02/blockchain/__init__.py -------------------------------------------------------------------------------- /03/02/blockchain/block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | from time import time 4 | 5 | 6 | class Block(object): 7 | def __init__(self, transactions, previous_block_hash): 8 | """ 9 | Args: 10 | transaction: ブロック内にセットされるトランザクション 11 | previous_block_hash: 直前のブロックのハッシュ値 12 | """ 13 | self.timestamp = time() 14 | self.transactions = transactions 15 | self.previous_block = previous_block_hash 16 | 17 | def to_dict(self): 18 | d = { 19 | 'timestamp' : self.timestamp, 20 | 'transactions': list(map(json.dumps, self.transactions)), 21 | 'previous_block': self.previous_block, 22 | } 23 | return d 24 | 25 | 26 | class GenesisBlock(Block): 27 | """ 28 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 29 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 30 | """ 31 | def __init__(self): 32 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 33 | 34 | def to_dict(self): 35 | d = { 36 | 'transactions': self.transactions, 37 | 'genesis_block': True, 38 | } 39 | return d -------------------------------------------------------------------------------- /03/02/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | from .block import GenesisBlock 2 | from .block import Block 3 | 4 | 5 | class BlockBuilder(object): 6 | 7 | def __init__(self): 8 | print('Initializing BlockBuilder...') 9 | pass 10 | 11 | def generate_genesis_block(self): 12 | genesis_block = GenesisBlock() 13 | return genesis_block 14 | 15 | def generate_new_block(self, transaction, previous_block_hash): 16 | new_block = Block(transaction, previous_block_hash) 17 | return new_block 18 | 19 | 20 | -------------------------------------------------------------------------------- /03/02/blockchain/blockchain_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import hashlib 3 | import binascii 4 | import threading 5 | 6 | class BlockchainManager: 7 | 8 | def __init__(self, genesis_block): 9 | print('Initializing BlockchainManager...') 10 | self.chain = [] 11 | self.lock = threading.Lock() 12 | self.__set_my_genesis_block(genesis_block) 13 | 14 | def __set_my_genesis_block(self, block): 15 | self.genesis_block = block 16 | self.chain.append(block) 17 | 18 | def set_new_block(self, block): 19 | with self.lock: 20 | self.chain.append(block) 21 | 22 | def is_valid(self, chain): 23 | 24 | last_block = chain[0] 25 | current_index = 1 26 | 27 | while current_index < len(chain): 28 | block = self.chain[current_index] 29 | if block['previous_block'] != self.get_hash(last_block): 30 | return False 31 | 32 | last_block = block 33 | current_index += 1 34 | 35 | return True 36 | 37 | def _get_double_sha256(self, message): 38 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 39 | 40 | def get_hash(self, block): 41 | """ 42 | 正当性確認に使うためブロックのハッシュ値を取る 43 | :param block: Block 44 | """ 45 | block_string = json.dumps(block, sort_keys=True) 46 | return binascii.hexlify(self._get_double_sha256((block_string).encode('utf-8'))).decode('ascii') 47 | -------------------------------------------------------------------------------- /03/02/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/02/transaction/__init__.py -------------------------------------------------------------------------------- /03/02/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class TransactionPool: 4 | 5 | def __init__(self): 6 | print('Initializing TransactionPool...') 7 | self.transactions = [] 8 | self.lock = threading.Lock() 9 | 10 | def set_new_transaction(self, transaction): 11 | with self.lock: 12 | print('set_new_transaction is called', transaction) 13 | self.transactions.append(transaction) 14 | 15 | def clear_my_transactions(self, index): 16 | with self.lock: 17 | if index <= len(self.transactions): 18 | new_transactions = self.transactions 19 | del new_transactions[0:index] 20 | print('transaction is now refreshed ... ', new_transactions) 21 | self.transactions = new_transactions 22 | 23 | def get_stored_transactions(self): 24 | if len(self.transactions) > 0: 25 | return self.transactions 26 | else: 27 | print("Currently, it seems transaction pool is empty...") 28 | return [] 29 | -------------------------------------------------------------------------------- /03/02/transaction/transaction_pool_old.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class TransactionPool: 4 | 5 | def __init__(self): 6 | print('Initializing TransactionPool...') 7 | self.transactions = [] 8 | self.lock = threading.Lock() 9 | 10 | def set_new_transaction(self, transaction): 11 | with self.lock: 12 | print('set_new_transaction is called', transaction) 13 | self.transactions.append(transaction) 14 | 15 | def clear_my_transactions(self): 16 | with self.lock: 17 | self.transactions = [] 18 | 19 | def get_stored_transactions(self): 20 | if len(self.transactions) > 0: 21 | return self.transactions 22 | else: 23 | print("Currently, it seems transaction pool is empty...") 24 | return None 25 | -------------------------------------------------------------------------------- /03/03/SampleClient.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_NEW_TRANSACTION 7 | 8 | 9 | my_p2p_client = None 10 | 11 | 12 | def signal_handler(signal, frame): 13 | shutdown_client() 14 | 15 | def shutdown_client(): 16 | global my_p2p_client 17 | my_p2p_client.shutdown() 18 | 19 | def main(): 20 | signal.signal(signal.SIGINT, signal_handler) 21 | global my_p2p_client 22 | my_p2p_client = ClientCore(50095, '10.1.1.126',50082) 23 | my_p2p_client.start() 24 | 25 | sleep(10) 26 | 27 | transaction = { 28 | 'sender': 'test4', 29 | 'recipient': 'test5', 30 | 'value' : 3 31 | } 32 | 33 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction)) 34 | 35 | transaction2 = { 36 | 'sender': 'test6', 37 | 'recipient': 'test7', 38 | 'value' : 2 39 | } 40 | 41 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction2)) 42 | 43 | sleep(10) 44 | 45 | transaction3 = { 46 | 'sender': 'test8', 47 | 'recipient': 'test9', 48 | 'value' : 10 49 | } 50 | 51 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction3)) 52 | 53 | if __name__ == '__main__': 54 | main() -------------------------------------------------------------------------------- /03/03/SampleClient2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_NEW_TRANSACTION 7 | 8 | 9 | my_p2p_client = None 10 | 11 | 12 | def signal_handler(signal, frame): 13 | shutdown_client() 14 | 15 | def shutdown_client(): 16 | global my_p2p_client 17 | my_p2p_client.shutdown() 18 | 19 | def main(): 20 | signal.signal(signal.SIGINT, signal_handler) 21 | global my_p2p_client 22 | my_p2p_client = ClientCore(50088, '10.1.1.126',50082) 23 | my_p2p_client.start() 24 | 25 | sleep(10) 26 | 27 | transaction = { 28 | 'sender': 'test1', 29 | 'recipient': 'test2', 30 | 'value' : 3 31 | } 32 | 33 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction)) 34 | 35 | transaction2 = { 36 | 'sender': 'test1', 37 | 'recipient': 'test3', 38 | 'value' : 2 39 | } 40 | 41 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction2)) 42 | 43 | sleep(10) 44 | 45 | transaction3 = { 46 | 'sender': 'test5', 47 | 'recipient': 'test6', 48 | 'value' : 10 49 | } 50 | 51 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction3)) 52 | 53 | if __name__ == '__main__': 54 | main() -------------------------------------------------------------------------------- /03/03/SampleServer1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | # 始原のCoreノードとして起動する 20 | my_p2p_server = ServerCore(50082) 21 | my_p2p_server.start() 22 | 23 | 24 | if __name__ == '__main__': 25 | main() -------------------------------------------------------------------------------- /03/03/SampleServer2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | def main(): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_server 20 | my_p2p_server = ServerCore(50090, '10.1.1.27',50082) 21 | my_p2p_server.start() 22 | my_p2p_server.join_network() 23 | 24 | if __name__ == '__main__': 25 | main() -------------------------------------------------------------------------------- /03/03/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/03/blockchain/__init__.py -------------------------------------------------------------------------------- /03/03/blockchain/block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | 4 | 5 | class Block: 6 | def __init__(self, transactions, previous_block_hash): 7 | """ 8 | Args: 9 | transaction: ブロック内にセットされるトランザクション 10 | previous_block_hash: 直前のブロックのハッシュ値 11 | """ 12 | self.timestamp = time() 13 | self.transactions = transactions 14 | self.previous_block = previous_block_hash 15 | 16 | 17 | def to_dict(self): 18 | d = { 19 | 'timestamp' : self.timestamp, 20 | 'transactions': list(map(json.dumps, self.transactions)), 21 | 'previous_block': self.previous_block, 22 | } 23 | return d 24 | 25 | 26 | class GenesisBlock(Block): 27 | """ 28 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 29 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 30 | """ 31 | def __init__(self): 32 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 33 | 34 | def to_dict(self): 35 | d = { 36 | 'transactions': self.transactions, 37 | 'genesis_block': True, 38 | } 39 | return d -------------------------------------------------------------------------------- /03/03/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | from .block import GenesisBlock 2 | from .block import Block 3 | 4 | class BlockBuilder: 5 | 6 | def __init__(self): 7 | print('Initializing BlockBuilder...') 8 | pass 9 | 10 | def generate_genesis_block(self): 11 | genesis_block = GenesisBlock() 12 | return genesis_block 13 | 14 | def generate_new_block(self, transaction, previous_block_hash): 15 | new_block = Block(transaction, previous_block_hash) 16 | return new_block 17 | 18 | 19 | -------------------------------------------------------------------------------- /03/03/blockchain/blockchain_manager.py: -------------------------------------------------------------------------------- 1 | import json 2 | import hashlib 3 | import binascii 4 | import threading 5 | 6 | 7 | class BlockchainManager: 8 | 9 | def __init__(self, genesis_block): 10 | print('Initializing BlockchainManager...') 11 | self.chain = [] 12 | self.lock = threading.Lock() 13 | self.__set_my_genesis_block(genesis_block) 14 | 15 | def __set_my_genesis_block(self, block): 16 | self.genesis_block = block 17 | self.chain.append(block) 18 | 19 | def set_new_block(self, block): 20 | with self.lock: 21 | self.chain.append(block) 22 | 23 | def is_valid_chain(self,chain): 24 | 25 | last_block = chain[0] 26 | current_index = 1 27 | 28 | while current_index < len(chain): 29 | block = self.chain[current_index] 30 | if block['previous_block'] != self.get_hash(last_block): 31 | return False 32 | 33 | last_block = block 34 | current_index += 1 35 | 36 | return True 37 | 38 | def _get_double_sha256(self,message): 39 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 40 | 41 | def get_hash(self,block): 42 | """ 43 | 正当性確認に使うためブロックのハッシュ値を取る 44 | :param block: Block 45 | """ 46 | block_string = json.dumps(block, sort_keys=True) 47 | return binascii.hexlify(self._get_double_sha256((block_string).encode('utf-8'))).decode('ascii') 48 | -------------------------------------------------------------------------------- /03/03/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager_4edge import ConnectionManager4Edge 4 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 5 | from p2p.message_manager import ( 6 | 7 | MessageManager, 8 | 9 | RSP_FULL_CHAIN, 10 | MSG_ENHANCED, 11 | ) 12 | 13 | STATE_INIT = 0 14 | STATE_ACTIVE = 1 15 | STATE_SHUTTING_DOWN = 2 16 | 17 | 18 | class ClientCore: 19 | 20 | def __init__(self, my_port=50082, core_host=None, core_port=None): 21 | self.client_state = STATE_INIT 22 | print('Initializing ClientCore...') 23 | self.my_ip = self.__get_myip() 24 | print('Server IP address is set to ... ', self.my_ip) 25 | self.my_port = my_port 26 | self.my_core_host = core_host 27 | self.my_core_port = core_port 28 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, core_host, core_port, self.__handle_message) 29 | self.mpm = MyProtocolMessageHandler() 30 | self.my_protocol_message_store = [] 31 | 32 | def start(self): 33 | self.client_state = STATE_ACTIVE 34 | self.cm.start() 35 | self.cm.connect_to_core_node() 36 | 37 | def shutdown(self): 38 | self.client_state = STATE_SHUTTING_DOWN 39 | print('Shutdown edge node ...') 40 | self.cm.connection_close() 41 | 42 | def get_my_current_state(self): 43 | return self.client_state 44 | 45 | def send_message_to_my_core_node(self, msg_type, msg): 46 | msg_txt = self.cm.get_message_text(msg_type, msg) 47 | print(msg_txt) 48 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 49 | 50 | def __client_api(self, request, message): 51 | 52 | if request == 'pass_message_to_client_application': 53 | self.my_protocol_message_store.append(message) 54 | elif request == 'api_type': 55 | return 'client_core_api' 56 | else: 57 | print('not implemented api was used') 58 | 59 | def get_my_protocol_messages(self): 60 | 61 | if self.my_protocol_message_store != []: 62 | return self.my_protocol_message_store 63 | else: 64 | return None 65 | 66 | def __handle_message(self, msg): 67 | if msg[2] == RSP_FULL_CHAIN: 68 | # TODO: ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証する処理を呼び出す 69 | pass 70 | elif msg[2] == MSG_ENHANCED: 71 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。SimpleBitcoin としてはこの種別は使わない 72 | self.mpm.handle_message(msg[4], self.__client_api, True) 73 | 74 | def __get_myip(self): 75 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 76 | s.connect(('8.8.8.8', 80)) 77 | return s.getsockname()[0] 78 | -------------------------------------------------------------------------------- /03/03/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/03/p2p/__init__.py -------------------------------------------------------------------------------- /03/03/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, peer): 11 | """ 12 | Coreノードをリストに追加する。 13 | 14 | param: 15 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding peer: ', peer) 19 | self.list.add((peer)) 20 | print('Current Core List: ', self.list) 21 | 22 | def remove(self, peer): 23 | """ 24 | 離脱したと判断されるCoreノードをリストから削除する。 25 | 26 | param: 27 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if peer in self.list: 31 | print('Removing peer: ', peer) 32 | self.list.remove(peer) 33 | print('Current Core list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('core node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Core list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるPeerの一覧を返却する 47 | """ 48 | return self.list 49 | 50 | def get_length(self): 51 | return len(self.list) 52 | 53 | def get_c_node_info(self): 54 | """ 55 | リストのトップにあるPeerを返却する 56 | """ 57 | return list(self.list)[0] 58 | 59 | def has_this_peer(self, peer): 60 | """ 61 | 与えられたpeerがリストに含まれているか?をチェックする 62 | 63 | param: 64 | peer : IPアドレスとポート番号のタプル 65 | return: 66 | True or False 67 | """ 68 | return peer in self.list 69 | -------------------------------------------------------------------------------- /03/03/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, edge): 11 | """ 12 | Edgeノードをリストに追加する。 13 | 14 | param: 15 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding edge: ', edge) 19 | self.list.add((edge)) 20 | print('Current Edge List: ', self.list) 21 | 22 | def remove(self, edge): 23 | """ 24 | 離脱したと判断されるEdgeノードをリストから削除する。 25 | 26 | param: 27 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if edge in self.list: 31 | print('Removing edge: ', edge) 32 | self.list.remove(edge) 33 | print('Current Edge list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('edge node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Edge list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるEdgeノードの一覧を返却する 47 | """ 48 | return self.list 49 | -------------------------------------------------------------------------------- /03/03/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /03/03/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 5 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | 10 | class MyProtocolMessageHandler: 11 | """ 12 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 13 | """ 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | """ 27 | msg = json.loads(msg) 28 | 29 | my_api = api('api_type', None) 30 | print('my_api: ', my_api) 31 | if my_api == 'server_core_api': 32 | print('Bloadcasting ...', json.dumps(msg)) 33 | # TODO: よく考えるとEdgeから受信した場合にしか他のCoreにブロードキャストしないようにすれば重複チェックもいらない… 34 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 35 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 36 | else: 37 | print('MyProtocolMessageHandler received ', msg) 38 | api(PASS_TO_CLIENT_APP, msg) 39 | 40 | return 41 | -------------------------------------------------------------------------------- /03/03/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/03/transaction/__init__.py -------------------------------------------------------------------------------- /03/03/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class TransactionPool: 4 | 5 | def __init__(self): 6 | print('Initializing TransactionPool...') 7 | self.transactions = [] 8 | self.lock = threading.Lock() 9 | 10 | def set_new_transaction(self, transaction): 11 | with self.lock: 12 | print('set_new_transaction is called', transaction) 13 | self.transactions.append(transaction) 14 | 15 | def clear_my_transactions(self, index): 16 | with self.lock: 17 | if index <= len(self.transactions): 18 | new_transactions = self.transactions 19 | del new_transactions[0:index] 20 | print('transaction is now refreshed ... ', new_transactions) 21 | self.transactions = new_transactions 22 | 23 | def get_stored_transactions(self): 24 | if len(self.transactions) > 0: 25 | return self.transactions 26 | else: 27 | print("Currently, it seems transaction pool is empty...") 28 | return [] 29 | -------------------------------------------------------------------------------- /03/04/SampleBlockchain4.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from time import sleep 4 | 5 | from blockchain.blockchain_manager import BlockchainManager 6 | from blockchain.block_builder import BlockBuilder 7 | from transaction.transaction_pool import TransactionPool 8 | 9 | # TransactionPoolの確認頻度 10 | CHECK_INTERVAL = 10 11 | 12 | FLAG_STOP_BLOCK_BUILD = False 13 | 14 | 15 | def start_thread(tp, bb, bm, prev_block_hash): 16 | t = threading.Thread(target=generate_block_with_tp , args=(tp, bb, bm, prev_block_hash)) 17 | t.start() 18 | 19 | 20 | def generate_block_with_tp(tp, bb, bm, prev_block_hash): 21 | 22 | t=time.time() 23 | print('Thread for generate_block_with_tp started!') 24 | global FLAG_STOP_BLOCK_BUILD 25 | while FLAG_STOP_BLOCK_BUILD is not True: 26 | if time.time()-t > CHECK_INTERVAL: 27 | result = tp.get_stored_transactions() 28 | if result != None: 29 | new_block = bb.generate_new_block(result, prev_block_hash) 30 | bm.set_new_block(new_block.to_dict()) 31 | prev_block_hash = bm.get_hash(new_block.to_dict()) 32 | # ブロック生成に成功したらTransaction Poolはクリアする 33 | index = len(result) 34 | tp.clear_my_transactions(index) 35 | else: 36 | print('Transaction Pool is empty ...') 37 | print('Current Blockchain is ... ', bm.chain) 38 | print('Current prev_block_hash is ... ', prev_block_hash) 39 | t=time.time() 40 | 41 | print('Thread for generate_block_with_tp Stopped') 42 | 43 | 44 | def main(): 45 | 46 | global FLAG_STOP_BLOCK_BUILD 47 | 48 | bb = BlockBuilder() 49 | my_genesis_block = bb.generate_genesis_block() 50 | bm = BlockchainManager(my_genesis_block.to_dict()) 51 | 52 | tp = TransactionPool() 53 | 54 | prev_block_hash = bm.get_hash(my_genesis_block.to_dict()) 55 | print('genesis_block_hash :' , prev_block_hash) 56 | 57 | transaction = { 58 | 'sender': 'test1', 59 | 'recipient': 'test2', 60 | 'value' : 3 61 | } 62 | 63 | tp.set_new_transaction(transaction) 64 | 65 | transaction2 = { 66 | 'sender': 'test1', 67 | 'recipient': 'test3', 68 | 'value' : 2 69 | } 70 | 71 | tp.set_new_transaction(transaction2) 72 | 73 | start_thread(tp, bb, bm, prev_block_hash) 74 | 75 | sleep(20) 76 | 77 | transaction3 = { 78 | 'sender': 'test5', 79 | 'recipient': 'test6', 80 | 'value' : 10 81 | } 82 | 83 | tp.set_new_transaction(transaction3) 84 | 85 | sleep(30) 86 | 87 | print('Stop the Thread for generate_block_with_tp') 88 | FLAG_STOP_BLOCK_BUILD = True 89 | 90 | 91 | 92 | if __name__ == '__main__': 93 | main() -------------------------------------------------------------------------------- /03/04/SampleClient.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_NEW_TRANSACTION 7 | 8 | my_p2p_client = None 9 | 10 | 11 | def signal_handler(signal, frame): 12 | shutdown_client() 13 | 14 | def shutdown_client(): 15 | global my_p2p_client 16 | my_p2p_client.shutdown() 17 | 18 | def main(): 19 | signal.signal(signal.SIGINT, signal_handler) 20 | global my_p2p_client 21 | my_p2p_client = ClientCore(50095, '10.1.1.126',50082) 22 | my_p2p_client.start() 23 | 24 | sleep(10) 25 | 26 | transaction = { 27 | 'sender': 'test4', 28 | 'recipient': 'test5', 29 | 'value' : 3 30 | } 31 | 32 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction)) 33 | print(json.dumps(transaction)) 34 | transaction2 = { 35 | 'sender': 'test6', 36 | 'recipient': 'test7', 37 | 'value' : 2 38 | } 39 | 40 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction2)) 41 | 42 | sleep(10) 43 | 44 | transaction3 = { 45 | 'sender': 'test8', 46 | 'recipient': 'test9', 47 | 'value' : 10 48 | } 49 | 50 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction3)) 51 | 52 | if __name__ == '__main__': 53 | main() -------------------------------------------------------------------------------- /03/04/SampleClient2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_NEW_TRANSACTION 7 | 8 | 9 | my_p2p_client = None 10 | 11 | 12 | def signal_handler(signal, frame): 13 | shutdown_client() 14 | 15 | 16 | def shutdown_client(): 17 | global my_p2p_client 18 | my_p2p_client.shutdown() 19 | 20 | 21 | def main(): 22 | signal.signal(signal.SIGINT, signal_handler) 23 | global my_p2p_client 24 | my_p2p_client = ClientCore(50088, '10.1.1.126',50090) 25 | my_p2p_client.start() 26 | 27 | sleep(10) 28 | 29 | transaction = { 30 | 'sender': 'test1', 31 | 'recipient': 'test2', 32 | 'value' : 3 33 | } 34 | 35 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction)) 36 | 37 | transaction2 = { 38 | 'sender': 'test1', 39 | 'recipient': 'test3', 40 | 'value' : 2 41 | } 42 | 43 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction2)) 44 | 45 | sleep(10) 46 | 47 | transaction3 = { 48 | 'sender': 'test5', 49 | 'recipient': 'test6', 50 | 'value' : 10 51 | } 52 | 53 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction3)) 54 | 55 | if __name__ == '__main__': 56 | main() -------------------------------------------------------------------------------- /03/04/SampleClient3.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | import json 4 | 5 | from core.client_core import ClientCore 6 | from p2p.message_manager import MSG_NEW_TRANSACTION 7 | 8 | 9 | my_p2p_client = None 10 | 11 | 12 | def signal_handler(signal, frame): 13 | shutdown_client() 14 | 15 | 16 | def shutdown_client(): 17 | global my_p2p_client 18 | my_p2p_client.shutdown() 19 | 20 | 21 | def main(): 22 | signal.signal(signal.SIGINT, signal_handler) 23 | global my_p2p_client 24 | my_p2p_client = ClientCore(50088, '10.1.1.126',50082) 25 | my_p2p_client.start() 26 | 27 | sleep(10) 28 | 29 | transaction = { 30 | 'sender': 'test1', 31 | 'recipient': 'test2', 32 | 'value' : 3 33 | } 34 | 35 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction)) 36 | 37 | transaction2 = { 38 | 'sender': 'test1', 39 | 'recipient': 'test3', 40 | 'value' : 2 41 | } 42 | 43 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction2)) 44 | 45 | sleep(10) 46 | 47 | transaction3 = { 48 | 'sender': 'test5', 49 | 'recipient': 'test6', 50 | 'value' : 10 51 | } 52 | 53 | my_p2p_client.send_message_to_my_core_node(MSG_NEW_TRANSACTION,json.dumps(transaction3)) 54 | 55 | if __name__ == '__main__': 56 | main() -------------------------------------------------------------------------------- /03/04/SampleServer1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | # 始原のCoreノードとして起動する 20 | my_p2p_server = ServerCore(50082) 21 | my_p2p_server.start() 22 | 23 | 24 | if __name__ == '__main__': 25 | main() -------------------------------------------------------------------------------- /03/04/SampleServer2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | my_p2p_server = ServerCore(50090, '10.1.1.126',50082) 20 | my_p2p_server.start() 21 | my_p2p_server.join_network() 22 | 23 | if __name__ == '__main__': 24 | main() -------------------------------------------------------------------------------- /03/04/SampleServer3.py: -------------------------------------------------------------------------------- 1 | import signal 2 | from time import sleep 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | my_p2p_server = ServerCore(50090, '10.1.1.126',50082) 20 | my_p2p_server.start() 21 | my_p2p_server.join_network() 22 | sleep(3) 23 | my_p2p_server.get_all_chains_for_resolve_conflict() 24 | 25 | 26 | if __name__ == '__main__': 27 | main() -------------------------------------------------------------------------------- /03/04/blockchain/Block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | import hashlib 4 | import binascii 5 | from datetime import datetime 6 | 7 | 8 | class Block: 9 | def __init__(self, transactions, previous_block_hash): 10 | """ 11 | Args: 12 | transaction: ブロック内にセットされるトランザクション 13 | previous_block_hash: 直前のブロックのハッシュ値 14 | """ 15 | snap_tr = json.dumps(transactions) # PoW計算中に値が変わるのでブロック計算開始時の値を退避させておく 16 | 17 | self.timestamp = time() 18 | self.transactions = json.loads(snap_tr) 19 | self.previous_block = previous_block_hash 20 | 21 | current = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 22 | print(current) 23 | 24 | json_block = json.dumps(self.to_dict(include_nonce=False) , sort_keys=True) 25 | print('json_block :', json_block) 26 | self.nonce = self._compute_nonce_for_pow(json_block) 27 | 28 | current2 = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 29 | print(current2) 30 | 31 | def to_dict(self,include_nonce= True): 32 | d = { 33 | 'timestamp' : self.timestamp, 34 | 'transactions' : list(map(json.dumps, self.transactions)), 35 | 'previous_block': self.previous_block 36 | } 37 | 38 | if include_nonce: 39 | d['nonce'] = self.nonce 40 | return d 41 | 42 | def _compute_nonce_for_pow(self,message, difficulty=5): 43 | # difficultyの数字を増やせば増やすほど、末尾で揃えなければならない桁数が増える。 44 | i = 0 45 | suffix = '0' * difficulty 46 | while True: 47 | nonce = str(i) 48 | digest = binascii.hexlify(self._get_double_sha256((message + nonce).encode('utf-8'))).decode('ascii') 49 | if digest.endswith(suffix): 50 | return nonce 51 | i += 1 52 | 53 | def _get_double_sha256(self,message): 54 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 55 | 56 | 57 | class GenesisBlock(Block): 58 | """ 59 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 60 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 61 | """ 62 | def __init__(self): 63 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 64 | 65 | def to_dict(self, include_nonce=True): 66 | d = { 67 | 'transactions': self.transactions, 68 | 'genesis_block': True, 69 | } 70 | if include_nonce: 71 | d['nonce'] = self.nonce 72 | return d -------------------------------------------------------------------------------- /03/04/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/04/blockchain/__init__.py -------------------------------------------------------------------------------- /03/04/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | from .block import GenesisBlock 2 | from .block import Block 3 | 4 | class BlockBuilder: 5 | 6 | def __init__(self): 7 | print('Initializing BlockBuilder...') 8 | pass 9 | 10 | def generate_genesis_block(self): 11 | genesis_block = GenesisBlock() 12 | return genesis_block 13 | 14 | def generate_new_block(self, transaction, previous_block_hash): 15 | new_block = Block(transaction, previous_block_hash) 16 | return new_block 17 | 18 | 19 | -------------------------------------------------------------------------------- /03/04/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from p2p.connection_manager_4edge import ConnectionManager4Edge 4 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 5 | from p2p.message_manager import ( 6 | MessageManager, 7 | RSP_FULL_CHAIN, 8 | MSG_ENHANCED, 9 | ) 10 | 11 | STATE_INIT = 0 12 | STATE_ACTIVE = 1 13 | STATE_SHUTTING_DOWN = 2 14 | 15 | 16 | class ClientCore: 17 | 18 | def __init__(self, my_port=50082, core_host=None, core_port=None): 19 | self.client_state = STATE_INIT 20 | print('Initializing ClientCore...') 21 | self.my_ip = self.__get_myip() 22 | print('Server IP address is set to ... ', self.my_ip) 23 | self.my_port = my_port 24 | self.my_core_host = core_host 25 | self.my_core_port = core_port 26 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, core_host, core_port, self.__handle_message) 27 | self.mpm = MyProtocolMessageHandler() 28 | self.my_protocol_message_store = [] 29 | 30 | def start(self): 31 | self.client_state = STATE_ACTIVE 32 | self.cm.start() 33 | self.cm.connect_to_core_node() 34 | 35 | def shutdown(self): 36 | self.client_state = STATE_SHUTTING_DOWN 37 | print('Shutdown edge node ...') 38 | self.cm.connection_close() 39 | 40 | def get_my_current_state(self): 41 | return self.client_state 42 | 43 | def send_message_to_my_core_node(self, msg_type, msg): 44 | msg_txt = self.cm.get_message_text(msg_type, msg) 45 | print(msg_txt) 46 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 47 | 48 | def __client_api(self, request, message): 49 | 50 | if request == 'pass_message_to_client_application': 51 | self.my_protocol_message_store.append(message) 52 | elif request == 'api_type': 53 | return 'client_core_api' 54 | else: 55 | print('not implemented api was used') 56 | 57 | def get_my_protocol_messages(self): 58 | 59 | if self.my_protocol_message_store != []: 60 | return self.my_protocol_message_store 61 | else: 62 | return None 63 | 64 | def __handle_message(self, msg): 65 | if msg[2] == RSP_FULL_CHAIN: 66 | # TODO: ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証する処理を呼び出す 67 | pass 68 | elif msg[2] == MSG_ENHANCED: 69 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。SimpleBitcoin としてはこの種別は使わない 70 | self.mpm.handle_message(msg[4], self.__client_api, True) 71 | 72 | def __get_myip(self): 73 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 74 | s.connect(('8.8.8.8', 80)) 75 | return s.getsockname()[0] 76 | -------------------------------------------------------------------------------- /03/04/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/04/p2p/__init__.py -------------------------------------------------------------------------------- /03/04/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, peer): 11 | """ 12 | Coreノードをリストに追加する。 13 | 14 | param: 15 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding peer: ', peer) 19 | self.list.add((peer)) 20 | print('Current Core List: ', self.list) 21 | 22 | def remove(self, peer): 23 | """ 24 | 離脱したと判断されるCoreノードをリストから削除する。 25 | 26 | param: 27 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if peer in self.list: 31 | print('Removing peer: ', peer) 32 | self.list.remove(peer) 33 | print('Current Core list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('core node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Core list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるPeerの一覧を返却する 47 | """ 48 | return self.list 49 | 50 | def get_length(self): 51 | return len(self.list) 52 | 53 | def get_c_node_info(self): 54 | """ 55 | リストのトップにあるPeerを返却する 56 | """ 57 | return list(self.list)[0] 58 | 59 | def has_this_peer(self, peer): 60 | """ 61 | 与えられたpeerがリストに含まれているか?をチェックする 62 | 63 | param: 64 | peer : IPアドレスとポート番号のタプル 65 | return: 66 | True or False 67 | """ 68 | return peer in self.list 69 | -------------------------------------------------------------------------------- /03/04/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | 6 | def __init__(self): 7 | self.lock = threading.Lock() 8 | self.list = set() 9 | 10 | def add(self, edge): 11 | """ 12 | Edgeノードをリストに追加する。 13 | 14 | param: 15 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 16 | """ 17 | with self.lock: 18 | print('Adding edge: ', edge) 19 | self.list.add((edge)) 20 | print('Current Edge List: ', self.list) 21 | 22 | def remove(self, edge): 23 | """ 24 | 離脱したと判断されるEdgeノードをリストから削除する。 25 | 26 | param: 27 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 28 | """ 29 | with self.lock: 30 | if edge in self.list: 31 | print('Removing edge: ', edge) 32 | self.list.remove(edge) 33 | print('Current Edge list: ', self.list) 34 | 35 | def overwrite(self, new_list): 36 | """ 37 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 38 | """ 39 | with self.lock: 40 | print('edge node list will be going to overwrite') 41 | self.list = new_list 42 | print('Current Edge list: ', self.list) 43 | 44 | def get_list(self): 45 | """ 46 | 現在接続状態にあるEdgeノードの一覧を返却する 47 | """ 48 | return self.list 49 | -------------------------------------------------------------------------------- /03/04/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /03/04/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 5 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | 10 | class MyProtocolMessageHandler: 11 | """ 12 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 13 | """ 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | """ 27 | msg = json.loads(msg) 28 | 29 | my_api = api('api_type', None) 30 | print('my_api: ', my_api) 31 | if my_api == 'server_core_api': 32 | print('Bloadcasting ...', json.dumps(msg)) 33 | # TODO: よく考えるとEdgeから受信した場合にしか他のCoreにブロードキャストしないようにすれば重複チェックもいらない… 34 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 35 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 36 | else: 37 | print('MyProtocolMessageHandler received ', msg) 38 | api(PASS_TO_CLIENT_APP, msg) 39 | 40 | return 41 | -------------------------------------------------------------------------------- /03/04/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/03/04/transaction/__init__.py -------------------------------------------------------------------------------- /03/04/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class TransactionPool: 4 | 5 | def __init__(self): 6 | print('Initializing TransactionPool...') 7 | self.transactions = [] 8 | self.lock = threading.Lock() 9 | 10 | def set_new_transaction(self, transaction): 11 | with self.lock: 12 | print('set_new_transaction is called', transaction) 13 | self.transactions.append(transaction) 14 | 15 | def clear_my_transactions(self, index): 16 | with self.lock: 17 | if index <= len(self.transactions): 18 | new_transactions = self.transactions 19 | del new_transactions[0:index] 20 | print('transaction is now refreshed ... ', new_transactions) 21 | self.transactions = new_transactions 22 | 23 | def get_stored_transactions(self): 24 | if len(self.transactions) > 0: 25 | return self.transactions 26 | else: 27 | print("Currently, it seems transaction pool is empty...") 28 | return [] 29 | 30 | def renew_my_transactions(self, transactions): 31 | with self.lock: 32 | print('transaction pool will be renewed to ...', transactions) 33 | self.transactions = transactions -------------------------------------------------------------------------------- /04/key_manager.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Signature import PKCS1_v1_5 5 | from Crypto.Hash import SHA256 6 | import binascii 7 | 8 | 9 | class KeyManager: 10 | 11 | def __init__(self): 12 | print('Initializing KeyManager...') 13 | random_gen = Crypto.Random.new().read 14 | self._private_key = RSA.generate(2048, random_gen) 15 | self._public_key = self._private_key.publickey() 16 | self._signer = PKCS1_v1_5.new(self._private_key) 17 | 18 | def my_address(self): 19 | """ 20 | UI表示用の公開鍵情報 21 | """ 22 | return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii') 23 | 24 | def compute_digital_signature(self, message): 25 | hashed_message = SHA256.new(message.encode('utf8')) 26 | signer = PKCS1_v1_5.new(self._private_key) 27 | return binascii.hexlify(signer.sign(hashed_message)).decode('ascii') 28 | 29 | def verify_signature(self, message, signature, sender_public_key): 30 | hashed_message = SHA256.new(message.encode('utf8')) 31 | verifier = PKCS1_v1_5.new(sender_public_key) 32 | return verifier.verify(hashed_message, binascii.unhexlify(signature)) 33 | 34 | def export_key_pair(self, pass_phrase): 35 | """ 36 | 鍵ペアをPEMフォーマットで書き出す(バックアップ用途) 37 | """ 38 | return self._private_key.exportKey(format='PEM', passphrase=pass_phrase) 39 | 40 | def import_key_pair(self, key_data, pass_phrase): 41 | """ 42 | PEMフォーマットでパスワード保護された鍵ペアをファイルから読み込んで設定する 43 | """ 44 | self._private_key = RSA.importKey(key_data, pass_phrase) 45 | self._public_key = self._private_key.publickey() 46 | self._signer = PKCS1_v1_5.new(self._private_key) 47 | -------------------------------------------------------------------------------- /04/pubkey_test.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.PublicKey import RSA 4 | 5 | 6 | # RSAの鍵ペアを生成する 7 | def generate_rsa_key_pair(): 8 | 9 | random_gen = Crypto.Random.new().read 10 | private_key = RSA.generate(2048, random_gen) 11 | public_key = private_key.publickey() 12 | 13 | return public_key, private_key 14 | 15 | 16 | def main(): 17 | 18 | test_txt = 'This is test message for getting understand about digital signature' 19 | 20 | pubkey, privkey = generate_rsa_key_pair() 21 | 22 | hashed = SHA256.new(test_txt.encode('utf8')).digest() 23 | print('hashed :' , hashed) 24 | 25 | #公開鍵で暗号化 26 | encrypto = pubkey.encrypt(test_txt.encode('utf-8'), 0) 27 | print('encrypto :', encrypto) 28 | 29 | #秘密鍵で復号 30 | decrypto = privkey.decrypt(encrypto) 31 | print('decrypto :', decrypto) 32 | 33 | if test_txt == decrypto.decode('utf-8'): 34 | print('test_txt and decrypto are same!') 35 | 36 | #秘密鍵で暗号化 37 | enc_with_priv = privkey.encrypt(hashed, 0)[0] 38 | print('enc_with_priv :', enc_with_priv) 39 | 40 | # 公開鍵で復号 41 | dec_with_pub = pubkey.decrypt(enc_with_priv)s 42 | print('dec_with_pub :', dec_with_pub) 43 | 44 | if hashed == dec_with_pub: 45 | print('hashed and dec_with_pub are same!') 46 | 47 | 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /04/pubkey_test2.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.Hash import SHA256 4 | from Crypto.PublicKey import RSA 5 | from Crypto.Signature import PKCS1_v1_5 6 | import binascii 7 | 8 | 9 | # RSAの鍵ペアを生成する 10 | def generate_rsa_key_pair(): 11 | 12 | random_gen = Crypto.Random.new().read 13 | private_key = RSA.generate(2048, random_gen) 14 | public_key = private_key.publickey() 15 | 16 | return public_key, private_key 17 | 18 | 19 | # 与えられた秘密鍵を使ってメッセージに署名する 20 | def compute_digital_signature(message, private_key): 21 | # まずメッセージをハッシュ値に変換する 22 | hashed_message = SHA256.new(message.encode('utf8')) 23 | # 署名にはRSASSA-PKCS1-v1_5を利用する 24 | signer = PKCS1_v1_5.new(private_key) 25 | return binascii.hexlify(signer.sign(hashed_message)).decode('ascii') 26 | 27 | def verify_signature(message, signature, pub_key): 28 | hashed_message = SHA256.new(message.encode('utf8')) 29 | verifier = PKCS1_v1_5.new(pub_key) 30 | return verifier.verify(hashed_message, binascii.unhexlify(signature)) 31 | 32 | def main(): 33 | 34 | test_txt = 'This is test message for getting understand about digital signature' 35 | 36 | pubkey, privkey = generate_rsa_key_pair() 37 | 38 | # 本来はデジタル署名を使う 39 | signed = compute_digital_signature(test_txt, privkey) 40 | print('signed :', signed) 41 | 42 | result = verify_signature(test_txt, signed, pubkey) 43 | 44 | print('result :' , result) 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /04/test_trx.py: -------------------------------------------------------------------------------- 1 | from tsx import Transaction 2 | from tsx import TransactionInput 3 | from tsx import TransactionOutput 4 | from tsx import CoinbaseTransaction 5 | 6 | def main(): 7 | 8 | t1 = CoinbaseTransaction('Itsuki_pubkey') 9 | 10 | print(t1.to_dict()) 11 | 12 | result = t1.is_enough_inputs() 13 | 14 | print(result) 15 | 16 | t2 = Transaction( 17 | [TransactionInput(t1, 0)], 18 | [TransactionOutput('Umika_pubkey', 10.0), TransactionOutput('Itsuki_pubkey', 20.0)] 19 | ) 20 | 21 | print(t2.to_dict()) 22 | result2 = t2.is_enough_inputs() 23 | print(result2) 24 | 25 | 26 | if __name__ == '__main__': 27 | main() -------------------------------------------------------------------------------- /04/transactions.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | class TransactionInput: 4 | """ 5 | トトランザクションの中でInputに格納するUTXOを指定する 6 | """ 7 | def __init__(self, transaction, output_index): 8 | self.transaction = transaction 9 | self.output_index = output_index 10 | 11 | def to_dict(self): 12 | d = { 13 | 'transaction': self.transaction, 14 | 'output_index': self.output_index 15 | } 16 | return d 17 | 18 | class TransactionOutput: 19 | """ 20 | トランザクションの中で Output (送金相手と送る金額)を管理する 21 | """ 22 | def __init__(self, recipient_address, value): 23 | self.recipient = recipient_address 24 | self.value = value 25 | 26 | def to_dict(self): 27 | d = { 28 | 'recipient': self.recipient, 29 | 'value': self.value 30 | } 31 | return d 32 | 33 | class Transaction: 34 | """ 35 | 持っていないコインを誰かに簡単に送金できてしまっては全く意味がないので、過去のトランザクションにて 36 | 自分を宛先として送金されたコインの総計を超える送金依頼を作ることができないよう、inputs と outputs 37 | のペアによって管理する 38 | """ 39 | def __init__(self, inputs, outputs): 40 | self.inputs = inputs 41 | self.outputs = outputs 42 | self.timestamp = time() 43 | 44 | 45 | def to_dict(self): 46 | d = { 47 | 'inputs': list(map(TransactionInput.to_dict, self.inputs)), 48 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 49 | 'timestamp' : self.timestamp 50 | } 51 | 52 | return d 53 | 54 | def is_enough_inputs(self, fee): 55 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 56 | total_out = sum(int(o.value) for o in self.outputs) + fee 57 | delta = total_in - total_out 58 | if delta >= 0: 59 | return True 60 | else: 61 | return False 62 | 63 | def compute_change(self, fee): 64 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 65 | total_out = sum(int(o.value) for o in self.outputs) + fee 66 | delta = total_in - total_out 67 | return delta 68 | 69 | 70 | class CoinbaseTransaction(Transaction): 71 | """ 72 | Coinbaseトランザクションは例外的にInputが存在しない。 73 | """ 74 | def __init__(self, recipient_address, value=30): 75 | self.inputs = [] 76 | self.outputs = [TransactionOutput(recipient_address, value)] 77 | self.timestamp = time() 78 | 79 | def to_dict(self): 80 | d = { 81 | 'inputs': [], 82 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 83 | 'timestamp' : self.timestamp, 84 | 'coinbase_transaction': True 85 | } 86 | 87 | return d -------------------------------------------------------------------------------- /04/tsx.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class TransactionInput: 4 | """ 5 | トトランザクションの中でInputに格納するUTXOを指定する 6 | """ 7 | def __init__(self, transaction, output_index): 8 | self.transaction = transaction 9 | self.output_index = output_index 10 | 11 | def to_dict(self): 12 | d = { 13 | 'transaction': self.transaction.to_dict(), 14 | 'output_index': self.output_index 15 | } 16 | return d 17 | 18 | class TransactionOutput: 19 | """ 20 | トランザクションの中で Output (送金相手と送る金額)を管理する 21 | """ 22 | def __init__(self, recipient_address, value): 23 | self.recipient = recipient_address 24 | self.value = value 25 | 26 | def to_dict(self): 27 | d = { 28 | 'recipient_address': self.recipient, 29 | 'value': self.value 30 | } 31 | return d 32 | 33 | class Transaction: 34 | """ 35 | 持っていないコインを誰かに簡単に送金できてしまっては全く意味がないので、過去のトランザクションにて 36 | 自分を宛先として送金されたコインの総計を超える送金依頼を作ることができないよう、inputs と outputs 37 | のペアによって管理する 38 | """ 39 | def __init__(self, inputs, outputs): 40 | self.inputs = inputs 41 | self.outputs = outputs 42 | 43 | 44 | def to_dict(self): 45 | d = { 46 | 'inputs': list(map(TransactionInput.to_dict, self.inputs)), 47 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 48 | } 49 | 50 | return d 51 | 52 | def is_enough_inputs(self): 53 | total_in = sum(i.transaction.outputs[i.output_index].value for i in self.inputs) 54 | total_out = sum(o.value for o in self.outputs) 55 | delta = total_in - total_out 56 | if delta >= 0: 57 | return True 58 | else: 59 | return False 60 | 61 | class CoinbaseTransaction(Transaction): 62 | """ 63 | Coinbaseトランザクションは例外的にInputが存在しない。 64 | """ 65 | def __init__(self, recipient_address, value=30): 66 | self.inputs = [] 67 | self.outputs = [TransactionOutput(recipient_address, value)] 68 | 69 | def to_dict(self): 70 | d = { 71 | 'inputs': [], 72 | 'outputs': self.outputs, 73 | 'coinbae_transaction': True 74 | } 75 | 76 | return d -------------------------------------------------------------------------------- /04/utxo_manager.py: -------------------------------------------------------------------------------- 1 | class UTXOManager: 2 | 3 | def __init__(self, address): 4 | print('Initializing UTXOManager...') 5 | self.my_address = address 6 | self.utxo_txs = [] 7 | self.my_balance = 0 8 | 9 | 10 | def extract_utxos(self, txs): 11 | """ 12 | 与えられたTransaction群の中からUTXOとしてまだ利用可能なもののみを抽出して保存する 13 | """ 14 | print('extract_utxos called!') 15 | outputs = [] 16 | inputs = [] 17 | idx = 0 18 | for t in txs: 19 | for txout in t['outputs']: 20 | recipient = txout['recipient'] 21 | if recipient == self.my_address: 22 | outputs.append(t) 23 | for txin in t['inputs']: 24 | t_in_txin = txin['transaction'] 25 | idx = txin['output_index'] 26 | o_recipient = t_in_txin['outputs'][idx]['recipient'] 27 | if o_recipient == self.my_address: 28 | inputs.append(t) 29 | 30 | if outputs is not []: 31 | for o in outputs: 32 | if inputs is not []: 33 | for i in inputs: 34 | for i_i in i['inputs']: 35 | if o == i_i['transaction']: 36 | outputs.remove(o) 37 | else: 38 | break 39 | else: 40 | print('No Transaction for UTXO') 41 | return 42 | 43 | self._set_my_utxo_txs(outputs) 44 | 45 | 46 | def _set_my_utxo_txs(self, txs): 47 | """ 48 | 一括でUTXOトランザクション群を書き換える 49 | """ 50 | print("_set_my_utxo_txs was called") 51 | self.utxo_txs = [] 52 | 53 | for t in txs: 54 | self.put_utxo_tx(t) 55 | 56 | 57 | def put_utxo_tx(self, tx): 58 | """ 59 | utxoトランザクションの追加。Transactionと自身宛のoutputが格納されている 60 | インデックスのタプルとして保存する 61 | """ 62 | print("put_utxo_tx was called") 63 | idx = 0 64 | for txout in tx['outputs']: 65 | if txout['recipient'] == self.my_address: 66 | self.utxo_txs.append((tx, idx)) 67 | else: 68 | idx += 1 69 | 70 | self._compute_my_balance() 71 | 72 | 73 | def get_utxo_tx(self, idx): 74 | """ 75 | idxで指定されたUTXOを返す 76 | """ 77 | return self.utxo_txs[idx] 78 | 79 | 80 | def remove_utxo_tx(self, tx): 81 | """ 82 | 使用済みトランザクションの削除 83 | """ 84 | self.utxo_txs.remove(tx) 85 | self._compute_my_balance() 86 | 87 | 88 | def _compute_my_balance(self): 89 | """ 90 | 利用可能額の合計値を計算する 91 | """ 92 | print("_compute_my_balance was called") 93 | balance = 0 94 | txs = self.utxo_txs 95 | for t in txs: 96 | for txout in t[0]['outputs']: 97 | print('txout:', txout) 98 | if txout['recipient'] == self.my_address: 99 | balance += txout['value'] 100 | 101 | self.my_balance = balance 102 | 103 | -------------------------------------------------------------------------------- /04/utxo_test.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from utxo_manager import UTXOManager 4 | from transactions import Transaction 5 | from transactions import TransactionInput 6 | from transactions import TransactionOutput 7 | from transactions import CoinbaseTransaction 8 | from key_manager import KeyManager 9 | 10 | 11 | def main(): 12 | 13 | k_m = KeyManager() 14 | um = UTXOManager(k_m.my_address()) 15 | 16 | i_k_m = KeyManager() 17 | u_k_m = KeyManager() 18 | 19 | t1 = CoinbaseTransaction(k_m.my_address()) 20 | t2 = CoinbaseTransaction(k_m.my_address()) 21 | t3 = CoinbaseTransaction(k_m.my_address()) 22 | 23 | t4 = Transaction( 24 | [TransactionInput(t1.to_dict(), 0)], 25 | [TransactionOutput(u_k_m.my_address(), 10.0), TransactionOutput(i_k_m.my_address(), 20.0)] 26 | ) 27 | 28 | 29 | transactions = [] 30 | transactions.append(t1.to_dict()) 31 | transactions.append(t2.to_dict()) 32 | transactions.append(t3.to_dict()) 33 | transactions.append(t4.to_dict()) 34 | 35 | um.extract_utxos(transactions) 36 | 37 | balance = um.my_balance 38 | 39 | print(balance) 40 | 41 | 42 | if __name__ == '__main__': 43 | main() -------------------------------------------------------------------------------- /05/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/05/blockchain/__init__.py -------------------------------------------------------------------------------- /05/blockchain/block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | import hashlib 4 | import binascii 5 | from datetime import datetime 6 | 7 | class Block: 8 | def __init__(self, transactions, previous_block_hash): 9 | """ 10 | Args: 11 | transaction: ブロック内にセットされるトランザクション 12 | previous_block_hash: 直前のブロックのハッシュ値 13 | """ 14 | snap_tr = json.dumps(transactions) # PoW計算中に値が変わるのでブロック計算開始時の値を退避させておく 15 | 16 | self.timestamp = time() 17 | self.transactions = json.loads(snap_tr) 18 | self.previous_block = previous_block_hash 19 | 20 | current = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 21 | print(current) 22 | 23 | json_block = json.dumps(self.to_dict(include_nonce=False) , sort_keys=True) 24 | print('json_block :', json_block) 25 | self.nonce = self.get_nonce_for_pow(json_block) 26 | 27 | current2 = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 28 | print(current2) 29 | 30 | def to_dict(self, include_nonce= True): 31 | d = { 32 | 'timestamp' : self.timestamp, 33 | 'transactions' : list(map(json.dumps, self.transactions)), 34 | 'previous_block': self.previous_block 35 | } 36 | 37 | if include_nonce: 38 | d['nonce'] = self.nonce 39 | return d 40 | 41 | def get_nonce_for_pow(self, message, difficulty=3): 42 | # difficultyの数字を増やせば増やすほど、末尾で揃えなければならない桁数が増える。 43 | # macbookくらいの端末で基準値は5 44 | i = 0 45 | suffix = '0' * difficulty 46 | while True: 47 | nonce = str(i) 48 | digest = binascii.hexlify(self._get_double_sha256((message + nonce).encode('utf-8'))).decode('ascii') 49 | if digest.endswith(suffix): 50 | return nonce 51 | i += 1 52 | 53 | def _get_double_sha256(self, message): 54 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 55 | 56 | 57 | class GenesisBlock(Block): 58 | """ 59 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 60 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 61 | """ 62 | def __init__(self): 63 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 64 | 65 | def to_dict(self, include_nonce=True): 66 | d = { 67 | 'transactions': self.transactions, 68 | 'genesis_block': True, 69 | } 70 | if include_nonce: 71 | d['nonce'] = self.nonce 72 | return d -------------------------------------------------------------------------------- /05/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .block import Block 3 | from .block import GenesisBlock 4 | 5 | class BlockBuilder: 6 | 7 | def __init__(self): 8 | print('Initializing BlockBuilder...') 9 | pass 10 | 11 | def generate_genesis_block(self): 12 | genesis_block = GenesisBlock() 13 | return genesis_block 14 | 15 | def generate_new_block(self, transaction, previous_block_hash): 16 | new_block = Block(transaction, previous_block_hash) 17 | return new_block 18 | 19 | 20 | -------------------------------------------------------------------------------- /05/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/05/core/__init__.py -------------------------------------------------------------------------------- /05/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import pickle 3 | 4 | from blockchain.blockchain_manager import BlockchainManager 5 | from blockchain.block_builder import BlockBuilder 6 | from p2p.my_protocol_message_store import MessageStore 7 | from p2p.connection_manager_4edge import ConnectionManager4Edge 8 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 9 | from p2p.message_manager import ( 10 | 11 | MSG_REQUEST_FULL_CHAIN, 12 | RSP_FULL_CHAIN, 13 | MSG_ENHANCED, 14 | ) 15 | 16 | 17 | STATE_INIT = 0 18 | STATE_ACTIVE = 1 19 | STATE_SHUTTING_DOWN = 2 20 | 21 | 22 | class ClientCore: 23 | 24 | def __init__(self, my_port=50082, core_host=None, core_port=None, callback=None): 25 | self.client_state = STATE_INIT 26 | print('Initializing ClientCore...') 27 | self.my_ip = self.__get_myip() 28 | print('Server IP address is set to ... ', self.my_ip) 29 | self.my_port = my_port 30 | self.my_core_host = core_host 31 | self.my_core_port = core_port 32 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, core_host, core_port, self.__handle_message) 33 | self.mpmh = MyProtocolMessageHandler() 34 | self.mpm_store = MessageStore() 35 | 36 | self.bb = BlockBuilder() 37 | my_genesis_block = self.bb.generate_genesis_block() 38 | self.bm = BlockchainManager(my_genesis_block.to_dict()) 39 | self.callback = callback 40 | 41 | def start(self): 42 | self.client_state = STATE_ACTIVE 43 | self.cm.start() 44 | self.cm.connect_to_core_node() 45 | 46 | def shutdown(self): 47 | self.client_state = STATE_SHUTTING_DOWN 48 | print('Shutdown edge node ...') 49 | self.cm.connection_close() 50 | 51 | def get_my_current_state(self): 52 | return self.client_state 53 | 54 | def send_message_to_my_core_node(self, msg_type, msg): 55 | msg_txt = self.cm.get_message_text(msg_type, msg) 56 | print(msg_txt) 57 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 58 | 59 | def send_req_full_chain_to_my_core_node(self): 60 | print('send_req_full_chain_to_my_core_node called') 61 | new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN) 62 | self.cm.send_msg((self.my_core_host, self.my_core_port), new_message) 63 | 64 | 65 | def __client_api(self, request, msg): 66 | 67 | if request == 'pass_message_to_client_application': 68 | print('Client Core API: pass_message_to_client_application') 69 | self.mpm_store.add(msg) 70 | elif request == 'api_type': 71 | return 'client_core_api' 72 | else: 73 | print('not implemented api was used') 74 | 75 | def get_my_protocol_messages(self): 76 | return self.my_protocol_message_store.get_list() 77 | 78 | def get_my_blockchain(self): 79 | return self.bm.get_my_blockchain() 80 | 81 | def get_stored_transactions_from_bc(self): 82 | return self.bm.get_stored_transactions_from_bc() 83 | 84 | 85 | def __handle_message(self, msg): 86 | if msg[2] == RSP_FULL_CHAIN: 87 | # ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証し、有効なものか検証した上で 88 | # 自分の持つチェインと比較し優位な方を今後のブロックチェーンとして利用する 89 | new_block_chain = pickle.loads(msg[4].encode('utf8')) 90 | result, _ = self.bm.resolve_conflicts(new_block_chain) 91 | print('blockchain received form central', result) 92 | if result is not None: 93 | self.prev_block_hash = result 94 | print('callback called') 95 | self.callback() 96 | else: 97 | print('Received blockchain is useless...') 98 | 99 | elif msg[2] == MSG_ENHANCED: 100 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。 101 | # SimpleBitcoin としてはこの種別は使わない 102 | self.mpmh.handle_message(msg[4], self.__client_api, True) 103 | 104 | def __get_myip(self): 105 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 106 | s.connect(('8.8.8.8', 80)) 107 | return s.getsockname()[0] 108 | -------------------------------------------------------------------------------- /05/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/05/p2p/__init__.py -------------------------------------------------------------------------------- /05/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | """ 6 | Peerのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, peer): 13 | """ 14 | Coreノードをリストに追加する。 15 | 16 | param: 17 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding peer: ', peer) 21 | self.list.add((peer)) 22 | print('Current Core Set: ', self.list) 23 | 24 | 25 | def remove(self, peer): 26 | """ 27 | 離脱したと判断されるCoreノードをリストから削除する。 28 | 29 | param: 30 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if peer in self.list: 34 | print('Removing peer: ', peer) 35 | self.list.remove(peer) 36 | print('Current Core list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('core node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Core list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるPeerの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | def get_c_node_info(self): 55 | c_list = [] 56 | for i in self.list: 57 | c_list.append(i) 58 | 59 | return c_list[0] 60 | 61 | def get_length(self): 62 | return len(self.list) 63 | 64 | 65 | def has_this_peer(self, peer): 66 | """ 67 | 与えられたpeerがリストに含まれているか?をチェックする 68 | 69 | param: 70 | peer : IPアドレスとポート番号のタプル 71 | return: 72 | True or False 73 | """ 74 | return peer in self.list 75 | 76 | -------------------------------------------------------------------------------- /05/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | """ 6 | Edgeノードのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, edge): 13 | """ 14 | Edgeノードをリストに追加する。 15 | 16 | param: 17 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding edge: ', edge) 21 | self.list.add((edge)) 22 | print('Current Edge List: ', self.list) 23 | 24 | 25 | def remove(self, edge): 26 | """ 27 | 離脱したと判断されるEdgeノードをリストから削除する。 28 | 29 | param: 30 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if edge in self.list: 34 | print('Removing edge: ', edge) 35 | self.list.remove(edge) 36 | print('Current Edge list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('edge node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Edge list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるEdgeノードの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | 55 | def has_this_edge(self, pubky_address): 56 | """ 57 | 指定の公開鍵を持ったEdgeノードがリストに存在しているかどうかを確認し結果を返却する 58 | 59 | param: 60 | pubky_address : 公開鍵) 61 | """ 62 | for e in self.list: 63 | print('edge: ', e[2]) 64 | print('pubkey: ', pubky_address) 65 | if e[2] == pubky_address: 66 | return True, e[0], e[1] 67 | 68 | return False, None, None -------------------------------------------------------------------------------- /05/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /05/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 4 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 5 | SEND_TO_THIS_ADDRESS = 'send_message_to_this_pubkey_address' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | class MyProtocolMessageHandler: 10 | """ 11 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 12 | """ 13 | 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api, is_core): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | 27 | is_core : 送信元ノードがCoreノードであるかどうか 28 | """ 29 | msg = json.loads(msg) 30 | my_api = api('api_type', None) 31 | print('my_api: ', my_api) 32 | if my_api == 'server_core_api': 33 | if msg['message_type'] == 'cipher_message': 34 | print('received cipher message!') 35 | target_address = msg['recipient'] 36 | result = api(SEND_TO_THIS_ADDRESS, (target_address, json.dumps(msg))) 37 | if result == None: 38 | if is_core is not True: 39 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 40 | else: 41 | print('Bloadcasting ...', json.dumps(msg)) 42 | if is_core is not True: 43 | # Coreノードからのメッセージでない時だけ他のCoreノードにもブロードキャストする 44 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 45 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 46 | else: 47 | print('MyProtocolMessageHandler received ', msg) 48 | api(PASS_TO_CLIENT_APP, msg) 49 | 50 | return 51 | -------------------------------------------------------------------------------- /05/p2p/my_protocol_message_store.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class MessageStore: 5 | """ 6 | 拡張メッセージのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = [] 11 | 12 | def add(self, msg): 13 | """ 14 | メッセージをリストに追加する。 15 | 16 | param: 17 | msg : 拡張メッセージとして届けられたもの 18 | """ 19 | print('Message store added', msg) 20 | with self.lock: 21 | self.list.append(msg) 22 | 23 | 24 | def remove(self, msg): 25 | """ 26 | メッセージをリストから削除する。今のところ使うか不明 27 | 28 | param: 29 | msg : 拡張メッセージとして届けられたもの) 30 | """ 31 | with self.lock: 32 | toBeRemoved = None 33 | for m in self.list: 34 | if msg == m: 35 | toBeRemoved = m 36 | break 37 | if not toBeRemoved: 38 | return 39 | self.list.remove(toBeRemoved) 40 | 41 | 42 | def overwrite(self, new_list): 43 | """ 44 | 一括での上書き処理をしたいような場合はこちら 45 | """ 46 | with self.lock: 47 | self.list = new_list 48 | 49 | 50 | def get_list(self): 51 | """ 52 | 現在保存されているメッセージの一覧を返却する 53 | """ 54 | if len(self.list) > 0: 55 | return self.list 56 | else: 57 | return None 58 | 59 | def get_length(self): 60 | return len(self.list) 61 | 62 | 63 | def has_this_msg(self, msg): 64 | for m in self.list: 65 | if m == msg: 66 | return True 67 | 68 | return False, 69 | -------------------------------------------------------------------------------- /05/sample_server1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | def main(my_port, p_phrase): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_server 20 | # 始原のCoreノードとして起動する 21 | my_p2p_server = ServerCore(my_port, None, None, p_phrase) 22 | my_p2p_server.start() 23 | 24 | 25 | if __name__ == '__main__': 26 | 27 | args = sys.argv 28 | 29 | if len(args) == 3: 30 | my_port = int(args[1]) 31 | p_phrase = args[2] 32 | else: 33 | print('Param Error') 34 | print('$ SmpleServer1.py ') 35 | quit() 36 | 37 | main(my_port, p_phrase) -------------------------------------------------------------------------------- /05/sample_server2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(my_port, c_host, c_port, p_phrase): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | my_p2p_server = ServerCore(my_port, c_host, c_port, p_phrase) 20 | my_p2p_server.start() 21 | my_p2p_server.join_network() 22 | 23 | if __name__ == '__main__': 24 | 25 | args = sys.argv 26 | 27 | if len(args) == 5: 28 | my_port = int(args[1]) 29 | c_host = args[2] 30 | c_port = int(args[3]) 31 | p_phrase = args[4] 32 | else: 33 | print('Param Error') 34 | print('$ SmpleServer2.py ') 35 | quit() 36 | 37 | main(my_port, c_host, c_port, p_phrase) -------------------------------------------------------------------------------- /05/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/05/transaction/__init__.py -------------------------------------------------------------------------------- /05/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class TransactionPool: 5 | 6 | def __init__(self): 7 | print('Initializing TransactionPool...') 8 | self.transactions = [] 9 | self.lock = threading.Lock() 10 | 11 | def set_new_transaction(self, transaction): 12 | with self.lock: 13 | print('set_new_transaction is called', transaction) 14 | self.transactions.append(transaction) 15 | 16 | def renew_my_transactions(self, transactions): 17 | with self.lock: 18 | print('transaction pool will be renewed to ...', transactions) 19 | self.transactions = transactions 20 | 21 | def clear_my_transactions(self, index): 22 | with self.lock: 23 | if index <= len(self.transactions): 24 | new_transactions = self.transactions 25 | del new_transactions[0:index] 26 | print('transaction is now refreshed ... ', new_transactions) 27 | self.transactions = new_transactions 28 | 29 | def get_stored_transactions(self): 30 | if len(self.transactions) > 0: 31 | return self.transactions 32 | else: 33 | print('Currently, it seems transaction pool is empty...') 34 | # エラー原因が増えてきたらエラーコードみたいなの考えよう、、、 35 | return [] 36 | 37 | def has_this_output_in_my_tp(self, transaction_output): 38 | """ 39 | TranactionPool内ですでにこのTransactionOutputがInputとして使われていないか?の確認 40 | """ 41 | print('has_this_output_in_my_tp is called') 42 | transactions = self.transactions 43 | for t in transactions: 44 | checked = self.check_type_of_transaction(t) 45 | if checked: 46 | inputs_t = t['inputs'] 47 | for it in inputs_t: 48 | if it == transaction_output: 49 | return True 50 | 51 | return False 52 | 53 | def get_total_fee_from_tp(self): 54 | """ 55 | TransactionPool内に格納されているTransaction全ての手数料の合計値を算出する 56 | """ 57 | print('get_total_fee_from_tp is called') 58 | transactions = self.transactions 59 | result = 0 60 | for t in transactions: 61 | checked = self.check_type_of_transaction(t) 62 | if checked: 63 | total_in = sum(i['transaction']['outputs'][i['output_index']]['value'] for i in t['inputs']) 64 | total_out = sum(o['value'] for o in t['outputs']) 65 | delta = total_in - total_out 66 | result += delta 67 | 68 | return result 69 | 70 | def check_type_of_transaction(self, transaction): 71 | if transaction['t_type'] == 'basic' or transaction['t_type'] == 'coinbase_transaction': 72 | return True 73 | else: 74 | return False 75 | 76 | -------------------------------------------------------------------------------- /05/transaction/transactions.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | class TransactionInput: 4 | """ 5 | トトランザクションの中でInputに格納するUTXOを指定する 6 | """ 7 | def __init__(self, transaction, output_index): 8 | self.transaction = transaction 9 | self.output_index = output_index 10 | 11 | def to_dict(self): 12 | d = { 13 | 'transaction': self.transaction, 14 | 'output_index': self.output_index 15 | } 16 | return d 17 | 18 | class TransactionOutput: 19 | """ 20 | トランザクションの中で Output (送金相手と送る金額)を管理する 21 | """ 22 | def __init__(self, recipient_address, value): 23 | self.recipient = recipient_address 24 | self.value = value 25 | 26 | def to_dict(self): 27 | d = { 28 | 'recipient': self.recipient, 29 | 'value': self.value 30 | } 31 | return d 32 | 33 | class Transaction: 34 | """ 35 | 持っていないコインを誰かに簡単に送金できてしまっては全く意味がないので、過去のトランザクションにて 36 | 自分を宛先として送金されたコインの総計を超える送金依頼を作ることができないよう、inputs と outputs 37 | のペアによって管理する 38 | 39 | Args: 40 | t_type : トランザクションのタイプ。今後の拡張で種別の切り分けに使う 41 | extra : 拡張用途で利用可能な文字列。例えば送金の際にその理由となった記事のURLを格納したい場合などに使う 42 | """ 43 | def __init__(self, inputs, outputs, extra=None): 44 | self.inputs = inputs 45 | self.outputs = outputs 46 | self.timestamp = time() 47 | self.t_type = 'basic' 48 | self.extra = extra 49 | 50 | 51 | def to_dict(self): 52 | d = { 53 | 'inputs': list(map(TransactionInput.to_dict, self.inputs)), 54 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 55 | 'timestamp': self.timestamp, 56 | 't_type': self.t_type, 57 | 'extra': self.extra 58 | } 59 | 60 | return d 61 | 62 | def is_enough_inputs(self, fee): 63 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 64 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 65 | delta = total_in - total_out 66 | if delta >= 0: 67 | return True 68 | else: 69 | return False 70 | 71 | def compute_change(self, fee): 72 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 73 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 74 | delta = total_in - total_out 75 | return delta 76 | 77 | 78 | class CoinbaseTransaction(Transaction): 79 | """ 80 | Coinbaseトランザクションは例外的にInputが存在しない。 81 | """ 82 | def __init__(self, recipient_address, value=30): 83 | self.inputs = [] 84 | self.outputs = [TransactionOutput(recipient_address, value)] 85 | self.timestamp = time() 86 | self.t_type = 'coinbase_transaction' 87 | 88 | def to_dict(self): 89 | d = { 90 | 'inputs': [], 91 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 92 | 'timestamp' : self.timestamp, 93 | 't_type': self.t_type 94 | } 95 | 96 | return d 97 | 98 | 99 | -------------------------------------------------------------------------------- /05/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/05/utils/__init__.py -------------------------------------------------------------------------------- /05/utils/key_manager.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Signature import PKCS1_v1_5 5 | from Crypto.Hash import SHA256 6 | 7 | import copy 8 | import binascii 9 | import json 10 | 11 | 12 | class KeyManager: 13 | 14 | def __init__(self, privatekey_text = None, pass_phrase= None): 15 | print('Initializing KeyManager...') 16 | if privatekey_text: 17 | self.import_key_pair(privatekey_text, pass_phrase) 18 | else: 19 | random_gen = Crypto.Random.new().read 20 | self._private_key = RSA.generate(2048, random_gen) 21 | self._public_key = self._private_key.publickey() 22 | self._signer = PKCS1_v1_5.new(self._private_key) 23 | if pass_phrase is not None: 24 | my_pem = self.export_key_pair(pass_phrase) 25 | my_pem_hex = binascii.hexlify(my_pem).decode('ascii') 26 | # とりあえずファイル名は固定 27 | path = 'my_server_key_pair.pem' 28 | f1 = open(path,'a') 29 | f1.write(my_pem_hex) 30 | f1.close() 31 | 32 | 33 | def my_address(self): 34 | """ 35 | UI表示用の公開鍵情報 36 | """ 37 | return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii') 38 | 39 | 40 | def compute_digital_signature(self, message): 41 | hashed_message = SHA256.new(message.encode('utf8')) 42 | signer = PKCS1_v1_5.new(self._private_key) 43 | return binascii.hexlify(signer.sign(hashed_message)).decode('ascii') 44 | 45 | 46 | def verify_my_signature(self, message, signature): 47 | print('verify_my_signature was called') 48 | hashed_message = SHA256.new(message.encode('utf8')) 49 | verifier = PKCS1_v1_5.new(self._public_key) 50 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 51 | print(result) 52 | return result 53 | 54 | def encrypt_with_my_pubkey(self, target): 55 | encrypto = self._public_key.encrypt(target, 0) 56 | return encrypto 57 | 58 | 59 | def decrypt_with_private_key(self, target): 60 | decrypto = self._private_key.decrypt(target) 61 | print('decrypto', decrypto) 62 | return decrypto 63 | 64 | 65 | def export_key_pair(self, pass_phrase): 66 | """ 67 | 鍵ペアをPEMフォーマットで書き出す(バックアップ用途) 68 | """ 69 | return self._private_key.exportKey(format='PEM', passphrase=pass_phrase) 70 | 71 | 72 | def import_key_pair(self, key_data, pass_phrase): 73 | """ 74 | PEMフォーマットでパスワード保護された鍵ペアをファイルから読み込んで設定する 75 | """ 76 | self._private_key = RSA.importKey(key_data, pass_phrase) 77 | self._public_key = self._private_key.publickey() 78 | self._signer = PKCS1_v1_5.new(self._private_key) 79 | -------------------------------------------------------------------------------- /05/utils/rsa_util.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | from Crypto.PublicKey import RSA 3 | from Crypto.Signature import PKCS1_v1_5 4 | from Crypto.Hash import SHA256 5 | 6 | import copy 7 | import binascii 8 | import json 9 | 10 | class RSAUtil: 11 | 12 | def __init__(self): 13 | pass 14 | 15 | def verify_signature(self, message, signature, sender_public_key): 16 | print('verify_signature was called') 17 | hashed_message = SHA256.new(message.encode('utf8')) 18 | verifier = PKCS1_v1_5.new(sender_public_key) 19 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 20 | print(result) 21 | return result 22 | 23 | def encrypt_with_pubkey(self, target, pubkey_text): 24 | """ 25 | 与えられた公開鍵で暗号化する 26 | """ 27 | pubkey = RSA.importKey(binascii.unhexlify(pubkey_text)) 28 | encrypto = pubkey.encrypt(target, 0) 29 | return encrypto 30 | 31 | def verify_sbc_transaction_sig(self, transaction): 32 | """ 33 | simple_bitcoin のTransactionの署名の正当性を検証する 34 | """ 35 | print('verify_sbc_transaction_sig was called') 36 | sender_pubkey_text, used_outputs = self._get_pubkey_from_sbc_transaction(transaction) 37 | signature = transaction['signature'] 38 | c_transaction = copy.deepcopy(transaction) 39 | del c_transaction['signature'] 40 | target_txt = json.dumps(c_transaction, sort_keys=True) 41 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 42 | result = self.verify_signature(target_txt, signature, sender_pubkey) 43 | return result, used_outputs 44 | 45 | def _get_pubkey_from_sbc_transaction(self, transaction): 46 | print('_get_pubkey_from_sbc_transaction was called') 47 | input_t_list = transaction['inputs'] 48 | used_outputs = [] 49 | sender_pubkey = '' 50 | for i in input_t_list: 51 | idx = i['output_index'] 52 | tx = i['transaction']['outputs'][idx] 53 | used_outputs.append(tx) 54 | sender_pubkey = tx['recipient'] 55 | 56 | return sender_pubkey, used_outputs 57 | 58 | def verify_general_transaction_sig(self, transaction): 59 | """ 60 | simple_bitcoin 以外のTransactionも署名の形式を統一することで検証を可能にしておく 61 | """ 62 | print('verify_general_transaction_sig was called') 63 | sender_pubkey_text = transaction['sender'] 64 | signature = transaction['signature'] 65 | c_transaction = copy.deepcopy(transaction) 66 | del c_transaction['signature'] 67 | target_txt = json.dumps(c_transaction, sort_keys=True) 68 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 69 | result = self.verify_signature(target_txt, signature, sender_pubkey) 70 | return result 71 | -------------------------------------------------------------------------------- /06/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/06/blockchain/__init__.py -------------------------------------------------------------------------------- /06/blockchain/block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | import hashlib 4 | import binascii 5 | from datetime import datetime 6 | 7 | class Block: 8 | def __init__(self, transactions, previous_block_hash): 9 | """ 10 | Args: 11 | transaction: ブロック内にセットされるトランザクション 12 | previous_block_hash: 直前のブロックのハッシュ値 13 | """ 14 | snap_tr = json.dumps(transactions) # PoW計算中に値が変わるのでブロック計算開始時の値を退避させておく 15 | 16 | self.timestamp = time() 17 | self.transactions = json.loads(snap_tr) 18 | self.previous_block = previous_block_hash 19 | 20 | current = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 21 | print(current) 22 | 23 | json_block = json.dumps(self.to_dict(include_nonce=False) , sort_keys=True) 24 | print('json_block :', json_block) 25 | self.nonce = self.get_nonce_for_pow(json_block) 26 | 27 | current2 = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 28 | print(current2) 29 | 30 | def to_dict(self, include_nonce= True): 31 | d = { 32 | 'timestamp' : self.timestamp, 33 | 'transactions' : list(map(json.dumps, self.transactions)), 34 | 'previous_block': self.previous_block 35 | } 36 | 37 | if include_nonce: 38 | d['nonce'] = self.nonce 39 | return d 40 | 41 | def get_nonce_for_pow(self, message, difficulty=3): 42 | # difficultyの数字を増やせば増やすほど、末尾で揃えなければならない桁数が増える。 43 | # macbookくらいの端末で基準値は5 44 | i = 0 45 | suffix = '0' * difficulty 46 | while True: 47 | nonce = str(i) 48 | digest = binascii.hexlify(self._get_double_sha256((message + nonce).encode('utf-8'))).decode('ascii') 49 | if digest.endswith(suffix): 50 | return nonce 51 | i += 1 52 | 53 | def _get_double_sha256(self, message): 54 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 55 | 56 | 57 | class GenesisBlock(Block): 58 | """ 59 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 60 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 61 | """ 62 | def __init__(self): 63 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 64 | 65 | def to_dict(self, include_nonce=True): 66 | d = { 67 | 'transactions': self.transactions, 68 | 'genesis_block': True, 69 | } 70 | if include_nonce: 71 | d['nonce'] = self.nonce 72 | return d -------------------------------------------------------------------------------- /06/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .block import Block 3 | from .block import GenesisBlock 4 | 5 | class BlockBuilder: 6 | 7 | def __init__(self): 8 | print('Initializing BlockBuilder...') 9 | pass 10 | 11 | def generate_genesis_block(self): 12 | genesis_block = GenesisBlock() 13 | return genesis_block 14 | 15 | def generate_new_block(self, transaction, previous_block_hash): 16 | new_block = Block(transaction, previous_block_hash) 17 | return new_block 18 | 19 | 20 | -------------------------------------------------------------------------------- /06/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/06/core/__init__.py -------------------------------------------------------------------------------- /06/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import pickle 3 | 4 | from blockchain.blockchain_manager import BlockchainManager 5 | from blockchain.block_builder import BlockBuilder 6 | from p2p.my_protocol_message_store import MessageStore 7 | from p2p.connection_manager_4edge import ConnectionManager4Edge 8 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 9 | from p2p.message_manager import ( 10 | 11 | MSG_REQUEST_FULL_CHAIN, 12 | RSP_FULL_CHAIN, 13 | MSG_ENHANCED, 14 | ) 15 | 16 | 17 | STATE_INIT = 0 18 | STATE_ACTIVE = 1 19 | STATE_SHUTTING_DOWN = 2 20 | 21 | 22 | class ClientCore: 23 | 24 | def __init__(self, my_port=50082, core_host=None, core_port=None, callback=None): 25 | self.client_state = STATE_INIT 26 | print('Initializing ClientCore...') 27 | self.my_ip = self.__get_myip() 28 | print('Server IP address is set to ... ', self.my_ip) 29 | self.my_port = my_port 30 | self.my_core_host = core_host 31 | self.my_core_port = core_port 32 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, core_host, core_port, self.__handle_message) 33 | self.mpmh = MyProtocolMessageHandler() 34 | self.mpm_store = MessageStore() 35 | 36 | self.bb = BlockBuilder() 37 | my_genesis_block = self.bb.generate_genesis_block() 38 | self.bm = BlockchainManager(my_genesis_block.to_dict()) 39 | self.callback = callback 40 | 41 | def start(self): 42 | self.client_state = STATE_ACTIVE 43 | self.cm.start() 44 | self.cm.connect_to_core_node() 45 | 46 | def shutdown(self): 47 | self.client_state = STATE_SHUTTING_DOWN 48 | print('Shutdown edge node ...') 49 | self.cm.connection_close() 50 | 51 | def get_my_current_state(self): 52 | return self.client_state 53 | 54 | def send_message_to_my_core_node(self, msg_type, msg): 55 | msg_txt = self.cm.get_message_text(msg_type, msg) 56 | print(msg_txt) 57 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 58 | 59 | def send_req_full_chain_to_my_core_node(self): 60 | print('send_req_full_chain_to_my_core_node called') 61 | new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN) 62 | self.cm.send_msg((self.my_core_host, self.my_core_port), new_message) 63 | 64 | 65 | def __client_api(self, request, msg): 66 | 67 | if request == 'pass_message_to_client_application': 68 | print('Client Core API: pass_message_to_client_application') 69 | self.mpm_store.add(msg) 70 | elif request == 'api_type': 71 | return 'client_core_api' 72 | else: 73 | print('not implemented api was used') 74 | 75 | def get_my_protocol_messages(self): 76 | return self.my_protocol_message_store.get_list() 77 | 78 | def get_my_blockchain(self): 79 | return self.bm.get_my_blockchain() 80 | 81 | def get_stored_transactions_from_bc(self): 82 | return self.bm.get_stored_transactions_from_bc() 83 | 84 | 85 | def __handle_message(self, msg): 86 | if msg[2] == RSP_FULL_CHAIN: 87 | # ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証し、有効なものか検証した上で 88 | # 自分の持つチェインと比較し優位な方を今後のブロックチェーンとして利用する 89 | new_block_chain = pickle.loads(msg[4].encode('utf8')) 90 | result, _ = self.bm.resolve_conflicts(new_block_chain) 91 | print('blockchain received form central', result) 92 | if result is not None: 93 | self.prev_block_hash = result 94 | print('callback called') 95 | self.callback() 96 | else: 97 | print('Received blockchain is useless...') 98 | 99 | elif msg[2] == MSG_ENHANCED: 100 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。 101 | # SimpleBitcoin としてはこの種別は使わない 102 | self.mpmh.handle_message(msg[4], self.__client_api, True) 103 | 104 | def __get_myip(self): 105 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 106 | s.connect(('8.8.8.8', 80)) 107 | return s.getsockname()[0] 108 | -------------------------------------------------------------------------------- /06/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/06/p2p/__init__.py -------------------------------------------------------------------------------- /06/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | """ 6 | Peerのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, peer): 13 | """ 14 | Coreノードをリストに追加する。 15 | 16 | param: 17 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding peer: ', peer) 21 | self.list.add((peer)) 22 | print('Current Core Set: ', self.list) 23 | 24 | 25 | def remove(self, peer): 26 | """ 27 | 離脱したと判断されるCoreノードをリストから削除する。 28 | 29 | param: 30 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if peer in self.list: 34 | print('Removing peer: ', peer) 35 | self.list.remove(peer) 36 | print('Current Core list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('core node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Core list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるPeerの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | def get_c_node_info(self): 55 | c_list = [] 56 | for i in self.list: 57 | c_list.append(i) 58 | 59 | return c_list[0] 60 | 61 | def get_length(self): 62 | return len(self.list) 63 | 64 | 65 | def has_this_peer(self, peer): 66 | """ 67 | 与えられたpeerがリストに含まれているか?をチェックする 68 | 69 | param: 70 | peer : IPアドレスとポート番号のタプル 71 | return: 72 | True or False 73 | """ 74 | return peer in self.list 75 | 76 | -------------------------------------------------------------------------------- /06/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | """ 6 | Edgeノードのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, edge): 13 | """ 14 | Edgeノードをリストに追加する。 15 | 16 | param: 17 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding edge: ', edge) 21 | self.list.add((edge)) 22 | print('Current Edge List: ', self.list) 23 | 24 | 25 | def remove(self, edge): 26 | """ 27 | 離脱したと判断されるEdgeノードをリストから削除する。 28 | 29 | param: 30 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if edge in self.list: 34 | print('Removing edge: ', edge) 35 | self.list.remove(edge) 36 | print('Current Edge list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('edge node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Edge list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるEdgeノードの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | 55 | def has_this_edge(self, pubky_address): 56 | """ 57 | 指定の公開鍵を持ったEdgeノードがリストに存在しているかどうかを確認し結果を返却する 58 | 59 | param: 60 | pubky_address : 公開鍵) 61 | """ 62 | for e in self.list: 63 | print('edge: ', e[2]) 64 | print('pubkey: ', pubky_address) 65 | if e[2] == pubky_address: 66 | return True, e[0], e[1] 67 | 68 | return False, None, None -------------------------------------------------------------------------------- /06/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /06/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 4 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 5 | SEND_TO_THIS_ADDRESS = 'send_message_to_this_pubkey_address' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | class MyProtocolMessageHandler: 10 | """ 11 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 12 | """ 13 | 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api, is_core): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | 27 | is_core : 送信元ノードがCoreノードであるかどうか 28 | """ 29 | msg = json.loads(msg) 30 | my_api = api('api_type', None) 31 | print('my_api: ', my_api) 32 | if my_api == 'server_core_api': 33 | if msg['message_type'] == 'cipher_message': 34 | print('received cipher message!') 35 | target_address = msg['recipient'] 36 | result = api(SEND_TO_THIS_ADDRESS, (target_address, json.dumps(msg))) 37 | if result == None: 38 | if is_core is not True: 39 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 40 | else: 41 | print('Bloadcasting ...', json.dumps(msg)) 42 | if is_core is not True: 43 | # Coreノードからのメッセージでない時だけ他のCoreノードにもブロードキャストする 44 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 45 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 46 | else: 47 | print('MyProtocolMessageHandler received ', msg) 48 | api(PASS_TO_CLIENT_APP, msg) 49 | 50 | return 51 | -------------------------------------------------------------------------------- /06/p2p/my_protocol_message_store.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class MessageStore: 5 | """ 6 | 拡張メッセージのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = [] 11 | 12 | def add(self, msg): 13 | """ 14 | メッセージをリストに追加する。 15 | 16 | param: 17 | msg : 拡張メッセージとして届けられたもの 18 | """ 19 | print('Message store added', msg) 20 | with self.lock: 21 | self.list.append(msg) 22 | 23 | 24 | def remove(self, msg): 25 | """ 26 | メッセージをリストから削除する。今のところ使うか不明 27 | 28 | param: 29 | msg : 拡張メッセージとして届けられたもの) 30 | """ 31 | with self.lock: 32 | toBeRemoved = None 33 | for m in self.list: 34 | if msg == m: 35 | toBeRemoved = m 36 | break 37 | if not toBeRemoved: 38 | return 39 | self.list.remove(toBeRemoved) 40 | 41 | 42 | def overwrite(self, new_list): 43 | """ 44 | 一括での上書き処理をしたいような場合はこちら 45 | """ 46 | with self.lock: 47 | self.list = new_list 48 | 49 | 50 | def get_list(self): 51 | """ 52 | 現在保存されているメッセージの一覧を返却する 53 | """ 54 | if len(self.list) > 0: 55 | return self.list 56 | else: 57 | return None 58 | 59 | def get_length(self): 60 | return len(self.list) 61 | 62 | 63 | def has_this_msg(self, msg): 64 | for m in self.list: 65 | if m == msg: 66 | return True 67 | 68 | return False, 69 | -------------------------------------------------------------------------------- /06/sample_server1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | def main(my_port, p_phrase): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_server 20 | # 始原のCoreノードとして起動する 21 | my_p2p_server = ServerCore(my_port, None, None, p_phrase) 22 | my_p2p_server.start() 23 | 24 | 25 | if __name__ == '__main__': 26 | 27 | args = sys.argv 28 | 29 | if len(args) == 3: 30 | my_port = int(args[1]) 31 | p_phrase = args[2] 32 | else: 33 | print('Param Error') 34 | print('$ SmpleServer1.py ') 35 | quit() 36 | 37 | main(my_port, p_phrase) -------------------------------------------------------------------------------- /06/sample_server2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(my_port, c_host, c_port, p_phrase): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | my_p2p_server = ServerCore(my_port, c_host, c_port, p_phrase) 20 | my_p2p_server.start() 21 | my_p2p_server.join_network() 22 | 23 | if __name__ == '__main__': 24 | 25 | args = sys.argv 26 | 27 | if len(args) == 5: 28 | my_port = int(args[1]) 29 | c_host = args[2] 30 | c_port = int(args[3]) 31 | p_phrase = args[4] 32 | else: 33 | print('Param Error') 34 | print('$ SmpleServer2.py ') 35 | quit() 36 | 37 | main(my_port, c_host, c_port, p_phrase) -------------------------------------------------------------------------------- /06/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/06/transaction/__init__.py -------------------------------------------------------------------------------- /06/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class TransactionPool: 5 | 6 | def __init__(self): 7 | print('Initializing TransactionPool...') 8 | self.transactions = [] 9 | self.lock = threading.Lock() 10 | 11 | def set_new_transaction(self, transaction): 12 | with self.lock: 13 | print('set_new_transaction is called', transaction) 14 | self.transactions.append(transaction) 15 | 16 | def renew_my_transactions(self, transactions): 17 | with self.lock: 18 | print('transaction pool will be renewed to ...', transactions) 19 | self.transactions = transactions 20 | 21 | def clear_my_transactions(self, index): 22 | with self.lock: 23 | if index <= len(self.transactions): 24 | new_transactions = self.transactions 25 | del new_transactions[0:index] 26 | print('transaction is now refreshed ... ', new_transactions) 27 | self.transactions = new_transactions 28 | 29 | def get_stored_transactions(self): 30 | if len(self.transactions) > 0: 31 | return self.transactions 32 | else: 33 | print('Currently, it seems transaction pool is empty...') 34 | # エラー原因が増えてきたらエラーコードみたいなの考えよう、、、 35 | return [] 36 | 37 | def has_this_output_in_my_tp(self, transaction_output): 38 | """ 39 | TranactionPool内ですでにこのTransactionOutputがInputとして使われていないか?の確認 40 | """ 41 | print('has_this_output_in_my_tp is called') 42 | transactions = self.transactions 43 | for t in transactions: 44 | checked = self.check_type_of_transaction(t) 45 | if checked: 46 | inputs_t = t['inputs'] 47 | for it in inputs_t: 48 | if it == transaction_output: 49 | return True 50 | 51 | return False 52 | 53 | def get_total_fee_from_tp(self): 54 | """ 55 | TransactionPool内に格納されているTransaction全ての手数料の合計値を算出する 56 | """ 57 | print('get_total_fee_from_tp is called') 58 | transactions = self.transactions 59 | result = 0 60 | for t in transactions: 61 | checked = self.check_type_of_transaction(t) 62 | if checked: 63 | total_in = sum(i['transaction']['outputs'][i['output_index']]['value'] for i in t['inputs']) 64 | total_out = sum(o['value'] for o in t['outputs']) 65 | delta = total_in - total_out 66 | result += delta 67 | 68 | return result 69 | 70 | def check_type_of_transaction(self, transaction): 71 | if transaction['t_type'] == 'basic' or transaction['t_type'] == 'coinbase_transaction': 72 | return True 73 | else: 74 | return False 75 | 76 | -------------------------------------------------------------------------------- /06/transaction/transactions.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | class TransactionInput: 4 | """ 5 | トトランザクションの中でInputに格納するUTXOを指定する 6 | """ 7 | def __init__(self, transaction, output_index): 8 | self.transaction = transaction 9 | self.output_index = output_index 10 | 11 | def to_dict(self): 12 | d = { 13 | 'transaction': self.transaction, 14 | 'output_index': self.output_index 15 | } 16 | return d 17 | 18 | class TransactionOutput: 19 | """ 20 | トランザクションの中で Output (送金相手と送る金額)を管理する 21 | """ 22 | def __init__(self, recipient_address, value): 23 | self.recipient = recipient_address 24 | self.value = value 25 | 26 | def to_dict(self): 27 | d = { 28 | 'recipient': self.recipient, 29 | 'value': self.value 30 | } 31 | return d 32 | 33 | class Transaction: 34 | """ 35 | 持っていないコインを誰かに簡単に送金できてしまっては全く意味がないので、過去のトランザクションにて 36 | 自分を宛先として送金されたコインの総計を超える送金依頼を作ることができないよう、inputs と outputs 37 | のペアによって管理する 38 | 39 | Args: 40 | t_type : トランザクションのタイプ。今後の拡張で種別の切り分けに使う 41 | extra : 拡張用途で利用可能な文字列。例えば送金の際にその理由となった記事のURLを格納したい場合などに使う 42 | """ 43 | def __init__(self, inputs, outputs, extra=None): 44 | self.inputs = inputs 45 | self.outputs = outputs 46 | self.timestamp = time() 47 | self.t_type = 'basic' 48 | self.extra = extra 49 | 50 | 51 | def to_dict(self): 52 | d = { 53 | 'inputs': list(map(TransactionInput.to_dict, self.inputs)), 54 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 55 | 'timestamp': self.timestamp, 56 | 't_type': self.t_type, 57 | 'extra': self.extra 58 | } 59 | 60 | return d 61 | 62 | def is_enough_inputs(self, fee): 63 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 64 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 65 | delta = total_in - total_out 66 | if delta >= 0: 67 | return True 68 | else: 69 | return False 70 | 71 | def compute_change(self, fee): 72 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 73 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 74 | delta = total_in - total_out 75 | return delta 76 | 77 | 78 | class CoinbaseTransaction(Transaction): 79 | """ 80 | Coinbaseトランザクションは例外的にInputが存在しない。 81 | """ 82 | def __init__(self, recipient_address, value=30): 83 | self.inputs = [] 84 | self.outputs = [TransactionOutput(recipient_address, value)] 85 | self.timestamp = time() 86 | self.t_type = 'coinbase_transaction' 87 | 88 | def to_dict(self): 89 | d = { 90 | 'inputs': [], 91 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 92 | 'timestamp' : self.timestamp, 93 | 't_type': self.t_type 94 | } 95 | 96 | return d 97 | 98 | 99 | -------------------------------------------------------------------------------- /06/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/06/utils/__init__.py -------------------------------------------------------------------------------- /06/utils/key_manager.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Signature import PKCS1_v1_5 5 | from Crypto.Hash import SHA256 6 | 7 | import copy 8 | import binascii 9 | import json 10 | 11 | 12 | class KeyManager: 13 | 14 | def __init__(self, privatekey_text = None, pass_phrase= None): 15 | print('Initializing KeyManager...') 16 | if privatekey_text: 17 | self.import_key_pair(privatekey_text, pass_phrase) 18 | else: 19 | random_gen = Crypto.Random.new().read 20 | self._private_key = RSA.generate(2048, random_gen) 21 | self._public_key = self._private_key.publickey() 22 | self._signer = PKCS1_v1_5.new(self._private_key) 23 | if pass_phrase is not None: 24 | my_pem = self.export_key_pair(pass_phrase) 25 | my_pem_hex = binascii.hexlify(my_pem).decode('ascii') 26 | # とりあえずファイル名は固定 27 | path = 'my_server_key_pair.pem' 28 | f1 = open(path,'a') 29 | f1.write(my_pem_hex) 30 | f1.close() 31 | 32 | 33 | def my_address(self): 34 | """ 35 | UI表示用の公開鍵情報 36 | """ 37 | return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii') 38 | 39 | 40 | def compute_digital_signature(self, message): 41 | hashed_message = SHA256.new(message.encode('utf8')) 42 | signer = PKCS1_v1_5.new(self._private_key) 43 | return binascii.hexlify(signer.sign(hashed_message)).decode('ascii') 44 | 45 | 46 | def verify_my_signature(self, message, signature): 47 | print('verify_my_signature was called') 48 | hashed_message = SHA256.new(message.encode('utf8')) 49 | verifier = PKCS1_v1_5.new(self._public_key) 50 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 51 | print(result) 52 | return result 53 | 54 | def encrypt_with_my_pubkey(self, target): 55 | encrypto = self._public_key.encrypt(target, 0) 56 | return encrypto 57 | 58 | 59 | def decrypt_with_private_key(self, target): 60 | decrypto = self._private_key.decrypt(target) 61 | print('decrypto', decrypto) 62 | return decrypto 63 | 64 | 65 | def export_key_pair(self, pass_phrase): 66 | """ 67 | 鍵ペアをPEMフォーマットで書き出す(バックアップ用途) 68 | """ 69 | return self._private_key.exportKey(format='PEM', passphrase=pass_phrase) 70 | 71 | 72 | def import_key_pair(self, key_data, pass_phrase): 73 | """ 74 | PEMフォーマットでパスワード保護された鍵ペアをファイルから読み込んで設定する 75 | """ 76 | self._private_key = RSA.importKey(key_data, pass_phrase) 77 | self._public_key = self._private_key.publickey() 78 | self._signer = PKCS1_v1_5.new(self._private_key) 79 | -------------------------------------------------------------------------------- /06/utils/rsa_util.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | from Crypto.PublicKey import RSA 3 | from Crypto.Signature import PKCS1_v1_5 4 | from Crypto.Hash import SHA256 5 | 6 | import copy 7 | import binascii 8 | import json 9 | 10 | class RSAUtil: 11 | 12 | def __init__(self): 13 | pass 14 | 15 | def verify_signature(self, message, signature, sender_public_key): 16 | print('verify_signature was called') 17 | hashed_message = SHA256.new(message.encode('utf8')) 18 | verifier = PKCS1_v1_5.new(sender_public_key) 19 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 20 | print(result) 21 | return result 22 | 23 | def encrypt_with_pubkey(self, target, pubkey_text): 24 | """ 25 | 与えられた公開鍵で暗号化する 26 | """ 27 | pubkey = RSA.importKey(binascii.unhexlify(pubkey_text)) 28 | encrypto = pubkey.encrypt(target, 0) 29 | return encrypto 30 | 31 | def verify_sbc_transaction_sig(self, transaction): 32 | """ 33 | simple_bitcoin のTransactionの署名の正当性を検証する 34 | """ 35 | print('verify_sbc_transaction_sig was called') 36 | sender_pubkey_text, used_outputs = self._get_pubkey_from_sbc_transaction(transaction) 37 | signature = transaction['signature'] 38 | c_transaction = copy.deepcopy(transaction) 39 | del c_transaction['signature'] 40 | target_txt = json.dumps(c_transaction, sort_keys=True) 41 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 42 | result = self.verify_signature(target_txt, signature, sender_pubkey) 43 | return result, used_outputs 44 | 45 | def _get_pubkey_from_sbc_transaction(self, transaction): 46 | print('_get_pubkey_from_sbc_transaction was called') 47 | input_t_list = transaction['inputs'] 48 | used_outputs = [] 49 | sender_pubkey = '' 50 | for i in input_t_list: 51 | idx = i['output_index'] 52 | tx = i['transaction']['outputs'][idx] 53 | used_outputs.append(tx) 54 | sender_pubkey = tx['recipient'] 55 | 56 | return sender_pubkey, used_outputs 57 | 58 | def verify_general_transaction_sig(self, transaction): 59 | """ 60 | simple_bitcoin 以外のTransactionも署名の形式を統一することで検証を可能にしておく 61 | """ 62 | print('verify_general_transaction_sig was called') 63 | sender_pubkey_text = transaction['sender'] 64 | signature = transaction['signature'] 65 | c_transaction = copy.deepcopy(transaction) 66 | del c_transaction['signature'] 67 | target_txt = json.dumps(c_transaction, sort_keys=True) 68 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 69 | result = self.verify_signature(target_txt, signature, sender_pubkey) 70 | return result 71 | -------------------------------------------------------------------------------- /07/aes_test.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import base64 3 | 4 | from utils.aes_util import AESUtil 5 | from utils.key_manager import KeyManager 6 | 7 | def main(): 8 | 9 | my_km = KeyManager() 10 | target_txt = '猫が寝込んだ' 11 | print('target text: ', target_txt) 12 | 13 | # 暗号化用の鍵を生成してメッセージを暗号化 14 | aes_util = AESUtil() 15 | cipher_txt = aes_util.encrypt(target_txt) 16 | print('cipher_txt : ', base64.b64encode(cipher_txt)) 17 | 18 | # 暗号化用の鍵を取り出す 19 | key = aes_util.get_aes_key() 20 | print('aes_key : ', binascii.hexlify(key).decode('ascii')) 21 | 22 | # まずは鍵を公開鍵で暗号化 23 | encrypted_key = my_km.encrypt_with_my_pubkey(key) 24 | print('encrypted_key : ', binascii.hexlify(encrypted_key[0]).decode('ascii')) 25 | 26 | # メッセージデータのJSONに入れ込めるようBase64エンコードした上で文字列化 27 | key_to_be_send = binascii.hexlify(base64.b64encode(encrypted_key[0])).decode('ascii') 28 | print('to_be_send', key_to_be_send) 29 | 30 | # Base64エンコードされたデータをまずデコードする 31 | un_hex = base64.b64decode(binascii.unhexlify(key_to_be_send)) 32 | print('un_hex', un_hex) 33 | 34 | # 鍵の取り出し 35 | decrypted_key2 = my_km.decrypt_with_private_key(un_hex) 36 | print('decrypted_key : ', binascii.hexlify(decrypted_key2).decode('ascii')) 37 | 38 | # 暗号メッセージの復号 39 | dec2 = aes_util.decrypt_with_key(cipher_txt, key) 40 | print('decoded text :', dec2.decode('utf-8')) 41 | 42 | if __name__ == '__main__': 43 | 44 | main() -------------------------------------------------------------------------------- /07/blockchain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/07/blockchain/__init__.py -------------------------------------------------------------------------------- /07/blockchain/block.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | import hashlib 4 | import binascii 5 | from datetime import datetime 6 | import copy 7 | 8 | class Block: 9 | def __init__(self, transactions, previous_block_hash): 10 | """ 11 | Args: 12 | transaction: ブロック内にセットされるトランザクション 13 | previous_block_hash: 直前のブロックのハッシュ値 14 | """ 15 | snap_tr = json.dumps(transactions) # PoW計算中に値が変わるのでブロック計算開始時の値を退避させておく 16 | 17 | self.timestamp = time() 18 | self.transactions = json.loads(snap_tr) 19 | self.previous_block = previous_block_hash 20 | 21 | current = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 22 | print(current) 23 | 24 | json_block = json.dumps(self.to_dict(include_nonce=False) , sort_keys=True) 25 | print('json_block :', json_block) 26 | self.nonce = self._compute_nonce_for_pow(json_block) 27 | 28 | current2 = datetime.now().strftime("%Y/%m/%d %H:%M:%S") 29 | print(current2) 30 | 31 | def to_dict(self, include_nonce= True): 32 | d = { 33 | 'timestamp' : self.timestamp, 34 | 'transactions' : list(map(json.dumps, self.transactions)), 35 | 'previous_block': self.previous_block 36 | } 37 | 38 | if include_nonce: 39 | d['nonce'] = self.nonce 40 | return d 41 | 42 | def _compute_nonce_for_pow(self, message, difficulty=3): 43 | # difficultyの数字を増やせば増やすほど、末尾で揃えなければならない桁数が増える。 44 | # macbookくらいの端末で基準値は5 45 | i = 0 46 | suffix = '0' * difficulty 47 | while True: 48 | nonce = str(i) 49 | digest = binascii.hexlify(self._get_double_sha256((message + nonce).encode('utf-8'))).decode('ascii') 50 | if digest.endswith(suffix): 51 | return nonce 52 | i += 1 53 | 54 | def _get_double_sha256(self, message): 55 | return hashlib.sha256(hashlib.sha256(message).digest()).digest() 56 | 57 | 58 | class GenesisBlock(Block): 59 | """ 60 | 前方にブロックを持たないブロックチェーンの始原となるブロック。 61 | transaction にセットしているのは「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256でハッシュしたもの。深い意味はない 62 | """ 63 | def __init__(self): 64 | super().__init__(transactions='AD9B477B42B22CDF18B1335603D07378ACE83561D8398FBFC8DE94196C65D806', previous_block_hash=None) 65 | 66 | def to_dict(self, include_nonce=True): 67 | d = { 68 | 'transactions': self.transactions, 69 | 'genesis_block': True, 70 | } 71 | if include_nonce: 72 | d['nonce'] = self.nonce 73 | return d -------------------------------------------------------------------------------- /07/blockchain/block_builder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .block import Block 3 | from .block import GenesisBlock 4 | 5 | class BlockBuilder: 6 | 7 | def __init__(self): 8 | print('Initializing BlockBuilder...') 9 | pass 10 | 11 | def generate_genesis_block(self): 12 | genesis_block = GenesisBlock() 13 | return genesis_block 14 | 15 | def generate_new_block(self, transaction, previous_block_hash): 16 | new_block = Block(transaction, previous_block_hash) 17 | return new_block 18 | 19 | 20 | -------------------------------------------------------------------------------- /07/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/07/core/__init__.py -------------------------------------------------------------------------------- /07/core/client_core.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import pickle 3 | 4 | from blockchain.blockchain_manager import BlockchainManager 5 | from blockchain.block_builder import BlockBuilder 6 | from p2p.my_protocol_message_store import MessageStore 7 | from p2p.connection_manager_4edge import ConnectionManager4Edge 8 | from p2p.my_protocol_message_handler import MyProtocolMessageHandler 9 | from p2p.message_manager import ( 10 | 11 | MSG_REQUEST_FULL_CHAIN, 12 | RSP_FULL_CHAIN, 13 | MSG_ENHANCED, 14 | ) 15 | 16 | 17 | STATE_INIT = 0 18 | STATE_ACTIVE = 1 19 | STATE_SHUTTING_DOWN = 2 20 | 21 | 22 | class ClientCore: 23 | 24 | def __init__(self, my_port=50082, core_host=None, core_port=None, callback=None, mpmh_callback=None): 25 | self.client_state = STATE_INIT 26 | print('Initializing ClientCore...') 27 | self.my_ip = self.__get_myip() 28 | print('Server IP address is set to ... ', self.my_ip) 29 | self.my_port = my_port 30 | self.my_core_host = core_host 31 | self.my_core_port = core_port 32 | self.cm = ConnectionManager4Edge(self.my_ip, self.my_port, core_host, core_port, self.__handle_message) 33 | self.mpmh = MyProtocolMessageHandler() 34 | self.mpm_store = MessageStore() 35 | self.mpmh_callback = mpmh_callback 36 | 37 | self.bb = BlockBuilder() 38 | my_genesis_block = self.bb.generate_genesis_block() 39 | self.bm = BlockchainManager(my_genesis_block.to_dict()) 40 | self.callback = callback 41 | 42 | def start(self, my_pubkey=None): 43 | self.client_state = STATE_ACTIVE 44 | self.cm.start() 45 | self.cm.connect_to_core_node(my_pubkey) 46 | 47 | def shutdown(self): 48 | self.client_state = STATE_SHUTTING_DOWN 49 | print('Shutdown edge node ...') 50 | self.cm.connection_close() 51 | 52 | def get_my_current_state(self): 53 | return self.client_state 54 | 55 | def send_message_to_my_core_node(self, msg_type, msg): 56 | msg_txt = self.cm.get_message_text(msg_type, msg) 57 | print(msg_txt) 58 | self.cm.send_msg((self.my_core_host, self.my_core_port), msg_txt) 59 | 60 | def send_req_full_chain_to_my_core_node(self): 61 | print('send_req_full_chain_to_my_core_node called') 62 | new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN) 63 | self.cm.send_msg((self.my_core_host, self.my_core_port), new_message) 64 | 65 | 66 | def __client_api(self, request, msg): 67 | 68 | if request == 'pass_message_to_client_application': 69 | print('Client Core API: pass_message_to_client_application') 70 | self.mpm_store.add(msg) 71 | self.mpmh_callback(msg) 72 | elif request == 'api_type': 73 | return 'client_core_api' 74 | else: 75 | print('not implemented api was used') 76 | 77 | def get_my_protocol_messages(self): 78 | return self.my_protocol_message_store.get_list() 79 | 80 | def get_my_blockchain(self): 81 | return self.bm.get_my_blockchain() 82 | 83 | def get_stored_transactions_from_bc(self): 84 | return self.bm.get_stored_transactions_from_bc() 85 | 86 | 87 | def __handle_message(self, msg): 88 | if msg[2] == RSP_FULL_CHAIN: 89 | # ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証し、有効なものか検証した上で 90 | # 自分の持つチェインと比較し優位な方を今後のブロックチェーンとして利用する 91 | new_block_chain = pickle.loads(msg[4].encode('utf8')) 92 | result, _ = self.bm.resolve_conflicts(new_block_chain) 93 | print('blockchain received form central', result) 94 | if result is not None: 95 | self.prev_block_hash = result 96 | print('callback called') 97 | self.callback() 98 | else: 99 | print('Received blockchain is useless...') 100 | 101 | elif msg[2] == MSG_ENHANCED: 102 | # P2P Network を単なるトランスポートして使っているアプリケーションが独自拡張したメッセージはここで処理する。 103 | # SimpleBitcoin としてはこの種別は使わない 104 | self.mpmh.handle_message(msg[4], self.__client_api, True) 105 | 106 | def __get_myip(self): 107 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 108 | s.connect(('8.8.8.8', 80)) 109 | return s.getsockname()[0] 110 | -------------------------------------------------------------------------------- /07/p2p/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/07/p2p/__init__.py -------------------------------------------------------------------------------- /07/p2p/core_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class CoreNodeList: 5 | """ 6 | Peerのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, peer): 13 | """ 14 | Coreノードをリストに追加する。 15 | 16 | param: 17 | peer : Coreノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding peer: ', peer) 21 | self.list.add((peer)) 22 | print('Current Core Set: ', self.list) 23 | 24 | 25 | def remove(self, peer): 26 | """ 27 | 離脱したと判断されるCoreノードをリストから削除する。 28 | 29 | param: 30 | peer : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if peer in self.list: 34 | print('Removing peer: ', peer) 35 | self.list.remove(peer) 36 | print('Current Core list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のpeerの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('core node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Core list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるPeerの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | def get_c_node_info(self): 55 | c_list = [] 56 | for i in self.list: 57 | c_list.append(i) 58 | 59 | return c_list[0] 60 | 61 | def get_length(self): 62 | return len(self.list) 63 | 64 | 65 | def has_this_peer(self, peer): 66 | """ 67 | 与えられたpeerがリストに含まれているか?をチェックする 68 | 69 | param: 70 | peer : IPアドレスとポート番号のタプル 71 | return: 72 | True or False 73 | """ 74 | return peer in self.list 75 | 76 | -------------------------------------------------------------------------------- /07/p2p/edge_node_list.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class EdgeNodeList: 5 | """ 6 | Edgeノードのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = set() 11 | 12 | def add(self, edge): 13 | """ 14 | Edgeノードをリストに追加する。 15 | 16 | param: 17 | edge : Edgeノードとして格納されるノードの接続情報(IPアドレスとポート番号) 18 | """ 19 | with self.lock: 20 | print('Adding edge: ', edge) 21 | self.list.add((edge)) 22 | print('Current Edge List: ', self.list) 23 | 24 | 25 | def remove(self, edge): 26 | """ 27 | 離脱したと判断されるEdgeノードをリストから削除する。 28 | 29 | param: 30 | edge : 削除するノードの接続先情報(IPアドレスとポート番号) 31 | """ 32 | with self.lock: 33 | if edge in self.list: 34 | print('Removing edge: ', edge) 35 | self.list.remove(edge) 36 | print('Current Edge list: ', self.list) 37 | 38 | def overwrite(self, new_list): 39 | """ 40 | 複数のEdgeノードの生存確認を行った後で一括での上書き処理をしたいような場合はこちら 41 | """ 42 | with self.lock: 43 | print('edge node list will be going to overwrite') 44 | self.list = new_list 45 | print('Current Edge list: ', self.list) 46 | 47 | 48 | def get_list(self): 49 | """ 50 | 現在接続状態にあるEdgeノードの一覧を返却する 51 | """ 52 | return self.list 53 | 54 | 55 | def has_this_edge(self, pubky_address): 56 | """ 57 | 指定の公開鍵を持ったEdgeノードがリストに存在しているかどうかを確認し結果を返却する 58 | 59 | param: 60 | pubky_address : 公開鍵) 61 | """ 62 | for e in self.list: 63 | print('edge: ', e[2]) 64 | print('pubkey: ', pubky_address) 65 | if e[2] == pubky_address: 66 | return True, e[0], e[1] 67 | 68 | return False, None, None -------------------------------------------------------------------------------- /07/p2p/message_manager.py: -------------------------------------------------------------------------------- 1 | from distutils.version import StrictVersion 2 | import json 3 | 4 | 5 | PROTOCOL_NAME = 'simple_bitcoin_protocol' 6 | MY_VERSION = '0.1.0' 7 | 8 | MSG_ADD = 0 9 | MSG_REMOVE = 1 10 | MSG_CORE_LIST = 2 11 | MSG_REQUEST_CORE_LIST = 3 12 | MSG_PING = 4 13 | MSG_ADD_AS_EDGE = 5 14 | MSG_REMOVE_EDGE = 6 15 | MSG_NEW_TRANSACTION = 7 16 | MSG_NEW_BLOCK = 8 17 | MSG_REQUEST_FULL_CHAIN = 9 18 | RSP_FULL_CHAIN = 10 19 | MSG_ENHANCED = 11 20 | 21 | ERR_PROTOCOL_UNMATCH = 0 22 | ERR_VERSION_UNMATCH = 1 23 | OK_WITH_PAYLOAD = 2 24 | OK_WITHOUT_PAYLOAD = 3 25 | 26 | 27 | class MessageManager: 28 | 29 | def __init__(self): 30 | print('Initializing MessageManager...') 31 | 32 | def build(self, msg_type, my_port=50082, payload=None): 33 | """ 34 | プロトコルメッセージの組み立て 35 | 36 | params: 37 | msg_type : 規定のメッセージ種別 38 | my_port : メッセージ送信者が受信用に待機させているServerSocketが使うポート番号 39 | payload : メッセージに組み込みたいデータがある場合に指定する 40 | 41 | return: 42 | message : JSON形式に変換されたプロトコルメッセージ 43 | """ 44 | 45 | message = { 46 | 'protocol': PROTOCOL_NAME, 47 | 'version': MY_VERSION, 48 | 'msg_type': msg_type, 49 | 'my_port': my_port 50 | } 51 | 52 | if payload is not None: 53 | message['payload'] = payload 54 | 55 | return json.dumps(message) 56 | 57 | def parse(self, msg): 58 | """ 59 | プロトコルメッセージをパースして返却する 60 | 61 | params 62 | msg : JSON形式のプロトコルメッセージデータ 63 | return : 64 | 65 | 結果(OK or NG)とパース結果の種別(ペイロードあり/なし)と送信元ポート番号およびペーロードのデータ 66 | """ 67 | msg = json.loads(msg) 68 | msg_ver = StrictVersion(msg['version']) 69 | 70 | cmd = msg.get('msg_type') 71 | my_port = msg.get('my_port') 72 | payload = msg.get('payload') 73 | 74 | if msg['protocol'] != PROTOCOL_NAME: 75 | return ('error', ERR_PROTOCOL_UNMATCH, None, None, None) 76 | elif msg_ver > StrictVersion(MY_VERSION): 77 | return ('error', ERR_VERSION_UNMATCH, None, None, None) 78 | elif cmd in (MSG_CORE_LIST, MSG_NEW_TRANSACTION, MSG_NEW_BLOCK, RSP_FULL_CHAIN, MSG_ENHANCED, MSG_ADD_AS_EDGE): 79 | result_type = OK_WITH_PAYLOAD 80 | return ('ok', result_type, cmd, my_port, payload) 81 | else: 82 | result_type = OK_WITHOUT_PAYLOAD 83 | return ('ok', result_type, cmd, my_port, None) 84 | -------------------------------------------------------------------------------- /07/p2p/my_protocol_message_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | SEND_TO_ALL_PEER = 'send_message_to_all_peer' 4 | SEND_TO_ALL_EDGE = 'send_message_to_all_edge' 5 | SEND_TO_THIS_ADDRESS = 'send_message_to_this_pubkey_address' 6 | 7 | PASS_TO_CLIENT_APP = 'pass_message_to_client_application' 8 | 9 | class MyProtocolMessageHandler: 10 | """ 11 | 独自に拡張したENHANCEDメッセージの処理や生成を担当する 12 | """ 13 | 14 | def __init__(self): 15 | print('Initializing MyProtocolMessageHandler...') 16 | 17 | def handle_message(self, msg, api, is_core): 18 | """ 19 | とりあえず受け取ったメッセージを自分がCoreノードならブロードキャスト、 20 | Edgeならコンソールに出力することでメッセっぽいものをデモ 21 | 22 | params: 23 | msg : 拡張プロトコルで送られてきたJSON形式のメッセージ 24 | api : ServerCore(or ClientCore)側で用意されているAPI呼び出しのためのコールバック 25 | api(param1, param2) という形で利用する 26 | 27 | is_core : 送信元ノードがCoreノードであるかどうか 28 | """ 29 | msg = json.loads(msg) 30 | my_api = api('api_type', None) 31 | print('my_api: ', my_api) 32 | if my_api == 'server_core_api': 33 | if msg['message_type'] == 'cipher_message': 34 | print('received cipher message!') 35 | target_address = msg['recipient'] 36 | result = api(SEND_TO_THIS_ADDRESS, (target_address, json.dumps(msg))) 37 | if result == None: 38 | if is_core is not True: 39 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 40 | else: 41 | print('Bloadcasting ...', json.dumps(msg)) 42 | if is_core is not True: 43 | # Coreノードからのメッセージでない時だけ他のCoreノードにもブロードキャストする 44 | api(SEND_TO_ALL_PEER, json.dumps(msg)) 45 | api(SEND_TO_ALL_EDGE, json.dumps(msg)) 46 | else: 47 | print('MyProtocolMessageHandler received ', msg) 48 | api(PASS_TO_CLIENT_APP, msg) 49 | 50 | return 51 | -------------------------------------------------------------------------------- /07/p2p/my_protocol_message_store.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class MessageStore: 5 | """ 6 | 拡張メッセージのリストをスレッドセーフに管理する 7 | """ 8 | def __init__(self): 9 | self.lock = threading.Lock() 10 | self.list = [] 11 | 12 | def add(self, msg): 13 | """ 14 | メッセージをリストに追加する。 15 | 16 | param: 17 | msg : 拡張メッセージとして届けられたもの 18 | """ 19 | print('Message store added', msg) 20 | with self.lock: 21 | self.list.append(msg) 22 | 23 | 24 | def remove(self, msg): 25 | """ 26 | メッセージをリストから削除する。今のところ使うか不明 27 | 28 | param: 29 | msg : 拡張メッセージとして届けられたもの) 30 | """ 31 | with self.lock: 32 | toBeRemoved = None 33 | for m in self.list: 34 | if msg == m: 35 | toBeRemoved = m 36 | break 37 | if not toBeRemoved: 38 | return 39 | self.list.remove(toBeRemoved) 40 | 41 | 42 | def overwrite(self, new_list): 43 | """ 44 | 一括での上書き処理をしたいような場合はこちら 45 | """ 46 | with self.lock: 47 | self.list = new_list 48 | 49 | 50 | def get_list(self): 51 | """ 52 | 現在保存されているメッセージの一覧を返却する 53 | """ 54 | if len(self.list) > 0: 55 | return self.list 56 | else: 57 | return None 58 | 59 | def get_length(self): 60 | return len(self.list) 61 | 62 | 63 | def has_this_msg(self, msg): 64 | for m in self.list: 65 | if m == msg: 66 | return True 67 | 68 | return False, 69 | -------------------------------------------------------------------------------- /07/sample_server1.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | 4 | from core.server_core import ServerCore 5 | 6 | my_p2p_server = None 7 | 8 | 9 | def signal_handler(signal, frame): 10 | shutdown_server() 11 | 12 | def shutdown_server(): 13 | global my_p2p_server 14 | my_p2p_server.shutdown() 15 | 16 | 17 | def main(my_port, p_phrase): 18 | signal.signal(signal.SIGINT, signal_handler) 19 | global my_p2p_server 20 | # 始原のCoreノードとして起動する 21 | my_p2p_server = ServerCore(my_port, None, None, p_phrase) 22 | my_p2p_server.start() 23 | 24 | 25 | if __name__ == '__main__': 26 | args = sys.argv 27 | 28 | if len(args) == 3: 29 | my_port = int(args[1]) 30 | p_phrase = args[2] 31 | else: 32 | print('Param Error') 33 | print('$ SmpleServer1.py ') 34 | quit() 35 | 36 | main(my_port, p_phrase) -------------------------------------------------------------------------------- /07/sample_server2.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import sys 3 | from core.server_core import ServerCore 4 | 5 | 6 | my_p2p_server = None 7 | 8 | def signal_handler(signal, frame): 9 | shutdown_server() 10 | 11 | def shutdown_server(): 12 | global my_p2p_server 13 | my_p2p_server.shutdown() 14 | 15 | 16 | def main(my_port, c_host, c_port, p_phrase): 17 | signal.signal(signal.SIGINT, signal_handler) 18 | global my_p2p_server 19 | my_p2p_server = ServerCore(my_port, c_host, c_port, p_phrase) 20 | my_p2p_server.start() 21 | my_p2p_server.join_network() 22 | 23 | if __name__ == '__main__': 24 | args = sys.argv 25 | 26 | if len(args) == 5: 27 | my_port = int(args[1]) 28 | c_host = args[2] 29 | c_port = int(args[3]) 30 | p_phrase = args[4] 31 | else: 32 | print('Param Error') 33 | print('$ SmpleServer2.py ') 34 | quit() 35 | 36 | main(my_port, c_host, c_port, p_phrase) -------------------------------------------------------------------------------- /07/transaction/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/07/transaction/__init__.py -------------------------------------------------------------------------------- /07/transaction/transaction_pool.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class TransactionPool: 5 | 6 | def __init__(self): 7 | print('Initializing TransactionPool...') 8 | self.transactions = [] 9 | self.lock = threading.Lock() 10 | 11 | def set_new_transaction(self, transaction): 12 | with self.lock: 13 | print('set_new_transaction is called', transaction) 14 | self.transactions.append(transaction) 15 | 16 | def renew_my_transactions(self, transactions): 17 | with self.lock: 18 | print('transaction pool will be renewed to ...', transactions) 19 | self.transactions = transactions 20 | 21 | def clear_my_transactions(self, index): 22 | with self.lock: 23 | if index <= len(self.transactions): 24 | new_transactions = self.transactions 25 | del new_transactions[0:index] 26 | print('transaction is now refreshed ... ', new_transactions) 27 | self.transactions = new_transactions 28 | 29 | def get_stored_transactions(self): 30 | if len(self.transactions) > 0: 31 | return self.transactions 32 | else: 33 | print('Currently, it seems transaction pool is empty...') 34 | return [] 35 | 36 | def has_this_output_in_my_tp(self, transaction_output): 37 | """ 38 | TranactionPool内ですでにこのTransactionOutputがInputとして使われていないか?の確認 39 | """ 40 | print('has_this_output_in_my_tp is called') 41 | transactions = self.transactions 42 | for t in transactions: 43 | checked = self.check_type_of_transaction(t) 44 | if checked: 45 | inputs_t = t['inputs'] 46 | for it in inputs_t: 47 | if it == transaction_output: 48 | return True 49 | 50 | return False 51 | 52 | def get_total_fee_from_tp(self): 53 | """ 54 | TransactionPool内に格納されているTransaction全ての手数料の合計値を算出する 55 | """ 56 | print('get_total_fee_from_tp is called') 57 | transactions = self.transactions 58 | result = 0 59 | for t in transactions: 60 | checked = self.check_type_of_transaction(t) 61 | if checked: 62 | total_in = sum(i['transaction']['outputs'][i['output_index']]['value'] for i in t['inputs']) 63 | total_out = sum(o['value'] for o in t['outputs']) 64 | delta = total_in - total_out 65 | result += delta 66 | 67 | return result 68 | 69 | def check_type_of_transaction(self, transaction): 70 | if transaction['t_type'] == 'basic' or transaction['t_type'] == 'coinbase_transaction': 71 | return True 72 | else: 73 | return False 74 | 75 | -------------------------------------------------------------------------------- /07/transaction/transactions.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | class TransactionInput: 4 | """ 5 | トトランザクションの中でInputに格納するUTXOを指定する 6 | """ 7 | def __init__(self, transaction, output_index): 8 | self.transaction = transaction 9 | self.output_index = output_index 10 | 11 | def to_dict(self): 12 | d = { 13 | 'transaction': self.transaction, 14 | 'output_index': self.output_index 15 | } 16 | return d 17 | 18 | class TransactionOutput: 19 | """ 20 | トランザクションの中で Output (送金相手と送る金額)を管理する 21 | """ 22 | def __init__(self, recipient_address, value): 23 | self.recipient = recipient_address 24 | self.value = value 25 | 26 | def to_dict(self): 27 | d = { 28 | 'recipient': self.recipient, 29 | 'value': self.value 30 | } 31 | return d 32 | 33 | class Transaction: 34 | """ 35 | 持っていないコインを誰かに簡単に送金できてしまっては全く意味がないので、過去のトランザクションにて 36 | 自分を宛先として送金されたコインの総計を超える送金依頼を作ることができないよう、inputs と outputs 37 | のペアによって管理する 38 | 39 | Args: 40 | t_type : トランザクションのタイプ。今後の拡張で種別の切り分けに使う 41 | extra : 拡張用途で利用可能な文字列。例えば送金の際にその理由となった記事のURLを格納したい場合などに使う 42 | """ 43 | def __init__(self, inputs, outputs, extra=None): 44 | self.inputs = inputs 45 | self.outputs = outputs 46 | self.timestamp = time() 47 | self.t_type = 'basic' 48 | self.extra = extra 49 | 50 | 51 | def to_dict(self): 52 | d = { 53 | 'inputs': list(map(TransactionInput.to_dict, self.inputs)), 54 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 55 | 'timestamp': self.timestamp, 56 | 't_type': self.t_type, 57 | 'extra': self.extra 58 | } 59 | 60 | return d 61 | 62 | def is_enough_inputs(self, fee): 63 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 64 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 65 | delta = total_in - total_out 66 | if delta >= 0: 67 | return True 68 | else: 69 | return False 70 | 71 | def compute_change(self, fee): 72 | total_in = sum(i.transaction['outputs'][i.output_index]['value'] for i in self.inputs) 73 | total_out = sum(int(o.value) for o in self.outputs) + int(fee) 74 | delta = total_in - total_out 75 | return delta 76 | 77 | 78 | class CoinbaseTransaction(Transaction): 79 | """ 80 | Coinbaseトランザクションは例外的にInputが存在しない。 81 | """ 82 | def __init__(self, recipient_address, value=30): 83 | self.inputs = [] 84 | self.outputs = [TransactionOutput(recipient_address, value)] 85 | self.timestamp = time() 86 | self.t_type = 'coinbase_transaction' 87 | 88 | def to_dict(self): 89 | d = { 90 | 'inputs': [], 91 | 'outputs': list(map(TransactionOutput.to_dict, self.outputs)), 92 | 'timestamp' : self.timestamp, 93 | 't_type': self.t_type 94 | } 95 | 96 | return d 97 | 98 | class EngravedTransaction: 99 | """ 100 | Twitter風のメッセージをブロックチェーンに刻み込むための拡張Transactionタイプ 101 | 各Transactionには後でSenderの秘密鍵で署名をつける 102 | """ 103 | def __init__(self, sender, sender_alt_name, message, icon_url=None, reply_to=None, original_reply_to=None): 104 | self.sender = sender 105 | self.sender_alt_name = sender_alt_name 106 | self.icon = icon_url 107 | self.message = message 108 | self.timestamp = time() 109 | self.reply_to = reply_to 110 | self.original_reply_to = original_reply_to 111 | self.content_id = sender + str(self.timestamp) 112 | self.t_type = 'engraved' 113 | 114 | def to_dict(self): 115 | d = { 116 | 'sender': self.sender, 117 | 'sender_alt_name': self.sender_alt_name, 118 | 'icon': self.icon, 119 | 'timestamp': self.timestamp, 120 | 'message' : self.message, 121 | 'reply_to': self.reply_to, 122 | 'original_reply_to': self.original_reply_to, 123 | 'id': self.content_id, 124 | 't_type': self.t_type, 125 | } 126 | 127 | return d 128 | -------------------------------------------------------------------------------- /07/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/07/utils/__init__.py -------------------------------------------------------------------------------- /07/utils/aes_util.py: -------------------------------------------------------------------------------- 1 | from Crypto import Random 2 | from Crypto.Cipher import AES 3 | 4 | import random 5 | import string 6 | import hashlib 7 | 8 | class AESUtil: 9 | 10 | def __init__(self): 11 | k_seed = ''.join(random.choices(string.ascii_letters + string.digits, k=AES.block_size)) 12 | self.secret_key = hashlib.sha256(k_seed.encode('utf-8')).digest() 13 | 14 | def get_aes_key(self): 15 | return self.secret_key 16 | 17 | def pad(self, target): 18 | target_len = len(target.encode("utf-8")) 19 | return target + (AES.block_size - target_len % AES.block_size) * chr(0) 20 | 21 | def encrypt(self, message): 22 | message = self.pad(message) 23 | print(message.encode('utf-8')) 24 | iv = Random.new().read(AES.block_size) 25 | cipher = AES.new(self.secret_key, AES.MODE_CBC, iv) 26 | return iv + cipher.encrypt(message) 27 | 28 | def decrypt(self, ciphertext): 29 | iv = ciphertext[:AES.block_size] 30 | cipher = AES.new(self.secret_key, AES.MODE_CBC, iv) 31 | plaintext = cipher.decrypt(ciphertext[AES.block_size:]) 32 | return plaintext.rstrip(b"\0") 33 | 34 | def decrypt_with_key(self, ciphertext, key): 35 | iv = ciphertext[:AES.block_size] 36 | cipher = AES.new(key, AES.MODE_CBC, iv) 37 | plaintext = cipher.decrypt(ciphertext[AES.block_size:]) 38 | return plaintext.rstrip(b"\0") -------------------------------------------------------------------------------- /07/utils/key_manager.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | import Crypto.Random 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Signature import PKCS1_v1_5 5 | from Crypto.Hash import SHA256 6 | 7 | import copy 8 | import binascii 9 | import json 10 | 11 | 12 | class KeyManager: 13 | 14 | def __init__(self, privatekey_text = None, pass_phrase= None): 15 | print('Initializing KeyManager...') 16 | if privatekey_text: 17 | self.import_key_pair(privatekey_text, pass_phrase) 18 | else: 19 | random_gen = Crypto.Random.new().read 20 | self._private_key = RSA.generate(2048, random_gen) 21 | self._public_key = self._private_key.publickey() 22 | self._signer = PKCS1_v1_5.new(self._private_key) 23 | if pass_phrase is not None: 24 | my_pem = self.export_key_pair(pass_phrase) 25 | my_pem_hex = binascii.hexlify(my_pem).decode('ascii') 26 | # とりあえずファイル名は固定 27 | path = 'my_server_key_pair.pem' 28 | f1 = open(path,'a') 29 | f1.write(my_pem_hex) 30 | f1.close() 31 | 32 | 33 | def my_address(self): 34 | """ 35 | UI表示用の公開鍵情報 36 | """ 37 | return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii') 38 | 39 | 40 | def compute_digital_signature(self, message): 41 | hashed_message = SHA256.new(message.encode('utf8')) 42 | signer = PKCS1_v1_5.new(self._private_key) 43 | return binascii.hexlify(signer.sign(hashed_message)).decode('ascii') 44 | 45 | 46 | def verify_my_signature(self, message, signature): 47 | print('verify_my_signature was called') 48 | hashed_message = SHA256.new(message.encode('utf8')) 49 | verifier = PKCS1_v1_5.new(self._public_key) 50 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 51 | print(result) 52 | return result 53 | 54 | def encrypt_with_my_pubkey(self, target): 55 | encrypto = self._public_key.encrypt(target, 0) 56 | return encrypto 57 | 58 | 59 | def decrypt_with_private_key(self, target): 60 | decrypto = self._private_key.decrypt(target) 61 | print('decrypto', decrypto) 62 | return decrypto 63 | 64 | 65 | def export_key_pair(self, pass_phrase): 66 | """ 67 | 鍵ペアをPEMフォーマットで書き出す(バックアップ用途) 68 | """ 69 | return self._private_key.exportKey(format='PEM', passphrase=pass_phrase) 70 | 71 | 72 | def import_key_pair(self, key_data, pass_phrase): 73 | """ 74 | PEMフォーマットでパスワード保護された鍵ペアをファイルから読み込んで設定する 75 | """ 76 | self._private_key = RSA.importKey(key_data, pass_phrase) 77 | self._public_key = self._private_key.publickey() 78 | self._signer = PKCS1_v1_5.new(self._private_key) 79 | -------------------------------------------------------------------------------- /07/utils/rsa_util.py: -------------------------------------------------------------------------------- 1 | import Crypto 2 | from Crypto.PublicKey import RSA 3 | from Crypto.Signature import PKCS1_v1_5 4 | from Crypto.Hash import SHA256 5 | 6 | import copy 7 | import binascii 8 | import json 9 | 10 | class RSAUtil: 11 | 12 | def __init__(self): 13 | pass 14 | 15 | def verify_signature(self, message, signature, sender_public_key): 16 | print('verify_signature was called') 17 | hashed_message = SHA256.new(message.encode('utf8')) 18 | verifier = PKCS1_v1_5.new(sender_public_key) 19 | result = verifier.verify(hashed_message, binascii.unhexlify(signature)) 20 | print(result) 21 | return result 22 | 23 | def encrypt_with_pubkey(self, target, pubkey_text): 24 | """ 25 | 与えられた公開鍵で暗号化する 26 | """ 27 | pubkey = RSA.importKey(binascii.unhexlify(pubkey_text)) 28 | encrypto = pubkey.encrypt(target, 0) 29 | return encrypto 30 | 31 | def verify_sbc_transaction_sig(self, transaction): 32 | """ 33 | simple_bitcoin のTransactionの署名の正当性を検証する 34 | """ 35 | print('verify_sbc_transaction_sig was called') 36 | sender_pubkey_text, used_outputs = self._get_pubkey_from_sbc_transaction(transaction) 37 | signature = transaction['signature'] 38 | c_transaction = copy.deepcopy(transaction) 39 | del c_transaction['signature'] 40 | target_txt = json.dumps(c_transaction, sort_keys=True) 41 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 42 | result = self.verify_signature(target_txt, signature, sender_pubkey) 43 | return result, used_outputs 44 | 45 | def _get_pubkey_from_sbc_transaction(self, transaction): 46 | print('_get_pubkey_from_sbc_transaction was called') 47 | input_t_list = transaction['inputs'] 48 | used_outputs = [] 49 | sender_pubkey = '' 50 | for i in input_t_list: 51 | idx = i['output_index'] 52 | tx = i['transaction']['outputs'][idx] 53 | used_outputs.append(tx) 54 | sender_pubkey = tx['recipient'] 55 | 56 | return sender_pubkey, used_outputs 57 | 58 | def verify_general_transaction_sig(self, transaction): 59 | """ 60 | simple_bitcoin 以外のTransactionも署名の形式を統一することで検証を可能にしておく 61 | """ 62 | print('verify_general_transaction_sig was called') 63 | sender_pubkey_text = transaction['sender'] 64 | signature = transaction['signature'] 65 | c_transaction = copy.deepcopy(transaction) 66 | del c_transaction['signature'] 67 | target_txt = json.dumps(c_transaction, sort_keys=True) 68 | sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text)) 69 | result = self.verify_signature(target_txt, signature, sender_pubkey) 70 | return result 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ゼロから創る暗号通貨サンプルコード置き場 3 | 4 | ![cover](doc/img/cover.png?raw=true "cover") 5 | 6 | 本リポジトリは書籍「[ゼロから創る暗号通貨](https://peaks.cc/cryptocurrency)」のサンプルコード置き場となっています。 7 | 「ゼロから創る暗号通貨」は現在β版公開中のステータスのため、本リポジトリ内のコードにつきましても、日々更新される可能性が 8 | あります点、あらかじめご承知おきください。 9 | 10 | 11 | ### 現在の構成 12 | 13 | ・02 : 第2章 P2Pネットワーク : 基盤作りからはじめよう 14 | ・03 : 第3章 Hello Blockchain ! : こんにちはブロックチェーン 15 | ・04 : 第4章 Wallet と Transaction : Transactionの中身について考える 16 | ・05 : 第5章 すべての機能を結合し、動かしてみよう 17 | ・06 : 第6章 SimpleBitcoin のセキュリティに関する考察 18 | ・07 : 第7章 ここから先のSimpleBitcoin 19 | 20 | 以上の内容と対応するサンプルコードが各フォルダに格納されています 21 | 22 | 23 | ## 依存関係: 24 | 25 | Python :3.6以降 26 | Tkinter: Python 3.6以降は標準サポート (sudo apt-get install python3-tk) 27 | PyCrypto : (pip install pycrypto) 28 | 29 | 30 | ## Tested System: 31 | 32 | * OSX 10.12.6 33 | * python 3.6.2 34 | 35 | 36 | 37 | 38 | ## 参考文献 39 | 40 | ### 参考リンク 41 | 42 | Bitcoin Developer Reference 43 | https://bitcoin.org/en/developer-reference 44 | 45 | Dumbcoin - An educational python implementation of a bitcoin-like blockchain 46 | https://github.com/julienr/ipynb_playground/blob/master/bitcoin/dumbcoin/dumbcoin.ipynb 47 | 48 | Learn Blockchains by Building One 49 | https://hackernoon.com/learn-blockchains-by-building-one-117428612f46 50 | 51 | PyCoin 52 | https://github.com/xran-deex/PyCoin 53 | 54 | 55 | ### 書籍 56 | 57 | Mastering Bitcoin: ビットコインとブロックチェーン:暗号通貨を支える技術 58 | ブロックチェーンプログラミング 仮想通貨入門 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peaks-cc/cryptocurrency-samplecode/dba73d5f4dd60634e171f6570b791b5d626fdd45/doc/img/cover.png --------------------------------------------------------------------------------