URI: 
       timplemented monitoring of deterministic wallets, only handles p2pkh keys for now - 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 ee671a20350e72a1210f4ad6530677ef563cf2d4
   DIR parent 480f603b2c9a988f5407e47105655152f7f58376
  HTML Author: chris-belcher <chris-belcher@users.noreply.github.com>
       Date:   Sat,  3 Mar 2018 16:15:42 +0000
       
       implemented monitoring of deterministic wallets, only handles p2pkh keys for now
       
       Diffstat:
         M config.cfg_sample                   |      42 ++++++++++++++++++++-----------
         A deterministicwallet.py              |     125 +++++++++++++++++++++++++++++++
         M merkleproof.py                      |       2 +-
         M server.py                           |     250 +++++++++++++++++++------------
         M util.py                             |      19 ++++++++++++-------
       
       5 files changed, 316 insertions(+), 122 deletions(-)
       ---
   DIR diff --git a/config.cfg_sample b/config.cfg_sample
       t@@ -2,17 +2,22 @@
        ## Electrum Personal Server configuration file
        ## Comments start with #
        
       -[wallets]
       -## Add addresses to this section
       +[electrum-master-public-keys]
       +## Add electrum master public keys to this section
       +
       +#any_key_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF
        
       -# These are just random example addresses found on a blockchain explorer
       +[bip39-master-public-keys]
       +## Add master public keys in the bip39 standard to this section
       +## Most hardware wallet keys go here
        
       -# A key can be anything
       -addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n
       -# A comma separated list is also accepted
       -my_test_addresses = 1KKszdQEpXgSY4EvsnGcGEzbQFmdcFwuNS,1EuEVUtQQ8hmuMHNdMdjLwjpkm6Ef7RYVk,bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
       -# And space separated
       -more_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1Arzu6mWZuXGTF9yR2hZhMgBJgu1Xh2bNC 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz
       +
       +[watch-only-addresses]
       +## Add addresses to this section
       +
       +#addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n
       +# A space or comma separated list is accepted
       +#my_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
        
        [bitcoin-rpc]
        host = localhost
       t@@ -20,18 +25,25 @@ port = 8332
        user = bitcoinrpc
        password = password
        
       -#how often in seconds to poll for new transactions when electrum not connected
       +# how often in seconds to poll for new transactions when electrum not connected
        poll_interval_listening = 30
       -#how often in seconds to poll for new transactions when electrum is connected
       -poll_interval_connected = 2
       +# how often in seconds to poll for new transactions when electrum is connected
       +poll_interval_connected = 5
       +
       +# Parameters for dealing with deterministic wallets
       +# how many addresses to import first time, should be big because if you import too little you may have to rescan again
       +initial_import_count = 200
       +# number of unused addresses kept at the head of the wallet
       +gap_limit = 25
       +
        
        [electrum-server]
       -#0.0.0.0 to accept connections from any IP
       +# 0.0.0.0 to accept connections from any IP
        #127.0.0.1 to accept from only localhost
       -#recommended you accept localhost only and connect with a ssh tunnel
       +# recommended you accept localhost only and for connecting remotely use a ssh tunnel
        host = 127.0.0.1
        port = 50002
        
        [misc]
       -#not implemented yet
       +# not implemented yet
        print_debug = false
   DIR diff --git a/deterministicwallet.py b/deterministicwallet.py
       t@@ -0,0 +1,125 @@
       +
       +import bitcoin as btc
       +import util
       +
       +def parse_electrum_master_public_key(keydata, gaplimit):
       +    if keydata[:4] == "xpub":
       +        return SingleSigP2PKHWallet(keydata, 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
       +
       +class DeterministicWallet(object):
       +    def __init__(self, gaplimit):
       +        self.gaplimit = gaplimit
       +
       +    def get_new_scriptpubkeys(self, change, count):
       +        """Returns newly-generated addresses from this deterministic wallet"""
       +        pass
       +
       +    def get_scriptpubkeys(self, change, from_index, count):
       +        """Returns addresses from this deterministic wallet"""
       +        pass
       +
       +    #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):
       +        """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:
       +                continue
       +            change, index = self.scriptpubkey_index[spk]
       +            distance_from_next = self.next_index[change] - index
       +            if distance_from_next > self.gaplimit:
       +                continue
       +            #need to import more
       +            if change in result:
       +                result[change] = max(result[change], self.gaplimit
       +                    - distance_from_next + 1)
       +            else:
       +                result[change] = self.gaplimit - distance_from_next + 1
       +        if len(result) > 0:
       +            return result
       +        else:
       +            return None
       +
       +    def rewind_one(self, change):
       +        self.next_index[change] -= 1
       +
       +'''
       +recv
       +76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac
       +76a914d8b6b932e892fad5132ea888111adac2171c5af588ac
       +76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac
       +change
       +76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac
       +76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac
       +76a91433bdb046a1d373728d7844df89aa24f788443a4588ac
       +'''
       +
       +#need test vectors for each kind of detwallet
       +
       +
       +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")
       +
       +if __name__ == "__main__":
       +    test()
   DIR diff --git a/merkleproof.py b/merkleproof.py
       t@@ -259,7 +259,7 @@ def test():
            print("All tests passed")
        
        '''
       -proof = "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc3001000000002793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa21aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a1902360baa5e4328e0105"
       +proof = ""
        def chunks(d, n):
            return [d[x:x + n] for x in range(0, len(d), n)]
        #print(proof)
   DIR diff --git a/server.py b/server.py
       t@@ -1,62 +1,14 @@
        #! /usr/bin/python3
        
       -#add a feature where it prints the first 3 addresses from a deterministic
       -# wallet, so you can check the addresses are correct before importing them
       -# into the node
       -
       -#or deterministic wallets
       -#should figure out what do regarding gap limits, when to import more addresses
       -# and how many addresses to start with
       -# maybe have a separate list of later addresses and if one of them get
       -#  requested then import more
       -
       -#TODO try to support ssl
       -#doesnt support ssl yet you you must run ./electrum --nossl
       -#https://github.com/spesmilo/electrum/commit/dc388d4c7c541fadb9869727e359edace4c9f6f0
       -#maybe copy from electrumx
       -#https://github.com/kyuupichan/electrumx/blob/35dd1f61996b02a84691ea71ff50f0900df969bc/server/peers.py#L476
       -#https://github.com/kyuupichan/electrumx/blob/2d7403f2efed7e8f33c5cb93e2cd9144415cbb9f/server/controller.py#L259
       -
       -#merkle trees cant be used if bitcoin core has pruning enabled, this will
       -# probably requires new code to be written for core
       -#another possible use of merkleproofs in wallet.dat
       -# https://github.com/JoinMarket-Org/joinmarket/issues/156#issuecomment-231059844
       -
       -#using core's multiple wallet feature might help, should read up on that
       -
       -#now that the rescanblockchain rpc call exists in 0.16 which allows specifying
       -# a starting height, that will cut down the time to rescan as long as the user
       -# has saved their wallet creation date
       -
       -#one day there could be a nice GUI which does everything, including converting
       -# the wallet creation date to a block height and rescanning
       -'''
       -<belcher> now that 0.16 has this rpc called rescanblockchain which takes an optional start_height, i wonder what the most practical way of converting date to block height is
       -<belcher> thinking about the situation where you have a mnemonic recovery phrase + the date you created it, and want to rescan
       -<belcher> binary search the timestamps in the block headers i guess, then subtract two weeks just in case
       -<wumpus> belcher: binary search in something that is not strictly increasing seems faulty
       -<belcher> yes true, so maybe binary search to roughly get to the right block height then linear search +- a few blocks
       -<wumpus> belcher: though my gut feeling is that subtracting the two weeks would fix it
       -<belcher> when people write down the wallet creation date they probably wont be precise, you could get away with writing only the year and month i bet
       -<wumpus> as the mismatch is at most 2 hours
       -<Sentineo> wumpus: 2 hours for the clock scew allowed by peers? (when they throw away a block which is older than 2 hours from their actual time)?
       -<wumpus> Sentineo: that's what I remember, I might be off though
       -<Sentineo> I am not sure if it s 2 or 4 :D
       -<Sentineo> lazyness :)
       -<wumpus> in any case it is a bounded value, which means binary search might work within that precision, too lazy to look for proof though :)
       -'''
       -
       -##### good things
       -
       -# well placed to take advantage of dandelion private tx broadcasting
       -# and broadcasting through tor
       +#the electrum protocol uses hash(scriptpubkey) as a key for lookups
       +# as an alternative to address or scriptpubkey
        
        import socket, time, json, datetime, struct, binascii, math, pprint
        from configparser import ConfigParser, NoSectionError
        from decimal import Decimal
        
        from jsonrpc import JsonRpc, JsonRpcError
       -import util, merkleproof
       +import util, merkleproof, deterministicwallet
        
        ADDRESSES_LABEL = "electrum-watchonly-addresses"
        
       t@@ -66,7 +18,7 @@ BANNER = \
        """Welcome to Electrum Personal Server
        https://github.com/chris-belcher/electrum-personal-server
        
       -Monitoring {addr} addresses
       +Monitoring {detwallets} deterministic wallets, in total {addr} addresses.
        
        Connected bitcoin node: {useragent}
        Peers: {peers}
       t@@ -103,11 +55,14 @@ def send_update(sock, update):
            sock.sendall(json.dumps(update).encode('utf-8') + b'\n')
            debug('<= ' + json.dumps(update))
        
       -def on_heartbeat_listening(rpc, address_history, unconfirmed_txes):
       +def on_heartbeat_listening(rpc, address_history, unconfirmed_txes,
       +        deterministic_wallets):
            debug("on heartbeat listening")
       -    check_for_updated_txes(rpc, address_history, unconfirmed_txes)
       +    check_for_updated_txes(rpc, address_history, unconfirmed_txes,
       +        deterministic_wallets)
        
       -def on_heartbeat_connected(sock, rpc, address_history, unconfirmed_txes):
       +def on_heartbeat_connected(sock, rpc, address_history, unconfirmed_txes,
       +        deterministic_wallets):
            debug("on heartbeat connected")
            is_tip_updated, header = check_for_new_blockchain_tip(rpc)
            if is_tip_updated:
       t@@ -117,7 +72,7 @@ def on_heartbeat_connected(sock, rpc, address_history, unconfirmed_txes):
                        "params": [header]}
                    send_update(sock, update)
            updated_scripthashes = check_for_updated_txes(rpc, address_history,
       -        unconfirmed_txes)
       +        unconfirmed_txes, deterministic_wallets)
            for scrhash in updated_scripthashes:
                if not address_history[scrhash]["subscribed"]:
                    continue
       t@@ -132,7 +87,7 @@ def on_disconnect(address_history):
            for srchash, his in address_history.items():
                his["subscribed"] = False
        
       -def handle_query(sock, line, rpc, address_history):
       +def handle_query(sock, line, rpc, address_history, deterministic_wallets):
            debug("=> " + line)
            try:
                query = json.loads(line)
       t@@ -238,6 +193,7 @@ def handle_query(sock, line, rpc, address_history):
                blockchaininfo = rpc.call("getblockchaininfo", [])
                uptime = rpc.call("uptime", [])
                send_response(sock, query, BANNER.format(
       +            detwallets=len(deterministic_wallets),
                    addr=len(address_history),
                    useragent=networkinfo["subversion"],
                    peers=networkinfo["connections"],
       t@@ -286,7 +242,8 @@ def create_server_socket(hostport):
            return server_sock
        
        def run_electrum_server(hostport, rpc, address_history, unconfirmed_txes,
       -        poll_interval_listening, poll_interval_connected):
       +        deterministic_wallets, poll_interval_listening,
       +        poll_interval_connected):
            log("Starting electrum server")
            while True:
                try:
       t@@ -299,7 +256,7 @@ def run_electrum_server(hostport, rpc, address_history, unconfirmed_txes,
                            break
                        except socket.timeout:
                            on_heartbeat_listening(rpc, address_history,
       -                        unconfirmed_txes)
       +                        unconfirmed_txes, deterministic_wallets)
                    server_sock.close()
                    sock.settimeout(poll_interval_connected)
                    log('Electrum connected from ' + str(addr))
       t@@ -318,10 +275,10 @@ def run_electrum_server(hostport, rpc, address_history, unconfirmed_txes,
                                recv_buffer = recv_buffer[lb + 1:]
                                lb = recv_buffer.find(b'\n')
                                handle_query(sock, line.decode("utf-8"), rpc,
       -                            address_history)
       +                            address_history, deterministic_wallets)
                        except socket.timeout:
                            on_heartbeat_connected(sock, rpc, address_history,
       -                        unconfirmed_txes)
       +                        unconfirmed_txes, deterministic_wallets)
                except (IOError, EOFError) as e:
                    if isinstance(e, EOFError):
                        log("Electrum wallet disconnected")
       t@@ -392,9 +349,10 @@ def sort_address_history_list(his):
            his["history"].extend(unconfirm_txes)
            return unconfirm_txes
        
       -def check_for_updated_txes(rpc, address_history, unconfirmed_txes):
       -    updated_srchashes1 = check_for_unconfirmed_txes(rpc, address_history,
       -        unconfirmed_txes)
       +def check_for_updated_txes(rpc, address_history, unconfirmed_txes,
       +        deterministic_wallets):
       +    updated_srchashes1 = check_for_new_txes(rpc, address_history,
       +        unconfirmed_txes, deterministic_wallets)
            updated_srchashes2 = check_for_confirmations(rpc, address_history,
                unconfirmed_txes)
            updated_srchashes = updated_srchashes1 | updated_srchashes2
       t@@ -435,7 +393,8 @@ def check_for_confirmations(rpc, address_history, unconfirmed_txes):
                updated_srchashes.update(set(srchashes))
            return updated_srchashes
        
       -def check_for_unconfirmed_txes(rpc, address_history, unconfirmed_txes):
       +def check_for_new_txes(rpc, address_history, unconfirmed_txes,
       +        deterministic_wallets):
            MAX_TX_REQUEST_COUNT = 256 
            tx_request_count = 2
            max_attempts = int(math.log(MAX_TX_REQUEST_COUNT, 2))
       t@@ -486,7 +445,6 @@ def check_for_unconfirmed_txes(rpc, address_history, unconfirmed_txes):
                obtained_txids.add(tx["txid"])
                output_scriptpubkeys, input_scriptpubkeys, txd = \
                    get_input_and_output_scriptpubkeys(rpc, tx["txid"])
       -
                matching_scripthashes = []
                for spk in (output_scriptpubkeys + input_scriptpubkeys):
                    scripthash = util.script_to_scripthash(spk)
       t@@ -494,9 +452,21 @@ def check_for_unconfirmed_txes(rpc, address_history, unconfirmed_txes):
                        matching_scripthashes.append(scripthash)
                if len(matching_scripthashes) == 0:
                    continue
       +
       +        for wal in deterministic_wallets:
       +            overrun_depths = wal.have_scriptpubkeys_overrun_gaplimit(
       +                output_scriptpubkeys)
       +            if overrun_depths != None:
       +                for change, import_count in overrun_depths.items():
       +                    spks = wal.get_new_scriptpubkeys(change, import_count)
       +                    new_addrs = [util.script_to_address(s, rpc) for s in spks]
       +                    debug("Importing " + str(len(spks)) + " into change="
       +                        + str(change))
       +                    import_addresses(rpc, new_addrs)
       +
                updated_scripthashes.extend(matching_scripthashes)
                new_history_element = generate_new_history_element(rpc, tx, txd)
       -        log("Found new unconfirmed tx: " + str(new_history_element))
       +        log("Found new tx: " + str(new_history_element))
                for srchash in matching_scripthashes:
                    address_history[srchash]["history"].append(new_history_element)
                    if new_history_element["height"] == 0:
       t@@ -504,16 +474,16 @@ def check_for_unconfirmed_txes(rpc, address_history, unconfirmed_txes):
                            unconfirmed_txes[tx["txid"]].append(srchash)
                        else:
                            unconfirmed_txes[tx["txid"]] = [srchash]
       +        #check whether the gap limits have been overrun and import more addrs
            return set(updated_scripthashes)
        
       -def build_address_history_index(rpc, wallet_addresses):
       -    log("Building history index with " + str(len(wallet_addresses)) +
       +def build_address_history(rpc, monitored_scriptpubkeys, deterministic_wallets):
       +    log("Building history with " + str(len(monitored_scriptpubkeys)) +
                " addresses")
            st = time.time()
            address_history = {}
       -    for addr in wallet_addresses:
       -        scripthash = util.address_to_scripthash(addr, rpc)
       -        address_history[scripthash] = {'addr': addr, 'history': [],
       +    for spk in monitored_scriptpubkeys:
       +        address_history[util.script_to_scripthash(spk)] = {'history': [],
                    'subscribed': False}
            wallet_addr_scripthashes = set(address_history.keys())
            #populate history
       t@@ -554,6 +524,18 @@ def build_address_history_index(rpc, wallet_addresses):
                    if len(sh_to_add) == 0:
                        continue
        
       +            for wal in deterministic_wallets:
       +                overrun_depths = wal.have_scriptpubkeys_overrun_gaplimit(
       +                    output_scriptpubkeys)
       +                if overrun_depths != None:
       +                    log("ERROR: Not enough addresses imported. Exiting.")
       +                    log("Delete wallet.dat and increase the value of " +
       +                        "`initial_import_count` in the file `config.cfg` " +
       +                        "then reimport and rescan")
       +                    #TODO make it so users dont have to delete wallet.dat
       +                    # check whether all initial_import_count addresses are
       +                    # imported rather than just the first one
       +                    return None, None
                    new_history_element = generate_new_history_element(rpc, tx, txd)
                    for scripthash in sh_to_add:
                        address_history[scripthash][
       t@@ -578,29 +560,92 @@ def build_address_history_index(rpc, wallet_addresses):
            debug("last_known_recent_txid = " + str(last_known_recent_txid[0]))
        
            et = time.time()
       -    log("Found " + str(count) + " txes. Address history index built in "
       -        + str(et - st) + "sec")
       +    log("Found " + str(count) + " txes. History built in " + str(et - st)
       +        + "sec")
            debug("address_history =\n" + pprint.pformat(address_history))
       -
            return address_history, unconfirmed_txes
        
       -def import_watchonly_addresses(rpc, addrs):
       -    log("Importing " + str(len(addrs)) + " watch-only addresses into the"
       -        + " Bitcoin node after 5 seconds . . .")
       -    debug("addrs = " + str(addrs))
       -    time.sleep(5)
       +def get_scriptpubkeys_to_monitor(rpc, config):
       +    imported_addresses = set(rpc.call("getaddressesbyaccount",
       +        [ADDRESSES_LABEL]))
       +
       +    deterministic_wallets = []
       +    for key in config.options("electrum-master-public-keys"):
       +        wal = deterministicwallet.parse_electrum_master_public_key(
       +            config.get("electrum-master-public-keys", key),
       +            int(config.get("bitcoin-rpc", "gap_limit")))
       +        deterministic_wallets.append(wal)
       +    #add bip39 wallets here
       +
       +    #check whether these deterministic wallets have already been imported
       +    import_needed = False
       +    wallets_imported = 0
       +    spks_to_import = []
       +    for wal in deterministic_wallets:
       +        first_addr = util.script_to_address(wal.get_scriptpubkeys(change=0,
       +            from_index=0, count=1)[0], rpc)
       +        if first_addr not in imported_addresses:
       +            import_needed = True
       +            wallets_imported += 1
       +            for change in [0, 1]:
       +                spks_to_import.extend(wal.get_scriptpubkeys(change, 0,
       +                    int(config.get("bitcoin-rpc", "initial_import_count"))))
       +    #check whether watch-only addresses have been imported
       +    watch_only_addresses = []
       +    for key in config.options("watch-only-addresses"):
       +        watch_only_addresses.extend(config.get("watch-only-addresses",
       +            key).replace(' ', ',').split(','))
       +    watch_only_addresses = set(watch_only_addresses)
       +    watch_only_addresses_to_import = []
       +    if not watch_only_addresses.issubset(imported_addresses):
       +        import_needed = True
       +        watch_only_addresses_to_import = wallet_addresses - imported_addresses
       +
       +    if import_needed:
       +        addresses_to_import = [util.script_to_address(spk, rpc)
       +            for spk in spks_to_import]
       +        #TODO minus imported_addresses
       +        log("Importing " + str(wallets_imported) + " wallets and " +
       +            str(len(watch_only_addresses_to_import)) + " watch-only " +
       +            "addresses into the Bitcoin node")
       +        time.sleep(5)
       +        return (True, addresses_to_import + list(
       +            watch_only_addresses_to_import), None)
       +
       +    #test
       +    # importing one det wallet and no addrs, two det wallets and no addrs
       +    # no det wallets and some addrs, some det wallets and some addrs
       +
       +    #at this point we know we dont need to import any addresses
       +    #find which index the deterministic wallets are up to
       +    spks_to_monitor = []
       +    for wal in deterministic_wallets:
       +        for change in [0, 1]:
       +            spks_to_monitor.extend(wal.get_scriptpubkeys(change, 0,
       +                int(config.get("bitcoin-rpc", "initial_import_count"))))
       +            #loop until one address found that isnt imported
       +            while True:
       +                spk = wal.get_new_scriptpubkeys(change, count=1)[0]
       +                spks_to_monitor.append(spk)
       +                if util.script_to_address(spk, rpc) not in imported_addresses:
       +                    break
       +            spks_to_monitor.pop()
       +            wal.rewind_one(change)
       +
       +    spks_to_monitor.extend([util.address_to_script(addr, rpc)
       +        for addr in watch_only_addresses])
       +    return False, spks_to_monitor, deterministic_wallets
       +
       +def import_addresses(rpc, addrs):
       +    debug("importing addrs = " + str(addrs))
            for a in addrs:
                rpc.call("importaddress", [a, ADDRESSES_LABEL, False])
       -    #TODO tell people about the `rescanblockchain` call which allows a range
       -    log("Done.\nIf recovering a wallet which already has existing " +
       -        "transactions, then\nrestart Bitcoin with -rescan. If your wallet " +
       -        "is new and empty then just restart this script")
        
        def main():
            try:
                config = ConfigParser()
                config.read(["config.cfg"])
       -        config.options("wallets")
       +        config.options("electrum-master-public-keys")
            except NoSectionError:
                log("Non-existant configuration file `config.cfg`")
                return
       t@@ -608,6 +653,7 @@ def main():
                        port = int(config.get("bitcoin-rpc", "port")),
                        user = config.get("bitcoin-rpc", "user"),
                        password = config.get("bitcoin-rpc", "password"))
       +
            #TODO somewhere here loop until rpc works and fully sync'd, to allow
            # people to run this script without waiting for their node to fully
            # catch up sync'd when getblockchaininfo blocks == headers, or use
       t@@ -618,25 +664,31 @@ def main():
                    bestblockhash[0] = rpc.call("getbestblockhash", [])
                except TypeError:
                    if not printed_error_msg:
       -                log("Error with bitcoin rpc, check host/port/username/password")
       +                log("Error with bitcoin rpc, check host/port/user/password")
                        printed_error_msg = True
                    time.sleep(5)
       -    wallet_addresses = []
       -    for key in config.options("wallets"):
       -        addrs = config.get("wallets", key).replace(' ', ',').split(',')
       -        wallet_addresses.extend(addrs)
       -    wallet_addresses = set(wallet_addresses)
       -    imported_addresses = set(rpc.call("getaddressesbyaccount",
       -        [ADDRESSES_LABEL]))
       -    if not wallet_addresses.issubset(imported_addresses):
       -        import_watchonly_addresses(rpc, wallet_addresses - imported_addresses)
       +
       +    import_needed, relevant_spks_addrs, deterministic_wallets = \
       +        get_scriptpubkeys_to_monitor(rpc, config)
       +    if import_needed:
       +        import_addresses(rpc, relevant_spks_addrs)
       +        #TODO tell people about the rescanblockchain call which allows a range
       +        log("Done.\nIf recovering a wallet which already has existing " +
       +            "transactions, then\nrestart Bitcoin with -rescan. If your " +
       +            "wallet is new and empty then just restart this script")
            else:
       -        address_history, unconfirmed_txes = build_address_history_index(
       -            rpc, wallet_addresses)
       +        address_history, unconfirmed_txes = build_address_history(
       +            rpc, relevant_spks_addrs, deterministic_wallets)
       +        if address_history == None:
       +            return
                hostport = (config.get("electrum-server", "host"),
                        int(config.get("electrum-server", "port")))
       +        poll_interval_listening = int(config.get("bitcoin-rpc",
       +            "poll_interval_listening"))
       +        poll_interval_connected = int(config.get("bitcoin-rpc",
       +            "poll_interval_connected"))
                run_electrum_server(hostport, rpc, address_history, unconfirmed_txes,
       -            int(config.get("bitcoin-rpc", "poll_interval_listening")),
       -            int(config.get("bitcoin-rpc", "poll_interval_connected")))
       +            deterministic_wallets, poll_interval_listening,
       +            poll_interval_connected)
        
        main()
   DIR diff --git a/util.py b/util.py
       t@@ -1,5 +1,4 @@
        
       -import bitcoin as btc
        import hashlib, binascii
        
        ## stuff copied from electrum's source
       t@@ -57,14 +56,20 @@ def hash_merkle_root(merkle_s, target_hash, pos):
                    h + hash_decode(item))
            return hash_encode(h)
        
       +def hash_160(public_key):
       +    try:
       +        md = hashlib.new('ripemd160')
       +        md.update(sha256(public_key))
       +        return md.digest()
       +    except BaseException:
       +        from . import ripemd
       +        md = ripemd.new(sha256(public_key))
       +        return md.digest()
       +
        ## end of electrum copypaste
        
       -def script_to_address(script):
       -    #TODO why is this even here? its not used anywhere, maybe old code
       -    #TODO bech32 addresses
       -    #TODO testnet, although everything uses scripthash so the address
       -    #     vbyte doesnt matter
       -    return btc.script_to_address(script, 0x00)
       +def script_to_address(scriptPubKey, rpc):
       +    return rpc.call("decodescript", [scriptPubKey])["addresses"][0]
        
        def address_to_script(addr, rpc):
            return rpc.call("validateaddress", [addr])["scriptPubKey"]