7fd16a3fd375d27067e5326f4544240b2de0e0aa
[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('addr_history',{})        # address -> list(txid, height)
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 o in tx['outputs']:
360                     addr = o.get('address')
361                     if not self.is_mine(addr) and addr not in self.addressbook:
362                         self.addressbook.append(addr)
363         # redo labels
364         # self.update_tx_labels()
365
366
367     def get_address_flags(self, addr):
368         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
369         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
370         return flags
371         
372
373     def get_tx_value(self, tx_hash, addresses = None):
374         # return the balance for that tx
375         if addresses is None: addresses = self.all_addresses()
376         with self.lock:
377             v = 0
378             d = self.transactions.get(tx_hash)
379             if not d: return 0
380             for item in d.get('inputs'):
381                 addr = item.get('address')
382                 if addr in addresses:
383                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
384                     value = self.prevout_values[ key ]
385                     v -= value
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             return v
392
393
394     
395     def update_tx_outputs(self, tx_hash):
396         tx = self.transactions.get(tx_hash)
397         for item in tx.get('outputs'):
398             value = item.get('value')
399             key = tx_hash+ ':%d'%item.get('index')
400             with self.lock:
401                 self.prevout_values[key] = value 
402
403         for item in tx.get('inputs'):
404             if self.is_mine(item.get('address')):
405                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
406                 self.spent_outputs.append(key)
407
408
409     def get_addr_balance(self, addr):
410         assert self.is_mine(addr)
411         h = self.history.get(addr,[])
412         c = u = 0
413         for tx_hash, tx_height in h:
414             v = self.get_tx_value(tx_hash, [addr])
415             if tx_height:
416                 c += v
417             else:
418                 u += v
419         return c, u
420
421     def get_balance(self):
422         conf = unconf = 0
423         for addr in self.all_addresses(): 
424             c, u = self.get_addr_balance(addr)
425             conf += c
426             unconf += u
427         return conf, unconf
428
429
430     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
431         """ todo: minimize tx size """
432         total = 0
433         fee = self.fee if fixed_fee is None else fixed_fee
434
435         coins = []
436         prioritized_coins = []
437         domain = [from_addr] if from_addr else self.all_addresses()
438         for i in self.frozen_addresses:
439             if i in domain: domain.remove(i)
440
441         for i in self.prioritized_addresses:
442             if i in domain: domain.remove(i)
443
444         for addr in domain:
445             h = self.history.get(addr, [])
446             for tx_hash, tx_height, in h:
447                 tx = self.transactions.get(tx_hash)
448                 for output in tx.get('outputs'):
449                     if output.get('address') != addr: continue
450                     key = tx_hash + ":%d" % output.get('index')
451                     if key in self.spent_outputs: continue
452                     output['tx_hash'] = tx_hash
453                     coins.append(output)
454
455         #coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
456
457         for addr in self.prioritized_addresses:
458             h = self.history.get(addr, [])
459             for tx_hash, tx_height, in h:
460                 for output in tx.get('outputs'):
461                     if output.get('address') != addr: continue
462                     key = tx_hash + ":%d" % output.get('index')
463                     if key in self.spent_outputs: continue
464                     output['tx_hash'] = tx_hash
465                     prioritized_coins.append(output)
466
467         #prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
468
469         inputs = []
470         coins = prioritized_coins + coins
471
472         for item in coins: 
473             addr = item.get('address')
474             v = item.get('value')
475             total += v
476             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
477             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
478             if total >= amount + fee: break
479         else:
480             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
481             inputs = []
482         return inputs, total, fee
483
484     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
485         outputs = [ (to_addr, amount) ]
486         change_amount = total - ( amount + fee )
487         if change_amount != 0:
488             # normally, the update thread should ensure that the last change address is unused
489             if not change_addr:
490                 change_addr = self.change_addresses[-1]
491             outputs.append( ( change_addr,  change_amount) )
492         return outputs
493
494     def sign_inputs( self, inputs, outputs, password ):
495         s_inputs = []
496         for i in range(len(inputs)):
497             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
498             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
499             public_key = private_key.get_verifying_key()
500             pubkey = public_key.to_string()
501             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
502             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
503             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
504             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
505         return s_inputs
506
507     def pw_encode(self, s, password):
508         if password:
509             secret = Hash(password)
510             return EncodeAES(secret, s)
511         else:
512             return s
513
514     def pw_decode(self, s, password):
515         if password is not None:
516             secret = Hash(password)
517             d = DecodeAES(secret, s)
518             if s == self.seed:
519                 try:
520                     d.decode('hex')
521                 except:
522                     raise ValueError("Invalid password")
523             return d
524         else:
525             return s
526
527
528     def get_history(self, address):
529         with self.lock:
530             return self.history.get(address)
531
532     def get_status(self, h):
533         if not h: return None
534         status = ''
535         for tx_hash, height in h:
536             status += tx_hash + ':%d:' % height
537         return hashlib.sha256( status ).digest().encode('hex')
538
539
540
541     def receive_tx_callback(self, tx_hash, tx):
542
543         if not self.check_new_tx(tx_hash, tx):
544             raise BaseException("error: received transaction is not consistent with history"%tx_hash)
545
546         with self.lock:
547             self.transactions[tx_hash] = tx
548
549         self.update_tx_outputs(tx_hash)
550
551         self.save()
552
553
554     def receive_history_callback(self, addr, hist):
555
556         if not self.check_new_history(addr, hist):
557             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
558             
559         with self.lock:
560             self.history[addr] = hist
561             self.save()
562             for tx_hash, tx_height in hist:
563                 if tx_height>0:
564                     self.verifier.add(tx_hash)
565
566
567
568     def get_tx_history(self):
569         with self.lock:
570             lines = self.transactions.values()
571
572         lines = sorted(lines, key=operator.itemgetter("timestamp"))
573         return lines
574
575     def get_transactions_at_height(self, height):
576         with self.lock:
577             values = self.transactions.values()[:]
578
579         out = []
580         for tx in values:
581             if tx['height'] == height:
582                 out.append(tx['tx_hash'])
583         return out
584
585
586     def get_label(self, tx_hash):
587         label = self.labels.get(tx_hash)
588         is_default = (label == '') or (label is None)
589         if is_default: label = self.get_default_label(tx_hash)
590         return label, is_default
591
592     def get_default_label(self, tx_hash):
593         tx = self.transactions.get(tx_hash)
594         if tx:
595             default_label = ''
596             if self.get_tx_value(tx_hash)<0:
597                 for o in tx['outputs']:
598                     o_addr = o.get('address')
599                     if not self.is_mine(o_addr):
600                         try:
601                             default_label = self.labels[o_addr]
602                         except KeyError:
603                             default_label = o_addr
604             else:
605                 for o in tx['outputs']:
606                     o_addr = o.get('address')
607                     if self.is_mine(o_addr) and not self.is_change(o_addr):
608                         break
609                 else:
610                     for o in tx['outputs']:
611                         o_addr = o.get('address')
612                         if self.is_mine(o_addr):
613                             break
614                     else:
615                         o_addr = None
616
617                 if o_addr:
618                     dest_label = self.labels.get(o_addr)
619                     try:
620                         default_label = self.labels[o_addr]
621                     except KeyError:
622                         default_label = o_addr
623
624         return default_label
625
626
627     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
628         if not self.is_valid(to_address):
629             raise ValueError("Invalid address")
630         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
631         if not inputs:
632             raise ValueError("Not enough funds")
633
634         if not self.use_change and not change_addr:
635             change_addr = inputs[0][0]
636             print "Sending change to", change_addr
637
638         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
639         s_inputs = self.sign_inputs( inputs, outputs, password )
640
641         tx = filter( raw_tx( s_inputs, outputs ) )
642         if to_address not in self.addressbook:
643             self.addressbook.append(to_address)
644         if label: 
645             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
646             self.labels[tx_hash] = label
647
648         return tx
649
650     def sendtx(self, tx):
651         # synchronous
652         h = self.send_tx(tx)
653         self.tx_event.wait()
654         self.receive_tx(h)
655
656     def send_tx(self, tx):
657         # asynchronous
658         self.tx_event.clear()
659         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
660         self.interface.send([('blockchain.transaction.broadcast', [tx])], 'synchronizer')
661         return tx_hash
662
663     def receive_tx(self,tx_hash):
664         out = self.tx_result 
665         if out != tx_hash:
666             return False, "error: " + out
667         if self.receipt:
668             self.receipts[tx_hash] = self.receipt
669             self.receipt = None
670         return True, out
671
672
673     def read_alias(self, alias):
674         # this might not be the right place for this function.
675         import urllib
676
677         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
678         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
679         if m1:
680             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
681         elif m2:
682             url = 'https://' + alias + '/bitcoin.id'
683         else:
684             return ''
685         try:
686             lines = urllib.urlopen(url).readlines()
687         except:
688             return ''
689
690         # line 0
691         line = lines[0].strip().split(':')
692         if len(line) == 1:
693             auth_name = None
694             target = signing_addr = line[0]
695         else:
696             target, auth_name, signing_addr, signature = line
697             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
698             print msg, signature
699             self.verify_message(signing_addr, signature, msg)
700         
701         # other lines are signed updates
702         for line in lines[1:]:
703             line = line.strip()
704             if not line: continue
705             line = line.split(':')
706             previous = target
707             print repr(line)
708             target, signature = line
709             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
710
711         if not self.is_valid(target):
712             raise ValueError("Invalid bitcoin address")
713
714         return target, signing_addr, auth_name
715
716     def update_password(self, seed, old_password, new_password):
717         if new_password == '': new_password = None
718         self.use_encryption = (new_password != None)
719         self.seed = self.pw_encode( seed, new_password)
720         for k in self.imported_keys.keys():
721             a = self.imported_keys[k]
722             b = self.pw_decode(a, old_password)
723             c = self.pw_encode(b, new_password)
724             self.imported_keys[k] = c
725         self.save()
726
727     def get_alias(self, alias, interactive = False, show_message=None, question = None):
728         try:
729             target, signing_address, auth_name = self.read_alias(alias)
730         except BaseException, e:
731             # raise exception if verify fails (verify the chain)
732             if interactive:
733                 show_message("Alias error: " + str(e))
734             return
735
736         print target, signing_address, auth_name
737
738         if auth_name is None:
739             a = self.aliases.get(alias)
740             if not a:
741                 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)
742                 if interactive and question( msg ):
743                     self.aliases[alias] = (signing_address, target)
744                 else:
745                     target = None
746             else:
747                 if signing_address != a[0]:
748                     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
749                     if interactive and question( msg ):
750                         self.aliases[alias] = (signing_address, target)
751                     else:
752                         target = None
753         else:
754             if signing_address not in self.authorities.keys():
755                 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)
756                 if interactive and question( msg ):
757                     self.authorities[signing_address] = auth_name
758                 else:
759                     target = None
760
761         if target:
762             self.aliases[alias] = (signing_address, target)
763             
764         return target
765
766
767     def parse_url(self, url, show_message, question):
768         o = url[8:].split('?')
769         address = o[0]
770         if len(o)>1:
771             params = o[1].split('&')
772         else:
773             params = []
774
775         amount = label = message = signature = identity = ''
776         for p in params:
777             k,v = p.split('=')
778             uv = urldecode(v)
779             if k == 'amount': amount = uv
780             elif k == 'message': message = uv
781             elif k == 'label': label = uv
782             elif k == 'signature':
783                 identity, signature = uv.split(':')
784                 url = url.replace('&%s=%s'%(k,v),'')
785             else: 
786                 print k,v
787
788         if label and self.labels.get(address) != label:
789             if question('Give label "%s" to address %s ?'%(label,address)):
790                 if address not in self.addressbook and address not in self.all_addresses(): 
791                     self.addressbook.append(address)
792                 self.labels[address] = label
793
794         if signature:
795             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
796                 signing_address = self.get_alias(identity, True, show_message, question)
797             elif self.is_valid(identity):
798                 signing_address = identity
799             else:
800                 signing_address = None
801             if not signing_address:
802                 return
803             try:
804                 self.verify_message(signing_address, signature, url )
805                 self.receipt = (signing_address, signature, url)
806             except:
807                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
808                 address = amount = label = identity = message = ''
809
810         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
811             payto_address = self.get_alias(address, True, show_message, question)
812             if payto_address:
813                 address = address + ' <' + payto_address + '>'
814
815         return address, amount, label, message, signature, identity, url
816
817
818     def update(self):
819         self.interface.poke('synchronizer')
820         self.up_to_date_event.wait(10000000000)
821
822
823     def freeze(self,addr):
824         if addr in self.all_addresses() and addr not in self.frozen_addresses:
825             self.unprioritize(addr)
826             self.frozen_addresses.append(addr)
827             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
828             return True
829         else:
830             return False
831
832     def unfreeze(self,addr):
833         if addr in self.all_addresses() and addr in self.frozen_addresses:
834             self.frozen_addresses.remove(addr)
835             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
836             return True
837         else:
838             return False
839
840     def prioritize(self,addr):
841         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
842             self.unfreeze(addr)
843             self.prioritized_addresses.append(addr)
844             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
845             return True
846         else:
847             return False
848
849     def unprioritize(self,addr):
850         if addr in self.all_addresses() and addr in self.prioritized_addresses:
851             self.prioritized_addresses.remove(addr)
852             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
853             return True
854         else:
855             return False
856
857     def save(self):
858         s = {
859             'seed_version': self.seed_version,
860             'use_encryption': self.use_encryption,
861             'use_change': self.use_change,
862             'master_public_key': self.master_public_key,
863             'fee': self.fee,
864             'seed': self.seed,
865             'addresses': self.addresses,
866             'change_addresses': self.change_addresses,
867             'addr_history': self.history, 
868             'labels': self.labels,
869             'contacts': self.addressbook,
870             'imported_keys': self.imported_keys,
871             'aliases': self.aliases,
872             'authorities': self.authorities,
873             'receipts': self.receipts,
874             'num_zeros': self.num_zeros,
875             'frozen_addresses': self.frozen_addresses,
876             'prioritized_addresses': self.prioritized_addresses,
877             'gap_limit': self.gap_limit,
878             'transactions': self.transactions,
879         }
880         for k, v in s.items():
881             self.config.set_key(k,v)
882         self.config.save()
883
884     def set_verifier(self, verifier):
885         self.verifier = verifier
886             
887         # set the timestamp for transactions that need it
888         for hist in self.history.values():
889             for tx_hash, tx_height in hist:
890                 tx = self.transactions.get(tx_hash)
891                 if tx and not tx.get('timestamp'):
892                     timestamp = self.verifier.get_timestamp(tx_height)
893                     if timestamp:
894                         self.set_tx_timestamp(tx_hash, timestamp)
895
896                 if tx_height>0:
897                     self.verifier.add(tx_hash)
898
899
900
901     def set_tx_timestamp(self, tx_hash, timestamp):
902         with self.lock:
903             self.transactions[tx_hash]['timestamp'] = timestamp
904
905
906
907     def is_addr_in_tx(self, addr, tx):
908         found = False
909         for txin in tx.get('inputs'):
910             if addr == txin.get('address'): 
911                 found = True
912                 break
913         for txout in tx.get('outputs'):
914             if addr == txout.get('address'): 
915                 found = True
916                 break
917         return found
918
919
920     def check_new_history(self, addr, hist):
921         # - check that all tx in hist are relevant
922         for tx_hash, height in hist:
923             tx = self.transactions.get(tx_hash)
924             if not tx: continue
925             if not self.is_addr_in_tx(addr,tx):
926                 return False
927
928         # todo: check that we are not "orphaning" a transaction
929         # if we are, reject tx if unconfirmed, else reject the server
930
931         return True
932
933
934
935     def check_new_tx(self, tx_hash, tx):
936         # 1 check that tx is referenced in addr_history. 
937         addresses = []
938         for addr, hist in self.history.items():
939             for txh, height in hist:
940                 if txh == tx_hash: 
941                     addresses.append(addr)
942
943         if not addresses:
944             return False
945
946         # 2 check that referencing addresses are in the tx
947         for addr in addresses:
948             if not self.is_addr_in_tx(addr, tx):
949                 return False
950
951         return True
952
953
954
955
956 class WalletSynchronizer(threading.Thread):
957
958
959     def __init__(self, wallet, config):
960         threading.Thread.__init__(self)
961         self.daemon = True
962         self.wallet = wallet
963         self.interface = self.wallet.interface
964         self.interface.register_channel('synchronizer')
965         self.wallet.interface.register_callback('connected', self.wallet.init_up_to_date)
966         self.wallet.interface.register_callback('connected', lambda: self.interface.send([('server.banner',[])],'synchronizer') )
967         self.was_updated = True
968
969     def synchronize_wallet(self):
970         new_addresses = self.wallet.synchronize()
971         if new_addresses:
972             self.subscribe_to_addresses(new_addresses)
973             self.wallet.up_to_date = False
974             return
975             
976         if not self.interface.is_up_to_date('synchronizer'):
977             if self.wallet.up_to_date:
978                 self.wallet.up_to_date = False
979                 self.was_updated = True
980             return
981
982         self.wallet.up_to_date = True
983         self.was_updated = True
984         self.wallet.up_to_date_event.set()
985
986     
987     def subscribe_to_addresses(self, addresses):
988         messages = []
989         for addr in addresses:
990             messages.append(('blockchain.address.subscribe', [addr]))
991         self.interface.send( messages, 'synchronizer')
992
993
994     def run(self):
995         requested_tx = []
996         missing_tx = []
997         requested_histories = {}
998
999         # request any missing transactions
1000         for history in self.wallet.history.values():
1001             for tx_hash, tx_height in history:
1002                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1003                     missing_tx.append( (tx_hash, tx_height) )
1004         print_error("missing tx", missing_tx)
1005
1006         # wait until we are connected, in case the user is not connected
1007         while not self.interface.is_connected:
1008             time.sleep(1)
1009         
1010         # request banner, because 'connected' event happens before this thread is started
1011         self.interface.send([('server.banner',[])],'synchronizer')
1012
1013         # subscriptions
1014         self.subscribe_to_addresses(self.wallet.all_addresses())
1015
1016         while True:
1017             # 1. send new requests
1018             self.synchronize_wallet()
1019
1020             for tx_hash, tx_height in missing_tx:
1021                 if (tx_hash, tx_height) not in requested_tx:
1022                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1023                     requested_tx.append( (tx_hash, tx_height) )
1024             missing_tx = []
1025
1026             if self.was_updated:
1027                 self.interface.trigger_callback('updated')
1028                 self.was_updated = False
1029
1030             # 2. get a response
1031             r = self.interface.get_response('synchronizer')
1032             if not r: 
1033                 continue
1034
1035             # 3. handle response
1036             method = r['method']
1037             params = r['params']
1038             result = r['result']
1039
1040             if method == 'blockchain.address.subscribe':
1041                 addr = params[0]
1042                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1043                     self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1044                     requested_histories[addr] = result
1045
1046             elif method == 'blockchain.address.get_history':
1047                 addr = params[0]
1048                 hist = []
1049
1050                 # check that txids are unique
1051                 txids = []
1052                 for item in result:
1053                     tx_hash = item['tx_hash']
1054                     if tx_hash not in txids:
1055                         txids.append(tx_hash)
1056                         hist.append( (tx_hash, item['height']) )
1057
1058                 if len(hist) != len(result):
1059                     raise BaseException("error: server sent history with non-unique txid")
1060
1061                 # check that the status corresponds to what was announced
1062                 if self.wallet.get_status(hist) != requested_histories.pop(addr):
1063                     raise BaseException("error: status mismatch: %s"%addr)
1064                 
1065                 # store received history
1066                 self.wallet.receive_history_callback(addr, hist)
1067
1068                 # request transactions that we don't have 
1069                 for tx_hash, tx_height in hist:
1070                     if self.wallet.transactions.get(tx_hash) is None:
1071                         if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1072                             missing_tx.append( (tx_hash, tx_height) )
1073                     else:
1074                         timestamp = self.wallet.verifier.get_timestamp(tx_height)
1075                         self.wallet.set_tx_timestamp(tx_hash, timestamp)
1076
1077             elif method == 'blockchain.transaction.get':
1078                 tx_hash = params[0]
1079                 tx_height = params[1]
1080                 d = self.deserialize_tx(tx_hash, tx_height, result)
1081                 self.wallet.receive_tx_callback(tx_hash, d)
1082                 self.was_updated = True
1083                 requested_tx.remove( (tx_hash, tx_height) )
1084                 print_error("received tx:", d)
1085
1086             elif method == 'blockchain.transaction.broadcast':
1087                 self.wallet.tx_result = result
1088                 self.wallet.tx_event.set()
1089
1090             elif method == 'server.banner':
1091                 self.wallet.banner = result
1092                 self.was_updated = True
1093
1094             else:
1095                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1096
1097             if self.was_updated and not requested_tx:
1098                 self.interface.trigger_callback('updated')
1099                 self.was_updated = False
1100
1101
1102     def deserialize_tx(self, tx_hash, tx_height, raw_tx):
1103
1104         assert tx_hash == hash_encode(Hash(raw_tx.decode('hex')))
1105         import deserialize
1106         vds = deserialize.BCDataStream()
1107         vds.write(raw_tx.decode('hex'))
1108         d = deserialize.parse_Transaction(vds)
1109         d['tx_hash'] = tx_hash
1110         d['timestamp'] = self.wallet.verifier.get_timestamp(tx_height)
1111         return d
1112