def _do_test_bip32(self, seed: str, sequence): node = BIP32Node.from_rootseed(bfh(seed), xtype='standard') xprv, xpub = node.to_xprv(), node.to_xpub() self.assertEqual("m/", sequence[0:2]) sequence = sequence[2:] for n in sequence.split('/'): if n[-1] != "'": xpub2 = BIP32Node.from_xkey(xpub).subkey_at_public_derivation( n).to_xpub() node = BIP32Node.from_xkey(xprv).subkey_at_private_derivation(n) xprv, xpub = node.to_xprv(), node.to_xpub() if n[-1] != "'": self.assertEqual(xpub, xpub2) return xpub, xprv
def update(self, window): wallet = window.wallet if type(wallet) != Multisig_Wallet: return if self.listener is None: self.logger.info("starting listener") self.listener = Listener(self) self.listener.start() elif self.listener: self.logger.info("shutting down listener") self.listener.stop() self.listener = None self.keys = [] self.cosigner_list = [] for key, keystore in wallet.keystores.items(): xpub = keystore.get_master_public_key() pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes(compressed=True) _hash = bh2u(crypto.sha256d(pubkey)) self.logger.info(_hash) if not keystore.is_watching_only(): self.keys.append((key, _hash, window)) server.xpub(_hash) else: self.cosigner_list.append((window, xpub, pubkey, _hash)) server.cosigners().append(_hash) for key in self.keys: self.logger.info(f'xpub: {key[1]}') for cosigner in self.cosigner_list: self.logger.info(f'cosigners: {cosigner[3]}') self.wallet_hash = server.wallet_hash() self.logger.info(self.wallet_hash) if self.listener: self.listener.set_keyhashes([t[1] for t in self.keys]) self.listener.set_wallet_hash(self.wallet_hash)
def mitm_verify(self, sig, expect_xpub): # verify a signature (65 bytes) over the session key, using the master bip32 node # - customized to use specific EC library of Electrum. pubkey = BIP32Node.from_xkey(expect_xpub).eckey try: pubkey.verify_message_hash(sig[1:65], self.session_key) return True except: return False
def make_xpub(xpub, s) -> str: rootnode = BIP32Node.from_xkey(xpub) child_pubkey, child_chaincode = bip32._CKD_pub( parent_pubkey=rootnode.eckey.get_public_key_bytes(compressed=True), parent_chaincode=rootnode.chaincode, child_index=s) child_node = BIP32Node(xtype=rootnode.xtype, eckey=ecc.ECPubkey(child_pubkey), chaincode=child_chaincode) return child_node.to_xpub()
def _make_node_path(self, xpub, address_n): bip32node = BIP32Node.from_xkey(xpub) node = self.types.HDNodeType( depth=bip32node.depth, fingerprint=int.from_bytes(bip32node.fingerprint, 'big'), child_num=int.from_bytes(bip32node.child_number, 'big'), chain_code=bip32node.chaincode, public_key=bip32node.eckey.get_public_key_bytes(compressed=True), ) return self.types.HDNodePathType(node=node, address_n=address_n)
def get_signing_xpub(xtype): if not constants.net.TESTNET: xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL" else: xpub = "tpubD6NzVbkrYhZ4XdmyJQcCPjQfg6RXVUzGFhPjZ7uvRC8JLcS7Hw1i7UTpyhp9grHpak4TyK2hzBJrujDVLXQ6qB5tNpVx9rC6ixijUXadnmY" if xtype not in ('standard', 'p2wsh'): raise NotImplementedError('xtype: {}'.format(xtype)) if xtype == 'standard': return xpub node = BIP32Node.from_xkey(xpub) return node._replace(xtype=xtype).to_xpub()
def make_billing_address(wallet, num, addr_type): long_id, short_id = wallet.get_user_id() xpub = make_xpub(get_billing_xpub(), long_id) usernode = BIP32Node.from_xkey(xpub) child_node = usernode.subkey_at_public_derivation([num]) pubkey = child_node.eckey.get_public_key_bytes(compressed=True) if addr_type == 'legacy': return bitcoin.public_key_to_p2pkh(pubkey) elif addr_type == 'segwit': return bitcoin.public_key_to_p2wpkh(pubkey) else: raise ValueError(f'unexpected billing type: {addr_type}')
def get_xpub(self, bip32_path, xtype): assert xtype in self.plugin.SUPPORTED_XTYPES reply = self._get_xpub(bip32_path) if reply: xpub = reply['xpub'] # Change type of xpub to the requested type. The firmware # only ever returns the mainnet standard type, but it is agnostic # to the type when signing. if xtype != 'standard' or constants.net.TESTNET: node = BIP32Node.from_xkey(xpub, net=constants.BitcoinMainnet) xpub = node._replace(xtype=xtype).to_xpub() return xpub else: raise Exception('no reply')
def get_xpub(self, bip32_path, xtype): assert xtype in ColdcardPlugin.SUPPORTED_XTYPES _logger.info('Derive xtype = %r' % xtype) xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000) # TODO handle timeout? # change type of xpub to the requested type try: node = BIP32Node.from_xkey(xpub) except InvalidMasterKeyVersionBytes: raise UserFacingException( _('Invalid xpub magic. Make sure your {} device is set to the correct chain.' ).format(self.device)) from None if xtype != 'standard': xpub = node._replace(xtype=xtype).to_xpub() return xpub
def f(xprv): rootnode = BIP32Node.from_xkey(xprv) key = rootnode.subkey_at_private_derivation((0, 0)).eckey sig = key.sign_message(message, True) return base64.b64encode(sig).decode()
def __init__(self, tx, parent, desc, prompt_if_unsaved): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None) self.tx = tx = copy.deepcopy(tx) # type: Transaction try: self.tx.deserialize() except BaseException as e: raise SerializationError(e) self.main_window = parent self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc self.locks = {} self.currently_signing = None # Set timeout flag self.timed_out = False self.main_window = parent self.wallet = parent.wallet # store the keyhash and cosigners for current wallet self.keyhashes = set() self.cosigner_list = set() if type(self.wallet) == Multisig_Wallet: for key, keystore in self.wallet.keystores.items(): xpub = keystore.get_master_public_key() pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes( compressed=True) _hash = bh2u(crypto.sha256d(pubkey)) if not keystore.is_watching_only(): self.keyhashes.add(_hash) else: self.cosigner_list.add(_hash) self.locks[_hash] = server.get(_hash + '_lock') if self.locks[_hash]: name = server.get(_hash + '_name') if name: self.currently_signing = name self.setMinimumWidth(200) self.setWindowTitle(_("Information")) vbox = QVBoxLayout() self.setLayout(vbox) self.warning = QLabel() vbox.addWidget(self.warning) warning_text = (_( 'A transaction with the following information is currently being signed' ) + '\n' + _( 'by a cosigner. A notification will appear within 30 seconds of either signing' ) + '\n' + _('or the transaction window expiring.')) self.warning.setText(warning_text) self.tx_desc = QLabel() vbox.addWidget(self.tx_desc) self.status_label = QLabel() vbox.addWidget(self.status_label) self.date_label = QLabel() vbox.addWidget(self.date_label) self.amount_label = QLabel() vbox.addWidget(self.amount_label) self.size_label = QLabel() vbox.addWidget(self.size_label) self.fee_label = QLabel() vbox.addWidget(self.fee_label) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) # Action buttons self.buttons = [self.cancel_button] # Add label for countdown timer self.time_out_label = QLabel() vbox.addWidget(self.time_out_label) run_hook('transaction_dialog', self) hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.time_left_int = int(DURATION_INT) for _hash, expire in self.locks.items(): if expire: # Set time left to desired duration self.time_left_int = int(DURATION_INT - (int(server.get_current_time()) - int(expire))) self.timer_start() self.update()
def on_receive(self, keyhash, data): self.logger.info(f"signal arrived for {keyhash}") for key, _hash, window in self.keys: if _hash == keyhash: break else: self.logger.info("keyhash not found") return # convert data from string to json data = json.loads(data) signed = data.get('signed') txs = data.get('txs') # invalid JSON structure if not signed or not txs: self.logger.info("cosigner data malformed, missing entry") return # check if user has signed if keyhash in signed: self.logger.info("user has already signed") return message = txs[keyhash] wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning(_('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.')) return elif wallet.has_keystore_encryption(): password = window.password_dialog(str(datetime.datetime.now().strftime("%a, %d %b %Y %H:%M:%S") + '\n\n' + _('An encrypted transaction was retrieved from cosigning pool.') + '\n' + _('Please enter your password to decrypt it. ')), parent=None) if not password: return else: password = None if not window.question(_("An encrypted transaction was retrieved from cosigning pool.") + '\n' + _("Do you want to open it now?")): return xprv = None try: xprv = wallet.keystore.get_master_private_key(password) if not xprv: return except InvalidPassword as e: self.logger.info("Incorrect Password") window.show_error(_("Incorrect Password")) return try: privkey = BIP32Node.from_xkey(xprv).eckey message = bh2u(privkey.decrypt_message(message)) except Exception as e: self.logger.exception('') window.show_error(_('Error decrypting message') + ':\n' + str(e)) return tx = transaction.Transaction(message) def calculate_wait_time(expire): # calculate wait time server_time = server.get_current_time() mins, secs = 0, 0 if server_time is not None: wait_time = int((WAIT_TIME - (int(server_time) - int(expire)))) mins, secs = divmod(wait_time, 60) return '{:02d}:{:02d}'.format(mins, secs) # check if lock has been placed for wallet lock = server.lock if lock: # calculate wait time based on lock expiry and server time timeformat = calculate_wait_time(lock['timestamp']) # display pop up window.show_warning(_("A cosigner is currently signing the transaction.") + '\n' + _("Please wait {} until the signing has concluded.".format(timeformat))) xpub = lock['xpub'] name = None try: name = server.get(xpub+'_name') except Exception as e: self.logger.exception(e) self.logger.error("Failed to get cosigner name from server") show_timeout_wait_dialog(tx, xpub, name, window, prompt_if_unsaved=True) return else: buffer = {'timestamp' : '', 'xpub' : ''} buffer['timestamp'] = str(server.get_current_time()) buffer['xpub'] = keyhash server.lock = buffer time_until_expired = '10 minutes' window.show_warning(_("You have {} to conclude signing after which the dialog will".format(time_until_expired)) + '\n' + _("automatically close."), parent=None) self.listener.clear(keyhash) show_transaction_timeout(tx, signed, window, prompt_if_unsaved=True)
def on_receive(self, keyhash, message): self.logger.info(f"signal arrived for {keyhash}") WAIT_TIME = 10 * 60 if self.suppress_notifications: for window, xpub, K, _hash in self.cosigner_list: if server.get(_hash + '_lock'): return self.suppress_notifications = False for key, _hash, window in self.keys: if _hash == keyhash: break else: self.logger.info("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.' )) return elif wallet.has_keystore_encryption(): # set pick to false when opening password dialog server.put(keyhash + '_pick', 'False') password = window.password_dialog( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('Please enter your password to decrypt it.')) if not password: # set pick back to true if password incorrect or omitted server.put(keyhash + '_pick', 'True') return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool." ) + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: privkey = BIP32Node.from_xkey(xprv).eckey message = bh2u(privkey.decrypt_message(message)) except Exception as e: self.logger.exception('') window.show_error(_('Error decrypting message') + ':\n' + str(e)) return #self.listener.clear(keyhash) tx = transaction.Transaction(message) def calculate_wait_time(expire): # calculate wait time wait_time = int( (WAIT_TIME - (int(server.get_current_time()) - int(expire)))) mins, secs = divmod(wait_time, 60) return '{:02d}:{:02d}'.format(mins, secs) # check if lock has been placed for any wallets for window, xpub, K, _hash in self.cosigner_list: expire = server.get(_hash + '_lock') if expire: # set pick back to true if user lock is present server.put(keyhash + '_pick', 'True') # suppress any further notifications self.suppress_notifications = True # calculate wait time based on lock expiry and server time timeformat = calculate_wait_time(expire) # display pop up window.show_warning( _("A cosigner is currently signing the transaction.") + '\n' + _("Please wait {} until the signing has concluded.".format( timeformat))) show_timeout_wait_dialog(tx, window, prompt_if_unsaved=True) return # test if wallet has previously placed a lock current_wallet_lock = server.get(keyhash + '_lock') if not current_wallet_lock: # no lock has been placed for current wallet => lock transaction dialog server.put(keyhash + '_lock', str(server.get_current_time())) time_until_expired = '10 minutes' else: time_until_expired = calculate_wait_time(current_wallet_lock) # place flag to test for graceful shutdown server.put(keyhash + '_shutdown', 'up') window.show_warning( _("You have {} to conclude signing after which the dialog will". format(time_until_expired)) + '\n' + _("automatically close.")) show_transaction_timeout(tx, window, prompt_if_unsaved=True)
def __init__(self, tx, parent, desc, prompt_if_unsaved): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None) # Take a copy; it might get updated in the main window by # e.g. the FX plugin. If this happens during or after a long # sign operation the signatures are lost. self.tx = tx = copy.deepcopy(tx) # type: Transaction try: self.tx.deserialize() except BaseException as e: raise SerializationError(e) self.main_window = parent # type: ElectrumWindow self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc # store the keyhash and cosigners for current wallet self.keyhashes = set() self.cosigner_list = set() if type(self.wallet) == Multisig_Wallet: for key, keystore in self.wallet.keystores.items(): xpub = keystore.get_master_public_key() pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes(compressed=True) _hash = bh2u(crypto.sha256d(pubkey)) if not keystore.is_watching_only(): self.keyhashes.add(_hash) else: self.cosigner_list.add(_hash) # if the wallet can populate the inputs with more info, do it now. # as a result, e.g. we might learn an imported address tx is segwit, # in which case it's ok to display txid tx.add_inputs_info(self.wallet) self.setMinimumWidth(950) self.setWindowTitle(_("Transaction")) vbox = QVBoxLayout() self.setLayout(vbox) vbox.addWidget(QLabel(_("Transaction ID:"))) self.tx_hash_e = ButtonsLineEdit() qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self) qr_icon = "qrcode_white.png" if ColorScheme.dark_scheme else "qrcode.png" self.tx_hash_e.addButton(qr_icon, qr_show, _("Show as QR code")) self.tx_hash_e.setReadOnly(True) vbox.addWidget(self.tx_hash_e) self.add_tx_stats(vbox) vbox.addSpacing(10) self.add_io(vbox) self.sign_button = b = QPushButton(_("Sign")) b.clicked.connect(self.sign) self.broadcast_button = b = QPushButton(_("Broadcast")) b.clicked.connect(self.do_broadcast) self.save_button = b = QPushButton(_("Save")) save_button_disabled = not tx.is_complete() b.setDisabled(save_button_disabled) if save_button_disabled: b.setToolTip(SAVE_BUTTON_DISABLED_TOOLTIP) else: b.setToolTip(SAVE_BUTTON_ENABLED_TOOLTIP) b.clicked.connect(self.save) self.export_button = b = QPushButton(_("Export")) b.clicked.connect(self.export) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) self.qr_button = b = QPushButton() b.setIcon(read_QIcon(qr_icon)) b.clicked.connect(self.show_qr) self.copy_button = CopyButton(lambda: str(self.tx), parent.app) # Action buttons self.buttons = [self.sign_button, self.broadcast_button, self.cancel_button] # Transaction sharing buttons self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button, self.save_button] run_hook('transaction_dialog', self) hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) self.update()