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