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