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