def __init__(self, my_port=50082, core_node_host=None, core_node_port=None, pass_phrase=None): self.server_state = STATE_INIT print('Initializing server...') self.my_ip = self.__get_myip() print('Server IP address is set to ... ', self.my_ip) self.my_port = my_port self.cm = ConnectionManager(self.my_ip, self.my_port, self.__handle_message) self.mpmh = MyProtocolMessageHandler() self.core_node_host = core_node_host self.core_node_port = core_node_port self.bb = BlockBuilder() my_genesis_block = self.bb.generate_genesis_block() self.bm = BlockchainManager(my_genesis_block.to_dict()) self.prev_block_hash = self.bm.get_hash(my_genesis_block.to_dict()) self.tp = TransactionPool() self.is_bb_running = False self.flag_stop_block_build = False self.mpm_store = MessageStore() self.km = KeyManager(None, pass_phrase) self.rsa_util = RSAUtil() self.um = UTXOManager(self.km.my_address())
def main(): k_m = KeyManager() um = UTXOManager(k_m.my_address()) i_k_m = KeyManager() u_k_m = KeyManager() t1 = CoinbaseTransaction(k_m.my_address()) t2 = CoinbaseTransaction(k_m.my_address()) t3 = CoinbaseTransaction(k_m.my_address()) t4 = Transaction([TransactionInput(t1.to_dict(), 0)], [ TransactionOutput(u_k_m.my_address(), 10.0), TransactionOutput(i_k_m.my_address(), 20.0) ]) transactions = [] transactions.append(t1.to_dict()) transactions.append(t2.to_dict()) transactions.append(t3.to_dict()) transactions.append(t4.to_dict()) um.extract_utxos(transactions) balance = um.my_balance print(balance)
def initApp(self, my_port, c_host, c_port): """ ClientCoreとの接続含めて必要な初期化処理はここで実行する """ print('SimpleBitcoin client is now activating ...: ') self.km = KeyManager() self.um = UTXM(self.km.my_address()) self.rsa_util = RSAUtil() self.c_core = Core(my_port, c_host, c_port, self.update_callback) self.c_core.start() # テスト用途(本来はこんな処理しない) t1 = CoinbaseTransaction(self.km.my_address()) t2 = CoinbaseTransaction(self.km.my_address()) t3 = CoinbaseTransaction(self.km.my_address()) transactions = [] transactions.append(t1.to_dict()) transactions.append(t2.to_dict()) transactions.append(t3.to_dict()) self.um.extract_utxos(transactions) self.update_balance()
def load_my_keys(): # ファイル選択ダイアログの表示 f2 = Tk() f2.withdraw() fTyp = [('','*.pem')] iDir = os.path.abspath(os.path.dirname(__file__)) messagebox.showinfo('Load key pair','please choose your key file') f_name = filedialog.askopenfilename(filetypes = fTyp,initialdir = iDir) try: file = open(f_name) data = file.read() target = binascii.unhexlify(data) # TODO: 本来は鍵ペアのファイルが不正などの異常系処理を考えるべき self.km.import_key_pair(target, p_phrase.get()) except Exception as e: print(e) finally: # TODO: 所有コインの再確認処理を入れる必要あり file.close() f.destroy() f2.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance()
def save_my_pem(): self.km = KeyManager() my_pem = self.km.export_key_pair(p_phrase) my_pem_hex = binascii.hexlify(my_pem).decode('ascii') # とりあえずファイル名は固定 path = 'my_key_pair.pem' f1 = open(path,'a') f1.write(my_pem_hex) f1.close() f.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance()
class ServerCore: def __init__(self, my_port=50082, core_node_host=None, core_node_port=None, pass_phrase=None): self.server_state = STATE_INIT print('Initializing server...') self.my_ip = self.__get_myip() print('Server IP address is set to ... ', self.my_ip) self.my_port = my_port self.cm = ConnectionManager(self.my_ip, self.my_port, self.__handle_message) self.mpmh = MyProtocolMessageHandler() self.core_node_host = core_node_host self.core_node_port = core_node_port self.bb = BlockBuilder() my_genesis_block = self.bb.generate_genesis_block() self.bm = BlockchainManager(my_genesis_block.to_dict()) self.prev_block_hash = self.bm.get_hash(my_genesis_block.to_dict()) self.tp = TransactionPool() self.is_bb_running = False self.flag_stop_block_build = False self.mpm_store = MessageStore() self.km = KeyManager(None, pass_phrase) self.rsa_util = RSAUtil() self.um = UTXOManager(self.km.my_address()) def start_block_building(self): self.bb_timer = threading.Timer(CHECK_INTERVAL, self.__generate_block_with_tp) self.bb_timer.start() def stop_block_building(self): print('Thread for __generate_block_with_tp is stopped now') self.bb_timer.cancel() def start(self): self.server_state = STATE_STANDBY self.cm.start() self.start_block_building() def join_network(self): if self.core_node_host != None: self.server_state = STATE_CONNECTED_TO_CENTRAL self.cm.join_network(self.core_node_host, self.core_node_port) else: print('This server is runnning as Genesis Core Node...') def shutdown(self): self.server_state = STATE_SHUTTING_DOWN self.flag_stop_block_build = True print('Shutdown server...') self.cm.connection_close() self.stop_block_building() def get_my_current_state(self): return self.server_state def send_req_full_chain_to_my_peer(self): print('send_req_full_chain_to_my_central called') new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN) self.cm.send_msg((self.core_node_host, self.core_node_port), new_message) def get_all_chains_for_resolve_conflict(self): print('get_all_chains_for_resolve_conflict called') new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN) self.cm.send_msg_to_all_peer(new_message) def __generate_block_with_tp(self): print('Thread for generate_block_with_tp started!') while not self.flag_stop_block_build: self.is_bb_running = True prev_hash = copy.copy(self.prev_block_hash) result = self.tp.get_stored_transactions() if len(result) == 0: print('Transaction Pool is empty ...') break new_tp = self.bm.remove_useless_transaction(result) self.tp.renew_my_transactions(new_tp) if len(new_tp) == 0: break # リワードとしてリストの先頭に自分宛のCoinbaseTransactionを追加する total_fee = self.tp.get_total_fee_from_tp() # TODO: インセンティブの値をここに直書きするのはイケてないのであとで対処する total_fee += 30 my_coinbase_t = CoinbaseTransaction(self.km.my_address(), total_fee) transactions_4_block = copy.deepcopy(new_tp) transactions_4_block.insert(0, my_coinbase_t.to_dict()) new_block = self.bb.generate_new_block(transactions_4_block, prev_hash) # タイミングがどうしてもクロスするので念のため保存前に再度確認 if new_block.to_dict()['previous_block'] == self.prev_block_hash: self.bm.set_new_block(new_block.to_dict()) self.prev_block_hash = self.bm.get_hash(new_block.to_dict()) msg_new_block = self.cm.get_message_text( MSG_NEW_BLOCK, json.dumps(new_block.to_dict())) self.cm.send_msg_to_all_peer(msg_new_block) # ブロック生成に成功したらTransaction Poolはクリアする index = len(new_tp) self.tp.clear_my_transactions(index) break else: print('Bad block. It seems someone already win the PoW.') break print('Current Blockchain is ... ', self.bm.chain) print('Current prev_block_hash is ... ', self.prev_block_hash) self.flag_stop_block_build = False self.is_bb_running = False self.bb_timer = threading.Timer(CHECK_INTERVAL, self.__generate_block_with_tp) self.bb_timer.start() def _check_availability_of_transaction(self, transaction): """ Transactionに含まれているTransactionInputの有効性(二重使用)を検証する """ v_result, used_outputs = self.rsa_util.verify_sbc_transaction_sig( transaction) if v_result is not True: print('signature verification error on new transaction') return False for used_o in used_outputs: print('used_o', used_o) bm_v_result = self.bm.has_this_output_in_my_chain(used_o) tp_v_result = self.tp.has_this_output_in_my_tp(used_o) bm_v_result2 = self.bm.is_valid_output_in_my_chain(used_o) if bm_v_result: print('This TransactionOutput is already used', used_o) return False if tp_v_result: print( 'This TransactionOutput is already stored in the TransactionPool', used_o) return False if bm_v_result2 is not True: print('This TransactionOutput is unknown', used_o) return False return True def _check_availability_of_transaction_in_block(self, transaction): """ Transactionの有効性を検証する(Block用) """ v_result, used_outputs = self.rsa_util.verify_sbc_transaction_sig( transaction) if v_result is not True: print('signature verification error on new transaction') return False print('used_outputs: ', used_outputs) for used_o in used_outputs: print('used_o: ', used_o) bm_v_result = self.bm.has_this_output_in_my_chain(used_o) bm_v_result2 = self.bm.is_valid_output_in_my_chain(used_o) if bm_v_result2 is not True: print('This TransactionOutput is unknown', used_o) return False if bm_v_result: print('This TransactionOutput is already used', used_o) return False return True def get_total_fee_on_block(self, block): """ ブロックに格納されているbasicなTransaction全ての手数料の合計値を算出する """ print('get_total_fee_on_block is called') transactions = block['transactions'] result = 0 for t in transactions: t = json.loads(t) is_sbc_t, t_type = self.um.is_sbc_transaction(t) if t_type == 'basic': total_in = sum( i['transaction']['outputs'][i['output_index']]['value'] for i in t['inputs']) total_out = sum(o['value'] for o in t['outputs']) delta = total_in - total_out result += delta return result def check_transactions_in_new_block(self, block): """ ブロック内のTranactionに不正がないか確認する """ fee_for_block = self.get_total_fee_on_block(block) fee_for_block += 30 print("fee_for_block: ", fee_for_block) transactions = block['transactions'] counter = 0 for t in transactions: t = json.loads(t) # basic, coinbase_transaction以外はスルーチェック is_sbc_t, t_type = self.um.is_sbc_transaction(t) if is_sbc_t: if t_type == 'basic': if self._check_availability_of_transaction_in_block( t) is not True: print('Bad Block. Having invalid Transaction') return False elif t_type == 'coinbase_transaction': if counter != 0: print('Coinbase Transaction is only for BlockBuilder') return False else: insentive = t['outputs'][0]['value'] print('insentive', insentive) if insentive != fee_for_block: print( 'Invalid value in fee for CoinbaseTransaction', insentive) return False else: is_verified = self.rsa_util.verify_general_transaction_sig(t) if is_verified is not True: return False print('ok. this block is acceptable.') return True def __core_api(self, request, message): if request == 'send_message_to_all_peer': new_message = self.cm.get_message_text(MSG_ENHANCED, message) self.cm.send_msg_to_all_peer(new_message) return 'ok' elif request == 'send_message_to_all_edge': new_message = self.cm.get_message_text(MSG_ENHANCED, message) self.cm.send_msg_to_all_edge(new_message) return 'ok' elif request == 'api_type': return 'server_core_api' elif request == 'send_message_to_this_pubkey_address': print('send_message_to_this_pubkey_address', message[0]) msg_type = MSG_ENHANCED msg_txt = self.cm.get_message_text(msg_type, message[1]) check_result, target_host, target_port = self.cm.has_this_edge( message[0]) print('check_result', check_result) if check_result: print('sending cipher direct message to... ', target_host, target_port) self.cm.send_msg((target_host, target_port), msg_txt) return 'ok' else: return None def __handle_message(self, msg, is_core, peer=None): if peer != None: if msg[2] == MSG_REQUEST_FULL_CHAIN: print('Send our latest blockchain for reply to : ', peer) mychain = self.bm.get_my_blockchain() chain_data = pickle.dumps(mychain, 0).decode() new_message = self.cm.get_message_text(RSP_FULL_CHAIN, chain_data) self.cm.send_msg(peer, new_message) else: if msg[2] == MSG_NEW_TRANSACTION: new_transaction = json.loads(msg[4]) print('received new_transaction', new_transaction) is_sbc_t, _ = self.um.is_sbc_transaction(new_transaction) current_transactions = self.tp.get_stored_transactions() if new_transaction in current_transactions: print('this is already pooled transaction: ', new_transaction) return if not is_sbc_t: print('this is not SimpleBitcoin transaction: ', new_transaction) else: # テスト用に最初のブロックだけ未知のCoinbaseTransactionを許すための暫定処置 if self.bm.get_my_chain_length() != 1: checked = self._check_availability_of_transaction( new_transaction) if not checked: print('Transaction Verification Error') return self.tp.set_new_transaction(new_transaction) if not is_core: new_message = self.cm.get_message_text( MSG_NEW_TRANSACTION, json.dumps(new_transaction)) self.cm.send_msg_to_all_peer(new_message) else: if not is_sbc_t: print('this is not SimpleBitcoin transaction: ', new_transaction) else: # テスト用に最初のブロックだけ未知のCoinbaseTransactionを許すための暫定処置 if self.bm.get_my_chain_length() != 1: checked = self._check_availability_of_transaction( new_transaction) if not checked: print('Transaction Verification Error') return self.tp.set_new_transaction(new_transaction) if not is_core: new_message = self.cm.get_message_text( MSG_NEW_TRANSACTION, json.dumps(new_transaction)) self.cm.send_msg_to_all_peer(new_message) elif msg[2] == MSG_NEW_BLOCK: if not is_core: print('block received from unknown') return new_block = json.loads(msg[4]) print('new_block: ', new_block) if self.bm.is_valid_block(self.prev_block_hash, new_block): block_check_result = self.check_transactions_in_new_block( new_block) print('block_check_result : ', block_check_result) if block_check_result is not True: print( 'previous block hash is ok. but still not acceptable.' ) self.get_all_chains_for_resolve_conflict() return # ブロック生成が行われていたら一旦停止してあげる(threadingなのでキレイに止まらない場合あり) if self.is_bb_running: self.flag_stop_block_build = True self.prev_block_hash = self.bm.get_hash(new_block) self.bm.set_new_block(new_block) else: # ブロックとして不正ではないがVerifyにコケる場合は自分がorphanブロックを生成している # 可能性がある self.get_all_chains_for_resolve_conflict() elif msg[2] == RSP_FULL_CHAIN: if not is_core: print('blockchain received from unknown') return # ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証し、有効なものか検証した上で # 自分の持つチェインと比較し優位な方を今後のブロックチェーンとして有効化する new_block_chain = pickle.loads(msg[4].encode('utf8')) result, pool_4_orphan_blocks = self.bm.resolve_conflicts( new_block_chain) print('blockchain received') if result is not None: self.prev_block_hash = result if len(pool_4_orphan_blocks) != 0: # orphanブロック群の中にあった未処理扱いになるTransactionをTransactionPoolに戻す new_transactions = self.bm.get_transactions_from_orphan_blocks( pool_4_orphan_blocks) for t in new_transactions: self.tp.set_new_transaction(t) else: print('Received blockchain is useless...') elif msg[2] == MSG_ENHANCED: # アプリケーションがP2P Network を単なるトランスポートして使うために独自拡張したメッセージはここで処理する。 # SimpleBitcoin としてはこの種別は使わない print('received enhanced message', msg[4]) has_same = self.mpm_store.has_this_msg(msg[4]) if has_same is not True: self.mpm_store.add(msg[4]) self.mpmh.handle_message(msg[4], self.__core_api, is_core) def __get_myip(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('8.8.8.8', 80)) return s.getsockname()[0]
class SimpleBC_Gui(Frame): def __init__(self, parent, my_port, c_host, c_port): Frame.__init__(self, parent) self.parent = parent self.parent.protocol('WM_DELETE_WINDOW', self.quit) self.coin_balance = StringVar(self.parent, '0') self.status_message = StringVar(self.parent, 'Ready') self.c_core = None self.initApp(my_port, c_host, c_port) self.setupGUI() def quit(self, event=None): """ アプリの終了 """ self.c_core.shutdown() self.parent.destroy() def initApp(self, my_port, c_host, c_port): """ ClientCoreとの接続含めて必要な初期化処理はここで実行する """ print('SimpleBitcoin client is now activating ...: ') self.km = KeyManager() self.um = UTXM(self.km.my_address()) self.rsa_util = RSAUtil() self.c_core = Core(my_port, c_host, c_port, self.update_callback) self.c_core.start() # テスト用途(本来はこんな処理しない) t1 = CoinbaseTransaction(self.km.my_address()) t2 = CoinbaseTransaction(self.km.my_address()) t3 = CoinbaseTransaction(self.km.my_address()) transactions = [] transactions.append(t1.to_dict()) transactions.append(t2.to_dict()) transactions.append(t3.to_dict()) self.um.extract_utxos(transactions) self.update_balance() def display_info(self, title, info): """ ダイアログボックスを使ったメッセージの表示 """ f = Tk() label = Label(f, text=title) label.pack() info_area = Text(f, width=70, height=50) info_area.insert(INSERT, info) info_area.pack() def update_callback(self): print('update_callback was called!') s_transactions = self.c_core.get_stored_transactions_from_bc() print(s_transactions) self.um.extract_utxos(s_transactions) self.update_balance() def update_status(self, info): """ 画面下部のステータス表示内容を変更する """ self.status_message.set(info) def update_balance(self): """ 総額表示の内容を最新状態に合わせて変更する """ bal = str(self.um.my_balance) self.coin_balance.set(bal) def create_menu(self): """ メニューバーに表示するメニューを定義する """ top = self.winfo_toplevel() self.menuBar = Menu(top) top['menu'] = self.menuBar self.subMenu = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Menu', menu=self.subMenu) self.subMenu.add_command(label='Show My Address', command=self.show_my_address) self.subMenu.add_command(label='Load my Keys', command=self.show_input_dialog_for_key_loading) self.subMenu.add_command(label='Update Blockchain', command=self.update_block_chain) self.subMenu.add_separator() self.subMenu.add_command(label='Quit', command=self.quit) self.subMenu2 = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Settings', menu=self.subMenu2) self.subMenu2.add_command(label='Renew my Keys', command=self.renew_my_keypairs) self.subMenu3 = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Advance', menu=self.subMenu3) self.subMenu3.add_command(label='Show Blockchain', command=self.show_my_block_chain) def show_my_address(self): f = Tk() label = Label(f, text='My Address') label.pack() key_info = Text(f, width=70, height=10) my_address = self.km.my_address() key_info.insert(INSERT, my_address) key_info.pack() def show_input_dialog_for_key_loading(self): def load_my_keys(): # ファイル選択ダイアログの表示 f2 = Tk() f2.withdraw() fTyp = [('','*.pem')] iDir = os.path.abspath(os.path.dirname(__file__)) messagebox.showinfo('Load key pair','please choose your key file') f_name = filedialog.askopenfilename(filetypes = fTyp,initialdir = iDir) try: file = open(f_name) data = file.read() target = binascii.unhexlify(data) # TODO: 本来は鍵ペアのファイルが不正などの異常系処理を考えるべき self.km.import_key_pair(target, p_phrase.get()) except Exception as e: print(e) finally: # TODO: 所有コインの再確認処理を入れる必要あり file.close() f.destroy() f2.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance() f = Tk() label0 = Label(f, text='Please enter pass phrase for your key pair') frame1 = ttk.Frame(f) label1 = ttk.Label(frame1, text='Pass Phrase:') p_phrase = StringVar() entry1 = ttk.Entry(frame1, textvariable=p_phrase) button1 = ttk.Button(frame1, text='Load', command=load_my_keys) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label1.grid(row=2,column=0,sticky=E) entry1.grid(row=2,column=1,sticky=W) button1.grid(row=3,column=1,sticky=W) def update_block_chain(self): self.c_core.send_req_full_chain_to_my_core_node() def renew_my_keypairs(self): """ 利用する鍵ペアを更新する。 """ def save_my_pem(): self.km = KeyManager() my_pem = self.km.export_key_pair(p_phrase) my_pem_hex = binascii.hexlify(my_pem).decode('ascii') # とりあえずファイル名は固定 path = 'my_key_pair.pem' f1 = open(path,'a') f1.write(my_pem_hex) f1.close() f.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance() f = Tk() f.title('New Key Gene') label0 = Label(f, text='Please enter pass phrase for your new key pair') frame1 = ttk.Frame(f) label1 = ttk.Label(frame1, text='Pass Phrase:') p_phrase = StringVar() entry1 = ttk.Entry(frame1, textvariable=p_phrase) button1 = ttk.Button(frame1, text='Generate', command=save_my_pem) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label1.grid(row=2,column=0,sticky=E) entry1.grid(row=2,column=1,sticky=W) button1.grid(row=3,column=1,sticky=W) def show_my_block_chain(self): """ 自分が保持しているブロックチェーンの中身を確認する """ mychain = self.c_core.get_my_blockchain() if mychain is not None: mychain_str = pprint.pformat(mychain, indent=2) self.display_info('Current Blockchain', mychain_str) else: self.display_info('Warning', 'Currently Blockchain is empty...') def setupGUI(self): """ 画面に必要なパーツを並べる """ self.parent.bind('<Control-q>', self.quit) self.parent.title('SimpleBitcoin GUI') self.pack(fill=BOTH, expand=1) self.create_menu() lf = LabelFrame(self, text='Current Balance') lf.pack(side=TOP, fill='both', expand='yes', padx=7, pady=7) lf2 = LabelFrame(self, text='') lf2.pack(side=BOTTOM, fill='both', expand='yes', padx=7, pady=7) #所持コインの総額表示領域のラベル self.balance = Label(lf, textvariable=self.coin_balance, font='Helvetica 20') self.balance.pack() #受信者となる相手の公開鍵 self.label = Label(lf2, text='Recipient Address:') self.label.grid(row=0, pady=5) self.recipient_pubkey = Entry(lf2, bd=2) self.recipient_pubkey.grid(row=0, column=1, pady=5) # 送金額 self.label2 = Label(lf2, text='Amount to pay :') self.label2.grid(row=1, pady=5) self.amountBox = Entry(lf2, bd=2) self.amountBox.grid(row=1, column=1, pady=5, sticky='NSEW') # 手数料 self.label3 = Label(lf2, text='Fee (Optional) :') self.label3.grid(row=2, pady=5) self.feeBox = Entry(lf2, bd=2) self.feeBox.grid(row=2, column=1, pady=5, sticky='NSEW') # 間隔の開け方がよくわからんので空文字で場所確保 self.label4 = Label(lf2, text='') self.label4.grid(row=5, pady=5) # 送金実行ボタン self.sendBtn = Button(lf2, text='\nSend Coin(s)\n', command=self.sendCoins) self.sendBtn.grid(row=6, column=1, sticky='NSEW') # 下部に表示するステータスバー stbar = Label(self.winfo_toplevel(), textvariable=self.status_message, bd=1, relief=SUNKEN, anchor=W) stbar.pack(side=BOTTOM, fill=X) # 送金実行ボタン押下時の処理実体 def sendCoins(self): sendAtp = self.amountBox.get() recipientKey = self.recipient_pubkey.get() sendFee = self.feeBox.get() utxo_len = len(self.um.utxo_txs) if not sendAtp: messagebox.showwarning('Warning', 'Please enter the Amount to pay.') return elif len(recipientKey) <= 1: messagebox.showwarning('Warning', 'Please enter the Recipient Address.') return else: result = messagebox.askyesno('Confirmation', 'Sending {} SimpleBitcoins to :\n {}'.format(sendAtp, recipientKey)) if not sendFee: sendFee = 0 if result: if 0 < utxo_len: print('Sending {} SimpleBitcoins to reciever:\n {}'.format(sendAtp, recipientKey)) else: messagebox.showwarning('Short of Coin.', 'Not enough coin to be sent...') return utxo, idx = self.um.get_utxo_tx(0) t = Transaction( [TransactionInput(utxo, idx)], [TransactionOutput(recipientKey, int(sendAtp))] ) counter = 1 # TransactionInputが送信額を超えるまで繰り返して取得しTransactionとして完成させる if type(sendFee) is not str: sendFee = int(sendFee) while t.is_enough_inputs(sendFee) is not True: new_utxo, new_idx = self.um.get_utxo_tx(counter) t.inputs.append(TransactionInput(new_utxo, new_idx)) counter += 1 if counter > utxo_len: messagebox.showwarning('Short of Coin.', 'Not enough coin to be sent...') break # 正常なTransactionが生成できた時だけ秘密鍵で署名を実行する if t.is_enough_inputs(sendFee) is True: # まずお釣り用Transactionを作る change = t.compute_change(sendFee) t.outputs.append(TransactionOutput(self.km.my_address(), change)) to_be_signed = json.dumps(t.to_dict(), sort_keys=True) signed = self.km.compute_digital_signature(to_be_signed) new_tx = json.loads(to_be_signed) new_tx['signature'] = signed # TransactionをP2P Networkに送信 tx_strings = json.dumps(new_tx) self.c_core.send_message_to_my_core_node(MSG_NEW_TRANSACTION, tx_strings) print('signed new_tx:', tx_strings) # 実験的にお釣り分の勘定のため新しく生成したTransactionをUTXOとして追加しておくが # 本来はブロックチェーンの更新に合わせて再計算した方が適切 self.um.put_utxo_tx(t.to_dict()) to_be_deleted = 0 del_list = [] while to_be_deleted < counter: del_tx = self.um.get_utxo_tx(to_be_deleted) del_list.append(del_tx) to_be_deleted += 1 for dx in del_list: self.um.remove_utxo_tx(dx) self.amountBox.delete(0,END) self.feeBox.delete(0,END) self.recipient_pubkey.delete(0,END) self.update_balance()
class SimpleBC_Gui(Frame): def __init__(self, parent, my_port, c_host, c_port): Frame.__init__(self, parent) self.parent = parent self.parent.protocol('WM_DELETE_WINDOW', self.quit) self.coin_balance = StringVar(self.parent, '0') self.status_message = StringVar(self.parent, 'Ready') self.c_core = None self.initApp(my_port, c_host, c_port) self.setupGUI() def quit(self, event=None): """ アプリの終了 """ self.c_core.shutdown() self.parent.destroy() def initApp(self, my_port, c_host, c_port): """ ClientCoreとの接続含めて必要な初期化処理はここで実行する """ print('SimpleBitcoin client is now activating ...: ') self.km = KeyManager() self.um = UTXM(self.km.my_address()) self.rsa_util = RSAUtil() self.c_core = Core(my_port, c_host, c_port, self.update_callback, self.get_message_callback) self.c_core.start(self.km.my_address()) # テスト用途(本来はこんな処理しない) t1 = CoinbaseTransaction(self.km.my_address()) t2 = CoinbaseTransaction(self.km.my_address()) t3 = CoinbaseTransaction(self.km.my_address()) transactions = [] transactions.append(t1.to_dict()) transactions.append(t2.to_dict()) transactions.append(t3.to_dict()) self.um.extract_utxos(transactions) self.update_balance() def display_info(self, title, info): """ ダイアログボックスを使ったメッセージの表示 """ f = Tk() label = Label(f, text=title) label.pack() info_area = Text(f, width=70, height=50) info_area.insert(INSERT, info) info_area.pack() def get_message_callback(self, target_message): print('get_message_callback called!') if target_message['message_type'] == 'cipher_message': try: encrypted_key = base64.b64decode(binascii.unhexlify(target_message['enc_key'])) print('encripted_key : ', encrypted_key) decrypted_key = self.km.decrypt_with_private_key(encrypted_key) print('decrypted_key : ', binascii.hexlify(decrypted_key).decode('ascii')) # 流石に名前解決をしないで公開鍵のまま出しても意味なさそうなのでコメントアウト #sender = binascii.unhexlify(target_message['sender']) aes_util = AESUtil() decrypted_message = aes_util.decrypt_with_key(base64.b64decode(binascii.unhexlify(target_message['body'])), decrypted_key) print(decrypted_message.decode('utf-8')) """ message = { 'from' : sender, 'message' : decrypted_message.decode('utf-8') } message_4_display = pprint.pformat(message, indent=2) """ messagebox.showwarning('You received an instant encrypted message !', decrypted_message.decode('utf-8')) except Exception as e: print(e, 'error occurred') elif target_message['message_type'] == 'engraved': sender_name = target_message['sender_alt_name'] msg_body = base64.b64decode(binascii.unhexlify(target_message['message'])).decode('utf-8') timestamp = datetime.datetime.fromtimestamp(int(target_message['timestamp'])) messagebox.showwarning('You received a new engraved message!', '{} :\n {} \n {}'.format(sender_name, msg_body, timestamp)) def update_callback(self): print('update_callback was called!') s_transactions = self.c_core.get_stored_transactions_from_bc() print(s_transactions) self.um.extract_utxos(s_transactions) self.update_balance() def update_status(self, info): """ 画面下部のステータス表示内容を変更する """ self.status_message.set(info) def update_balance(self): """ 総額表示の内容を最新状態に合わせて変更する """ bal = str(self.um.my_balance) self.coin_balance.set(bal) def create_menu(self): """ メニューバーに表示するメニューを定義する """ top = self.winfo_toplevel() self.menuBar = Menu(top) top['menu'] = self.menuBar self.subMenu = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Menu', menu=self.subMenu) self.subMenu.add_command(label='Show My Address', command=self.show_my_address) self.subMenu.add_command(label='Load my Keys', command=self.show_input_dialog_for_key_loading) self.subMenu.add_command(label='Update Blockchain', command=self.update_block_chain) self.subMenu.add_separator() self.subMenu.add_command(label='Quit', command=self.quit) self.subMenu2 = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Settings', menu=self.subMenu2) self.subMenu2.add_command(label='Renew my Keys', command=self.renew_my_keypairs) self.subMenu3 = Menu(self.menuBar, tearoff=0) self.menuBar.add_cascade(label='Advance', menu=self.subMenu3) self.subMenu3.add_command(label='Send Encrypted Instant Message', command=self.send_instant_message) self.subMenu3.add_command(label='Show Logs (Received)', command=self.open_r_log_window) self.subMenu3.add_command(label='Show Logs (Send)', command=self.open_s_log_window) self.subMenu3.add_command(label='Show Blockchain', command=self.show_my_block_chain) self.subMenu3.add_command(label='Engrave Message', command=self.engrave_message) def show_my_address(self): f = Tk() label = Label(f, text='My Address') label.pack() key_info = Text(f, width=70, height=10) my_address = self.km.my_address() key_info.insert(INSERT, my_address) key_info.pack() def show_input_dialog_for_key_loading(self): def load_my_keys(): # ファイル選択ダイアログの表示 f2 = Tk() f2.withdraw() fTyp = [('','*.pem')] iDir = os.path.abspath(os.path.dirname(__file__)) messagebox.showinfo('Load key pair','please choose your key file') f_name = filedialog.askopenfilename(filetypes = fTyp,initialdir = iDir) try: file = open(f_name) data = file.read() target = binascii.unhexlify(data) # TODO: 本来は鍵ペアのファイルが不正などの異常系処理を考えるべき self.km.import_key_pair(target, p_phrase.get()) except Exception as e: print(e) finally: # TODO: 所有コインの再確認処理を入れる必要あり file.close() f.destroy() f2.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance() f = Tk() label0 = Label(f, text='Please enter pass phrase for your key pair') frame1 = ttk.Frame(f) label1 = ttk.Label(frame1, text='Pass Phrase:') p_phrase = StringVar() entry1 = ttk.Entry(frame1, textvariable=p_phrase) button1 = ttk.Button(frame1, text='Load', command=load_my_keys) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label1.grid(row=2,column=0,sticky=E) entry1.grid(row=2,column=1,sticky=W) button1.grid(row=3,column=1,sticky=W) def update_block_chain(self): self.c_core.send_req_full_chain_to_my_core_node() def renew_my_keypairs(self): """ 利用する鍵ペアを更新する。 """ def save_my_pem(): self.km = KeyManager() my_pem = self.km.export_key_pair(p_phrase) my_pem_hex = binascii.hexlify(my_pem).decode('ascii') # とりあえずファイル名は固定 path = 'my_key_pair.pem' f1 = open(path,'a') f1.write(my_pem_hex) f1.close() f.destroy() self.um = UTXM(self.km.my_address()) self.um.my_balance = 0 self.update_balance() f = Tk() f.title('New Key Gene') label0 = Label(f, text='Please enter pass phrase for your new key pair') frame1 = ttk.Frame(f) label1 = ttk.Label(frame1, text='Pass Phrase:') p_phrase = StringVar() entry1 = ttk.Entry(frame1, textvariable=p_phrase) button1 = ttk.Button(frame1, text='Generate', command=save_my_pem) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label1.grid(row=2,column=0,sticky=E) entry1.grid(row=2,column=1,sticky=W) button1.grid(row=3,column=1,sticky=W) def send_instant_message(self): def send_message(): r_pkey = entry1.get() print('pubkey', r_pkey) new_message = {} aes_util = AESUtil() cipher_txt = aes_util.encrypt(entry2.get()) new_message['message_type'] = 'cipher_message' new_message['recipient'] = r_pkey new_message['sender'] = self.km.my_address() new_message['body'] = binascii.hexlify(base64.b64encode(cipher_txt)).decode('ascii') key = aes_util.get_aes_key() encrypted_key = self.rsa_util.encrypt_with_pubkey(key, r_pkey) print('encrypted_key: ', encrypted_key[0]) new_message['enc_key'] = binascii.hexlify(base64.b64encode(encrypted_key[0])).decode('ascii') msg_type = MSG_ENHANCED message_strings = json.dumps(new_message) self.c_core.send_message_to_my_core_node(msg_type, message_strings) f.destroy() f = Tk() f.title('New Message') label0 = Label(f, text='Please input recipient address and message') frame1 = ttk.Frame(f) label1 = ttk.Label(frame1, text='Recipient:') pkey = StringVar() entry1 = ttk.Entry(frame1, textvariable=pkey) label2 = ttk.Label(frame1, text='Message:') message = StringVar() entry2 = ttk.Entry(frame1, textvariable=message) button1 = ttk.Button(frame1, text='Send Message', command=send_message) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label1.grid(row=2,column=0,sticky=E) entry1.grid(row=2,column=1,sticky=W) label2.grid(row=3,column=0,sticky=E) entry2.grid(row=3,column=1,sticky=W) button1.grid(row=4,column=1,sticky=W) def open_r_log_window(self): """ 別ウィンドウでログ情報を表示する。これまでに受け取った自分宛のTransactionを時系列で並べる """ s_transactions = self.c_core.get_stored_transactions_from_bc() my_transactions = self.um.get_txs_to_my_address(s_transactions) informations = [] receive_date = None sender = None value = None reason = None description = None for t in my_transactions: result, t_type = self.um.is_sbc_transaction(t) receive_date = datetime.datetime.fromtimestamp(int(t['timestamp'])) if t_type == 'basic': reason = base64.b64decode(binascii.unhexlify(t['extra']['reason'])).decode('utf-8') description = base64.b64decode(binascii.unhexlify(t['extra']['description'])).decode('utf-8') for txout in t['outputs']: recipient = txout['recipient'] if recipient == self.km.my_address(): value = txout['value'] for txin in t['inputs']: t_in_txin = txin['transaction'] idx = txin['output_index'] sender = t_in_txin['outputs'][idx]['recipient'] if sender == self.km.my_address(): sender = 'Change to myself' else: reason = 'CoinbaseTransaction' description = 'CoinbaseTransaction' sender = self.km.my_address() for txout in t['outputs']: recipient = txout['recipient'] if recipient == self.km.my_address(): value = txout['value'] info = { 'date' : receive_date, 'From' : sender, 'Value' : value, 'reason' : reason, 'description' : description } informations.append(info) log = pprint.pformat(informations, indent=2) if log is not None: self.display_info('Log : Received Transaction', log) else: self.display_info('Warning', 'Currently you received NO Transaction to you...') def open_s_log_window(self): """ 別ウィンドウでログ情報を表示する。これまでに自分が送信したTransactionを時系列で並べる """ s_transactions = self.c_core.get_stored_transactions_from_bc() my_transactions = self.um.get_txs_from_my_address(s_transactions) informations = [] send_date = None recipient = None value = None reason = None description = None for t in my_transactions: result, t_type = self.um.is_sbc_transaction(t) send_date = datetime.datetime.fromtimestamp(int(t['timestamp'])) if t_type == 'basic': reason = base64.b64decode(binascii.unhexlify(t['extra']['reason'])).decode('utf-8') description = base64.b64decode(binascii.unhexlify(t['extra']['description'])).decode('utf-8') for txout in t['outputs']: recipient = txout['recipient'] if recipient == self.km.my_address(): recipient = 'Change to myself' value = txout['value'] info = { 'date' : send_date, 'To' : recipient, 'Value' : value, 'reason' : reason, 'description' : description } informations.append(info) log = pprint.pformat(informations, indent=2) if log is not None: self.display_info('Log : Sent Transaction', log) else: self.display_info('Warning', 'NO Transaction which was sent from you...') def show_my_block_chain(self): """ 自分が保持しているブロックチェーンの中身を確認する """ mychain = self.c_core.get_my_blockchain() if mychain is not None: mychain_str = pprint.pformat(mychain, indent=2) self.display_info('Current Blockchain', mychain_str) else: self.display_info('Warning', 'Currently Blockchain is empty...') def engrave_message(self): """ ブロックチェーンにTwitter風のメッセージを格納する """ def send_e_message(): # P2PによるブロードキャストとTransactionのハイブリッド new_message = {} msg_txt = entry.get().encode('utf-8') msg = EngravedTransaction( self.km.my_address(), 'Testman', binascii.hexlify(base64.b64encode(msg_txt)).decode('ascii') ) to_be_signed = json.dumps(msg.to_dict(), sort_keys=True) signed = self.km.compute_digital_signature(to_be_signed) new_tx = json.loads(to_be_signed) new_tx['signature'] = signed tx_strings = json.dumps(new_tx) self.c_core.send_message_to_my_core_node(MSG_NEW_TRANSACTION, tx_strings) new_tx2 = copy.deepcopy(new_tx) new_tx2['message_type'] = 'engraved' tx_strings2 = json.dumps(new_tx2) self.c_core.send_message_to_my_core_node(MSG_ENHANCED, tx_strings2) f.destroy() f = Tk() f.title('Engrave New Message') label0 = Label(f, text='Any idea?') frame1 = ttk.Frame(f) label = ttk.Label(frame1, text='Message:') entry = ttk.Entry(frame1, width=30) button1 = ttk.Button(frame1, text='Engrave this on Blockchain', command=send_e_message) label0.grid(row=0,column=0,sticky=(N,E,S,W)) frame1.grid(row=1,column=0,sticky=(N,E,S,W)) label.grid(row=2,column=0,sticky=E) entry.grid(row=2,column=1,sticky=W) button1.grid(row=3,column=1,sticky=W) def setupGUI(self): """ 画面に必要なパーツを並べる """ self.parent.bind('<Control-q>', self.quit) self.parent.title('SimpleBitcoin GUI') self.pack(fill=BOTH, expand=1) self.create_menu() lf = LabelFrame(self, text='Current Balance') lf.pack(side=TOP, fill='both', expand='yes', padx=7, pady=7) lf2 = LabelFrame(self, text='') lf2.pack(side=BOTTOM, fill='both', expand='yes', padx=7, pady=7) #所持コインの総額表示領域のラベル self.balance = Label(lf, textvariable=self.coin_balance, font='Helvetica 20') self.balance.pack() #受信者となる相手の公開鍵 self.label = Label(lf2, text='Recipient Address:') self.label.grid(row=0, pady=5) self.recipient_pubkey = Entry(lf2, bd=2) self.recipient_pubkey.grid(row=0, column=1, pady=5) # 送金額 self.label2 = Label(lf2, text='Amount to pay :') self.label2.grid(row=1, pady=5) self.amountBox = Entry(lf2, bd=2) self.amountBox.grid(row=1, column=1, pady=5, sticky='NSEW') # 手数料 self.label3 = Label(lf2, text='Fee (Optional) :') self.label3.grid(row=2, pady=5) self.feeBox = Entry(lf2, bd=2) self.feeBox.grid(row=2, column=1, pady=5, sticky='NSEW') # 送金理由を書く欄(主にURLとか) self.label4 = Label(lf2, text='reason (Optional) :') self.label4.grid(row=3, pady=5) self.reasonBox = Entry(lf2, bd=2) self.reasonBox.grid(row=3, column=1, pady=5, sticky='NSEW') # 通信欄(公開メッセージ) self.label5 = Label(lf2, text='message (Optional) :') self.label5.grid(row=4, pady=5) self.messageBox = Entry(lf2, bd=2) self.messageBox.grid(row=4, column=1, pady=5, sticky='NSEW') # 間隔の開け方がよくわからんので空文字で場所確保 self.label4 = Label(lf2, text='') self.label4.grid(row=5, pady=5) # 送金実行ボタン self.sendBtn = Button(lf2, text='\nSend Coin(s)\n', command=self.sendCoins) self.sendBtn.grid(row=6, column=1, sticky='NSEW') # 下部に表示するステータスバー stbar = Label(self.winfo_toplevel(), textvariable=self.status_message, bd=1, relief=SUNKEN, anchor=W) stbar.pack(side=BOTTOM, fill=X) # 送金実行ボタン押下時の処理実体 def sendCoins(self): sendAtp = self.amountBox.get() recipientKey = self.recipient_pubkey.get() sendFee = self.feeBox.get() reason = binascii.hexlify(base64.b64encode(self.reasonBox.get().encode('utf-8'))).decode('ascii') desc = binascii.hexlify(base64.b64encode(self.messageBox.get().encode('utf-8'))).decode('ascii') utxo_len = len(self.um.utxo_txs) if not sendAtp: messagebox.showwarning('Warning', 'Please enter the Amount to pay.') return elif len(recipientKey) <= 1: messagebox.showwarning('Warning', 'Please enter the Recipient Address.') return else: result = messagebox.askyesno('Confirmation', 'Sending {} SimpleBitcoins to :\n {}'.format(sendAtp, recipientKey)) if not sendFee: sendFee = 0 if not reason: reason = 'No information' if not desc: desc = 'No description' extra = { 'reason': reason, 'description': desc, } if result: if 0 < utxo_len: print('Sending {} SimpleBitcoins to reciever:\n {}'.format(sendAtp, recipientKey)) else: messagebox.showwarning('Short of Coin.', 'Not enough coin to be sent...') return utxo, idx = self.um.get_utxo_tx(0) t = Transaction( [TransactionInput(utxo, idx)], [TransactionOutput(recipientKey, int(sendAtp))], extra ) counter = 1 # TransactionInputが送信額を超えるまで繰り返して取得しTransactionとして完成させる if type(sendFee) is not str: sendFee = int(sendFee) while t.is_enough_inputs(sendFee) is not True: new_utxo, new_idx = self.um.get_utxo_tx(counter) t.inputs.append(TransactionInput(new_utxo, new_idx)) counter += 1 if counter > utxo_len: messagebox.showwarning('Short of Coin.', 'Not enough coin to be sent...') break # 正常なTransactionが生成できた時だけ秘密鍵で署名を実行する if t.is_enough_inputs(sendFee) is True: # まずお釣り用Transactionを作る change = t.compute_change(sendFee) t.outputs.append(TransactionOutput(self.km.my_address(), change)) to_be_signed = json.dumps(t.to_dict(), sort_keys=True) signed = self.km.compute_digital_signature(to_be_signed) new_tx = json.loads(to_be_signed) new_tx['signature'] = signed # TransactionをP2P Networkに送信 msg_type = MSG_NEW_TRANSACTION tx_strings = json.dumps(new_tx) self.c_core.send_message_to_my_core_node(msg_type, tx_strings) print('signed new_tx:', tx_strings) # 実験的にお釣り分の勘定のため新しく生成したTransactionをUTXOとして追加しておくが # 本来はブロックチェーンの更新に合わせて再計算した方が適切 self.um.put_utxo_tx(t.to_dict()) to_be_deleted = 0 del_list = [] while to_be_deleted < counter: del_tx = self.um.get_utxo_tx(to_be_deleted) del_list.append(del_tx) to_be_deleted += 1 for dx in del_list: self.um.remove_utxo_tx(dx) self.amountBox.delete(0,END) self.feeBox.delete(0,END) self.recipient_pubkey.delete(0,END) self.reasonBox.delete(0,END) self.messageBox.delete(0,END) self.update_balance()