add error message for import_key failure
[electrum-nvc.git] / lib / wallet.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import sys
20 import base64
21 import os
22 import re
23 import hashlib
24 import copy
25 import operator
26 import ast
27 import threading
28 import random
29 import aes
30 import Queue
31 import time
32
33 from util import print_msg, print_error, user_dir, format_satoshis
34 from bitcoin import *
35
36
37 # AES encryption
38 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
39 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
40
41 def pw_encode(s, password):
42     if password:
43         secret = Hash(password)
44         return EncodeAES(secret, s)
45     else:
46         return s
47
48 def pw_decode(s, password):
49     if password is not None:
50         secret = Hash(password)
51         try:
52             d = DecodeAES(secret, s)
53         except:
54             raise BaseException('Invalid password')
55         return d
56     else:
57         return s
58
59
60
61
62
63 from version import ELECTRUM_VERSION, SEED_VERSION
64
65
66 class Wallet:
67     def __init__(self, config={}):
68
69         self.config = config
70         self.electrum_version = ELECTRUM_VERSION
71         self.gap_limit_for_change = 3 # constant
72
73         # saved fields
74         self.seed_version          = config.get('seed_version', SEED_VERSION)
75         self.gap_limit             = config.get('gap_limit', 5)
76         self.use_change            = config.get('use_change',True)
77         self.fee                   = int(config.get('fee_per_kb',20000))
78         self.num_zeros             = int(config.get('num_zeros',0))
79         self.use_encryption        = config.get('use_encryption', False)
80         self.seed                  = config.get('seed', '')               # encrypted
81         self.labels                = config.get('labels', {})
82         self.frozen_addresses      = config.get('frozen_addresses',[])
83         self.prioritized_addresses = config.get('prioritized_addresses',[])
84         self.addressbook           = config.get('contacts', [])
85         self.imported_keys         = config.get('imported_keys',{})
86         self.history               = config.get('addr_history',{})        # address -> list(txid, height)
87         self.accounts              = config.get('accounts', {})   # this should not include public keys
88
89         self.SequenceClass = ElectrumSequence
90         self.sequences = {}
91         self.sequences[0] = self.SequenceClass(self.config.get('master_public_key'))
92
93         if self.accounts.get(0) is None:
94             self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
95
96         self.transactions = {}
97         tx = config.get('transactions',{})
98         try:
99             for k,v in tx.items(): self.transactions[k] = Transaction(v)
100         except:
101             print_msg("Warning: Cannot deserialize transactions. skipping")
102         
103         # not saved
104         self.prevout_values = {}     # my own transaction outputs
105         self.spent_outputs = []
106
107         # spv
108         self.verifier = None
109
110         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
111         # interface.is_up_to_date() returns true when all requests have been answered and processed
112         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
113         
114         self.up_to_date = False
115         self.lock = threading.Lock()
116         self.transaction_lock = threading.Lock()
117         self.tx_event = threading.Event()
118
119         if self.seed_version != SEED_VERSION:
120             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
121
122         for tx_hash in self.transactions.keys():
123             self.update_tx_outputs(tx_hash)
124
125
126     def set_up_to_date(self,b):
127         with self.lock: self.up_to_date = b
128
129     def is_up_to_date(self):
130         with self.lock: return self.up_to_date
131
132     def update(self):
133         self.up_to_date = False
134         self.interface.poke('synchronizer')
135         while not self.is_up_to_date(): time.sleep(0.1)
136
137     def import_key(self, sec, password):
138         # check password
139         seed = self.decode_seed(password)
140         try:
141             address = address_from_private_key(sec)
142         except:
143             raise BaseException('Invalid private key')
144
145         if self.is_mine(address):
146             raise BaseException('Address already in wallet')
147         
148         # store the originally requested keypair into the imported keys table
149         self.imported_keys[address] = pw_encode(sec, password )
150         self.config.set_key('imported_keys', self.imported_keys, True)
151         return address
152         
153     def delete_imported_key(self, addr):
154         if addr in self.imported_keys:
155             self.imported_keys.pop(addr)
156             self.config.set_key('imported_keys', self.imported_keys, True)
157
158
159     def init_seed(self, seed):
160         if self.seed: raise BaseException("a seed exists")
161         if not seed: 
162             seed = random_seed(128)
163         self.seed = seed
164
165     def save_seed(self):
166         self.config.set_key('seed', self.seed, True)
167         self.config.set_key('seed_version', self.seed_version, True)
168         mpk = self.SequenceClass.mpk_from_seed(self.seed)
169         self.init_sequence(mpk)
170
171
172     def init_sequence(self, mpk):
173         self.config.set_key('master_public_key', mpk, True)
174         self.sequences[0] = self.SequenceClass(mpk)
175         self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
176         self.config.set_key('accounts', self.accounts, True)
177
178
179     def addresses(self, include_change = True):
180         o = self.get_account_addresses(-1, include_change)
181         for a in self.accounts.keys():
182             o += self.get_account_addresses(a, include_change)
183         return o
184
185
186     def is_mine(self, address):
187         return address in self.addresses(True)
188
189     def is_change(self, address):
190         if not self.is_mine(address): return False
191         if address in self.imported_keys.keys(): return False
192         acct, s = self.get_address_index(address)
193         return s[0] == 1
194
195     def get_master_public_key(self):
196         return self.config.get("master_public_key")
197
198     def get_address_index(self, address):
199         if address in self.imported_keys.keys():
200             return -1, None
201         for account in self.accounts.keys():
202             for for_change in [0,1]:
203                 addresses = self.accounts[account][for_change]
204                 for addr in addresses:
205                     if address == addr:
206                         return account, (for_change, addresses.index(addr))
207         raise BaseException("not found")
208         
209
210     def get_public_key(self, address):
211         account, sequence = self.get_address_index(address)
212         return self.sequences[account].get_pubkey( sequence )
213
214
215     def decode_seed(self, password):
216         seed = pw_decode(self.seed, password)
217         self.sequences[0].check_seed(seed)
218         return seed
219         
220     def get_private_key(self, address, password):
221         return self.get_private_keys([address], password).get(address)
222
223     def get_private_keys(self, addresses, password):
224         if not self.seed: return {}
225         # decode seed in any case, in order to test the password
226         seed = self.decode_seed(password)
227         out = {}
228         l_sequences = []
229         l_addresses = []
230         for address in addresses:
231             if address in self.imported_keys.keys():
232                 out[address] = pw_decode( self.imported_keys[address], password )
233             else:
234                 account, sequence = self.get_address_index(address)
235                 if account == 0:
236                     l_sequences.append(sequence)
237                     l_addresses.append(address)
238
239         pk = self.sequences[0].get_private_keys(l_sequences, seed)
240         for i, address in enumerate(l_addresses): out[address] = pk[i]                     
241         return out
242
243
244     def signrawtransaction(self, tx, input_info, private_keys, password):
245         unspent_coins = self.get_unspent_coins()
246         seed = self.decode_seed(password)
247
248         # convert private_keys to dict 
249         pk = {}
250         for sec in private_keys:
251             address = address_from_private_key(sec)
252             pk[address] = sec
253         private_keys = pk
254
255         for txin in tx.inputs:
256             # convert to own format
257             txin['tx_hash'] = txin['prevout_hash']
258             txin['index'] = txin['prevout_n']
259
260             for item in input_info:
261                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
262                     txin['raw_output_script'] = item['scriptPubKey']
263                     txin['redeemScript'] = item.get('redeemScript')
264                     txin['KeyID'] = item.get('KeyID')
265                     break
266             else:
267                 for item in unspent_coins:
268                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
269                         txin['raw_output_script'] = item['raw_output_script']
270                         break
271                 else:
272                     # if neither, we might want to get it from the server..
273                     raise
274
275             # find the address:
276             if txin.get('KeyID'):
277                 account, name, sequence = txin.get('KeyID')
278                 if name != 'Electrum': continue
279                 sec = self.sequences[account].get_private_key(sequence, seed)
280                 addr = self.sequences[account].get_address(sequence)
281                 txin['address'] = addr
282                 private_keys[addr] = sec
283
284             elif txin.get("redeemScript"):
285                 txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
286
287             elif txin.get("raw_output_script"):
288                 import deserialize
289                 addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
290                 sec = self.get_private_key(addr, password)
291                 if sec: 
292                     private_keys[addr] = sec
293                     txin['address'] = addr
294
295         tx.sign( private_keys )
296
297     def sign_message(self, address, message, password):
298         sec = self.get_private_key(address, password)
299         key = regenerate_key(sec)
300         compressed = is_compressed(sec)
301         return key.sign_message(message, compressed, address)
302
303     def verify_message(self, address, signature, message):
304         try:
305             EC_KEY.verify_message(address, signature, message)
306             return True
307         except BaseException as e:
308             print_error("Verification error: {0}".format(e))
309             return False
310
311     def create_new_address(self, account, for_change):
312         addresses = self.accounts[account][for_change]
313         n = len(addresses)
314         address = self.get_new_address( account, for_change, n)
315         self.accounts[account][for_change].append(address)
316         self.history[address] = []
317         print_msg(address)
318         return address
319         
320
321     def get_new_address(self, account, for_change, n):
322         return self.sequences[account].get_address((for_change, n))
323         print address
324         return address
325
326     def change_gap_limit(self, value):
327         if value >= self.gap_limit:
328             self.gap_limit = value
329             self.config.set_key('gap_limit', self.gap_limit, True)
330             self.interface.poke('synchronizer')
331             return True
332
333         elif value >= self.min_acceptable_gap():
334             for key, account in self.accounts.items():
335                 addresses = account[0]
336                 k = self.num_unused_trailing_addresses(addresses)
337                 n = len(addresses) - k + value
338                 addresses = addresses[0:n]
339                 self.accounts[key][0] = addresses
340
341             self.gap_limit = value
342             self.config.set_key('gap_limit', self.gap_limit, True)
343             self.config.set_key('accounts', self.accounts, True)
344             return True
345         else:
346             return False
347
348     def num_unused_trailing_addresses(self, addresses):
349         k = 0
350         for a in addresses[::-1]:
351             if self.history.get(a):break
352             k = k + 1
353         return k
354
355     def min_acceptable_gap(self):
356         # fixme: this assumes wallet is synchronized
357         n = 0
358         nmax = 0
359
360         for account in self.accounts.values():
361             addresses = account[0]
362             k = self.num_unused_trailing_addresses(addresses)
363             for a in addresses[0:-k]:
364                 if self.history.get(a):
365                     n = 0
366                 else:
367                     n += 1
368                     if n > nmax: nmax = n
369         return nmax + 1
370
371
372     def address_is_old(self, address):
373         age = -1
374         h = self.history.get(address, [])
375         if h == ['*']:
376             return True
377         for tx_hash, tx_height in h:
378             if tx_height == 0:
379                 tx_age = 0
380             else: 
381                 tx_age = self.verifier.height - tx_height + 1
382             if tx_age > age:
383                 age = tx_age
384         return age > 2
385
386
387     def synchronize_sequence(self, account, for_change):
388         limit = self.gap_limit_for_change if for_change else self.gap_limit
389         addresses = self.accounts[account][for_change]
390         new_addresses = []
391         while True:
392             if len(addresses) < limit:
393                 new_addresses.append( self.create_new_address(account, for_change) )
394                 continue
395             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
396                 break
397             else:
398                 new_addresses.append( self.create_new_address(account, for_change) )
399         return new_addresses
400         
401
402     def synchronize_account(self, account):
403         new = []
404         new += self.synchronize_sequence(account, 0)
405         new += self.synchronize_sequence(account, 1)
406         return new
407
408     def synchronize(self):
409         new = []
410         for account in self.accounts.keys():
411             new += self.synchronize_account(account)
412         if new:
413             self.config.set_key('accounts', self.accounts, True)
414             self.config.set_key('addr_history', self.history, True)
415         return new
416
417
418     def is_found(self):
419         return self.history.values() != [[]] * len(self.history) 
420
421
422     def add_contact(self, address, label=None):
423         self.addressbook.append(address)
424         self.config.set_key('addressbook', self.addressbook, True)
425         if label:  
426             self.labels[address] = label
427             self.config.set_key('labels', self.labels)
428
429     def delete_contact(self, addr):
430         if addr in self.addressbook:
431             self.addressbook.remove(addr)
432             self.config.set_key('addressbook', self.addressbook, True)
433
434
435     def fill_addressbook(self):
436         for tx_hash, tx in self.transactions.items():
437             is_relevant, is_send, _, _ = self.get_tx_value(tx)
438             if is_send:
439                 for addr, v in tx.outputs:
440                     if not self.is_mine(addr) and addr not in self.addressbook:
441                         self.addressbook.append(addr)
442         # redo labels
443         # self.update_tx_labels()
444
445     def get_num_tx(self, address):
446         n = 0 
447         for tx in self.transactions.values():
448             if address in map(lambda x:x[0], tx.outputs): n += 1
449         return n
450
451
452     def get_address_flags(self, addr):
453         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
454         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
455         return flags
456         
457
458     def get_tx_value(self, tx, account=None):
459         domain = self.get_account_addresses(account)
460         return tx.get_value(domain, self.prevout_values)
461
462     
463     def update_tx_outputs(self, tx_hash):
464         tx = self.transactions.get(tx_hash)
465
466         for i, (addr, value) in enumerate(tx.outputs):
467             key = tx_hash+ ':%d'%i
468             self.prevout_values[key] = value
469
470         for item in tx.inputs:
471             if self.is_mine(item.get('address')):
472                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
473                 self.spent_outputs.append(key)
474
475
476     def get_addr_balance(self, address):
477         assert self.is_mine(address)
478         h = self.history.get(address,[])
479         if h == ['*']: return 0,0
480         c = u = 0
481         received_coins = []   # list of coins received at address
482
483         for tx_hash, tx_height in h:
484             tx = self.transactions.get(tx_hash)
485             if not tx: continue
486
487             for i, (addr, value) in enumerate(tx.outputs):
488                 if addr == address:
489                     key = tx_hash + ':%d'%i
490                     received_coins.append(key)
491
492         for tx_hash, tx_height in h:
493             tx = self.transactions.get(tx_hash)
494             if not tx: continue
495             v = 0
496
497             for item in tx.inputs:
498                 addr = item.get('address')
499                 if addr == address:
500                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
501                     value = self.prevout_values.get( key )
502                     if key in received_coins: 
503                         v -= value
504
505             for i, (addr, value) in enumerate(tx.outputs):
506                 key = tx_hash + ':%d'%i
507                 if addr == address:
508                     v += value
509
510             if tx_height:
511                 c += v
512             else:
513                 u += v
514         return c, u
515
516
517     def get_accounts(self):
518         accounts = {}
519         for k, account in self.accounts.items():
520             accounts[k] = account.get('name')
521         if self.imported_keys:
522             accounts[-1] = 'Imported keys'
523         return accounts
524
525     def get_account_addresses(self, a, include_change=True):
526         if a is None:
527             o = self.addresses(True)
528         elif a == -1:
529             o = self.imported_keys.keys()
530         else:
531             ac = self.accounts[a]
532             o = ac[0][:]
533             if include_change: o += ac[1]
534         return o
535
536     def get_imported_balance(self):
537         cc = uu = 0
538         for addr in self.imported_keys.keys():
539             c, u = self.get_addr_balance(addr)
540             cc += c
541             uu += u
542         return cc, uu
543
544     def get_account_balance(self, account):
545         if account is None:
546             return self.get_balance()
547         elif account == -1:
548             return self.get_imported_balance()
549         
550         conf = unconf = 0
551         for addr in self.get_account_addresses(account): 
552             c, u = self.get_addr_balance(addr)
553             conf += c
554             unconf += u
555         return conf, unconf
556
557     def get_frozen_balance(self):
558         conf = unconf = 0
559         for addr in self.frozen_addresses:
560             c, u = self.get_addr_balance(addr)
561             conf += c
562             unconf += u
563         return conf, unconf
564
565         
566     def get_balance(self):
567         cc = uu = 0
568         for a in self.accounts.keys():
569             c, u = self.get_account_balance(a)
570             cc += c
571             uu += u
572         c, u = self.get_imported_balance()
573         cc += c
574         uu += u
575         return cc, uu
576
577
578     def get_unspent_coins(self, domain=None):
579         coins = []
580         if domain is None: domain = self.addresses(True)
581         for addr in domain:
582             h = self.history.get(addr, [])
583             if h == ['*']: continue
584             for tx_hash, tx_height in h:
585                 tx = self.transactions.get(tx_hash)
586                 if tx is None: raise BaseException("Wallet not synchronized")
587                 for output in tx.d.get('outputs'):
588                     if output.get('address') != addr: continue
589                     key = tx_hash + ":%d" % output.get('index')
590                     if key in self.spent_outputs: continue
591                     output['tx_hash'] = tx_hash
592                     coins.append(output)
593         return coins
594
595
596
597     def choose_tx_inputs( self, amount, fixed_fee, account = None ):
598         """ todo: minimize tx size """
599         total = 0
600         fee = self.fee if fixed_fee is None else fixed_fee
601         domain = self.get_account_addresses(account)
602         coins = []
603         prioritized_coins = []
604         for i in self.frozen_addresses:
605             if i in domain: domain.remove(i)
606
607         for i in self.prioritized_addresses:
608             if i in domain: domain.remove(i)
609
610         coins = self.get_unspent_coins(domain)
611         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
612
613         inputs = []
614         coins = prioritized_coins + coins
615
616         for item in coins: 
617             addr = item.get('address')
618             v = item.get('value')
619             total += v
620             inputs.append( item )
621             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
622             if total >= amount + fee: break
623         else:
624             inputs = []
625
626         return inputs, total, fee
627
628
629     def estimated_fee(self, inputs):
630         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
631         fee = self.fee * int(round(estimated_size/1024.))
632         if fee == 0: fee = self.fee
633         return fee
634
635
636     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None, account=0 ):
637         "add change to a transaction"
638         change_amount = total - ( amount + fee )
639         if change_amount != 0:
640             if not change_addr:
641                 if account is None: 
642                     # send change to one of the accounts involved in the tx
643                     address = inputs[0].get('address')
644                     account, _ = self.get_address_index(address)
645
646                 if not self.use_change or account == -1:
647                     change_addr = inputs[-1]['address']
648                 else:
649                     change_addr = self.accounts[account][1][-self.gap_limit_for_change]
650
651             # Insert the change output at a random position in the outputs
652             posn = random.randint(0, len(outputs))
653             outputs[posn:posn] = [( change_addr,  change_amount)]
654         return outputs
655
656
657     def get_history(self, address):
658         with self.lock:
659             return self.history.get(address)
660
661
662     def get_status(self, h):
663         if not h: return None
664         if h == ['*']: return '*'
665         status = ''
666         for tx_hash, height in h:
667             status += tx_hash + ':%d:' % height
668         return hashlib.sha256( status ).digest().encode('hex')
669
670
671     def receive_tx_callback(self, tx_hash, tx, tx_height):
672
673         if not self.check_new_tx(tx_hash, tx):
674             # may happen due to pruning
675             print_error("received transaction that is no longer referenced in history", tx_hash)
676             return
677
678         with self.transaction_lock:
679             self.transactions[tx_hash] = tx
680             self.save_transactions()
681             if self.verifier and tx_height>0: 
682                 self.verifier.add(tx_hash, tx_height)
683             self.update_tx_outputs(tx_hash)
684
685
686     def save_transactions(self):
687         tx = {}
688         for k,v in self.transactions.items():
689             tx[k] = str(v)
690         self.config.set_key('transactions', tx, True)
691
692
693     def receive_history_callback(self, addr, hist):
694
695         if not self.check_new_history(addr, hist):
696             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
697             
698         with self.lock:
699             self.history[addr] = hist
700             self.config.set_key('addr_history', self.history, True)
701
702         if hist != ['*']:
703             for tx_hash, tx_height in hist:
704                 if tx_height>0:
705                     # add it in case it was previously unconfirmed
706                     if self.verifier: self.verifier.add(tx_hash, tx_height)
707
708
709     def get_tx_history(self, account=None):
710         with self.transaction_lock:
711             history = self.transactions.items()
712             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
713             result = []
714     
715             balance = 0
716             for tx_hash, tx in history:
717                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
718                 if v is not None: balance += v
719
720             c, u = self.get_account_balance(account)
721
722             if balance != c+u:
723                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
724
725             balance = c + u - balance
726             for tx_hash, tx in history:
727                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
728                 if not is_relevant:
729                     continue
730                 if value is not None:
731                     balance += value
732
733                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
734                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
735
736         return result
737
738
739     def get_label(self, tx_hash):
740         label = self.labels.get(tx_hash)
741         is_default = (label == '') or (label is None)
742         if is_default: label = self.get_default_label(tx_hash)
743         return label, is_default
744
745
746     def get_default_label(self, tx_hash):
747         tx = self.transactions.get(tx_hash)
748         default_label = ''
749         if tx:
750             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
751             if is_mine:
752                 for o in tx.outputs:
753                     o_addr, _ = o
754                     if not self.is_mine(o_addr):
755                         try:
756                             default_label = self.labels[o_addr]
757                         except KeyError:
758                             default_label = o_addr
759                         break
760                 else:
761                     default_label = '(internal)'
762             else:
763                 for o in tx.outputs:
764                     o_addr, _ = o
765                     if self.is_mine(o_addr) and not self.is_change(o_addr):
766                         break
767                 else:
768                     for o in tx.outputs:
769                         o_addr, _ = o
770                         if self.is_mine(o_addr):
771                             break
772                     else:
773                         o_addr = None
774
775                 if o_addr:
776                     dest_label = self.labels.get(o_addr)
777                     try:
778                         default_label = self.labels[o_addr]
779                     except KeyError:
780                         default_label = o_addr
781
782         return default_label
783
784
785     def mktx(self, outputs, password, fee=None, change_addr=None, account=None ):
786         """
787         create a transaction
788         account parameter:
789            None means use all accounts
790            -1 means imported keys
791            0, 1, etc are seed accounts
792         """
793         
794         for address, x in outputs:
795             assert is_valid(address)
796
797         amount = sum( map(lambda x:x[1], outputs) )
798
799         inputs, total, fee = self.choose_tx_inputs( amount, fee, account )
800         if not inputs:
801             raise ValueError("Not enough funds")
802
803         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account)
804
805         tx = Transaction.from_io(inputs, outputs)
806
807         pk_addresses = []
808         for i in range(len(tx.inputs)):
809             txin = tx.inputs[i]
810             address = txin['address']
811             if address in self.imported_keys.keys():
812                 pk_addresses.append(address)
813                 continue
814             account, sequence = self.get_address_index(address)
815             txin['KeyID'] = (account, 'Electrum', sequence) # used by the server to find the key
816             pk_addr, redeemScript = self.sequences[account].get_input_info(sequence)
817             if redeemScript: txin['redeemScript'] = redeemScript
818             pk_addresses.append(pk_addr)
819
820         # get all private keys at once.
821         if self.seed:
822             private_keys = self.get_private_keys(pk_addresses, password)
823             tx.sign(private_keys)
824
825         for address, x in outputs:
826             if address not in self.addressbook and not self.is_mine(address):
827                 self.addressbook.append(address)
828
829         return tx
830
831
832
833     def sendtx(self, tx):
834         # synchronous
835         h = self.send_tx(tx)
836         self.tx_event.wait()
837         return self.receive_tx(h)
838
839     def send_tx(self, tx):
840         # asynchronous
841         self.tx_event.clear()
842         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
843         return tx.hash()
844
845     def receive_tx(self,tx_hash):
846         out = self.tx_result 
847         if out != tx_hash:
848             return False, "error: " + out
849         return True, out
850
851
852
853     def update_password(self, seed, old_password, new_password):
854         if new_password == '': new_password = None
855         # this will throw an exception if unicode cannot be converted
856         self.seed = pw_encode( seed, new_password)
857         self.config.set_key('seed', self.seed, True)
858         self.use_encryption = (new_password != None)
859         self.config.set_key('use_encryption', self.use_encryption,True)
860         for k in self.imported_keys.keys():
861             a = self.imported_keys[k]
862             b = pw_decode(a, old_password)
863             c = pw_encode(b, new_password)
864             self.imported_keys[k] = c
865         self.config.set_key('imported_keys', self.imported_keys, True)
866
867
868     def freeze(self,addr):
869         if self.is_mine(addr) and addr not in self.frozen_addresses:
870             self.unprioritize(addr)
871             self.frozen_addresses.append(addr)
872             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
873             return True
874         else:
875             return False
876
877     def unfreeze(self,addr):
878         if self.is_mine(addr) and addr in self.frozen_addresses:
879             self.frozen_addresses.remove(addr)
880             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
881             return True
882         else:
883             return False
884
885     def prioritize(self,addr):
886         if self.is_mine(addr) and addr not in self.prioritized_addresses:
887             self.unfreeze(addr)
888             self.prioritized_addresses.append(addr)
889             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
890             return True
891         else:
892             return False
893
894     def unprioritize(self,addr):
895         if self.is_mine(addr) and addr in self.prioritized_addresses:
896             self.prioritized_addresses.remove(addr)
897             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
898             return True
899         else:
900             return False
901
902     def set_fee(self, fee):
903         if self.fee != fee:
904             self.fee = fee
905             self.config.set_key('fee_per_kb', self.fee, True)
906         
907
908     def save(self):
909         print_error("Warning: wallet.save() is deprecated")
910         tx = {}
911         for k,v in self.transactions.items():
912             tx[k] = str(v)
913             
914         s = {
915             'use_change': self.use_change,
916             'fee_per_kb': self.fee,
917             'accounts': self.accounts,
918             'addr_history': self.history, 
919             'labels': self.labels,
920             'contacts': self.addressbook,
921             'num_zeros': self.num_zeros,
922             'frozen_addresses': self.frozen_addresses,
923             'prioritized_addresses': self.prioritized_addresses,
924             'gap_limit': self.gap_limit,
925             'transactions': tx,
926         }
927         for k, v in s.items():
928             self.config.set_key(k,v)
929         self.config.save()
930
931     def set_verifier(self, verifier):
932         self.verifier = verifier
933
934         # review transactions that are in the history
935         for addr, hist in self.history.items():
936             if hist == ['*']: continue
937             for tx_hash, tx_height in hist:
938                 if tx_height>0:
939                     # add it in case it was previously unconfirmed
940                     self.verifier.add(tx_hash, tx_height)
941
942
943         # if we are on a pruning server, remove unverified transactions
944         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
945         for tx_hash in self.transactions.keys():
946             if tx_hash not in vr:
947                 self.transactions.pop(tx_hash)
948
949
950
951     def check_new_history(self, addr, hist):
952         
953         # check that all tx in hist are relevant
954         if hist != ['*']:
955             for tx_hash, height in hist:
956                 tx = self.transactions.get(tx_hash)
957                 if not tx: continue
958                 if not tx.has_address(addr):
959                     return False
960
961         # check that we are not "orphaning" a transaction
962         old_hist = self.history.get(addr,[])
963         if old_hist == ['*']: return True
964
965         for tx_hash, height in old_hist:
966             if tx_hash in map(lambda x:x[0], hist): continue
967             found = False
968             for _addr, _hist in self.history.items():
969                 if _addr == addr: continue
970                 if _hist == ['*']: continue
971                 _tx_hist = map(lambda x:x[0], _hist)
972                 if tx_hash in _tx_hist:
973                     found = True
974                     break
975
976             if not found:
977                 tx = self.transactions.get(tx_hash)
978                 # tx might not be there
979                 if not tx: continue
980                 
981                 # already verified?
982                 if self.verifier.get_height(tx_hash):
983                     continue
984                 # unconfirmed tx
985                 print_error("new history is orphaning transaction:", tx_hash)
986                 # check that all outputs are not mine, request histories
987                 ext_requests = []
988                 for _addr, _v in tx.outputs:
989                     # assert not self.is_mine(_addr)
990                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
991
992                 ext_h = self.interface.synchronous_get(ext_requests)
993                 print_error("sync:", ext_requests, ext_h)
994                 height = None
995                 for h in ext_h:
996                     if h == ['*']: continue
997                     for item in h:
998                         if item.get('tx_hash') == tx_hash:
999                             height = item.get('height')
1000                 if height:
1001                     print_error("found height for", tx_hash, height)
1002                     self.verifier.add(tx_hash, height)
1003                 else:
1004                     print_error("removing orphaned tx from history", tx_hash)
1005                     self.transactions.pop(tx_hash)
1006
1007         return True
1008
1009
1010
1011     def check_new_tx(self, tx_hash, tx):
1012         # 1 check that tx is referenced in addr_history. 
1013         addresses = []
1014         for addr, hist in self.history.items():
1015             if hist == ['*']:continue
1016             for txh, height in hist:
1017                 if txh == tx_hash: 
1018                     addresses.append(addr)
1019
1020         if not addresses:
1021             return False
1022
1023         # 2 check that referencing addresses are in the tx
1024         for addr in addresses:
1025             if not tx.has_address(addr):
1026                 return False
1027
1028         return True
1029
1030
1031
1032
1033 class WalletSynchronizer(threading.Thread):
1034
1035
1036     def __init__(self, wallet, config):
1037         threading.Thread.__init__(self)
1038         self.daemon = True
1039         self.wallet = wallet
1040         self.interface = self.wallet.interface
1041         self.interface.register_channel('synchronizer')
1042         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1043         self.was_updated = True
1044         self.running = False
1045         self.lock = threading.Lock()
1046
1047     def stop(self):
1048         with self.lock: self.running = False
1049         self.interface.poke('synchronizer')
1050
1051     def is_running(self):
1052         with self.lock: return self.running
1053
1054     
1055     def subscribe_to_addresses(self, addresses):
1056         messages = []
1057         for addr in addresses:
1058             messages.append(('blockchain.address.subscribe', [addr]))
1059         self.interface.send( messages, 'synchronizer')
1060
1061
1062     def run(self):
1063         with self.lock: self.running = True
1064
1065         requested_tx = []
1066         missing_tx = []
1067         requested_histories = {}
1068
1069         # request any missing transactions
1070         for history in self.wallet.history.values():
1071             if history == ['*']: continue
1072             for tx_hash, tx_height in history:
1073                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1074                     missing_tx.append( (tx_hash, tx_height) )
1075         print_error("missing tx", missing_tx)
1076
1077         # wait until we are connected, in case the user is not connected
1078         while not self.interface.is_connected:
1079             time.sleep(1)
1080         
1081         # subscriptions
1082         self.subscribe_to_addresses(self.wallet.addresses(True))
1083
1084         while self.is_running():
1085             # 1. create new addresses
1086             new_addresses = self.wallet.synchronize()
1087
1088             # request missing addresses
1089             if new_addresses:
1090                 self.subscribe_to_addresses(new_addresses)
1091
1092             # request missing transactions
1093             for tx_hash, tx_height in missing_tx:
1094                 if (tx_hash, tx_height) not in requested_tx:
1095                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1096                     requested_tx.append( (tx_hash, tx_height) )
1097             missing_tx = []
1098
1099             # detect if situation has changed
1100             if not self.interface.is_up_to_date('synchronizer'):
1101                 if self.wallet.is_up_to_date():
1102                     self.wallet.set_up_to_date(False)
1103                     self.was_updated = True
1104             else:
1105                 if not self.wallet.is_up_to_date():
1106                     self.wallet.set_up_to_date(True)
1107                     self.was_updated = True
1108
1109             if self.was_updated:
1110                 self.interface.trigger_callback('updated')
1111                 self.was_updated = False
1112
1113             # 2. get a response
1114             r = self.interface.get_response('synchronizer')
1115
1116             # poke sends None. (needed during stop)
1117             if not r: continue
1118
1119             # 3. handle response
1120             method = r['method']
1121             params = r['params']
1122             result = r.get('result')
1123             error = r.get('error')
1124             if error:
1125                 print "error", r
1126                 continue
1127
1128             if method == 'blockchain.address.subscribe':
1129                 addr = params[0]
1130                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1131                     if requested_histories.get(addr) is None:
1132                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1133                         requested_histories[addr] = result
1134
1135             elif method == 'blockchain.address.get_history':
1136                 addr = params[0]
1137                 print_error("receiving history", addr, result)
1138                 if result == ['*']:
1139                     assert requested_histories.pop(addr) == '*'
1140                     self.wallet.receive_history_callback(addr, result)
1141                 else:
1142                     hist = []
1143                     # check that txids are unique
1144                     txids = []
1145                     for item in result:
1146                         tx_hash = item['tx_hash']
1147                         if tx_hash not in txids:
1148                             txids.append(tx_hash)
1149                             hist.append( (tx_hash, item['height']) )
1150
1151                     if len(hist) != len(result):
1152                         raise BaseException("error: server sent history with non-unique txid", result)
1153
1154                     # check that the status corresponds to what was announced
1155                     rs = requested_histories.pop(addr)
1156                     if self.wallet.get_status(hist) != rs:
1157                         raise BaseException("error: status mismatch: %s"%addr)
1158                 
1159                     # store received history
1160                     self.wallet.receive_history_callback(addr, hist)
1161
1162                     # request transactions that we don't have 
1163                     for tx_hash, tx_height in hist:
1164                         if self.wallet.transactions.get(tx_hash) is None:
1165                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1166                                 missing_tx.append( (tx_hash, tx_height) )
1167
1168             elif method == 'blockchain.transaction.get':
1169                 tx_hash = params[0]
1170                 tx_height = params[1]
1171                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1172                 tx = Transaction(result)
1173                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1174                 self.was_updated = True
1175                 requested_tx.remove( (tx_hash, tx_height) )
1176                 print_error("received tx:", tx_hash, len(tx.raw))
1177
1178             elif method == 'blockchain.transaction.broadcast':
1179                 self.wallet.tx_result = result
1180                 self.wallet.tx_event.set()
1181
1182             else:
1183                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1184
1185             if self.was_updated and not requested_tx:
1186                 self.interface.trigger_callback('updated')
1187                 self.was_updated = False
1188
1189