accounts
[electrum-nvc.git] / lib / wallet.py
index 1ca123d..2a193b8 100644 (file)
@@ -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,