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