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