def get_addr(self, mixing_depth, forchange, i): return btc.privtoaddr(self.get_key(mixing_depth, forchange, i), magicbyte=get_p2pk_vbyte())
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
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
def get_addr(self, mixing_depth, forchange, i): return btc.privtoaddr( self.get_key(mixing_depth, forchange, i), magicbyte=get_p2pk_vbyte())
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
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
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