URI: 
       tkeystore: ignore fingerprint for pubkeys in psbt, try to match all keys - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c3c64a37c26f965c89479923f3fc5f3ae2bf24c2
   DIR parent 8872e43f27d92f2ed090b76bd589c7159485e20c
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 10 Dec 2020 17:06:28 +0100
       
       keystore: ignore fingerprint for pubkeys in psbt, try to match all keys
       
       Diffstat:
         M electrum/keystore.py                |      28 ++++++++++++++++++----------
         M electrum/tests/test_wallet_vertica… |      21 +++++++++++++++++++++
       
       2 files changed, 39 insertions(+), 10 deletions(-)
       ---
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -371,8 +371,9 @@ class MasterPublicKeyMixin(ABC):
                    *,
                    only_der_suffix=True,
            ) -> Union[Sequence[int], str, None]:
       +        EXPECTED_DER_SUFFIX_LEN = 2
                def test_der_suffix_against_pubkey(der_suffix: Sequence[int], pubkey: bytes) -> bool:
       -            if len(der_suffix) != 2:
       +            if len(der_suffix) != EXPECTED_DER_SUFFIX_LEN:
                        return False
                    try:
                        if pubkey != self.derive_pubkey(*der_suffix):
       t@@ -387,11 +388,11 @@ class MasterPublicKeyMixin(ABC):
                der_suffix = None
                full_path = None
                # 1. try fp against our root
       -        my_root_fingerprint_hex = self.get_root_fingerprint()
       -        my_der_prefix_str = self.get_derivation_prefix()
       -        ks_der_prefix = convert_bip32_path_to_list_of_uint32(my_der_prefix_str) if my_der_prefix_str else None
       -        if (my_root_fingerprint_hex is not None and ks_der_prefix is not None and
       -                fp_found.hex() == my_root_fingerprint_hex):
       +        ks_root_fingerprint_hex = self.get_root_fingerprint()
       +        ks_der_prefix_str = self.get_derivation_prefix()
       +        ks_der_prefix = convert_bip32_path_to_list_of_uint32(ks_der_prefix_str) if ks_der_prefix_str else None
       +        if (ks_root_fingerprint_hex is not None and ks_der_prefix is not None and
       +                fp_found.hex() == ks_root_fingerprint_hex):
                    if path_found[:len(ks_der_prefix)] == ks_der_prefix:
                        der_suffix = path_found[len(ks_der_prefix):]
                        if not test_der_suffix_against_pubkey(der_suffix, pubkey):
       t@@ -402,10 +403,17 @@ class MasterPublicKeyMixin(ABC):
                    der_suffix = path_found
                    if not test_der_suffix_against_pubkey(der_suffix, pubkey):
                        der_suffix = None
       -        # NOTE: problem: if we don't know our root fp, but tx contains root fp and full path,
       -        #       we will miss the pubkey (false negative match). Though it might still work
       -        #       within gap limit due to tx.add_info_from_wallet overwriting the fields.
       -        #       Example: keystore has intermediate xprv without root fp; tx contains root fp and full path.
       +        # 3. hack/bruteforce: ignore fp and check pubkey anyway
       +        #    This is only to resolve the following scenario/problem:
       +        #    problem: if we don't know our root fp, but tx contains root fp and full path,
       +        #             we will miss the pubkey (false negative match). Though it might still work
       +        #             within gap limit due to tx.add_info_from_wallet overwriting the fields.
       +        #             Example: keystore has intermediate xprv without root fp; tx contains root fp and full path.
       +        if der_suffix is None:
       +            der_suffix = path_found[-EXPECTED_DER_SUFFIX_LEN:]
       +            if not test_der_suffix_against_pubkey(der_suffix, pubkey):
       +                der_suffix = None
       +        # if all attempts/methods failed, we give up now:
                if der_suffix is None:
                    return None
                if ks_der_prefix is not None:
   DIR diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -2046,6 +2046,27 @@ class TestWalletOfflineSigning(TestCaseForTestnet):
                self.assertEqual('484e350beaa722a744bb3e2aa38de005baa8526d86536d6143e5814355acf775', tx.wtxid())
        
            @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
       +    def test_signing_where_offline_ks_does_not_have_keyorigin_but_psbt_contains_it(self, mock_save_db):
       +        # keystore has intermediate xprv without root fp; tx contains root fp and full path.
       +        # tx has input with key beyond gap limit
       +        wallet_offline = WalletIntegrityHelper.create_standard_wallet(
       +            # bip39 seed: "brave scare company drastic consider confirm grow differ alter wide olympic utility"
       +            # der: m/84'/1'/0'
       +            keystore.from_xprv('vprv9KXDgRXYp3WCozCS3bMehASe2cJhY28DihCZ3KuyiTTjngopkfRC9QkH1SUREyCvnV7TSD6EgEHTTYa5yod7ZveBhVReEU1uDgfVASFqLNw'),
       +            gap_limit=4,
       +            config=self.config
       +        )
       +
       +        tx = tx_from_any('70736274ff01005202000000017b748828553b1127b86674e71ad0cd4a2e5e8baeab8792a3c3263f7ea0ba86500000000000fdffffff01ad16010000000000160014d74b54300bc0d4b6e8f506fe540b47ce0da38b4a08f21c00000100bf0200000000010163a419b779be17167c54ff3acb1205e5347fbd72963f89fb1d66b5cf09f329c90000000000fdffffff011b17010000000000160014ed420532f0c33477b9b3fbb57431b4a1adce99c90247304402204e4ad4992fa8798e3b595d17c59961b905ca71c32dc3ba910ae14f139259ffbe02206ee2281f21499e46aa77f4bec2edce3674fea529d9dd340439365c2232bad35701210334080358ffdac08f83d6800a8e477e3512ad5c39ede553089db8c4bbe16f59aad7f11c00220602d137f257a96cbc58c7e60f2085cd65a311e242459e23d1efbed77dd8f372513818cc2bdaaa540000800100008000000080000000001e000000002202030671d324eeba0f85499a8749f783a4883103d23f5dedbe048391ff18c3da067818cc2bdaaa540000800100008000000080000000000100000000')
       +        self.assertEqual('065b6e0a5731107641828337f5e000c9ddd94a12d074708643b0bca517374c6a', tx.txid())
       +
       +        # sign tx
       +        tx = wallet_offline.sign_transaction(tx, password=None)
       +        self.assertTrue(tx.is_complete())
       +        self.assertEqual('020000000001017b748828553b1127b86674e71ad0cd4a2e5e8baeab8792a3c3263f7ea0ba86500000000000fdffffff01ad16010000000000160014d74b54300bc0d4b6e8f506fe540b47ce0da38b4a0247304402203098741bf4d4f956e96f2706a517a1c0a63f67a242a50d155fbc56ad0bbac8b102207e535391c03bdab641f3205762311c1e6648b3459681e53d68fa44e63604a7f6012102d137f257a96cbc58c7e60f2085cd65a311e242459e23d1efbed77dd8f372513808f21c00',
       +                         str(tx))
       +
       +    @mock.patch.object(wallet.Abstract_Wallet, 'save_db')
            def test_sending_offline_wif_online_addr_p2pkh(self, mock_save_db):  # compressed pubkey
                wallet_offline = WalletIntegrityHelper.create_imported_wallet(privkeys=True, config=self.config)
                wallet_offline.import_private_key('p2pkh:cQDxbmQfwRV3vP1mdnVHq37nJekHLsuD3wdSQseBRA2ct4MFk5Pq', password=None)