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