示例#1
0
 def get_addr(self, mixing_depth, forchange, i):
     return btc.privtoaddr(self.get_key(mixing_depth, forchange, i),
                           magicbyte=get_p2pk_vbyte())
示例#2
0
    def sync_addresses(self, wallet, restart_cb=None):
        from jmclient.wallet import BitcoinCoreWallet

        if isinstance(wallet, BitcoinCoreWallet):
            return
        log.debug('requesting detailed wallet history')
        wallet_name = self.get_wallet_name(wallet)
        #TODO It is worth considering making this user configurable:
        addr_req_count = 20
        wallet_addr_list = []
        for mix_depth in range(wallet.max_mix_depth):
            for forchange in [0, 1]:
                #If we have an index-cache available, we can use it
                #to decide how much to import (note that this list
                #*always* starts from index 0 on each branch).
                #In cases where the Bitcoin Core instance is fresh,
                #this will allow the entire import+rescan to occur
                #in 2 steps only.
                if wallet.index_cache != [[0, 0]] * wallet.max_mix_depth:
                    #Need to request N*addr_req_count where N is least s.t.
                    #N*addr_req_count > index_cache val. This is so that the batching
                    #process in the main loop *always* has already imported enough
                    #addresses to complete.
                    req_count = int(wallet.index_cache[mix_depth][forchange] /
                                    addr_req_count) + 1
                    req_count *= addr_req_count
                else:
                    #If we have *nothing* - no index_cache, and no info
                    #in Core wallet (imports), we revert to a batching mode
                    #with a default size.
                    #In this scenario it could require several restarts *and*
                    #rescans; perhaps user should set addr_req_count high
                    #(see above TODO)
                    req_count = addr_req_count
                wallet_addr_list += [
                    wallet.get_new_addr(mix_depth, forchange)
                    for _ in range(req_count)
                ]
                #Indices are reset here so that the next algorithm step starts
                #from the beginning of each branch
                wallet.index[mix_depth][forchange] = 0
        # makes more sense to add these in an account called "joinmarket-imported" but its much
        # simpler to add to the same account here
        for privkey_list in wallet.imported_privkeys.values():
            for privkey in privkey_list:
                imported_addr = btc.privtoaddr(privkey,
                                               magicbyte=get_p2pk_vbyte())
                wallet_addr_list.append(imported_addr)
        imported_addr_list = self.rpc('getaddressesbyaccount', [wallet_name])
        if not set(wallet_addr_list).issubset(set(imported_addr_list)):
            self.add_watchonly_addresses(wallet_addr_list, wallet_name,
                                         restart_cb)
            return

        buf = self.rpc('listtransactions', [wallet_name, 1000, 0, True])
        txs = buf
        # If the buffer's full, check for more, until it ain't
        while len(buf) == 1000:
            buf = self.rpc(
                'listtransactions',
                [wallet_name, 1000, len(txs), True])
            txs += buf
        # TODO check whether used_addr_list can be a set, may be faster (if
        # its a hashset) and allows using issubset() here and setdiff() for
        # finding which addresses need importing

        # TODO also check the fastest way to build up python lists, i suspect
        #  using += is slow
        used_addr_list = [
            tx['address'] for tx in txs if tx['category'] == 'receive'
        ]
        too_few_addr_mix_change = []
        for mix_depth in range(wallet.max_mix_depth):
            for forchange in [0, 1]:
                unused_addr_count = 0
                last_used_addr = ''
                breakloop = False
                while not breakloop:
                    if unused_addr_count >= wallet.gaplimit and \
                            is_index_ahead_of_cache(wallet, mix_depth,
                                                    forchange):
                        break
                    mix_change_addrs = [
                        wallet.get_new_addr(mix_depth, forchange)
                        for _ in range(addr_req_count)
                    ]
                    for mc_addr in mix_change_addrs:
                        if mc_addr not in imported_addr_list:
                            too_few_addr_mix_change.append(
                                (mix_depth, forchange))
                            breakloop = True
                            break
                        if mc_addr in used_addr_list:
                            last_used_addr = mc_addr
                            unused_addr_count = 0
                        else:
                            unused_addr_count += 1
#index setting here depends on whether we broke out of the loop
#early; if we did, it means we need to prepare the index
#at the level of the last used address or zero so as to not
#miss any imports in add_watchonly_addresses.
#If we didn't, we need to respect the index_cache to avoid
#potential address reuse.
                if breakloop:
                    if last_used_addr == '':
                        wallet.index[mix_depth][forchange] = 0
                    else:
                        wallet.index[mix_depth][forchange] = \
                            wallet.addr_cache[last_used_addr][2] + 1
                else:
                    if last_used_addr == '':
                        next_avail_idx = max(
                            [wallet.index_cache[mix_depth][forchange], 0])
                    else:
                        next_avail_idx = max([
                            wallet.addr_cache[last_used_addr][2] + 1,
                            wallet.index_cache[mix_depth][forchange]
                        ])
                    wallet.index[mix_depth][forchange] = next_avail_idx

        wallet_addr_list = []
        if len(too_few_addr_mix_change) > 0:
            indices = [
                wallet.index[mc[0]][mc[1]] for mc in too_few_addr_mix_change
            ]
            log.debug('too few addresses in ' + str(too_few_addr_mix_change) +
                      ' at ' + str(indices))
            for mix_depth, forchange in too_few_addr_mix_change:
                wallet_addr_list += [
                    wallet.get_new_addr(mix_depth, forchange)
                    for _ in range(addr_req_count * 3)
                ]

            self.add_watchonly_addresses(wallet_addr_list, wallet_name,
                                         restart_cb)
            return

        self.wallet_synced = True
示例#3
0
    def read_wallet_file_data(self, filename, pwd=None, wallet_dir=None):
        self.path = None
        wallet_dir = wallet_dir if wallet_dir else 'wallets'
        self.index_cache = [[0, 0]] * self.max_mix_depth
        path = os.path.join(wallet_dir, filename)
        if not os.path.isfile(path):
            if get_network() == 'testnet':
                log.debug('filename interpreted as seed, only available in '
                          'testnet because this probably has lower entropy')
                return "FAKESEED" + filename
            else:
                raise IOError('wallet file not found')
        if not pwd:
            log.info("Password required for non-testnet seed wallet")
            return None
        self.path = path
        fd = open(path, 'r')
        walletfile = fd.read()
        fd.close()
        walletdata = json.loads(walletfile)
        if walletdata['network'] != get_network():
            raise ValueError('wallet network(%s) does not match '
                             'joinmarket configured network(%s)' %
                             (walletdata['network'], get_network()))
        if 'index_cache' in walletdata:
            self.index_cache = walletdata['index_cache']
            if self.max_mix_depth > len(self.index_cache):
                #This can happen e.g. in tumbler when we need more mixdepths
                #than currently exist. Since we have no info for those extra
                #depths, we must default to (0,0) (but sync should find used
                #adddresses).
                self.index_cache += [[0, 0]] * (self.max_mix_depth -
                                                len(self.index_cache))
        password_key = btc.bin_dbl_sha256(pwd)
        if 'encrypted_seed' in walletdata:  #accept old field name
            encrypted_entropy = walletdata['encrypted_seed']
        elif 'encrypted_entropy' in walletdata:
            encrypted_entropy = walletdata['encrypted_entropy']
        try:
            decrypted_entropy = decryptData(
                password_key, encrypted_entropy.decode('hex')).encode('hex')
            # there is a small probability of getting a valid PKCS7
            # padding by chance from a wrong password; sanity check the
            # seed length
            if len(decrypted_entropy) != 32:
                raise ValueError
        except ValueError:
            log.info('Incorrect password')
            return None

        if 'encrypted_mnemonic_extension' in walletdata:
            try:
                cleartext = decryptData(
                    password_key,
                    walletdata['encrypted_mnemonic_extension'].decode('hex'))
                #theres a small chance of not getting a ValueError from the wrong
                # password so also check the sum
                if cleartext[-9] != '\xff':
                    raise ValueError
                chunks = cleartext.split('\xff')
                if len(chunks) < 3 or cleartext[-8:] != btc.dbl_sha256(
                        chunks[1])[:8]:
                    raise ValueError
                mnemonic_extension = chunks[1]
            except ValueError:
                log.info('incorrect password')
                return None
        else:
            mnemonic_extension = None

        if self.storepassword:
            self.password_key = password_key
            self.walletdata = walletdata
        if 'imported_keys' in walletdata:
            for epk_m in walletdata['imported_keys']:
                privkey = decryptData(
                    password_key,
                    epk_m['encrypted_privkey'].decode('hex')).encode('hex')
                #Imported keys are stored as 32 byte strings only, so the
                #second version below is sufficient, really.
                if len(privkey) != 64:
                    raise Exception(
                        "Unexpected privkey format; already compressed?:" +
                        privkey)
                privkey += "01"
                if epk_m['mixdepth'] not in self.imported_privkeys:
                    self.imported_privkeys[epk_m['mixdepth']] = []
                self.addr_cache[btc.privtoaddr(
                    privkey, magicbyte=get_p2pk_vbyte())] = (
                        epk_m['mixdepth'], -1,
                        len(self.imported_privkeys[epk_m['mixdepth']]))
                self.imported_privkeys[epk_m['mixdepth']].append(privkey)

        if mnemonic_extension:
            return (decrypted_entropy, mnemonic_extension)
        else:
            return decrypted_entropy
示例#4
0
 def get_addr(self, mixing_depth, forchange, i):
     return btc.privtoaddr(
             self.get_key(mixing_depth, forchange, i), magicbyte=get_p2pk_vbyte())
示例#5
0
 def read_wallet_file_data(self, filename):
     self.path = None
     self.index_cache = [[0, 0]] * self.max_mix_depth
     path = os.path.join('wallets', filename)
     if not os.path.isfile(path):
         if get_network() == 'testnet':
             log.debug('filename interpreted as seed, only available in '
                       'testnet because this probably has lower entropy')
             return filename
         else:
             raise IOError('wallet file not found')
     self.path = path
     fd = open(path, 'r')
     walletfile = fd.read()
     fd.close()
     walletdata = json.loads(walletfile)
     if walletdata['network'] != get_network():
         print ('wallet network(%s) does not match '
                'joinmarket configured network(%s)' % (
             walletdata['network'], get_network()))
         sys.exit(0)
     if 'index_cache' in walletdata:
         self.index_cache = walletdata['index_cache']
     decrypted = False
     while not decrypted:
         password = getpass('Enter wallet decryption passphrase: ')
         password_key = btc.bin_dbl_sha256(password)
         encrypted_seed = walletdata['encrypted_seed']
         try:
             decrypted_seed = decryptData(
                     password_key,
                     encrypted_seed.decode('hex')).encode('hex')
             # there is a small probability of getting a valid PKCS7
             # padding by chance from a wrong password; sanity check the
             # seed length
             if len(decrypted_seed) == 32:
                 decrypted = True
             else:
                 raise ValueError
         except ValueError:
             print('Incorrect password')
             decrypted = False
     if self.storepassword:
         self.password_key = password_key
         self.walletdata = walletdata
     if 'imported_keys' in walletdata:
         for epk_m in walletdata['imported_keys']:
             privkey = decryptData(
                     password_key,
                     epk_m['encrypted_privkey'].decode( 'hex')).encode('hex')
             #Imported keys are stored as 32 byte strings only, so the
             #second version below is sufficient, really.
             if not btc.secp_present:
                 privkey = btc.encode_privkey(privkey, 'hex_compressed')
             else:
                 if len(privkey) != 64:
                     raise Exception(
                     "Unexpected privkey format; already compressed?:" + privkey)
                 privkey += "01"
             if epk_m['mixdepth'] not in self.imported_privkeys:
                 self.imported_privkeys[epk_m['mixdepth']] = []
             self.addr_cache[btc.privtoaddr(
                     privkey, magicbyte=get_p2pk_vbyte())] = (epk_m['mixdepth'], -1,
                 len(self.imported_privkeys[epk_m['mixdepth']]))
             self.imported_privkeys[epk_m['mixdepth']].append(privkey)
     return decrypted_seed
示例#6
0
 def read_wallet_file_data(self, filename):
     self.path = None
     self.index_cache = [[0, 0]] * self.max_mix_depth
     path = os.path.join('wallets', filename)
     if not os.path.isfile(path):
         if get_network() == 'testnet':
             log.debug('filename interpreted as seed, only available in '
                       'testnet because this probably has lower entropy')
             return filename
         else:
             raise IOError('wallet file not found')
     self.path = path
     fd = open(path, 'r')
     walletfile = fd.read()
     fd.close()
     walletdata = json.loads(walletfile)
     if walletdata['network'] != get_network():
         print('wallet network(%s) does not match '
               'joinmarket configured network(%s)' %
               (walletdata['network'], get_network()))
         sys.exit(0)
     if 'index_cache' in walletdata:
         self.index_cache = walletdata['index_cache']
     decrypted = False
     while not decrypted:
         password = getpass('Enter wallet decryption passphrase: ')
         password_key = btc.bin_dbl_sha256(password)
         encrypted_seed = walletdata['encrypted_seed']
         try:
             decrypted_seed = decryptData(
                 password_key, encrypted_seed.decode('hex')).encode('hex')
             # there is a small probability of getting a valid PKCS7
             # padding by chance from a wrong password; sanity check the
             # seed length
             if len(decrypted_seed) == 32:
                 decrypted = True
             else:
                 raise ValueError
         except ValueError:
             print('Incorrect password')
             decrypted = False
     if self.storepassword:
         self.password_key = password_key
         self.walletdata = walletdata
     if 'imported_keys' in walletdata:
         for epk_m in walletdata['imported_keys']:
             privkey = decryptData(
                 password_key,
                 epk_m['encrypted_privkey'].decode('hex')).encode('hex')
             #Imported keys are stored as 32 byte strings only, so the
             #second version below is sufficient, really.
             if not btc.secp_present:
                 privkey = btc.encode_privkey(privkey, 'hex_compressed')
             else:
                 if len(privkey) != 64:
                     raise Exception(
                         "Unexpected privkey format; already compressed?:" +
                         privkey)
                 privkey += "01"
             if epk_m['mixdepth'] not in self.imported_privkeys:
                 self.imported_privkeys[epk_m['mixdepth']] = []
             self.addr_cache[btc.privtoaddr(
                 privkey, magicbyte=get_p2pk_vbyte())] = (
                     epk_m['mixdepth'], -1,
                     len(self.imported_privkeys[epk_m['mixdepth']]))
             self.imported_privkeys[epk_m['mixdepth']].append(privkey)
     return decrypted_seed
示例#7
0
    def sync_addresses(self, wallet):
        from joinmarket.wallet import BitcoinCoreWallet

        if isinstance(wallet, BitcoinCoreWallet):
            return
        log.debug('requesting wallet history')
        wallet_name = self.get_wallet_name(wallet)
        addr_req_count = 20
        wallet_addr_list = []
        for mix_depth in range(wallet.max_mix_depth):
            for forchange in [0, 1]:
                wallet_addr_list += [wallet.get_new_addr(mix_depth, forchange)
                                     for _ in range(addr_req_count)]
                wallet.index[mix_depth][forchange] = 0
        # makes more sense to add these in an account called "joinmarket-imported" but its much
        # simpler to add to the same account here
        for privkey_list in wallet.imported_privkeys.values():
            for privkey in privkey_list:
                imported_addr = btc.privtoaddr(privkey, magicbyte=get_p2pk_vbyte())
                wallet_addr_list.append(imported_addr)
        imported_addr_list = self.rpc('getaddressesbyaccount', [wallet_name])
        if not set(wallet_addr_list).issubset(set(imported_addr_list)):
            self.add_watchonly_addresses(wallet_addr_list, wallet_name)
            return

        buf = self.rpc('listtransactions', [wallet_name, 1000, 0, True])
        txs = buf
        # If the buffer's full, check for more, until it ain't
        while len(buf) == 1000:
            buf = self.rpc('listtransactions', [wallet_name, 1000, len(txs),
                                                True])
            txs += buf
        # TODO check whether used_addr_list can be a set, may be faster (if
        # its a hashset) and allows using issubset() here and setdiff() for
        # finding which addresses need importing

        # TODO also check the fastest way to build up python lists, i suspect
        #  using += is slow
        used_addr_list = [tx['address']
                          for tx in txs if tx['category'] == 'receive']
        too_few_addr_mix_change = []
        for mix_depth in range(wallet.max_mix_depth):
            for forchange in [0, 1]:
                unused_addr_count = 0
                last_used_addr = ''
                breakloop = False
                while not breakloop:
                    if unused_addr_count >= wallet.gaplimit and \
                            is_index_ahead_of_cache(wallet, mix_depth,
                                                    forchange):
                        break
                    mix_change_addrs = [
                        wallet.get_new_addr(mix_depth, forchange)
                        for _ in range(addr_req_count)]
                    for mc_addr in mix_change_addrs:
                        if mc_addr not in imported_addr_list:
                            too_few_addr_mix_change.append(
                                    (mix_depth, forchange))
                            breakloop = True
                            break
                        if mc_addr in used_addr_list:
                            last_used_addr = mc_addr
                            unused_addr_count = 0
                        else:
                            unused_addr_count += 1

                if last_used_addr == '':
                    wallet.index[mix_depth][forchange] = 0
                else:
                    wallet.index[mix_depth][forchange] = \
                        wallet.addr_cache[last_used_addr][2] + 1

        wallet_addr_list = []
        if len(too_few_addr_mix_change) > 0:
            log.debug('too few addresses in ' + str(too_few_addr_mix_change))
            for mix_depth, forchange in too_few_addr_mix_change:
                wallet_addr_list += [
                    wallet.get_new_addr(mix_depth, forchange)
                    for _ in range(addr_req_count * 3)]

            self.add_watchonly_addresses(wallet_addr_list, wallet_name)
            return

        self.wallet_synced = True