bundle dependencies in 'packages' dir. use qrcode instead of pyqrnative
[electrum-nvc.git] / lib / wallet.py
index ecd0ea7..edda943 100644 (file)
@@ -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)
@@ -478,7 +492,7 @@ class Abstract_Wallet:
         secret = keys[0]
         ec = regenerate_key(secret)
         decrypted = ec.decrypt_message(message)
-        return decrypted[0]
+        return decrypted
 
 
 
@@ -647,17 +661,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 +853,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,8 +866,8 @@ 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)
         if keypairs:
@@ -1070,6 +1085,13 @@ class Abstract_Wallet:
             d[k] = v.dump()
         self.storage.put('accounts', d, True)
 
+    def can_import(self):
+        return not self.is_watching_only()
+
+    def is_used(self, address):
+        h = self.history.get(address,[])
+        c, u = self.get_addr_balance(address)
+        return len(h), len(h) > 0 and c == -u
     
 
 class Imported_Wallet(Abstract_Wallet):
@@ -1079,6 +1101,7 @@ class Imported_Wallet(Abstract_Wallet):
         a = self.accounts.get(IMPORTED_ACCOUNT)
         if not a:
             self.accounts[IMPORTED_ACCOUNT] = ImportedAccount({'imported':{}})
+        self.storage.put('wallet_type', 'imported', True)
 
 
     def is_watching_only(self):
@@ -1095,6 +1118,9 @@ class Imported_Wallet(Abstract_Wallet):
     def check_password(self, password):
         self.accounts[IMPORTED_ACCOUNT].get_private_key((0,0), self, password)
 
+    def is_used(self, address):
+        h = self.history.get(address,[])
+        return len(h), False
 
 
 class Deterministic_Wallet(Abstract_Wallet):
@@ -1378,7 +1404,7 @@ class NewWallet(Deterministic_Wallet):
 
 
     def create_master_keys(self, password):
-        xpriv, xpub = bip32_root(self.get_seed(password))
+        xpriv, xpub = bip32_root(mnemonic_to_seed(self.get_seed(password),'').encode('hex'))
         self.add_master_public_key("m/", xpub)
         self.add_master_private_key("m/", xpriv, password)
 
@@ -1391,7 +1417,12 @@ class NewWallet(Deterministic_Wallet):
 
 
     def num_accounts(self):
-        keys = self.accounts.keys()
+        keys = []
+        for k, v in self.accounts.items():
+            if type(v) != BIP32_Account:
+                continue
+            keys.append(k)
+
         i = 0
         while True:
             account_id = self.account_id(i)
@@ -1450,6 +1481,12 @@ class Wallet_2of2(NewWallet):
         NewWallet.__init__(self, storage)
         self.storage.put('wallet_type', '2of2', True)
 
+    def can_create_accounts(self):
+        return False
+
+    def can_import(self):
+        return False
+
     def create_account(self):
         xpub1 = self.master_public_keys.get("m/")
         xpub2 = self.master_public_keys.get("cold/")
@@ -1494,12 +1531,13 @@ class Wallet_2of3(Wallet_2of2):
         xpub1 = self.master_public_keys.get("m/")
         xpub2 = self.master_public_keys.get("cold/")
         xpub3 = self.master_public_keys.get("remote/")
-        if xpub2 is None:
-            return 'create_2of3_1'
+        # fixme: we use order of creation
+        if xpub2 and xpub1 is None:
+            return 'create_2fa_2'
         if xpub1 is None:
+            return 'create_2of3_1'
+        if xpub2 is None or xpub3 is None:
             return 'create_2of3_2'
-        if xpub3 is None:
-            return 'create_2of3_3'
 
 
 
@@ -1532,7 +1570,7 @@ class OldWallet(Deterministic_Wallet):
 
 
     def create_master_keys(self, password):
-        seed = pw_decode(self.seed, password)
+        seed = self.get_seed(password)
         mpk = OldAccount.mpk_from_seed(seed)
         self.storage.put('master_public_key', mpk, True)
 
@@ -1557,22 +1595,21 @@ class OldWallet(Deterministic_Wallet):
         self.create_account(mpk)
 
     def get_seed(self, password):
-        seed = pw_decode(self.seed, password)
+        seed = pw_decode(self.seed, password).encode('utf8')
         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):
         import mnemonic
-        s = pw_decode(self.seed, password)
+        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:
@@ -1585,9 +1622,10 @@ class OldWallet(Deterministic_Wallet):
                 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
+                pk = account.get_private_key((for_change, num), self, password)
+                for sec in pk:
+                    pubkey = public_key_from_private_key(sec)
+                    keypairs[pubkey] = sec
 
 
 
@@ -1612,8 +1650,7 @@ class Wallet(object):
         if storage.get('wallet_type') == '2of3':
             return Wallet_2of3(storage)
 
-        if storage.file_exists and not storage.get('seed'):
-            # wallet made of imported keys
+        if storage.get('wallet_type') == 'imported':
             return Imported_Wallet(storage)
 
 
@@ -1667,6 +1704,8 @@ class Wallet(object):
 
     @classmethod
     def is_address(self, text):
+        if not text:
+            return False
         for x in text.split():
             if not bitcoin.is_address(x):
                 return False
@@ -1674,6 +1713,8 @@ class Wallet(object):
 
     @classmethod
     def is_private_key(self, text):
+        if not text:
+            return False
         for x in text.split():
             if not bitcoin.is_private_key(x):
                 return False