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