accounts
This commit is contained in:
218
lib/wallet.py
218
lib/wallet.py
@@ -80,8 +80,6 @@ class Wallet:
|
||||
self.fee = int(config.get('fee',100000))
|
||||
self.num_zeros = int(config.get('num_zeros',0))
|
||||
self.use_encryption = config.get('use_encryption', False)
|
||||
self.addresses = config.get('addresses', []) # receiving addresses visible for user
|
||||
self.change_addresses = config.get('change_addresses', []) # addresses used as change
|
||||
self.seed = config.get('seed', '') # encrypted
|
||||
self.labels = config.get('labels', {})
|
||||
self.aliases = config.get('aliases', {}) # aliases for addresses
|
||||
@@ -93,9 +91,15 @@ class Wallet:
|
||||
self.imported_keys = config.get('imported_keys',{})
|
||||
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
||||
self.tx_height = config.get('tx_height',{})
|
||||
self.requested_amounts = config.get('requested_amounts',{})
|
||||
self.accounts = config.get('accounts', {}) # this should not include public keys
|
||||
self.sequences = {}
|
||||
|
||||
mpk1 = self.config.get('master_public_key')
|
||||
self.sequences[0] = DeterministicSequence(mpk1)
|
||||
if self.accounts.get(0) is None:
|
||||
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||
|
||||
master_public_key = config.get('master_public_key','')
|
||||
self.sequence = DeterministicSequence(master_public_key)
|
||||
|
||||
self.transactions = {}
|
||||
tx = config.get('transactions',{})
|
||||
@@ -105,7 +109,6 @@ class Wallet:
|
||||
print_msg("Warning: Cannot deserialize transactions. skipping")
|
||||
|
||||
|
||||
self.requested_amounts = config.get('requested_amounts',{})
|
||||
|
||||
# not saved
|
||||
self.prevout_values = {} # my own transaction outputs
|
||||
@@ -162,21 +165,33 @@ class Wallet:
|
||||
self.seed = seed
|
||||
self.config.set_key('seed', self.seed, True)
|
||||
self.config.set_key('seed_version', self.seed_version, True)
|
||||
self.init_mpk(self.seed)
|
||||
|
||||
def init_mpk(self,seed):
|
||||
self.init_main_account(self.seed)
|
||||
|
||||
|
||||
def init_main_account(self, seed):
|
||||
# public key
|
||||
self.sequence = DeterministicSequence.from_seed(seed)
|
||||
self.config.set_key('master_public_key', self.sequence.master_public_key, True)
|
||||
sequence = DeterministicSequence.from_seed(seed)
|
||||
self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
|
||||
self.sequences[0] = sequence
|
||||
self.config.set_key('accounts', self.accounts, True)
|
||||
|
||||
|
||||
|
||||
def all_addresses(self):
|
||||
return self.addresses + self.change_addresses + self.imported_keys.keys()
|
||||
o = self.imported_keys.keys()
|
||||
for a in self.accounts.values():
|
||||
o += a[0]
|
||||
o += a[1]
|
||||
return o
|
||||
|
||||
|
||||
def is_mine(self, address):
|
||||
return address in self.all_addresses()
|
||||
|
||||
def is_change(self, address):
|
||||
return address in self.change_addresses
|
||||
#return address in self.change_addresses
|
||||
return False
|
||||
|
||||
def get_master_public_key(self):
|
||||
return self.sequence.master_public_key
|
||||
@@ -184,47 +199,40 @@ class Wallet:
|
||||
def get_address_index(self, address):
|
||||
if address in self.imported_keys.keys():
|
||||
raise BaseException("imported key")
|
||||
|
||||
if address in self.addresses:
|
||||
n = self.addresses.index(address)
|
||||
for_change = False
|
||||
elif address in self.change_addresses:
|
||||
n = self.change_addresses.index(address)
|
||||
for_change = True
|
||||
return n,for_change
|
||||
for account in self.accounts.keys():
|
||||
for for_change in [0,1]:
|
||||
addresses = self.accounts[account][for_change]
|
||||
for addr in addresses:
|
||||
if address == addr:
|
||||
return account, for_change, addresses.index(addr)
|
||||
raise BaseException("not found")
|
||||
|
||||
|
||||
def get_public_key(self, address):
|
||||
n, for_change = self.get_address_index(address)
|
||||
return self.sequence.get_pubkey(n, for_change)
|
||||
account, n, for_change = self.get_address_index(address)
|
||||
return self.sequences[account].get_pubkey(n, for_change)
|
||||
|
||||
|
||||
def decode_seed(self, password):
|
||||
seed = pw_decode(self.seed, password)
|
||||
self.sequence.check_seed(seed)
|
||||
self.sequences[0].check_seed(seed)
|
||||
return seed
|
||||
|
||||
def get_private_key(self, address, password):
|
||||
return self.get_private_keys([address], password)[address]
|
||||
return self.get_private_keys([address], password).get(address)
|
||||
|
||||
def get_private_keys(self, addresses, password):
|
||||
# decode seed in any case, in order to test the password
|
||||
seed = self.decode_seed(password)
|
||||
secexp = self.sequence.stretch_key(seed)
|
||||
secexp = self.sequences[0].stretch_key(seed)
|
||||
out = {}
|
||||
for address in addresses:
|
||||
if address in self.imported_keys.keys():
|
||||
pk = pw_decode( self.imported_keys[address], password )
|
||||
out[address] = pw_decode( self.imported_keys[address], password )
|
||||
else:
|
||||
if address in self.addresses:
|
||||
n = self.addresses.index(address)
|
||||
for_change = False
|
||||
elif address in self.change_addresses:
|
||||
n = self.change_addresses.index(address)
|
||||
for_change = True
|
||||
else:
|
||||
raise BaseException("unknown address", address)
|
||||
pk = self.sequence.get_private_key_from_stretched_exponent(n, for_change, secexp)
|
||||
out[address] = pk
|
||||
account, for_change, n = self.get_address_index(address)
|
||||
if account == 0:
|
||||
out[address] = self.sequences[0].get_private_key_from_stretched_exponent(n, for_change, secexp)
|
||||
return out
|
||||
|
||||
|
||||
@@ -261,11 +269,11 @@ class Wallet:
|
||||
|
||||
# find the address:
|
||||
if txin.get('electrumKeyID'):
|
||||
n, for_change = txin.get('electrumKeyID')
|
||||
sec = self.sequence.get_private_key(n, for_change, seed)
|
||||
address = address_from_private_key(sec)
|
||||
txin['address'] = address
|
||||
private_keys[address] = sec
|
||||
account, for_change, n = txin.get('electrumKeyID')
|
||||
sec = self.sequences[account].get_private_key(n, for_change, seed)
|
||||
addr = self.sequences[account].get_address(n, for_change)
|
||||
txin['address'] = addr
|
||||
private_keys[addr] = sec
|
||||
|
||||
elif txin.get("redeemScript"):
|
||||
txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
|
||||
@@ -280,26 +288,24 @@ class Wallet:
|
||||
|
||||
tx.sign( private_keys )
|
||||
|
||||
|
||||
def sign_message(self, address, message, password):
|
||||
sec = self.get_private_key(address, password)
|
||||
key = regenerate_key(sec)
|
||||
compressed = is_compressed(sec)
|
||||
return key.sign_message(message, compressed, address)
|
||||
|
||||
def create_new_address(self, for_change):
|
||||
n = len(self.change_addresses) if for_change else len(self.addresses)
|
||||
address = self.get_new_address(n, for_change)
|
||||
if for_change:
|
||||
self.change_addresses.append(address)
|
||||
else:
|
||||
self.addresses.append(address)
|
||||
|
||||
|
||||
def create_new_address(self, account, for_change):
|
||||
addresses = self.accounts[account][for_change]
|
||||
n = len(addresses)
|
||||
address = self.get_new_address( account, for_change, n)
|
||||
self.accounts[account][for_change].append(address)
|
||||
self.history[address] = []
|
||||
return address
|
||||
|
||||
def get_new_address(self, n, for_change):
|
||||
pubkey = self.sequence.get_pubkey(n, for_change)
|
||||
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||
|
||||
def get_new_address(self, account, for_change, n):
|
||||
return self.sequences[account].get_address(for_change, n)
|
||||
print_msg( address )
|
||||
return address
|
||||
|
||||
@@ -311,18 +317,22 @@ class Wallet:
|
||||
return True
|
||||
|
||||
elif value >= self.min_acceptable_gap():
|
||||
k = self.num_unused_trailing_addresses()
|
||||
n = len(self.addresses) - k + value
|
||||
self.addresses = self.addresses[0:n]
|
||||
for key, account in self.accounts.items():
|
||||
addresses = account[0]
|
||||
k = self.num_unused_trailing_addresses(addresses)
|
||||
n = len(addresses) - k + value
|
||||
addresses = addresses[0:n]
|
||||
self.accounts[key][0] = addresses
|
||||
|
||||
self.gap_limit = value
|
||||
self.save()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def num_unused_trailing_addresses(self):
|
||||
def num_unused_trailing_addresses(self, addresses):
|
||||
k = 0
|
||||
for a in self.addresses[::-1]:
|
||||
for a in addresses[::-1]:
|
||||
if self.history.get(a):break
|
||||
k = k + 1
|
||||
return k
|
||||
@@ -331,13 +341,16 @@ class Wallet:
|
||||
# fixme: this assumes wallet is synchronized
|
||||
n = 0
|
||||
nmax = 0
|
||||
k = self.num_unused_trailing_addresses()
|
||||
for a in self.addresses[0:-k]:
|
||||
if self.history.get(a):
|
||||
n = 0
|
||||
else:
|
||||
n += 1
|
||||
if n > nmax: nmax = n
|
||||
|
||||
for account in self.accounts.values():
|
||||
addresses = account[0]
|
||||
k = self.num_unused_trailing_addresses(addresses)
|
||||
for a in addresses[0:-k]:
|
||||
if self.history.get(a):
|
||||
n = 0
|
||||
else:
|
||||
n += 1
|
||||
if n > nmax: nmax = n
|
||||
return nmax + 1
|
||||
|
||||
|
||||
@@ -356,26 +369,32 @@ class Wallet:
|
||||
return age > 2
|
||||
|
||||
|
||||
def synchronize_sequence(self, addresses, n, for_change):
|
||||
def synchronize_sequence(self, account, for_change):
|
||||
limit = self.gap_limit_for_change if for_change else self.gap_limit
|
||||
addresses = self.accounts[account][for_change]
|
||||
new_addresses = []
|
||||
while True:
|
||||
if len(self.addresses) < n:
|
||||
new_addresses.append( self.create_new_address(for_change) )
|
||||
if len(addresses) < limit:
|
||||
new_addresses.append( self.create_new_address(account, for_change) )
|
||||
continue
|
||||
if map( lambda a: self.address_is_old(a), addresses[-n:] ) == n*[False]:
|
||||
if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
|
||||
break
|
||||
else:
|
||||
new_addresses.append( self.create_new_address(for_change) )
|
||||
new_addresses.append( self.create_new_address(account, for_change) )
|
||||
return new_addresses
|
||||
|
||||
|
||||
def synchronize_account(self, account):
|
||||
new = []
|
||||
new += self.synchronize_sequence(account, 0)
|
||||
new += self.synchronize_sequence(account, 1)
|
||||
return new
|
||||
|
||||
def synchronize(self):
|
||||
if not self.sequence.master_public_key:
|
||||
return []
|
||||
new_addresses = []
|
||||
new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
|
||||
new_addresses += self.synchronize_sequence(self.change_addresses, self.gap_limit_for_change, True)
|
||||
return new_addresses
|
||||
new = []
|
||||
for account in self.accounts.keys():
|
||||
new += self.synchronize_account(account)
|
||||
return new
|
||||
|
||||
|
||||
def is_found(self):
|
||||
@@ -506,14 +525,27 @@ class Wallet:
|
||||
u += v
|
||||
return c, u
|
||||
|
||||
def get_balance(self):
|
||||
def get_account_addresses(self, a):
|
||||
ac = self.accounts[a]
|
||||
return ac[0] + ac[1]
|
||||
|
||||
|
||||
def get_account_balance(self, account):
|
||||
conf = unconf = 0
|
||||
for addr in self.all_addresses():
|
||||
for addr in self.get_account_addresses(account):
|
||||
c, u = self.get_addr_balance(addr)
|
||||
conf += c
|
||||
unconf += u
|
||||
return conf, unconf
|
||||
|
||||
def get_balance(self):
|
||||
cc = uu = 0
|
||||
for a in self.accounts.keys():
|
||||
c, u = self.get_account_balance(a)
|
||||
cc += c
|
||||
uu += u
|
||||
return cc, uu
|
||||
|
||||
|
||||
def get_unspent_coins(self, domain=None):
|
||||
coins = []
|
||||
@@ -557,7 +589,7 @@ class Wallet:
|
||||
addr = item.get('address')
|
||||
v = item.get('value')
|
||||
total += v
|
||||
item['pubkeysig'] = [(None, None)]
|
||||
|
||||
inputs.append( item )
|
||||
fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
|
||||
if total >= amount + fee: break
|
||||
@@ -718,15 +750,18 @@ class Wallet:
|
||||
outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
|
||||
|
||||
tx = Transaction.from_io(inputs, outputs)
|
||||
for i in range(len(tx.inputs)):
|
||||
addr = tx.inputs[i]['address']
|
||||
n, is_change = self.get_address_index(addr)
|
||||
tx.input_info[i]['electrumKeyID'] = (n, is_change)
|
||||
|
||||
if not self.seed:
|
||||
return tx
|
||||
|
||||
self.sign_tx(tx, password)
|
||||
pk_addresses = []
|
||||
for i in range(len(tx.inputs)):
|
||||
txin = tx.inputs[i]
|
||||
account, is_change, n = self.get_address_index(txin['address'])
|
||||
pk_addr = self.sequences[account].add_input_info(txin, account, is_change, n)
|
||||
pk_addresses.append(pk_addr)
|
||||
|
||||
# get all private keys at once.
|
||||
private_keys = self.get_private_keys(pk_addresses, password)
|
||||
print "private keys", private_keys
|
||||
tx.sign(private_keys)
|
||||
|
||||
for address, x in outputs:
|
||||
if address not in self.addressbook and not self.is_mine(address):
|
||||
@@ -734,13 +769,7 @@ class Wallet:
|
||||
|
||||
return tx
|
||||
|
||||
def sign_tx(self, tx, password):
|
||||
private_keys = {}
|
||||
for txin in tx.inputs:
|
||||
addr = txin['address']
|
||||
sec = self.get_private_key(addr, password)
|
||||
private_keys[addr] = sec
|
||||
tx.sign(private_keys)
|
||||
|
||||
|
||||
def sendtx(self, tx):
|
||||
# synchronous
|
||||
@@ -954,8 +983,7 @@ class Wallet:
|
||||
'use_encryption': self.use_encryption,
|
||||
'use_change': self.use_change,
|
||||
'fee': self.fee,
|
||||
'addresses': self.addresses,
|
||||
'change_addresses': self.change_addresses,
|
||||
'accounts': self.accounts,
|
||||
'addr_history': self.history,
|
||||
'labels': self.labels,
|
||||
'contacts': self.addressbook,
|
||||
|
||||
Reference in New Issue
Block a user