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