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