def get_address_and_pubkey(main_ui, bip32_path): client = main_ui.hw_client if client: bip32_path.strip() if bip32_path.lower().find('m/') >= 0: # removing m/ prefix because of keepkey library bip32_path = bip32_path[2:] if main_ui.config.hw_type in (HWType.trezor, HWType.keepkey): if isinstance(bip32_path, str): # trezor/keepkey require bip32 path argument as an array of integers bip32_path = client.expand_path(bip32_path) return { 'address': client.get_address('Dash', bip32_path, False), 'publicKey': client.get_public_node(bip32_path).node.public_key } elif main_ui.config.hw_type == HWType.ledger_nano_s: import hw_intf_ledgernano as ledger if isinstance(bip32_path, list): # ledger requires bip32 path argument as a string bip32_path = bip32_path_n_to_string(bip32_path) return ledger.get_address_and_pubkey(client, bip32_path) else: raise Exception('Unknown hwardware wallet type: ' + main_ui.config.hw_type)
def get_address(hw_session: HwSessionInfo, bip32_path): client = hw_session.hw_client if client: if isinstance(bip32_path, str): bip32_path.strip() if bip32_path.lower().find('m/') >= 0: # removing m/ prefix because of keepkey library bip32_path = bip32_path[2:] if hw_session.app_config.hw_type in (HWType.trezor, HWType.keepkey): if isinstance(bip32_path, str): # trezor/keepkey require bip32 path argument as an array of integers bip32_path = client.expand_path(bip32_path) return client.get_address(hw_session.app_config.hw_coin_name, bip32_path, False) elif hw_session.app_config.hw_type == HWType.ledger_nano_s: import hw_intf_ledgernano as ledger if isinstance(bip32_path, list): # ledger requires bip32 path argument as a string bip32_path = bip32_path_n_to_string(bip32_path) adr_pubkey = ledger.get_address_and_pubkey(client, bip32_path) return adr_pubkey.get('address') else: raise Exception('Unknown hwardware wallet type: ' + hw_session.app_config.hw_type) else: raise Exception('HW client not open.')
def get_xpub(hw_session: HwSessionInfo, bip32_path): client = hw_session.hw_client if client: if isinstance(bip32_path, str): bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] if hw_session.app_config.hw_type == HWType.trezor: from trezorlib import btc if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return btc.get_public_node(client, bip32_path).xpub elif hw_session.app_config.hw_type == HWType.keepkey: if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return client.get_public_node(bip32_path).xpub elif hw_session.app_config.hw_type == HWType.ledger_nano_s: import hw_intf_ledgernano as ledger if isinstance(bip32_path, list): # ledger requires bip32 path argument as a string bip32_path = bip32_path_n_to_string(bip32_path) return ledger.get_xpub(client, bip32_path) else: raise Exception('Unknown hardware wallet type: ' + hw_session.app_config.hw_type) else: raise Exception('HW client not open.')
def prepare_hw_encryption_attrs(hw_session: HwSessionInfo, label: str) -> \ Tuple[int, int, List[int], bytes, bytes, bytes]: """ :param hw_session: :param label: :return: 0: protocol id 1: hw type id (see below) 2: bip32 path to the entryption key 3: encryption key hash 4: encryption key binary 5: pub key hash of the encryption key """ # generate a new random password which will be used to encrypt with Trezor method + Fernet protocol = 1 hw_type_bin = { HWType.trezor: 1, HWType.keepkey: 2, HWType.ledger_nano: 3 }[hw_session.hw_type] key = Fernet.generate_key() # encryption key key_bin = base64.urlsafe_b64decode(key) bip32_path_n = [10, 100, 1000] if hw_session.hw_type in (HWType.trezor, HWType.keepkey): # for trezor method, for encryption we use the raw key and the key encrypted with a device # will be part of a header encrypted_key_bin, pub_key = hw_session.hw_encrypt_value(bip32_path_n, label=label, value=key_bin) pub_key_hash = SHA256.new(pub_key).digest() return protocol, hw_type_bin, bip32_path_n, key, encrypted_key_bin, pub_key_hash elif hw_session.hw_type == HWType.ledger_nano: # Ledger Nano S does not have encryption/decryption features, so for encryption and decryption will use # a hash of a signed message, where the message the raw key itself; # The raw key will be part of the encrypted header. display_label = f'<b>Click the sign message confirmation button on the <br>hardware wallet to ' \ f'encrypt \'{label}\'.</b>' bip32_path_str = bip32_path_n_to_string(bip32_path_n) sig = hw_sign_message(hw_session, 'Dash', bip32_path_str, key_bin.hex(), display_label=display_label) adr_pk = get_address_and_pubkey(hw_session, 'Dash', bip32_path_str) pub_key_hash = SHA256.new(adr_pk.get('publicKey')).digest() enc_key_hash = SHA256.new(sig.signature).digest() enc_key_hash = base64.urlsafe_b64encode(enc_key_hash) return protocol, hw_type_bin, bip32_path_n, enc_key_hash, key_bin, pub_key_hash
def get_child_entry(self, index) -> 'Bip44Entry': child = self.child_entries.get(index) if not child: key = self.get_bip32key() child_key = key.ChildKey(index) child_xpub = child_key.ExtendedKey(False, True) if self.bip32_path: bip32_path_n = bip32_path_string_to_n(self.bip32_path) bip32_path_n.append(index) bip32_path = bip32_path_n_to_string(bip32_path_n) else: raise Exception('Unknown BIP32 path of the parrent') child = Bip44Entry(tree_id=self.tree_id, id=None, parent=self, xpub=child_xpub, address_index=index, bip32_path=bip32_path, bip32_key=child_key) self.child_entries[index] = child return child
def get_address_and_pubkey(hw_session: HwSessionInfo, bip32_path): client = hw_session.hw_client if client: if isinstance(bip32_path, str): bip32_path.strip() if bip32_path.lower().find('m/') >= 0: # removing m/ prefix because of keepkey library bip32_path = bip32_path[2:] if hw_session.app_config.hw_type == HWType.trezor: from trezorlib import btc if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return { 'address': btc.get_address(client, hw_session.app_config.hw_coin_name, bip32_path, False), 'publicKey': btc.get_public_node(client, bip32_path).node.public_key } elif hw_session.app_config.hw_type == HWType.keepkey: if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return { 'address': client.get_address(hw_session.app_config.hw_coin_name, bip32_path, False), 'publicKey': client.get_public_node(bip32_path).node.public_key } elif hw_session.app_config.hw_type == HWType.ledger_nano_s: import hw_intf_ledgernano as ledger if isinstance(bip32_path, list): # ledger requires bip32 path argument as a string bip32_path = bip32_path_n_to_string(bip32_path) return ledger.get_address_and_pubkey(client, bip32_path) else: raise Exception('Unknown hardware wallet type: ' + hw_session.app_config.hw_type)
def _get_address(ctrl, hw_session: HwSessionInfo, bip32_path: str, show_display: bool = False, message_to_display: str = None): if ctrl: ctrl.dlg_config_fun(dlg_title="Please confirm", show_progress_bar=False) if message_to_display: ctrl.display_msg_fun(message_to_display) else: ctrl.display_msg_fun('<b>Click the confirmation button on your hardware wallet to exit...</b>') client = hw_session.hw_client if client: if isinstance(bip32_path, str): bip32_path.strip() if bip32_path.lower().find('m/') >= 0: # removing m/ prefix because of keepkey library bip32_path = bip32_path[2:] if hw_session.app_config.hw_type == HWType.trezor: from trezorlib import btc if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return btc.get_address(client, hw_session.app_config.hw_coin_name, bip32_path, show_display) elif hw_session.app_config.hw_type == HWType.keepkey: if isinstance(bip32_path, str): bip32_path = dash_utils.bip32_path_string_to_n(bip32_path) return client.get_address(hw_session.app_config.hw_coin_name, bip32_path, show_display) elif hw_session.app_config.hw_type == HWType.ledger_nano_s: import hw_intf_ledgernano as ledger if isinstance(bip32_path, list): # ledger requires bip32 path argument as a string bip32_path = bip32_path_n_to_string(bip32_path) adr_pubkey = ledger.get_address_and_pubkey(client, bip32_path) return adr_pubkey.get('address') else: raise Exception('Unknown hardware wallet type: ' + hw_session.app_config.hw_type) else: raise Exception('HW client not open.')
def read_file_encrypted( file_name: str, ret_attrs: dict, hw_session: HwSessionInfo) -> Generator[bytes, None, None]: ret_attrs['encrypted'] = False try: hw_session.save_state() with open(file_name, 'rb') as f_ptr: data = f_ptr.read(len(DMT_ENCRYPTED_DATA_PREFIX)) if data == DMT_ENCRYPTED_DATA_PREFIX: ret_attrs['encrypted'] = True protocol = read_varint_from_file(f_ptr) if protocol == 1: # with Trezor method + Fernet hw_type_bin = read_varint_from_file(f_ptr) hw_type = { 1: HWType.trezor, 2: HWType.keepkey, 3: HWType.ledger_nano }.get(hw_type_bin) if hw_type: # connect hardware wallet, choosing the type compatible with the type read from # the encrypted file if hw_session.hw_client: if (hw_type in (HWType.trezor, HWType.keepkey) and hw_session.hw_type not in (HWType.trezor, HWType.keepkey)) or \ (hw_type == HWType.ledger_nano and hw_type != hw_session.hw_type): # if the currently connected hardware wallet type is not compatible with the # type from the encrypted file, disconnect it to give a user a chance to choose # the correct one in the code below hw_session.disconnect_hardware_wallet() if not hw_session.hw_client: if hw_type in (HWType.trezor, HWType.keepkey): hw_session.set_hw_types_allowed( (HWType.trezor, HWType.keepkey)) else: hw_session.set_hw_types_allowed((hw_type, )) if not hw_session.connect_hardware_wallet(): raise HWNotConnectedException( f'This file was encrypted with {HWType.get_desc(hw_type)} hardware wallet, ' f'which has to be connected to the computer decrypt the file.' ) data_label_bin = read_bytes_from_file(f_ptr) label = base64.urlsafe_b64decode( data_label_bin).decode('utf-8') encrypted_key_bin = read_bytes_from_file(f_ptr) bip32_path_n = read_int_list_from_file(f_ptr) pub_key_hash_hdr = read_bytes_from_file(f_ptr) while True: if not hw_session.hw_client: raise HWNotConnectedException( f'This file was encrypted with {HWType.get_desc(hw_type)} hardware wallet, ' f'which has to be connected to the computer decrypt the file.' ) if hw_session.hw_type in (HWType.trezor, HWType.keepkey): key_bin, pub_key = hw_session.hw_decrypt_value( bip32_path_n, label=label, value=encrypted_key_bin) elif hw_session.hw_type == HWType.ledger_nano: display_label = f'<b>Click the sign message confirmation button on the <br>' \ f'hardware wallet to decrypt \'{label}\'.</b>' bip32_path_str = bip32_path_n_to_string( bip32_path_n) sig = hw_sign_message( hw_session, 'Dash', bip32_path_str, encrypted_key_bin.hex(), display_label=display_label) adr_pk = get_address_and_pubkey( hw_session, 'Dash', bip32_path_str) pub_key = adr_pk.get('publicKey') key_bin = SHA256.new(sig.signature).digest() else: raise Exception( 'Invalid hardware wallet type.') pub_key_hash = SHA256.new(pub_key).digest() if pub_key_hash_hdr == pub_key_hash: break url = get_note_url('DMT0003') if WndUtils.query_dlg( message= 'Inconsistency between encryption and decryption keys.\n\n' 'The reason may be using a different passphrase than it was used ' 'for encryption or running another application communicating with the ' 'device simultaneously, like Trezor web wallet (see <a href="{url}">' 'here</a>).\n\n' 'Do you want to try again?', buttons=QMessageBox.Yes | QMessageBox.Cancel, default_button=QMessageBox.Cancel, icon=QMessageBox.Warning ) == QMessageBox.Cancel: raise CancelException('User cancelled.') hw_session.disconnect_hardware_wallet() hw_session.connect_hardware_wallet() key = base64.urlsafe_b64encode(key_bin) fer = Fernet(key) while True: # data is written in blocks; if front of each block there is a block size value data_bin = f_ptr.read(8) if len(data_bin) == 0: break # end of file elif len(data_bin) < 8: raise ValueError( 'File end before read completed.') data_chunk_size = int.from_bytes( data_bin, byteorder='little') if data_chunk_size < 0 or data_chunk_size > 2000000000: raise ValueError( 'Data corrupted: invalid data chunk size.') data_bin = f_ptr.read(data_chunk_size) if data_chunk_size != len(data_bin): raise ValueError( 'File end before read completed.') data_base64 = base64.urlsafe_b64encode(data_bin) try: data_decr = fer.decrypt(data_base64) except InvalidToken: raise Exception( 'Couldn\'t decrypt file (InvalidToken error). The file is probably ' 'corrupted or is encrypted with a different encryption method.' ) yield data_decr else: raise ValueError('Invalid hardware wallet type value.') else: raise ValueError('Invalid protocol value.') else: # the data inside the file isn't encrypted # read and yield raw data while True: # data is written in blocks; if front of each block there is a block size value data += f_ptr.read(ENC_FILE_BLOCK_SIZE) if not len(data): break yield data data = bytes() finally: hw_session.restore_state()
def get_addresses_to_scan(self, thread_ctrl: CtrlObject, addr_scan_ctrl: dict): """ :param self: :param addr_scan_ctrl: (only for self.utxo_src_mode == 2) penultimate element of bip32 path to scan, used to switch sanning between normal and change addresses :return: yield List[Tuple[str (address), str (bip32 path)]] """ try: if self.utxo_src_mode == 1: if self.mn_src_index is not None: if self.mn_src_index == len(self.masternode_addresses): # show addresses of all masternodes for chunk_nr in range(int(math.ceil(len(self.masternode_addresses) / ADDRESS_CHUNK))): if self.finishing or thread_ctrl.finish: return yield [x for x in self.masternode_addresses[ chunk_nr * ADDRESS_CHUNK : (chunk_nr + 1) * ADDRESS_CHUNK] if x[0] and x[1]] elif self.mn_src_index < len(self.masternode_addresses) and self.mn_src_index >= 0: if self.finishing or thread_ctrl.finish: return if self.masternode_addresses[self.mn_src_index][0] and \ self.masternode_addresses[self.mn_src_index][1]: yield [self.masternode_addresses[self.mn_src_index]] elif self.utxo_src_mode == 2: # hw wallet account: scan all addresses and change addresses for a specific account # stop when a defined number of subsequent address has balance 0 addr_count = 0 addr_n = dash_utils.bip32_path_string_to_n(self.hw_account_base_bip32_path) db_cur = self.db_intf.get_cursor() try: bip32_path_n = addr_n[:] + [self.hw_account_number + 0x80000000, 0, 0] cur_addr_buf = [] last_level2_nr = addr_scan_ctrl.get('level2') while True: restart_iteration = False for nr in range(1000): if self.finishing or thread_ctrl.finish: return if last_level2_nr != addr_scan_ctrl.get('level2'): last_level2_nr = addr_scan_ctrl.get('level2') restart_iteration = True break bip32_path_n[-2] = addr_scan_ctrl.get('level2') bip32_path_n[-1] = nr cur_addr = hw_intf.get_address_ext(self.main_ui.hw_session, bip32_path_n, db_cur, self.app_config.hw_encrypt_string, self.app_config.hw_decrypt_string) bip32_path = dash_utils.bip32_path_n_to_string(bip32_path_n) cur_addr_buf.append((cur_addr, bip32_path)) addr_count += 1 if len(cur_addr_buf) >= ADDRESS_CHUNK: yield cur_addr_buf cur_addr_buf.clear() if restart_iteration: continue if cur_addr_buf: yield cur_addr_buf break finally: if db_cur.connection.total_changes > 0: self.db_intf.commit() self.db_intf.release_cursor() elif self.utxo_src_mode == 3: db_cur = self.db_intf.get_cursor() try: # address from a specific bip32 path bip32_path_n = dash_utils.bip32_path_string_to_n(self.hw_src_bip32_path) cur_addr = hw_intf.get_address_ext(self.main_ui.hw_session, bip32_path_n, db_cur, self.app_config.hw_encrypt_string, self.app_config.hw_decrypt_string) self.hw_src_address = cur_addr yield [(cur_addr, self.hw_src_bip32_path)] finally: if db_cur.connection.total_changes > 0: self.db_intf.commit() self.db_intf.release_cursor() except Exception as e: logging.exception('Exception occurred') raise
def get_address_ext(hw_session: HwSessionInfo, bip32_path_n: List[int], db_cursor: sqlite3.Cursor, encrypt_fun: Callable, decrypt_fun: Callable): """ Reads address of a specific bip32 path from hardware wallet, using db cache to speed-up operation by avoiding utilization the hardware wallet device as quite slow for this operation. :param hw_session: :param bip32_path_n: :param db_cursor: :param encrypt_fun: :param decrypt_fun: :return: """ global hd_tree_db_map, bip32_address_map def get_hd_tree_db_id(tree_ident: str): db_id = hd_tree_db_map.get(tree_ident) if not db_id: db_cursor.execute('select id from ADDRESS_HD_TREE where ident=?', (tree_ident, )) row = db_cursor.fetchone() if not row: db_cursor.execute( 'insert into ADDRESS_HD_TREE(ident) values(?)', (tree_ident, )) db_id = db_cursor.lastrowid hd_tree_db_map[tree_ident] = db_id else: db_id = row[0] return db_id try: map_dict = bip32_address_map.get(hw_session.hd_tree_ident) if not map_dict: map_dict = {} bip32_address_map[hw_session.hd_tree_ident] = map_dict path_str = dash_utils.bip32_path_n_to_string(bip32_path_n) address = map_dict.get(path_str) db_id = None if not address: # look for address in db cache hd_tree_id = get_hd_tree_db_id(hw_session.hd_tree_ident) db_cursor.execute( 'select id, address from ADDRESS where tree_id=? and path=?', (hd_tree_id, path_str)) row = db_cursor.fetchone() if row: db_id, address = row # address is encrypted; try to decrypt it try: address = decrypt_fun(address).decode('ascii') if not dash_utils.validate_address( address, hw_session.app_config.dash_network): address = None except Exception: address = None if not address: address = get_address(hw_session, bip32_path_n) map_dict[path_str] = address address_encrypted = encrypt_fun(bytes(address, 'ascii')) if db_id: # update db record: it was encrypted with no longer valid encryption key db_cursor.execute( 'update ADDRESS set address=? where id=?', (address_encrypted, db_id)) else: db_cursor.execute( 'insert into ADDRESS(tree_id, path, address) values(?,?,?)', (hd_tree_id, path_str, address_encrypted)) return address except Exception as e: logging.exception('Unhandled exception occurred') return get_address(hw_session, bip32_path_n)