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