URI: 
       tadded support for bech32 and multisig master public keys, and more tests - electrum-personal-server - Maximally lightweight electrum server for a single user
  HTML git clone https://git.parazyd.org/electrum-personal-server
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit 86f51b7d87f8c39956fb3478cb0ed2609f5cc96b
   DIR parent 314443ac47f77ce90490744ecef5071581ffe2c1
  HTML Author: chris-belcher <chris-belcher@users.noreply.github.com>
       Date:   Fri,  9 Mar 2018 00:19:21 +0000
       
       added support for bech32 and multisig master public keys, and more tests
       
       Diffstat:
         M bitcoin/deterministic.py            |      21 ++++++++++++++++++---
         M config.cfg_sample                   |       5 ++++-
         M deterministicwallet.py              |     338 ++++++++++++++++++++++++-------
         M server.py                           |       3 ++-
       
       4 files changed, 289 insertions(+), 78 deletions(-)
       ---
   DIR diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py
       t@@ -5,11 +5,26 @@ from binascii import hexlify
        
        # Below code ASSUMES binary inputs and compressed pubkeys
        MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
       -MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
       +#MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
        TESTNET_PRIVATE = b'\x04\x35\x83\x94'
       -TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
       +#TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
        PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE]
       -PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
       +#PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
       +
       +#updated for electrum's bip32 version bytes
       +#only public keys because electrum personal server only needs them
       +#https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
       +PUBLIC = [  b'\x04\x88\xb2\x1e', #mainnet p2pkh or p2sh xpub
       +            b'\x04\x9d\x7c\xb2', #mainnet p2wpkh-p2sh ypub
       +            b'\x02\x95\xb4\x3f', #mainnet p2wsh-p2sh Ypub
       +            b'\x04\xb2\x47\x46', #mainnet p2wpkh zpub
       +            b'\x02\xaa\x7e\xd3', #mainnet p2wsh Zpub
       +            b'\x04\x35\x87\xcf', #testnet p2pkh or p2sh tpub
       +            b'\x04\x4a\x52\x62', #testnet p2wpkh-p2sh upub
       +            b'\x02\x42\x85\xef', #testnet p2wsh-p2sh Upub
       +            b'\x04\x5f\x1c\xf6', #testnet p2wpkh vpub
       +            b'\x02\x57\x54\x83' #testnet p2wsh Vpub
       +        ]
        
        # BIP32 child key derivation
        
   DIR diff --git a/config.cfg_sample b/config.cfg_sample
       t@@ -7,6 +7,9 @@
        
        #any_key_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF
        
       +# Multisig wallets use format `required-signatures [list of master pub keys]`
       +#multisig_wallet = 2 xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF
       +
        [bip39-master-public-keys]
        ## Add master public keys in the bip39 standard to this section
        ## Most hardware wallet keys go here
       t@@ -16,7 +19,7 @@
        ## Add addresses to this section
        
        #addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n
       -# A space or comma separated list is accepted
       +# A space separated list is accepted
        #my_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
        
        [bitcoin-rpc]
   DIR diff --git a/deterministicwallet.py b/deterministicwallet.py
       t@@ -2,22 +2,43 @@
        import bitcoin as btc
        import util
        
       +#the wallet types are here
       +#https://github.com/spesmilo/electrum/blob/3.0.6/RELEASE-NOTES
       +#and
       +#https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
       +
        def parse_electrum_master_public_key(keydata, gaplimit):
       -    if keydata[:4] == "xpub":
       +    if keydata[:4] in ("xpub", "tpub"):
                return SingleSigP2PKHWallet(keydata, gaplimit)
       +    elif keydata[:4] in ("zpub", "vpub"):
       +        return SingleSigP2WPKHWallet(keydata, gaplimit)
       +    elif keydata.find(" ") != -1: #multiple keys = multisig
       +        chunks = keydata.split(" ")
       +        try:
       +            m = int(chunks[0])
       +        except ValueError:
       +            raise ValueError("Unable to parse m in multisig key data: "
       +                + chunks[0])
       +        pubkeys = chunks[1:]
       +        if not all([pubkeys[0][:4] == pub[:4] for pub in pubkeys[1:]]):
       +            raise ValueError("inconsistent bip32 pubkey types")
       +        if pubkeys[0][:4] in ("xpub", "tpub"):
       +            return MultisigP2SHWallet(m, pubkeys, gaplimit)
       +        if pubkeys[0][:4] in("Zpub", "Vpub"):
       +            return MultisigP2WSHWallet(m, pubkeys, gaplimit)
            else:
       -        raise RuntimeError("Unrecognized electrum mpk format: " + keydata[:4])
       -
       -#the wallet types are here
       -#https://github.com/spesmilo/electrum/blob/3.0.6/RELEASE-NOTES
       +        raise ValueError("Unrecognized electrum mpk format: " + keydata[:4])
        
        class DeterministicWallet(object):
            def __init__(self, gaplimit):
                self.gaplimit = gaplimit
       +        self.next_index = [0, 0]
       +        self.scriptpubkey_index = {}
        
            def get_new_scriptpubkeys(self, change, count):
                """Returns newly-generated addresses from this deterministic wallet"""
       -        pass
       +        return self.get_scriptpubkeys(change, self.next_index[change],
       +            count)
        
            def get_scriptpubkeys(self, change, from_index, count):
                """Returns addresses from this deterministic wallet"""
       t@@ -25,45 +46,9 @@ class DeterministicWallet(object):
        
            #called in check_for_new_txes() when a new tx of ours arrives
            #to see if we need to import more addresses
       -    def have_scriptpubkeys_overrun_gaplimit(self, scripts):
       +    def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys):
                """Return None if they havent, or how many addresses to
                   import if they have"""
       -        pass
       -
       -    def rewind_one(self, change):
       -        """Go back one pubkey in a branch"""
       -        pass
       -
       -class SingleSigP2PKHWallet(DeterministicWallet):
       -    def __init__(self, mpk, gaplimit):
       -        super(SingleSigP2PKHWallet, self).__init__(gaplimit)
       -        self.branches = (btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1))
       -        self.next_index = [0, 0]
       -        self.scriptpubkey_index = {}
       -
       -    def pubkey_to_scriptpubkey(self, pubkey):
       -        pkh = util.bh2u(util.hash_160(util.bfh(pubkey)))
       -        #op_dup op_hash_160 length hash160 op_equalverify op_checksig
       -        return "76a914" + pkh + "88ac"
       -        #for p2sh its "a9" + hash160 + "87" #op_hash_160 op_equal
       -
       -    def get_new_scriptpubkeys(self, change, count):
       -        return self.get_scriptpubkeys(change, self.next_index[change],
       -            count)
       -
       -    def get_scriptpubkeys(self, change, from_index, count):
       -        #m/change/i
       -        result = []
       -        for index in range(from_index, from_index + count):
       -            pubkey = btc.bip32_extract_key(btc.bip32_ckd(self.branches[change],
       -                index))
       -            scriptpubkey = self.pubkey_to_scriptpubkey(pubkey)
       -            self.scriptpubkey_index[scriptpubkey] = (change, index)
       -            result.append(scriptpubkey)
       -        self.next_index[change] = max(self.next_index[change], from_index+count)
       -        return result
       -
       -    def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys):
                result = {}
                for spk in scriptpubkeys:
                    if spk not in self.scriptpubkey_index:
       t@@ -84,42 +69,249 @@ class SingleSigP2PKHWallet(DeterministicWallet):
                    return None
        
            def rewind_one(self, change):
       +        """Go back one pubkey in a branch"""
                self.next_index[change] -= 1
        
       -'''
       -recv
       -76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac
       -76a914d8b6b932e892fad5132ea888111adac2171c5af588ac
       -76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac
       -change
       -76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac
       -76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac
       -76a91433bdb046a1d373728d7844df89aa24f788443a4588ac
       -'''
       +class SingleSigWallet(DeterministicWallet):
       +    def __init__(self, mpk, gaplimit):
       +        super(SingleSigWallet, self).__init__(gaplimit)
       +        self.branches = (btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1))
       +        #m/change/i
        
       -#need test vectors for each kind of detwallet
       +    def pubkey_to_scriptpubkey(self, pubkey):
       +        raise RuntimeError()
        
       +    def get_scriptpubkeys(self, change, from_index, count):
       +        result = []
       +        for index in range(from_index, from_index + count):
       +            pubkey = btc.bip32_extract_key(btc.bip32_ckd(self.branches[change],
       +                index))
       +            scriptpubkey = self.pubkey_to_scriptpubkey(pubkey)
       +            self.scriptpubkey_index[scriptpubkey] = (change, index)
       +            result.append(scriptpubkey)
       +        self.next_index[change] = max(self.next_index[change], from_index+count)
       +        return result
       +
       +class SingleSigP2PKHWallet(SingleSigWallet):
       +    def pubkey_to_scriptpubkey(self, pubkey):
       +        pkh = util.bh2u(util.hash_160(util.bfh(pubkey)))
       +        #op_dup op_hash_160 length hash160 op_equalverify op_checksig
       +        return "76a914" + pkh + "88ac"
       +
       +class SingleSigP2WPKHWallet(SingleSigWallet):
       +    def pubkey_to_scriptpubkey(self, pubkey):
       +        pkh = util.bh2u(util.hash_160(util.bfh(pubkey)))
       +        #witness-version length hash160
       +        #witness version is always 0, length is always 0x14
       +        return "0014" + pkh
       +
       +class MultisigWallet(DeterministicWallet):
       +    def __init__(self, m, mpk_list, gaplimit):
       +        super(MultisigWallet, self).__init__(gaplimit)
       +        self.m = m
       +        self.pubkey_branches = [(btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1))
       +            for mpk in mpk_list]
       +        #derivation path for pubkeys is m/change/index
       +
       +    def redeem_script_to_scriptpubkey(self, redeem_script):
       +        raise RuntimeError()
       +
       +    def get_scriptpubkeys(self, change, from_index, count):
       +        result = []
       +        for index in range(from_index, from_index + count):
       +            pubkeys = [btc.bip32_extract_key(btc.bip32_ckd(branch[change],
       +                index)) for branch in self.pubkey_branches]
       +            pubkeys = sorted(pubkeys)
       +            redeemScript = ""
       +            redeemScript += "%x"%(0x50 + self.m) #op_m
       +            for p in pubkeys:
       +                redeemScript += "21" #length
       +                redeemScript += p
       +            redeemScript += "%x"%(0x50 + len(pubkeys)) #op_n
       +            redeemScript += "ae" # op_checkmultisig
       +            scriptpubkey = self.redeem_script_to_scriptpubkey(redeemScript)
       +            self.scriptpubkey_index[scriptpubkey] = (change, index)
       +            result.append(scriptpubkey)
       +        self.next_index[change] = max(self.next_index[change], from_index+count)
       +        return result
       +
       +class MultisigP2SHWallet(MultisigWallet):
       +    def redeem_script_to_scriptpubkey(self, redeem_script):
       +        sh = util.bh2u(util.hash_160(util.bfh(redeem_script)))
       +        return "a914" + sh + "87"
       +        #op_hash160 length hash160 op_equal
       +
       +class MultisigP2WSHWallet(MultisigWallet):
       +    def redeem_script_to_scriptpubkey(self, redeem_script):
       +        sh = util.bh2u(util.sha256(util.bfh(redeem_script)))
       +        return "0020" + sh
       +        #witness-version length sha256
       +        #witness version is always 0, length is always 0x20
       +
       +electrum_keydata_test_vectors = [
       +    #p2pkh wallet
       +    ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" +
       +    "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #pubkey
       +    ["76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac" #recv scriptpubkeys
       +    ,"76a914d8b6b932e892fad5132ea888111adac2171c5af588ac"
       +    ,"76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac"],
       +    ["76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac" #change scriptpubkeys
       +    ,"76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac"
       +    ,"76a91433bdb046a1d373728d7844df89aa24f788443a4588ac"])
       +    , #p2wpkh wallet
       +    ("zpub6mr7wBKy3oJn89TCiXUAPBWpTTTx58BgEjPLzDNf5kMThvd6xchrobPTsJ5mP" +
       +    "w3NJ7zRhckN8cv4FhQBfwurZzNE5uTW5C5PYqNTkRAnTkP", #pubkey
       +    ['00142b82c61a7a48b7b10801f0eb247af46821bd33f5' #recv scriptpubkeys
       +    ,'0014073dc6bcbb18d6468c5996bdeba926f6805b74b1'
       +    ,'001400fa0b5cb21e8d442a7bd61af3d558a62be0c9aa'],
       +    ['00144f4a0655a4b586be1e08d97a2f55125120b84c69' #change scriptpubkeys
       +    ,'0014ef7967a7a56c23bbc9f317e612c93a5e23d25ffe'
       +    ,'0014ad768a11730bf54d10c72184d53239de0f310bc9'])
       +    ,#p2sh 2of2 multisig wallet
       +    ("2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
       +    "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" +
       +    "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" +
       +    "T74sszGaxaf64o5s", #m=2, 2 pubkeys, n=len(pubkeys)
       +    ['a914fe30a46a4e1b41f9bb758448fd84ee4628c103e187' #recv
       +    ,'a914dad5dd605871560ae5d219cd6275e6ad19bc6b9987'
       +    ,'a914471e158e2db190acdd8c76ed6d2ade102fe1e8ac87'
       +    ,'a914013449715a32f21d1a8a2b95a01b40eb41ada16f87'
       +    ,'a914ae3dd25567fb7c2f87be41220dd14025ca68b0e087'
       +    ,'a91462b90344947b610c4eadb7dd460fee3f32fefe7687'
       +    ,'a914d4388c7d5771ebf26b6e650c42e60e4cf7d4c5a187'
       +    ,'a914e4f0832e56591d01b71c72b9a3777dc8f9d9a92e87'
       +    ,'a914a5d5accd96d27403c7663b92fdb57299d7a871eb87'
       +    ,'a914f8f2c6ef2d80f972e4d8b418a15337a3c38af37f87'
       +    ,'a914a2bd2f67fac7c24e609b574ccc8cfaa2f90ebf8c87'
       +    ,'a914a56298a7decde1d18306f55d9305577c3fce690187'
       +    ,'a91430f2f83238ac29125a539055fa59efc86a73a23987'
       +    ,'a914263b4585d0735c5065987922af359d5eabeb880d87'
       +    ,'a91455d9d47113fb8b37705bdf6d4107d438afd63e4687'
       +    ,'a914970d754163b8957b73f4e8baaf23dea5f6e3db2287'
       +    ,'a914facbc921203a9ffd751cc246a884918beaac21b687'
       +    ,'a914fc7556833eca1e0f84c6d7acb875e645f7ed4e9687'
       +    ,'a914bbfe6a032d633f113b5d605e3a97cc08a47cc87d87'
       +    ,'a91403d733c4ca337b5fa1de95970ba6f898a9d36c4887'
       +    ,'a9148af27dc7c950e17c11e164065e672cd60ae3d48d87'
       +    ,'a914c026aa45377f2a4a62136bac1d3350c318fee5c587'
       +    ,'a9146337f59e3ea55e73725c9f2fc52a5ca5d68c361687'],
       +    ['a914aeaebf9d567ab8a6813e89668e16f40bf419408e87' #change
       +    ,'a914f2a6264dd3975297fa2a5a8e17321299a44f76d987'
       +    ,'a9142067a6c47958090a645137cc0898c0c7bbc69b5387'
       +    ,'a914210840f77ea5b7eb11cb55e5d719a93b7746fb9387'
       +    ,'a914163db6b8ca00362be63a26502c5f7bf64787506b87'
       +    ,'a91479b2c527594059c056e5367965ae92bbcf63512187'])
       +    ,#p2sh 2of3 multisig wallet
       +    ("2 tpubD6NzVbkrYhZ4WwaMJ3od4hANxdMVpb63Du3ERq1xjtowxVJEcTbGH2rFd9TFXxw" +
       +    "KJRKDn9vQjDPxFeaku6BHW6wHn2KPF1ijS4LwgwQFJ3B tpubD6NzVbkrYhZ4Wjv4ZRPD6" +
       +    "MNdiLmfvXztbKuuatkqHjukU3S6GXhmKnbAF5eU9bR2Nryiq8v67emUUSM1VUrAx5wcZ19" +
       +    "AsaGg3ZLmjbbwLXr tpubD6NzVbkrYhZ4Xxa2fEp7YsbnFnwuQNaogijbiX42Deqd4NiAD" +
       +    "tqNU6AXCU2d2kPFWBpAGG7K3HAKYwUfZBPgTLkfQp2dDg9SLVnkgYPgEXN",
       +    ['a914167c95beb25b984ace517d4346e6cdbf1381793687', #recv addrs
       +     'a914378bbda1ba7a713de18c3ba3c366f42212bfb45087',
       +     'a9142a5c9881c70906180f37dd02d8c830e9b6328d4a87',
       +     'a914ffe0832375b72ee5307bfa502896ba28cc470ee987',
       +     'a9147607d40e039fbea57d9c04e48b198c9fcf3356c187',
       +     'a9148d9582ad4cf0581c6e0697e4cba6a12e66ca1a0087',
       +     'a914d153a743b315ba19690823119019e16e3762104d87',
       +     'a914b4accc89e48610043e70371153fd8cb5a3eef34287',
       +     'a91406febca615e3631253fd75a1d819436e1d046e0487',
       +     'a914b863cbb888c6b28291cb87a2390539e28be37a9587',
       +     'a914ec39094e393184d2c352a29b9d7a3caddaccb6cf87',
       +     'a914da4faa4babbdf611caf511d287133f06c1c3244a87',
       +     'a9146e64561d0c5e2e9159ecff65db02e04b3277402487',
       +     'a914377d66386972492192ae827fb2208596af0941d187',
       +     'a914448d364ff2374449e57df13db33a40f5b099997c87',
       +     'a914f24b875d2cb99e0b138ab0e6dd65027932b3c6e787',
       +     'a914aa4bcee53406b1ef6c83852e3844e38a3a9d9f3087',
       +     'a9145e5ec40fdab54be0d6e21107bc38c39df97e37fc87',
       +     'a9141de4d402c82f4e9b0e6b792b331232a5405ebd3f87',
       +     'a9148873ee280e51f9c64d257dd6dedc8712fd652cc687'],
       +    ['a9142cc87d7562a85029a57cc37026e12dab72223db287', #change
       +     'a91499f4aee0b274f0b3ab48549a2c58cd667a62c0cb87',
       +     'a91497a89cd5ada3a766a1275f8151e9256fcf537f6c87',
       +     'a9147ffc9f3a3b60635ea1783243274f4d07ab617cb487',
       +     'a9143423113ab913d86fd47e55488a0c559e18b457b987',
       +     'a914a28a3773a37c52ff6fd7dff497d0eaf80a46febb87'])
       +    , #p2wsh 1of2 multisig wallet
       +    ("1 Vpub5fAqpSRkLmvXwqbuR61MaKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHp" +
       +    "dvrg2LHHXkKxSXBHNWNpZz9b1VqADjmcCs3arSoxN3F3r Vpub5fvEo4MUpbVs9sZqr45" +
       +    "zmRVEsTcQ49MA9m3MLht3XzdZvS9eMXLLu1H6TL1j2SMnykHqXNzG5ycMyQmFDvEE5B32" +
       +    "sP8TmRe6wW8HjBgMssh",
       +    #recv scriptpubkeys
       +    ['002031fbaa839e96fc1abaf3453b9f770e0ccfe2d8e3e990bb381fdcb7db4722986a',
       +     '0020820ae739b36f4feb1c299ced201db383bbcf1634e0071e489b385f43c2323761',
       +     '0020eff05f4d14aa1968a7142b1009aa57a6208fb01b212f8b8f7df63645d26a1292',
       +     '002049c6e17979dca380ffb66295d27f609bea2879d4f0b590c96c70ff12260a8721',
       +     '002002bf2430fc7ebc6fb27da1cb80e52702edcc62a29f65c997e5c924dcd98411bd',
       +     '0020c7a58dcf9633453ba12860b57c14af67d87d022be5c52bf6be7a6abdc295c6e0',
       +     '0020136696059a5e932c72f4f0a05fa7f52faf9b54f1b7694e15acce710e6cc9e89d',
       +     '0020c372e880227f35c2ee35d0724bf05cea95e74dcb3e6aa67ff15f561a29c0645d',
       +     '002095c705590e2b84996fa44bff64179b26669e53bbd58d76bb6bbb5c5498a981ce',
       +     '00207217754dae083c3c365c7e1ce3ad889ca2bd88e4f809cec66b9987adc390aa26',
       +     '0020bee30906450e099357cc96a1f472c1ef70089cd4a0cba96749adfe1c9a2f9e87',
       +     '0020b1838b3d5a386ad6c90eeae9a27a9b812e32ce06376f261dea89e405bc8209d9',
       +     '0020231a3d05886efff601f0702d4c8450dfcce8d6a4bd90f17f7ff76f5c25c632de',
       +     '002071220f3941b5f65aca90e464db4291cd5ea63f37fa858fd5b66d5019f0dbab0f',
       +     '0020fc3c7db9f0e773f9f9c725d4286ddcc88db9575c45b2441d458018150eb4ef10',
       +     '00209f037bfc98dee2fc0d3cca54df09b2d20e92a0133fa381a4dd74c49e4d0a89f5',
       +     '0020c9060d0554ba2ca92048e1772e806d796ba41f10bf6aee2653a9eba96b05c944',
       +     '0020a7cb1dd2730dba564f414ed8d9312370ff89c34df1441b83125cb4d97a96005a',
       +     '00209fddc9b4e070b887dec034ed74f15f62d075a3ac8cf6eb95a88c635e0207534c',
       +     '0020c48f9c50958ab8e386a8bd3888076f31d12e5cf011ff46cc83c6fadfe6d47d20',
       +     '0020a659f4621dca404571917e73dedb26b6d7c49a07dacbf15890760ac0583d3267'],
       +    #change scriptpubkeys
       +    ['002030213b5d3b6988b86aa13a9eaca08e718d51f32dc130c70981abb0102173c791',
       +     '002027bd198f9783a58e9bc4d3fdbd1c75cc74154905cce1d23c7bd3e051695418fe',
       +     '0020c1fd2cdebf120d3b1dc990dfdaca62382ff9525beeb6a79a908ddecb40e2162c',
       +     '00207a3e478266e5fe49fe22e3d8f04d3adda3b6a0835806a0db1f77b84d0ba7f79c',
       +     '002059e66462023ecd54e20d4dce286795e7d5823af511989736edc0c7a844e249f5',
       +     '0020bd8077906dd367d6d107d960397e46db2daba5793249f1f032d8d7e12e6f193c'])
       +]
       +
       +electrum_bad_keydata_test_vectors = [
       +    "zpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" +
       +    "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #bad checksum
       +    "a tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
       +    "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" +
       +    "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" +
       +    "T74sszGaxaf64o5s", #unparsable m number
       +    "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" +
       +    "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ Vpub5fAqpSRkLmvXwqbuR61M" +
       +    "aKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHpdvrg2LHHXkKxSXBHNWNpZz9b1Vq" +
       +    "ADjmcCs3arSoxN3F3r" #inconsistent magic
       +]
        
        def test():
       -    xpub = ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2V" +
       -        "JV7usvsFLZz2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz")
       -    wal = parse_electrum_master_public_key(xpub)
       -    initial_count = 15
       -    gaplimit = 5
       -    spks = wal.get_scriptpubkeys(0, 0, initial_count)
       -    #for test, generate 15, check that the last 5 lead to gap limit overrun
       -    for i in range(initial_count - gaplimit):
       -        ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]], gaplimit)
       -        assert ret == None
       -    for i in range(gaplimit):
       -        index = i + initial_count - gaplimit
       -        ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]], gaplimit)
       -        assert ret != None and ret[0] == i+1
       -    last_index_add = 3
       -    last_index = initial_count - gaplimit + last_index_add
       -    ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index], gaplimit)
       -    assert ret[0] == last_index_add
       -    print("Test passed successfully")
       +    for keydata, recv_spks, change_spks in electrum_keydata_test_vectors:
       +        initial_count = 15
       +        gaplimit = 5
       +        wal = parse_electrum_master_public_key(keydata, gaplimit)
       +        spks = wal.get_scriptpubkeys(0, 0, initial_count)
       +        #for test, generate 15, check that the last 5 lead to gap limit overrun
       +        for i in range(initial_count - gaplimit):
       +            ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]])
       +            assert ret == None
       +        for i in range(gaplimit):
       +            index = i + initial_count - gaplimit
       +            ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]])
       +            assert ret != None and ret[0] == i+1
       +        last_index_add = 3
       +        last_index = initial_count - gaplimit + last_index_add
       +        ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index])
       +        assert ret[0] == last_index_add
       +        assert wal.get_scriptpubkeys(0, 0, len(recv_spks)) == recv_spks
       +        assert wal.get_scriptpubkeys(1, 0, len(change_spks)) == change_spks
       +    for keydata in electrum_bad_keydata_test_vectors:
       +        try:
       +            parse_electrum_master_public_key(keydata, 5)
       +            raised_error = False
       +        except (ValueError, Exception):
       +            raised_error = True
       +        assert raised_error
       +    print("All tests passed successfully")
        
        if __name__ == "__main__":
            test()
       +    pass
       +
   DIR diff --git a/server.py b/server.py
       t@@ -209,6 +209,7 @@ def handle_query(sock, line, rpc, address_history, deterministic_wallets):
                send_response(sock, query, []) #no peers to report
            else:
                log("*** BUG! Not handling method: " + method + " query=" + str(query))
       +        #TODO just send back the same query will result = []
        
        def get_block_header(rpc, blockhash):
            rpc_head = rpc.call("getblockheader", [blockhash])
       t@@ -594,7 +595,7 @@ def get_scriptpubkeys_to_monitor(rpc, config):
            watch_only_addresses = []
            for key in config.options("watch-only-addresses"):
                watch_only_addresses.extend(config.get("watch-only-addresses",
       -            key).replace(' ', ',').split(','))
       +            key).split(' '))
            watch_only_addresses = set(watch_only_addresses)
            watch_only_addresses_to_import = []
            if not watch_only_addresses.issubset(imported_addresses):