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