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