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