3afc81898368ee8cdbb34a2a6215b93d36b534fe
[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             if h == ['*']: continue
449             for tx_hash, tx_height, in h:
450                 tx = self.transactions.get(tx_hash)
451                 for output in tx.get('outputs'):
452                     if output.get('address') != addr: continue
453                     key = tx_hash + ":%d" % output.get('index')
454                     if key in self.spent_outputs: continue
455                     output['tx_hash'] = tx_hash
456                     coins.append(output)
457
458         #coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
459
460         for addr in self.prioritized_addresses:
461             h = self.history.get(addr, [])
462             for tx_hash, tx_height, in h:
463                 for output in tx.get('outputs'):
464                     if output.get('address') != addr: continue
465                     key = tx_hash + ":%d" % output.get('index')
466                     if key in self.spent_outputs: continue
467                     output['tx_hash'] = tx_hash
468                     prioritized_coins.append(output)
469
470         #prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
471
472         inputs = []
473         coins = prioritized_coins + coins
474
475         for item in coins: 
476             addr = item.get('address')
477             v = item.get('value')
478             total += v
479             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
480             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
481             if total >= amount + fee: break
482         else:
483             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
484             inputs = []
485         return inputs, total, fee
486
487     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
488         outputs = [ (to_addr, amount) ]
489         change_amount = total - ( amount + fee )
490         if change_amount != 0:
491             # normally, the update thread should ensure that the last change address is unused
492             if not change_addr:
493                 change_addr = self.change_addresses[-1]
494             outputs.append( ( change_addr,  change_amount) )
495         return outputs
496
497     def sign_inputs( self, inputs, outputs, password ):
498         s_inputs = []
499         for i in range(len(inputs)):
500             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
501             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
502             public_key = private_key.get_verifying_key()
503             pubkey = public_key.to_string()
504             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
505             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
506             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
507             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
508         return s_inputs
509
510     def pw_encode(self, s, password):
511         if password:
512             secret = Hash(password)
513             return EncodeAES(secret, s)
514         else:
515             return s
516
517     def pw_decode(self, s, password):
518         if password is not None:
519             secret = Hash(password)
520             d = DecodeAES(secret, s)
521             if s == self.seed:
522                 try:
523                     d.decode('hex')
524                 except:
525                     raise ValueError("Invalid password")
526             return d
527         else:
528             return s
529
530
531     def get_history(self, address):
532         with self.lock:
533             return self.history.get(address)
534
535     def get_status(self, h):
536         if not h: return None
537         if h == ['*']: return '*'
538         status = ''
539         for tx_hash, height in h:
540             status += tx_hash + ':%d:' % height
541         return hashlib.sha256( status ).digest().encode('hex')
542
543
544
545     def receive_tx_callback(self, tx_hash, tx):
546
547         if not self.check_new_tx(tx_hash, tx):
548             raise BaseException("error: received transaction is not consistent with history"%tx_hash)
549
550         with self.lock:
551             self.transactions[tx_hash] = tx
552
553         tx_height = tx.get('height')
554         if tx_height>0: self.verifier.add(tx_hash, tx_height)
555
556         self.update_tx_outputs(tx_hash)
557
558         self.save()
559
560
561     def receive_history_callback(self, addr, hist):
562
563         if hist != ['*']:
564             if not self.check_new_history(addr, hist):
565                 raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
566             
567         with self.lock:
568             self.history[addr] = hist
569             self.save()
570
571         if hist != ['*']:
572             for tx_hash, tx_height in hist:
573                 if tx_height>0:
574                     self.verifier.add(tx_hash, tx_height)
575
576
577     def get_tx_history(self):
578         with self.lock:
579             lines = self.transactions.values()
580
581         lines = sorted(lines, key=operator.itemgetter("timestamp"))
582         return lines
583
584     def get_transactions_at_height(self, height):
585         with self.lock:
586             values = self.transactions.values()[:]
587
588         out = []
589         for tx in values:
590             if tx['height'] == height:
591                 out.append(tx['tx_hash'])
592         return out
593
594
595     def get_label(self, tx_hash):
596         label = self.labels.get(tx_hash)
597         is_default = (label == '') or (label is None)
598         if is_default: label = self.get_default_label(tx_hash)
599         return label, is_default
600
601     def get_default_label(self, tx_hash):
602         tx = self.transactions.get(tx_hash)
603         if tx:
604             default_label = ''
605             if self.get_tx_value(tx_hash)<0:
606                 for o in tx['outputs']:
607                     o_addr = o.get('address')
608                     if not self.is_mine(o_addr):
609                         try:
610                             default_label = self.labels[o_addr]
611                         except KeyError:
612                             default_label = o_addr
613             else:
614                 for o in tx['outputs']:
615                     o_addr = o.get('address')
616                     if self.is_mine(o_addr) and not self.is_change(o_addr):
617                         break
618                 else:
619                     for o in tx['outputs']:
620                         o_addr = o.get('address')
621                         if self.is_mine(o_addr):
622                             break
623                     else:
624                         o_addr = None
625
626                 if o_addr:
627                     dest_label = self.labels.get(o_addr)
628                     try:
629                         default_label = self.labels[o_addr]
630                     except KeyError:
631                         default_label = o_addr
632
633         return default_label
634
635
636     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
637         if not self.is_valid(to_address):
638             raise ValueError("Invalid address")
639         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
640         if not inputs:
641             raise ValueError("Not enough funds")
642
643         if not self.use_change and not change_addr:
644             change_addr = inputs[0][0]
645             print "Sending change to", change_addr
646
647         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
648         s_inputs = self.sign_inputs( inputs, outputs, password )
649
650         tx = filter( raw_tx( s_inputs, outputs ) )
651         if to_address not in self.addressbook:
652             self.addressbook.append(to_address)
653         if label: 
654             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
655             self.labels[tx_hash] = label
656
657         return tx
658
659     def sendtx(self, tx):
660         # synchronous
661         h = self.send_tx(tx)
662         self.tx_event.wait()
663         self.receive_tx(h)
664
665     def send_tx(self, tx):
666         # asynchronous
667         self.tx_event.clear()
668         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
669         self.interface.send([('blockchain.transaction.broadcast', [tx])], 'synchronizer')
670         return tx_hash
671
672     def receive_tx(self,tx_hash):
673         out = self.tx_result 
674         if out != tx_hash:
675             return False, "error: " + out
676         if self.receipt:
677             self.receipts[tx_hash] = self.receipt
678             self.receipt = None
679         return True, out
680
681
682     def read_alias(self, alias):
683         # this might not be the right place for this function.
684         import urllib
685
686         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
687         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
688         if m1:
689             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
690         elif m2:
691             url = 'https://' + alias + '/bitcoin.id'
692         else:
693             return ''
694         try:
695             lines = urllib.urlopen(url).readlines()
696         except:
697             return ''
698
699         # line 0
700         line = lines[0].strip().split(':')
701         if len(line) == 1:
702             auth_name = None
703             target = signing_addr = line[0]
704         else:
705             target, auth_name, signing_addr, signature = line
706             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
707             print msg, signature
708             self.verify_message(signing_addr, signature, msg)
709         
710         # other lines are signed updates
711         for line in lines[1:]:
712             line = line.strip()
713             if not line: continue
714             line = line.split(':')
715             previous = target
716             print repr(line)
717             target, signature = line
718             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
719
720         if not self.is_valid(target):
721             raise ValueError("Invalid bitcoin address")
722
723         return target, signing_addr, auth_name
724
725     def update_password(self, seed, old_password, new_password):
726         if new_password == '': new_password = None
727         self.use_encryption = (new_password != None)
728         self.seed = self.pw_encode( seed, new_password)
729         for k in self.imported_keys.keys():
730             a = self.imported_keys[k]
731             b = self.pw_decode(a, old_password)
732             c = self.pw_encode(b, new_password)
733             self.imported_keys[k] = c
734         self.save()
735
736     def get_alias(self, alias, interactive = False, show_message=None, question = None):
737         try:
738             target, signing_address, auth_name = self.read_alias(alias)
739         except BaseException, e:
740             # raise exception if verify fails (verify the chain)
741             if interactive:
742                 show_message("Alias error: " + str(e))
743             return
744
745         print target, signing_address, auth_name
746
747         if auth_name is None:
748             a = self.aliases.get(alias)
749             if not a:
750                 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)
751                 if interactive and question( msg ):
752                     self.aliases[alias] = (signing_address, target)
753                 else:
754                     target = None
755             else:
756                 if signing_address != a[0]:
757                     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
758                     if interactive and question( msg ):
759                         self.aliases[alias] = (signing_address, target)
760                     else:
761                         target = None
762         else:
763             if signing_address not in self.authorities.keys():
764                 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)
765                 if interactive and question( msg ):
766                     self.authorities[signing_address] = auth_name
767                 else:
768                     target = None
769
770         if target:
771             self.aliases[alias] = (signing_address, target)
772             
773         return target
774
775
776     def parse_url(self, url, show_message, question):
777         o = url[8:].split('?')
778         address = o[0]
779         if len(o)>1:
780             params = o[1].split('&')
781         else:
782             params = []
783
784         amount = label = message = signature = identity = ''
785         for p in params:
786             k,v = p.split('=')
787             uv = urldecode(v)
788             if k == 'amount': amount = uv
789             elif k == 'message': message = uv
790             elif k == 'label': label = uv
791             elif k == 'signature':
792                 identity, signature = uv.split(':')
793                 url = url.replace('&%s=%s'%(k,v),'')
794             else: 
795                 print k,v
796
797         if label and self.labels.get(address) != label:
798             if question('Give label "%s" to address %s ?'%(label,address)):
799                 if address not in self.addressbook and address not in self.all_addresses(): 
800                     self.addressbook.append(address)
801                 self.labels[address] = label
802
803         if signature:
804             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
805                 signing_address = self.get_alias(identity, True, show_message, question)
806             elif self.is_valid(identity):
807                 signing_address = identity
808             else:
809                 signing_address = None
810             if not signing_address:
811                 return
812             try:
813                 self.verify_message(signing_address, signature, url )
814                 self.receipt = (signing_address, signature, url)
815             except:
816                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
817                 address = amount = label = identity = message = ''
818
819         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
820             payto_address = self.get_alias(address, True, show_message, question)
821             if payto_address:
822                 address = address + ' <' + payto_address + '>'
823
824         return address, amount, label, message, signature, identity, url
825
826
827     def update(self):
828         self.interface.poke('synchronizer')
829         self.up_to_date_event.wait(10000000000)
830
831
832     def freeze(self,addr):
833         if addr in self.all_addresses() and addr not in self.frozen_addresses:
834             self.unprioritize(addr)
835             self.frozen_addresses.append(addr)
836             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
837             return True
838         else:
839             return False
840
841     def unfreeze(self,addr):
842         if addr in self.all_addresses() and addr in self.frozen_addresses:
843             self.frozen_addresses.remove(addr)
844             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
845             return True
846         else:
847             return False
848
849     def prioritize(self,addr):
850         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
851             self.unfreeze(addr)
852             self.prioritized_addresses.append(addr)
853             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
854             return True
855         else:
856             return False
857
858     def unprioritize(self,addr):
859         if addr in self.all_addresses() and addr in self.prioritized_addresses:
860             self.prioritized_addresses.remove(addr)
861             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
862             return True
863         else:
864             return False
865
866     def save(self):
867         s = {
868             'seed_version': self.seed_version,
869             'use_encryption': self.use_encryption,
870             'use_change': self.use_change,
871             'master_public_key': self.master_public_key,
872             'fee': self.fee,
873             'seed': self.seed,
874             'addresses': self.addresses,
875             'change_addresses': self.change_addresses,
876             'addr_history': self.history, 
877             'labels': self.labels,
878             'contacts': self.addressbook,
879             'imported_keys': self.imported_keys,
880             'aliases': self.aliases,
881             'authorities': self.authorities,
882             'receipts': self.receipts,
883             'num_zeros': self.num_zeros,
884             'frozen_addresses': self.frozen_addresses,
885             'prioritized_addresses': self.prioritized_addresses,
886             'gap_limit': self.gap_limit,
887             'transactions': self.transactions,
888         }
889         for k, v in s.items():
890             self.config.set_key(k,v)
891         self.config.save()
892
893     def set_verifier(self, verifier):
894         self.verifier = verifier
895             
896         for tx_hash, tx in self.transactions.items():
897             tx_height = tx.get('height')
898             if tx_height <1:
899                 print_error( "skipping", tx_hash, tx_height )
900                 continue
901             
902             if tx_height>0:
903                 self.verifier.add(tx_hash, tx_height)
904
905             # set the timestamp for transactions that need it
906             if tx and not tx.get('timestamp'):
907                 timestamp = self.verifier.get_timestamp(tx_height)
908                 if timestamp:
909                     self.set_tx_timestamp(tx_hash, timestamp)
910
911
912
913
914     def set_tx_timestamp(self, tx_hash, timestamp):
915         with self.lock:
916             self.transactions[tx_hash]['timestamp'] = timestamp
917
918
919
920     def is_addr_in_tx(self, addr, tx):
921         found = False
922         for txin in tx.get('inputs'):
923             if addr == txin.get('address'): 
924                 found = True
925                 break
926         for txout in tx.get('outputs'):
927             if addr == txout.get('address'): 
928                 found = True
929                 break
930         return found
931
932
933     def check_new_history(self, addr, hist):
934         # - check that all tx in hist are relevant
935         for tx_hash, height in hist:
936             tx = self.transactions.get(tx_hash)
937             if not tx: continue
938             if not self.is_addr_in_tx(addr,tx):
939                 return False
940
941         # todo: check that we are not "orphaning" a transaction
942         # if we are, reject tx if unconfirmed, else reject the server
943
944         return True
945
946
947
948     def check_new_tx(self, tx_hash, tx):
949         # 1 check that tx is referenced in addr_history. 
950         addresses = []
951         for addr, hist in self.history.items():
952             if hist == ['*']:continue
953             for txh, height in hist:
954                 if txh == tx_hash: 
955                     addresses.append(addr)
956
957         if not addresses:
958             return False
959
960         # 2 check that referencing addresses are in the tx
961         for addr in addresses:
962             if not self.is_addr_in_tx(addr, tx):
963                 return False
964
965         return True
966
967
968
969
970 class WalletSynchronizer(threading.Thread):
971
972
973     def __init__(self, wallet, config):
974         threading.Thread.__init__(self)
975         self.daemon = True
976         self.wallet = wallet
977         self.interface = self.wallet.interface
978         self.interface.register_channel('synchronizer')
979         self.wallet.interface.register_callback('connected', self.wallet.init_up_to_date)
980         self.wallet.interface.register_callback('connected', lambda: self.interface.send([('server.banner',[])],'synchronizer') )
981         self.was_updated = True
982
983     def synchronize_wallet(self):
984         new_addresses = self.wallet.synchronize()
985         if new_addresses:
986             self.subscribe_to_addresses(new_addresses)
987             self.wallet.up_to_date = False
988             return
989             
990         if not self.interface.is_up_to_date('synchronizer'):
991             if self.wallet.up_to_date:
992                 self.wallet.up_to_date = False
993                 self.was_updated = True
994             return
995
996         self.wallet.up_to_date = True
997         self.was_updated = True
998         self.wallet.up_to_date_event.set()
999
1000     
1001     def subscribe_to_addresses(self, addresses):
1002         messages = []
1003         for addr in addresses:
1004             messages.append(('blockchain.address.subscribe', [addr]))
1005         self.interface.send( messages, 'synchronizer')
1006
1007
1008     def run(self):
1009         requested_tx = []
1010         missing_tx = []
1011         requested_histories = {}
1012
1013         # request any missing transactions
1014         for history in self.wallet.history.values():
1015             if history == ['*']: continue
1016             for tx_hash, tx_height in history:
1017                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1018                     missing_tx.append( (tx_hash, tx_height) )
1019         print_error("missing tx", missing_tx)
1020
1021         # wait until we are connected, in case the user is not connected
1022         while not self.interface.is_connected:
1023             time.sleep(1)
1024         
1025         # request banner, because 'connected' event happens before this thread is started
1026         self.interface.send([('server.banner',[])],'synchronizer')
1027
1028         # subscriptions
1029         self.subscribe_to_addresses(self.wallet.all_addresses())
1030
1031         while True:
1032             # 1. send new requests
1033             self.synchronize_wallet()
1034
1035             for tx_hash, tx_height in missing_tx:
1036                 if (tx_hash, tx_height) not in requested_tx:
1037                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1038                     requested_tx.append( (tx_hash, tx_height) )
1039             missing_tx = []
1040
1041             if self.was_updated:
1042                 self.interface.trigger_callback('updated')
1043                 self.was_updated = False
1044
1045             # 2. get a response
1046             r = self.interface.get_response('synchronizer')
1047             if not r: 
1048                 continue
1049
1050             # 3. handle response
1051             method = r['method']
1052             params = r['params']
1053             result = r.get('result')
1054             error = r.get('error')
1055             if error:
1056                 print "error", r
1057                 continue
1058
1059             if method == 'blockchain.address.subscribe':
1060                 addr = params[0]
1061                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1062                     self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1063                     requested_histories[addr] = result
1064
1065             elif method == 'blockchain.address.get_history':
1066                 addr = params[0]
1067                 if result == ['*']:
1068                     assert requested_histories.pop(addr) == '*'
1069                     self.wallet.receive_history_callback(addr, result)
1070                 else:
1071                     hist = []
1072                     # check that txids are unique
1073                     txids = []
1074                     for item in result:
1075                         tx_hash = item['tx_hash']
1076                         if tx_hash not in txids:
1077                             txids.append(tx_hash)
1078                             hist.append( (tx_hash, item['height']) )
1079
1080                     if len(hist) != len(result):
1081                         raise BaseException("error: server sent history with non-unique txid", result)
1082
1083                     # check that the status corresponds to what was announced
1084                     rs = requested_histories.pop(addr)
1085                     if self.wallet.get_status(hist) != rs:
1086                         raise BaseException("error: status mismatch: %s"%addr)
1087                 
1088                     # store received history
1089                     self.wallet.receive_history_callback(addr, hist)
1090
1091                     # request transactions that we don't have 
1092                     for tx_hash, tx_height in hist:
1093                         if self.wallet.transactions.get(tx_hash) is None:
1094                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1095                                 missing_tx.append( (tx_hash, tx_height) )
1096                         else:
1097                             timestamp = self.wallet.verifier.get_timestamp(tx_height)
1098                             self.wallet.set_tx_timestamp(tx_hash, timestamp)
1099
1100             elif method == 'blockchain.transaction.get':
1101                 tx_hash = params[0]
1102                 tx_height = params[1]
1103                 d = self.deserialize_tx(tx_hash, tx_height, result)
1104                 self.wallet.receive_tx_callback(tx_hash, d)
1105                 self.was_updated = True
1106                 requested_tx.remove( (tx_hash, tx_height) )
1107                 print_error("received tx:", d)
1108
1109             elif method == 'blockchain.transaction.broadcast':
1110                 self.wallet.tx_result = result
1111                 self.wallet.tx_event.set()
1112
1113             elif method == 'server.banner':
1114                 self.wallet.banner = result
1115                 self.was_updated = True
1116
1117             else:
1118                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1119
1120             if self.was_updated and not requested_tx:
1121                 self.interface.trigger_callback('updated')
1122                 self.was_updated = False
1123
1124
1125     def deserialize_tx(self, tx_hash, tx_height, raw_tx):
1126
1127         assert tx_hash == hash_encode(Hash(raw_tx.decode('hex')))
1128         import deserialize
1129         vds = deserialize.BCDataStream()
1130         vds.write(raw_tx.decode('hex'))
1131         d = deserialize.parse_Transaction(vds)
1132         d['height'] = tx_height
1133         d['tx_hash'] = tx_hash
1134         d['timestamp'] = self.wallet.verifier.get_timestamp(tx_height)
1135         return d
1136