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