fix bug with the order of signatures in tx input
[electrum-nvc.git] / lib / wallet.py
index e08a2fc..6b241c6 100644 (file)
@@ -34,7 +34,7 @@ import math
 from util import print_msg, print_error, format_satoshis
 from bitcoin import *
 from account import *
-from transaction import Transaction
+from transaction import Transaction, is_extended_pubkey
 from plugins import run_hook
 import bitcoin
 from synchronizer import WalletSynchronizer
@@ -118,7 +118,7 @@ class WalletStorage:
         with self.lock:
             if value is not None:
                 self.data[key] = value
-            else:
+            elif key in self.data:
                 self.data.pop(key)
             if save: 
                 self.write()
@@ -221,11 +221,21 @@ class Abstract_Wallet:
     def get_action(self):
         pass
 
+
+    def convert_imported_keys(self, password):
+        for k, v in self.imported_keys.items():
+            sec = pw_decode(v, password)
+            pubkey = public_key_from_private_key(sec)
+            address = public_key_to_bc_address(pubkey.decode('hex'))
+            assert address == k
+            self.import_key(sec, password)
+            self.imported_keys.pop(k)
+        self.storage.put('imported_keys', self.imported_keys)
+
+
     def load_accounts(self):
         self.accounts = {}
         self.imported_keys = self.storage.get('imported_keys',{})
-        if self.imported_keys:
-            print_error("cannot load imported keys")
 
         d = self.storage.get('accounts', {})
         for k, v in d.items():
@@ -271,6 +281,10 @@ class Abstract_Wallet:
         else:
             return False
 
+    def has_imported_keys(self):
+        account = self.accounts.get(IMPORTED_ACCOUNT)
+        return account is not None
+
     def import_key(self, sec, password):
         try:
             pubkey = public_key_from_private_key(sec)
@@ -378,73 +392,54 @@ class Abstract_Wallet:
         return self.accounts[account_id].get_pubkeys(sequence)
 
 
-    def add_keypairs_from_wallet(self, tx, keypairs, password):
+    def add_keypairs(self, tx, keypairs, password):
+        # first check the provided password
+        seed = self.get_seed(password)
+
         for txin in tx.inputs:
+            x_pubkeys = txin['x_pubkeys']
             address = txin['address']
-            if not self.is_mine(address):
-                continue
-            private_keys = self.get_private_key(address, password)
-            for sec in private_keys:
-                pubkey = public_key_from_private_key(sec)
-                keypairs[ pubkey ] = sec
 
+            if self.is_mine(address):
 
+                private_keys = self.get_private_key(address, password)
+                for sec in private_keys:
+                    pubkey = public_key_from_private_key(sec)
+                    keypairs[ pubkey ] = sec
 
-    def add_keypairs_from_KeyID(self, tx, keypairs, password):
-        # first check the provided password
-        seed = self.get_seed(password)
+            else:
 
-        for txin in tx.inputs:
-            keyid = txin.get('KeyID')
-            if keyid:
-                roots = []
-                for s in keyid.split('&'):
-                    m = re.match("bip32\((.*),(/\d+/\d+)\)", s)
-                    if not m: continue
-                    xpub = m.group(1)
-                    sequence = m.group(2)
-                    root = self.find_root_by_master_key(xpub)
-                    if not root: continue
-                    sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
-                    root = root + '%d'%sequence[0]
-                    sequence = sequence[1:]
-                    roots.append((root,sequence)) 
-
-                account_id = " & ".join( map(lambda x:x[0], roots) )
-                account = self.accounts.get(account_id)
-                if not account: continue
-                addr = account.get_address(*sequence)
-                txin['address'] = addr # fixme: side effect
-                pk = self.get_private_key(addr, password)
-                for sec in pk:
-                    pubkey = public_key_from_private_key(sec)
-                    keypairs[pubkey] = sec
+                from account import BIP32_Account, OldAccount
+                for x_pubkey in x_pubkeys:
+                    if not is_extended_pubkey(x_pubkey):
+                        continue
 
+                    if x_pubkey[0:2] == 'ff':
+                        xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
+                    elif x_pubkey[0:2] == 'fe':
+                        xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
 
+                    # look for account that can sign
+                    for k, account in self.accounts.items():
+                        if xpub in account.get_master_pubkeys():
+                            break
+                    else:
+                        continue
+
+                    addr = account.get_address(*sequence)
+                    assert txin['address'] == addr
+                    pk = self.get_private_key(addr, password)
+                    for sec in pk:
+                        pubkey = public_key_from_private_key(sec)
+                        keypairs[pubkey] = sec
 
-    def signrawtransaction(self, tx, input_info, private_keys, password):
 
-        # check that the password is correct
-        seed = self.get_seed(password)
 
-        # if input_info is not known, build it using wallet UTXOs
-        if not input_info:
-            input_info = []
-            unspent_coins = self.get_unspent_coins()
-            for txin in tx.inputs:
-                for item in unspent_coins:
-                    if txin['prevout_hash'] == item['prevout_hash'] and txin['prevout_n'] == item['prevout_n']:
-                        info = { 'address':item['address'], 'scriptPubKey':item['scriptPubKey'] }
-                        self.add_input_info(info)
-                        input_info.append(info)
-                        break
-                else:
-                    print_error( "input not in UTXOs" )
-                    input_info.append(None)
 
-        # add input_info to the transaction
-        print_error("input_info", input_info)
-        tx.add_input_info(input_info)
+    def signrawtransaction(self, tx, private_keys, password):
+
+        # check that the password is correct
+        seed = self.get_seed(password)
 
         # build a list of public/private keys
         keypairs = {}
@@ -454,10 +449,9 @@ class Abstract_Wallet:
             pubkey = public_key_from_private_key(sec)
             keypairs[ pubkey ] = sec
 
-        # add private_keys from KeyID
-        self.add_keypairs_from_KeyID(tx, keypairs, password)
-        # add private keys from wallet
-        self.add_keypairs_from_wallet(tx, keypairs, password)
+        # add private_keys
+        self.add_keypairs(tx, keypairs, password)
+
         # sign the transaction
         self.sign_transaction(tx, keypairs, password)
 
@@ -478,7 +472,7 @@ class Abstract_Wallet:
         secret = keys[0]
         ec = regenerate_key(secret)
         decrypted = ec.decrypt_message(message)
-        return decrypted[0]
+        return decrypted
 
 
 
@@ -647,17 +641,18 @@ class Abstract_Wallet:
         return [x[1] for x in coins]
 
 
-    def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None ):
+    def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None, coins = None ):
         """ todo: minimize tx size """
         total = 0
         fee = self.fee if fixed_fee is None else fixed_fee
-        if domain is None:
-            domain = self.addresses(True)
 
-        for i in self.frozen_addresses:
-            if i in domain: domain.remove(i)
+        if not coins:
+            if domain is None:
+                domain = self.addresses(True)
+            for i in self.frozen_addresses:
+                if i in domain: domain.remove(i)
+            coins = self.get_unspent_coins(domain)
 
-        coins = self.get_unspent_coins(domain)
         inputs = []
 
         for item in coins:
@@ -838,11 +833,11 @@ class Abstract_Wallet:
         return default_label
 
 
-    def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None ):
+    def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None, coins=None ):
         for address, x in outputs:
             assert is_valid(address), "Address " + address + " is invalid!"
         amount = sum( map(lambda x:x[1], outputs) )
-        inputs, total, fee = self.choose_tx_inputs( amount, fee, len(outputs), domain )
+        inputs, total, fee = self.choose_tx_inputs( amount, fee, len(outputs), domain, coins )
         if not inputs:
             raise ValueError("Not enough funds")
         for txin in inputs:
@@ -851,10 +846,10 @@ class Abstract_Wallet:
         return Transaction.from_io(inputs, outputs)
 
 
-    def mktx(self, outputs, password, fee=None, change_addr=None, domain= None ):
-        tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain)
+    def mktx(self, outputs, password, fee=None, change_addr=None, domain= None, coins = None ):
+        tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain, coins)
         keypairs = {}
-        self.add_keypairs_from_wallet(tx, keypairs, password)
+        self.add_keypairs(tx, keypairs, password)
         if keypairs:
             self.sign_transaction(tx, keypairs, password)
         return tx
@@ -864,12 +859,17 @@ class Abstract_Wallet:
         address = txin['address']
         account_id, sequence = self.get_address_index(address)
         account = self.accounts[account_id]
-        txin['KeyID'] = account.get_keyID(sequence)
         redeemScript = account.redeem_script(sequence)
+        txin['x_pubkeys'] = account.get_xpubkeys(sequence)
+        txin['pubkeys'] = pubkeys = account.get_pubkeys(sequence)
+        txin['signatures'] = [None] * len(pubkeys)
+
         if redeemScript: 
             txin['redeemScript'] = redeemScript
+            txin['num_sig'] = 2
         else:
             txin['redeemPubkey'] = account.get_pubkey(*sequence)
+            txin['num_sig'] = 1
 
 
     def sign_transaction(self, tx, keypairs, password):
@@ -1584,7 +1584,7 @@ class OldWallet(Deterministic_Wallet):
         return seed
 
     def check_password(self, password):
-        seed = pw_decode(self.seed, password)
+        seed = self.get_seed(password)
         self.accounts[0].check_seed(seed)
 
     def get_mnemonic(self, password):
@@ -1592,28 +1592,6 @@ class OldWallet(Deterministic_Wallet):
         s = self.get_seed(password)
         return ' '.join(mnemonic.mn_encode(s))
 
-
-    def add_keypairs_from_KeyID(self, tx, keypairs, password):
-        # first check the provided password
-        seed = self.get_seed(password)
-        for txin in tx.inputs:
-            keyid = txin.get('KeyID')
-            if keyid:
-                m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
-                if not m: continue
-                mpk = m.group(1)
-                if mpk != self.storage.get('master_public_key'): continue 
-                for_change = int(m.group(2))
-                num = int(m.group(3))
-                account = self.accounts[0]
-                addr = account.get_address(for_change, num)
-                txin['address'] = addr # fixme: side effect
-                pk = account.get_private_key(seed, (for_change, num))
-                pubkey = public_key_from_private_key(pk)
-                keypairs[pubkey] = pk
-
-
-
     def check_pending_accounts(self):
         pass