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