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()
Beispiel #2
0
    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
Beispiel #3
0
 def get_xpub(self, bip32_path, xtype):
     self.checkDevice()
     # bip32_path is of the form 44'/0'/1'
     # S-L-O-W - we don't handle the fingerprint directly, so compute
     # it manually from the previous node
     # This only happens once so it's bearable
     #self.get_client() # prompt for the PIN before displaying the dialog if necessary
     #self.handler.show_message("Computing master public key")
     if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():
         raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT)
     if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():
         raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT)
     splitPath = bip32_path.split('/')
     if splitPath[0] == 'm':
         splitPath = splitPath[1:]
         bip32_path = bip32_path[2:]
     fingerprint = 0
     if len(splitPath) > 1:
         prevPath = "/".join(splitPath[0:len(splitPath) - 1])
         nodeData = self.dongleObject.getWalletPublicKey(prevPath)
         publicKey = compress_public_key(nodeData['publicKey'])
         h = hashlib.new('ripemd160')
         h.update(hashlib.sha256(publicKey).digest())
         fingerprint = unpack(">I", h.digest()[0:4])[0]
     nodeData = self.dongleObject.getWalletPublicKey(bip32_path)
     publicKey = compress_public_key(nodeData['publicKey'])
     depth = len(splitPath)
     lastChild = splitPath[len(splitPath) - 1].split('\'')
     childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0])
     return BIP32Node(xtype=xtype,
                      eckey=ecc.ECPubkey(publicKey),
                      chaincode=nodeData['chainCode'],
                      depth=depth,
                      fingerprint=self.i4b(fingerprint),
                      child_number=self.i4b(childnum)).to_xpub()
 def get_xkeys(self, seed, t, passphrase, derivation):
     assert is_any_2fa_seed_type(t)
     xtype = 'standard' if t == '2fa' else 'p2wsh'
     bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
     rootnode = BIP32Node.from_rootseed(bip32_seed, xtype=xtype)
     child_node = rootnode.subkey_at_private_derivation(derivation)
     return child_node.to_xprv(), child_node.to_xpub()
Beispiel #5
0
 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)
Beispiel #6
0
 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
Beispiel #7
0
 def get_xpub(self, bip32_path, xtype):
     address_n = self.expand_path(bip32_path)
     creating = False
     node = self.get_public_node(address_n, creating).node
     return BIP32Node(xtype=xtype,
                      eckey=ecc.ECPubkey(node.public_key),
                      chaincode=node.chain_code,
                      depth=node.depth,
                      fingerprint=self.i4b(node.fingerprint),
                      child_number=self.i4b(node.child_num)).to_xpub()
Beispiel #8
0
 def get_xpub(self, bip32_path, xtype, creating=False):
     address_n = parse_path(bip32_path)
     with self.run_flow(creating_wallet=creating):
         node = trezorlib.btc.get_public_node(self.client, address_n).node
     return BIP32Node(xtype=xtype,
                      eckey=ecc.ECPubkey(node.public_key),
                      chaincode=node.chain_code,
                      depth=node.depth,
                      fingerprint=self.i4b(node.fingerprint),
                      child_number=self.i4b(node.child_num)).to_xpub()
Beispiel #9
0
 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)
Beispiel #10
0
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()
Beispiel #11
0
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')
Beispiel #13
0
 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
Beispiel #14
0
 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()
Beispiel #15
0
    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)
Beispiel #16
0
    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()
Beispiel #17
0
    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 __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()