URI: 
       tfollow-up prev: do all checks, and add tests - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c744fc4e3d30939e8690d8e591056b64d5fad38e
   DIR parent a987a2bbbee67ebfa79b1c3dbe1550e3b8c38f5f
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 27 Feb 2020 05:13:31 +0100
       
       follow-up prev: do all checks, and add tests
       
       Diffstat:
         M electrum/bip32.py                   |      23 +++++++++++++++++++++++
         M electrum/keystore.py                |      13 +++++--------
         M electrum/tests/test_bitcoin.py      |      72 +++++++++++++++++++++++++++++++
       
       3 files changed, 100 insertions(+), 8 deletions(-)
       ---
   DIR diff --git a/electrum/bip32.py b/electrum/bip32.py
       t@@ -401,3 +401,26 @@ def root_fp_and_der_prefix_from_xkey(xkey: str) -> Tuple[Optional[str], Optional
                derivation_prefix = convert_bip32_intpath_to_strpath([child_number_int])
                root_fingerprint = node.fingerprint.hex()
            return root_fingerprint, derivation_prefix
       +
       +
       +def is_xkey_consistent_with_key_origin_info(xkey: str, *,
       +                                            derivation_prefix: str = None,
       +                                            root_fingerprint: str = None) -> bool:
       +    bip32node = BIP32Node.from_xkey(xkey)
       +    int_path = None
       +    if derivation_prefix is not None:
       +        int_path = convert_bip32_path_to_list_of_uint32(derivation_prefix)
       +    if int_path is not None and len(int_path) != bip32node.depth:
       +        return False
       +    if bip32node.depth == 0:
       +        if bfh(root_fingerprint) != bip32node.calc_fingerprint_of_this_node():
       +            return False
       +        if bip32node.child_number != bytes(4):
       +            return False
       +    if int_path is not None and bip32node.depth > 0:
       +        if int.from_bytes(bip32node.child_number, 'big') != int_path[-1]:
       +            return False
       +    if bip32node.depth == 1:
       +        if bfh(root_fingerprint) != bip32node.fingerprint:
       +            return False
       +    return True
   DIR diff --git a/electrum/keystore.py b/electrum/keystore.py
       t@@ -36,7 +36,7 @@ from .bitcoin import deserialize_privkey, serialize_privkey
        from .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, TxInput
        from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
                            is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation,
       -                    convert_bip32_intpath_to_strpath)
       +                    convert_bip32_intpath_to_strpath, is_xkey_consistent_with_key_origin_info)
        from .ecc import string_to_number
        from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
                             SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160)
       t@@ -468,13 +468,10 @@ class Xpub(MasterPublicKeyMixin):
                if not (root_fingerprint is None or (is_hex_str(root_fingerprint) and len(root_fingerprint) == 8)):
                    raise Exception("root fp must be 8 hex characters")
                derivation_prefix = normalize_bip32_derivation(derivation_prefix)
       -        calc_root_fp, calc_der_prefix = bip32.root_fp_and_der_prefix_from_xkey(self.xpub)
       -        if (calc_root_fp is not None and root_fingerprint is not None
       -                and calc_root_fp != root_fingerprint):
       -            raise Exception("provided root fp inconsistent with xpub")
       -        if (calc_der_prefix is not None and derivation_prefix is not None
       -                and calc_der_prefix != derivation_prefix):
       -            raise Exception("provided der prefix inconsistent with xpub")
       +        if not is_xkey_consistent_with_key_origin_info(self.xpub,
       +                                                       derivation_prefix=derivation_prefix,
       +                                                       root_fingerprint=root_fingerprint):
       +            raise Exception("xpub inconsistent with provided key origin info")
                if root_fingerprint is not None:
                    self._root_fingerprint = root_fingerprint
                if derivation_prefix is not None:
   DIR diff --git a/electrum/tests/test_bitcoin.py b/electrum/tests/test_bitcoin.py
       t@@ -9,6 +9,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
                                      is_compressed_privkey, EncodeBase58Check, DecodeBase58Check,
                                      script_num_to_hex, push_script, add_number_to_script, int_to_hex,
                                      opcodes, base_encode, base_decode, BitcoinException)
       +from electrum import bip32
        from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath,
                                    xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation,
                                    is_xpub, convert_bip32_path_to_list_of_uint32,
       t@@ -457,6 +458,77 @@ class Test_xprv_xpub(ElectrumTestCase):
                self.assertEqual("m/0/2/1'", normalize_bip32_derivation("m/0/2/-1/"))
                self.assertEqual("m/0/1'/1'/5'", normalize_bip32_derivation("m/0//-1/1'///5h"))
        
       +    def test_is_xkey_consistent_with_key_origin_info(self):
       +        ### actual data (high depth path)
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/1'/0'/2'",
       +            root_fingerprint="b2768d2f"))
       +        # ok to skip args
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/1'/0'/2'"))
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            root_fingerprint="b2768d2f"))
       +        # path changed: wrong depth
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/0'/2'",
       +            root_fingerprint="b2768d2f"))
       +        # path changed: wrong child index
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/1'/0'/3'",
       +            root_fingerprint="b2768d2f"))
       +        # path changed: but cannot tell
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/1'/1'/2'",
       +            root_fingerprint="b2768d2f"))
       +        # fp changed: but cannot tell
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "Zpub75NQordWKAkaF7utBw95GEodyxqwFdR3idtTqQtrvWkYFeiuYdg5c3Q9L9bLjPLhEahLCTjmmS2YQcXPwr6twYCEJ55k6uhE5JxRqvUowmd",
       +            derivation_prefix="m/48'/1'/0'/2'",
       +            root_fingerprint="aaaaaaaa"))
       +
       +        ### actual data (depth=1 path)
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ",
       +            derivation_prefix="m/0'",
       +            root_fingerprint="b2e35a7d"))
       +        # path changed: wrong depth
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ",
       +            derivation_prefix="m/0'/0'",
       +            root_fingerprint="b2e35a7d"))
       +        # path changed: wrong child index
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ",
       +            derivation_prefix="m/1'",
       +            root_fingerprint="b2e35a7d"))
       +        # fp changed: can tell
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "zpub6nsHdRuY92FsMKdbn9BfjBCG6X8pyhCibNP6uDvpnw2cyrVhecvHRMa3Ne8kdJZxjxgwnpbHLkcR4bfnhHy6auHPJyDTQ3kianeuVLdkCYQ",
       +            derivation_prefix="m/0'",
       +            root_fingerprint="aaaaaaaa"))
       +
       +        ### actual data (depth=0 path)
       +        self.assertTrue(bip32.is_xkey_consistent_with_key_origin_info(
       +            "xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U",
       +            derivation_prefix="m",
       +            root_fingerprint="48adc7a0"))
       +        # path changed: wrong depth
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U",
       +            derivation_prefix="m/0",
       +            root_fingerprint="48adc7a0"))
       +        # fp changed: can tell
       +        self.assertFalse(bip32.is_xkey_consistent_with_key_origin_info(
       +            "xpub661MyMwAqRbcFWohJWt7PHsFEJfZAvw9ZxwQoDa4SoMgsDDM1T7WK3u9E4edkC4ugRnZ8E4xDZRpk8Rnts3Nbt97dPwT52CwBdDWroaZf8U",
       +            derivation_prefix="m",
       +            root_fingerprint="aaaaaaaa"))
       +
            def test_is_all_public_derivation(self):
                self.assertFalse(is_all_public_derivation("m/0/1'/1'"))
                self.assertFalse(is_all_public_derivation("m/0/2/1'"))