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