URI: 
       tkeystore: handle unusual derivation paths in PSBT - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit ac223073ba7998901bd9a10d32594ebf0583021f
   DIR parent 0c3f36038599401c226addb05249c964247579da
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Wed,  9 Dec 2020 09:38:40 +0100
       
       keystore: handle unusual derivation paths in PSBT
       
       If a tx contained a derivation path for a pubkey,
       with a length=2 der suffix,
       with the first element of the suffix not in (0, 1),
       with a fingerprint that matches either our root or intermediate fp,
       tthen processing that tx would raise and result in a crash reporter.
       
       Traceback (most recent call last):
         File ".../electrum/electrum/gui/qt/main_window.py", line 2718, in do_process_from_text
           self.show_transaction(tx)
         File ".../electrum/electrum/gui/qt/main_window.py", line 1041, in show_transaction
           show_transaction(tx, parent=self, desc=tx_desc)
         File ".../electrum/electrum/gui/qt/transaction_dialog.py", line 84, in show_transaction
           d = TxDialog(tx, parent=parent, desc=desc, prompt_if_unsaved=prompt_if_unsaved)
         File ".../electrum/electrum/gui/qt/transaction_dialog.py", line 680, in __init__
           self.set_tx(tx)
         File ".../electrum/electrum/gui/qt/transaction_dialog.py", line 218, in set_tx
           tx.add_info_from_wallet(self.wallet)
         File ".../electrum/electrum/transaction.py", line 1944, in add_info_from_wallet
           wallet.add_input_info(txin, only_der_suffix=only_der_suffix)
         File ".../electrum/electrum/wallet.py", line 1573, in add_input_info
           is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address)
         File ".../electrum/electrum/wallet.py", line 2609, in _learn_derivation_path_for_address_from_txinout
           pubkey, der_suffix = ks.find_my_pubkey_in_txinout(txinout, only_der_suffix=True)
         File ".../electrum/electrum/keystore.py", line 155, in find_my_pubkey_in_txinout
           path = self.get_pubkey_derivation(pubkey, txinout, only_der_suffix=only_der_suffix)
         File ".../electrum/electrum/keystore.py", line 391, in get_pubkey_derivation
           if not test_der_suffix_against_pubkey(der_suffix, pubkey):
         File ".../electrum/electrum/keystore.py", line 368, in test_der_suffix_against_pubkey
           if pubkey != self.derive_pubkey(*der_suffix):
         File ".../electrum/electrum/keystore.py", line 491, in derive_pubkey
           assert for_change in (0, 1)
       AssertionError
       
       Diffstat:
         M electrum/keystore.py                |      28 +++++++++++++++++++++-------
         M electrum/wallet.py                  |       8 ++++++--
       
       2 files changed, 27 insertions(+), 9 deletions(-)
       ---
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -52,6 +52,9 @@ if TYPE_CHECKING:
            from .wallet_db import WalletDB
        
        
       +class CannotDerivePubkey(Exception): pass
       +
       +
        class KeyStore(Logger, ABC):
            type: str
        
       t@@ -356,16 +359,25 @@ class MasterPublicKeyMixin(ABC):
        
            @abstractmethod
            def derive_pubkey(self, for_change: int, n: int) -> bytes:
       +        """Returns pubkey at given path.
       +        May raise CannotDerivePubkey.
       +        """
                pass
        
       -    def get_pubkey_derivation(self, pubkey: bytes,
       -                              txinout: Union['PartialTxInput', 'PartialTxOutput'],
       -                              *, only_der_suffix=True) \
       -            -> Union[Sequence[int], str, None]:
       +    def get_pubkey_derivation(
       +            self,
       +            pubkey: bytes,
       +            txinout: Union['PartialTxInput', 'PartialTxOutput'],
       +            *,
       +            only_der_suffix=True,
       +    ) -> Union[Sequence[int], str, None]:
                def test_der_suffix_against_pubkey(der_suffix: Sequence[int], pubkey: bytes) -> bool:
                    if len(der_suffix) != 2:
                        return False
       -            if pubkey != self.derive_pubkey(*der_suffix):
       +            try:
       +                if pubkey != self.derive_pubkey(*der_suffix):
       +                    return False
       +            except CannotDerivePubkey:
                        return False
                    return True
        
       t@@ -488,7 +500,8 @@ class Xpub(MasterPublicKeyMixin):
            @lru_cache(maxsize=None)
            def derive_pubkey(self, for_change: int, n: int) -> bytes:
                for_change = int(for_change)
       -        assert for_change in (0, 1)
       +        if for_change not in (0, 1):
       +            raise CannotDerivePubkey("forbidden path")
                xpub = self.xpub_change if for_change else self.xpub_receive
                if xpub is None:
                    rootnode = self.get_bip32_node_for_xpub()
       t@@ -658,7 +671,8 @@ class Old_KeyStore(MasterPublicKeyMixin, Deterministic_KeyStore):
            @lru_cache(maxsize=None)
            def derive_pubkey(self, for_change, n) -> bytes:
                for_change = int(for_change)
       -        assert for_change in (0, 1)
       +        if for_change not in (0, 1):
       +            raise CannotDerivePubkey("forbidden path")
                return self.get_pubkey_from_mpk(self.mpk, for_change, n)
        
            def _get_private_key_from_stretched_exponent(self, for_change, n, secexp):
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -61,7 +61,8 @@ from .bitcoin import COIN, TYPE_ADDRESS
        from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold
        from .crypto import sha256d
        from . import keystore
       -from .keystore import load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK, AddressIndexGeneric
       +from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
       +                       AddressIndexGeneric, CannotDerivePubkey)
        from .util import multisig_type
        from .storage import StorageEncryptionVersion, WalletStorage
        from .wallet_db import WalletDB
       t@@ -2611,7 +2612,10 @@ class Deterministic_Wallet(Abstract_Wallet):
                        # note: we already know the pubkey belongs to the keystore,
                        #       but the script template might be different
                        if len(der_suffix) != 2: continue
       -                my_address = self.derive_address(*der_suffix)
       +                try:
       +                    my_address = self.derive_address(*der_suffix)
       +                except CannotDerivePubkey:
       +                    my_address = None
                        if my_address == address:
                            self._ephemeral_addr_to_addr_index[address] = list(der_suffix)
                            return True