b252f4522b799f6ee03be11b08f308015effda97
[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 = {}     # my own transaction outputs
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         
91         self.up_to_date = False
92         self.lock = threading.Lock()
93         self.tx_event = threading.Event()
94
95         if self.seed_version != SEED_VERSION:
96             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
97
98         for tx_hash in self.transactions.keys():
99             self.update_tx_outputs(tx_hash)
100
101
102     def set_up_to_date(self,b):
103         with self.lock: self.up_to_date = b
104
105     def is_up_to_date(self):
106         with self.lock: return self.up_to_date
107
108     def update(self):
109         self.up_to_date = False
110         self.interface.poke('synchronizer')
111         while not self.is_up_to_date(): time.sleep(0.1)
112
113     def import_key(self, keypair, password):
114         address, key = keypair.split(':')
115         if not self.is_valid(address):
116             raise BaseException('Invalid Bitcoin address')
117         if address in self.all_addresses():
118             raise BaseException('Address already in wallet')
119         b = ASecretToSecret( key )
120         if not b: 
121             raise BaseException('Unsupported key format')
122         secexp = int( b.encode('hex'), 16)
123         private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 )
124         # sanity check
125         public_key = private_key.get_verifying_key()
126         if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ):
127             raise BaseException('Address does not match private key')
128         self.imported_keys[address] = self.pw_encode( key, password )
129
130
131     def new_seed(self, password):
132         seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
133         #self.init_mpk(seed)
134         # encrypt
135         self.seed = self.pw_encode( seed, password )
136
137
138     def init_mpk(self,seed):
139         # public key
140         curve = SECP256k1
141         secexp = self.stretch_key(seed)
142         master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
143         self.master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
144
145     def all_addresses(self):
146         return self.addresses + self.change_addresses + self.imported_keys.keys()
147
148     def is_mine(self, address):
149         return address in self.all_addresses()
150
151     def is_change(self, address):
152         return address in self.change_addresses
153
154     def is_valid(self,addr):
155         ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z')
156         if not ADDRESS_RE.match(addr): return False
157         try:
158             h = bc_address_to_hash_160(addr)
159         except:
160             return False
161         return addr == hash_160_to_bc_address(h)
162
163     def stretch_key(self,seed):
164         oldseed = seed
165         for i in range(100000):
166             seed = hashlib.sha256(seed + oldseed).digest()
167         return string_to_number( seed )
168
169     def get_sequence(self,n,for_change):
170         return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
171
172     def get_private_key_base58(self, address, password):
173         pk = self.get_private_key(address, password)
174         if pk is None: return None
175         return SecretToASecret( pk )
176
177     def get_private_key(self, address, password):
178         """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
179         order = generator_secp256k1.order()
180         
181         if address in self.imported_keys.keys():
182             b = self.pw_decode( self.imported_keys[address], password )
183             if not b: return None
184             b = ASecretToSecret( b )
185             secexp = int( b.encode('hex'), 16)
186         else:
187             if address in self.addresses:
188                 n = self.addresses.index(address)
189                 for_change = False
190             elif address in self.change_addresses:
191                 n = self.change_addresses.index(address)
192                 for_change = True
193             else:
194                 raise BaseException("unknown address")
195             try:
196                 seed = self.pw_decode( self.seed, password)
197             except:
198                 raise BaseException("Invalid password")
199             if not seed: return None
200             secexp = self.stretch_key(seed)
201             secexp = ( secexp + self.get_sequence(n,for_change) ) % order
202
203         pk = number_to_string(secexp,order)
204         return pk
205
206     def msg_magic(self, message):
207         return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
208
209     def sign_message(self, address, message, password):
210         private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 )
211         public_key = private_key.get_verifying_key()
212         signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
213         assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
214         for i in range(4):
215             sig = base64.b64encode( chr(27+i) + signature )
216             try:
217                 self.verify_message( address, sig, message)
218                 return sig
219             except:
220                 continue
221         else:
222             raise BaseException("error: cannot sign message")
223
224
225     def verify_message(self, address, signature, message):
226         """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
227         from ecdsa import numbertheory, ellipticcurve, util
228         import msqr
229         curve = curve_secp256k1
230         G = generator_secp256k1
231         order = G.order()
232         # extract r,s from signature
233         sig = base64.b64decode(signature)
234         if len(sig) != 65: raise BaseException("Wrong encoding")
235         r,s = util.sigdecode_string(sig[1:], order)
236         nV = ord(sig[0])
237         if nV < 27 or nV >= 35:
238             raise BaseException("Bad encoding")
239         if nV >= 31:
240             compressed = True
241             nV -= 4
242         else:
243             compressed = False
244
245         recid = nV - 27
246         # 1.1
247         x = r + (recid/2) * order
248         # 1.3
249         alpha = ( x * x * x  + curve.a() * x + curve.b() ) % curve.p()
250         beta = msqr.modular_sqrt(alpha, curve.p())
251         y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
252         # 1.4 the constructor checks that nR is at infinity
253         R = ellipticcurve.Point(curve, x, y, order)
254         # 1.5 compute e from message:
255         h = Hash( self.msg_magic( message ) )
256         e = string_to_number(h)
257         minus_e = -e % order
258         # 1.6 compute Q = r^-1 (sR - eG)
259         inv_r = numbertheory.inverse_mod(r,order)
260         Q = inv_r * ( s * R + minus_e * G )
261         public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
262         # check that Q is the public key
263         public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
264         # check that we get the original signing address
265         addr = public_key_to_bc_address( encode_point(public_key, compressed) )
266         if address != addr:
267             raise BaseException("Bad signature")
268     
269
270     def create_new_address(self, for_change):
271         n = len(self.change_addresses) if for_change else len(self.addresses)
272         address = self.get_new_address(n, for_change)
273         if for_change:
274             self.change_addresses.append(address)
275         else:
276             self.addresses.append(address)
277         self.history[address] = []
278         return address
279         
280     def get_new_address(self, n, for_change):
281         """   Publickey(type,n) = Master_public_key + H(n|S|type)*point  """
282         curve = SECP256k1
283         z = self.get_sequence(n, for_change)
284         master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), curve = SECP256k1 )
285         pubkey_point = master_public_key.pubkey.point + z*curve.generator
286         public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
287         address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() )
288         print address
289         return address
290                                                                       
291
292     def change_gap_limit(self, value):
293         if value >= self.gap_limit:
294             self.gap_limit = value
295             self.save()
296             self.interface.poke('synchronizer')
297             return True
298
299         elif value >= self.min_acceptable_gap():
300             k = self.num_unused_trailing_addresses()
301             n = len(self.addresses) - k + value
302             self.addresses = self.addresses[0:n]
303             self.gap_limit = value
304             self.save()
305             return True
306         else:
307             return False
308
309     def num_unused_trailing_addresses(self):
310         k = 0
311         for a in self.addresses[::-1]:
312             if self.history.get(a):break
313             k = k + 1
314         return k
315
316     def min_acceptable_gap(self):
317         # fixme: this assumes wallet is synchronized
318         n = 0
319         nmax = 0
320         k = self.num_unused_trailing_addresses()
321         for a in self.addresses[0:-k]:
322             if self.history.get(a):
323                 n = 0
324             else:
325                 n += 1
326                 if n > nmax: nmax = n
327         return nmax + 1
328
329
330     def synchronize(self):
331         if not self.master_public_key:
332             return []
333
334         new_addresses = []
335         while True:
336             if self.change_addresses == []:
337                 new_addresses.append( self.create_new_address(True) )
338                 continue
339             a = self.change_addresses[-1]
340             if self.history.get(a):
341                 new_addresses.append( self.create_new_address(True) )
342             else:
343                 break
344
345         n = self.gap_limit
346         while True:
347             if len(self.addresses) < n:
348                 new_addresses.append( self.create_new_address(False) )
349                 continue
350             if map( lambda a: self.history.get(a, []), self.addresses[-n:] ) == n*[[]]:
351                 break
352             else:
353                 new_addresses.append( self.create_new_address(False) )
354
355         return new_addresses
356
357
358     def is_found(self):
359         return (len(self.change_addresses) > 1 ) or ( len(self.addresses) > self.gap_limit )
360
361     def fill_addressbook(self):
362         for tx_hash, tx in self.transactions.items():
363             is_send, _, _ = self.get_tx_value(tx_hash)
364             if is_send:
365                 for o in tx['outputs']:
366                     addr = o.get('address')
367                     if not self.is_mine(addr) and addr not in self.addressbook:
368                         self.addressbook.append(addr)
369         # redo labels
370         # self.update_tx_labels()
371
372
373     def get_address_flags(self, addr):
374         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
375         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
376         return flags
377         
378
379     def get_tx_value(self, tx_hash, addresses = None):
380         # return the balance for that tx
381         if addresses is None: addresses = self.all_addresses()
382         with self.lock:
383             is_send = False
384             is_pruned = False
385             v_in = v_out = v_out_mine = 0
386             d = self.transactions.get(tx_hash)
387             if not d: 
388                 return 0, 0, 0
389
390             for item in d.get('inputs'):
391                 addr = item.get('address')
392                 if addr in addresses:
393                     is_send = True
394                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
395                     value = self.prevout_values.get( key )
396                     if value is None:
397                         is_pruned = True
398                     else:
399                         v_in += value
400                 else:
401                     is_pruned = True
402                     
403             for item in d.get('outputs'):
404                 addr = item.get('address')
405                 value = item.get('value')
406                 v_out += value
407                 if addr in addresses:
408                     v_out_mine += value
409
410         if not is_pruned:
411             # all inputs are mine:
412             fee = v_out - v_in
413             v = v_out_mine - v_in
414         else:
415             # some inputs are mine:
416             fee = None
417             if is_send:
418                 v = v_out_mine - v_out
419             else:
420                 # no input is mine
421                 v = v_out_mine
422             
423         return is_send, v, fee
424
425
426     def get_tx_details(self, tx_hash):
427         import datetime
428         if not tx_hash: return ''
429         tx = self.transactions.get(tx_hash)
430         is_mine, v, fee = self.get_tx_value(tx_hash)
431         conf = self.verifier.get_confirmations(tx_hash)
432         timestamp = tx.get('timestamp')
433         if conf and timestamp:
434             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
435         else:
436             time_str = 'pending'
437
438         inputs = map(lambda x: x.get('address'), tx['inputs'])
439         outputs = map(lambda x: x.get('address'), tx['outputs'])
440         tx_details = "Transaction Details" +"\n\n" \
441             + "Transaction ID:\n" + tx_hash + "\n\n" \
442             + "Status: %d confirmations\n"%conf
443         if is_mine:
444             if fee: 
445                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
446                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
447             else:
448                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
449                               + "Transaction fee: unknown\n"
450         else:
451             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
452
453         tx_details += "Date: %s\n\n"%time_str \
454             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
455             + "Outputs:\n-"+ '\n-'.join(outputs)
456
457         r = self.receipts.get(tx_hash)
458         if r:
459             tx_details += "\n_______________________________________" \
460                 + '\n\nSigned URI: ' + r[2] \
461                 + "\n\nSigned by: " + r[0] \
462                 + '\n\nSignature: ' + r[1]
463
464         return tx_details
465
466     
467     def update_tx_outputs(self, tx_hash):
468         tx = self.transactions.get(tx_hash)
469         for item in tx.get('outputs'):
470             value = item.get('value')
471             key = tx_hash+ ':%d'%item.get('index')
472             with self.lock:
473                 self.prevout_values[key] = value 
474
475         for item in tx.get('inputs'):
476             if self.is_mine(item.get('address')):
477                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
478                 self.spent_outputs.append(key)
479
480
481     def get_addr_balance(self, address):
482         assert self.is_mine(address)
483         h = self.history.get(address,[])
484         if h == ['*']: return 0,0
485         c = u = 0
486         received_coins = [] 
487         
488         for tx_hash, tx_height in h:
489             d = self.transactions.get(tx_hash)
490             if not d: continue
491             for item in d.get('outputs'):
492                 addr = item.get('address')
493                 key = tx_hash + ':%d'%item['index']
494                 received_coins.append(key)
495
496         for tx_hash, tx_height in h:
497             d = self.transactions.get(tx_hash)
498             if not d: continue
499             v = 0
500
501             for item in d.get('inputs'):
502                 addr = item.get('address')
503                 if addr == address:
504                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
505                     value = self.prevout_values.get( key )
506                     if key in received_coins: 
507                         v -= value
508                     
509             for item in d.get('outputs'):
510                 addr = item.get('address')
511                 key = tx_hash + ':%d'%item['index']
512                 if addr == address:
513                     v += item.get('value')
514
515             if tx_height:
516                 c += v
517             else:
518                 u += v
519         return c, u
520
521     def get_balance(self):
522         conf = unconf = 0
523         for addr in self.all_addresses(): 
524             c, u = self.get_addr_balance(addr)
525             conf += c
526             unconf += u
527         return conf, unconf
528
529
530     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
531         """ todo: minimize tx size """
532         total = 0
533         fee = self.fee if fixed_fee is None else fixed_fee
534
535         coins = []
536         prioritized_coins = []
537         domain = [from_addr] if from_addr else self.all_addresses()
538         for i in self.frozen_addresses:
539             if i in domain: domain.remove(i)
540
541         for i in self.prioritized_addresses:
542             if i in domain: domain.remove(i)
543
544         for addr in domain:
545             h = self.history.get(addr, [])
546             if h == ['*']: continue
547             for tx_hash, tx_height in h:
548                 tx = self.transactions.get(tx_hash)
549                 for output in tx.get('outputs'):
550                     if output.get('address') != addr: continue
551                     key = tx_hash + ":%d" % output.get('index')
552                     if key in self.spent_outputs: continue
553                     output['tx_hash'] = tx_hash
554                     coins.append(output)
555
556
557         for addr in self.prioritized_addresses:
558             h = self.history.get(addr, [])
559             if h == ['*']: continue
560             for tx_hash, tx_height in h:
561                 tx = self.transactions.get(tx_hash)
562                 for output in tx.get('outputs'):
563                     if output.get('address') != addr: continue
564                     key = tx_hash + ":%d" % output.get('index')
565                     if key in self.spent_outputs: continue
566                     output['tx_hash'] = tx_hash
567                     prioritized_coins.append(output)
568
569
570         inputs = []
571         coins = prioritized_coins + coins
572
573         for item in coins: 
574             addr = item.get('address')
575             v = item.get('value')
576             total += v
577             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
578             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
579             if total >= amount + fee: break
580         else:
581             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
582             inputs = []
583         return inputs, total, fee
584
585     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
586         outputs = [ (to_addr, amount) ]
587         change_amount = total - ( amount + fee )
588         if change_amount != 0:
589             # normally, the update thread should ensure that the last change address is unused
590             if not change_addr:
591                 change_addr = self.change_addresses[-1]
592             outputs.append( ( change_addr,  change_amount) )
593         return outputs
594
595     def sign_inputs( self, inputs, outputs, password ):
596         s_inputs = []
597         for i in range(len(inputs)):
598             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
599             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
600             public_key = private_key.get_verifying_key()
601             pubkey = public_key.to_string()
602             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
603             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
604             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
605             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
606         return s_inputs
607
608     def pw_encode(self, s, password):
609         if password:
610             secret = Hash(password)
611             return EncodeAES(secret, s)
612         else:
613             return s
614
615     def pw_decode(self, s, password):
616         if password is not None:
617             secret = Hash(password)
618             d = DecodeAES(secret, s)
619             if s == self.seed:
620                 try:
621                     d.decode('hex')
622                 except:
623                     raise ValueError("Invalid password")
624             return d
625         else:
626             return s
627
628
629     def get_history(self, address):
630         with self.lock:
631             return self.history.get(address)
632
633     def get_status(self, h):
634         if not h: return None
635         if h == ['*']: return '*'
636         status = ''
637         for tx_hash, height in h:
638             status += tx_hash + ':%d:' % height
639         return hashlib.sha256( status ).digest().encode('hex')
640
641
642
643     def receive_tx_callback(self, tx_hash, tx):
644
645         if not self.check_new_tx(tx_hash, tx):
646             raise BaseException("error: received transaction is not consistent with history"%tx_hash)
647
648         with self.lock:
649             self.transactions[tx_hash] = tx
650
651         tx_height = tx.get('height')
652         if self.verifier and tx_height>0: 
653             self.verifier.add(tx_hash, tx_height)
654
655         self.update_tx_outputs(tx_hash)
656
657         self.save()
658
659
660     def receive_history_callback(self, addr, hist):
661
662         if not self.check_new_history(addr, hist):
663             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
664             
665         with self.lock:
666             self.history[addr] = hist
667             self.save()
668
669         if hist != ['*']:
670             for tx_hash, tx_height in hist:
671                 if tx_height>0:
672                     # add it in case it was previously unconfirmed
673                     if self.verifier: self.verifier.add(tx_hash, tx_height)
674                     # set the height in case it changed
675                     tx = self.transactions.get(tx_hash)
676                     if tx:
677                         if tx.get('height') != tx_height:
678                             print_error( "changing height for tx", tx_hash )
679                             tx['height'] = tx_height
680
681
682     def get_tx_history(self):
683         with self.lock:
684             history = self.transactions.values()
685         history.sort(key = lambda x: x.get('timestamp') if x.get('timestamp') else 1e12)
686         result = []
687     
688         balance = 0
689         for tx in history:
690             is_mine, v, fee = self.get_tx_value(tx['tx_hash'])
691             if v is not None: balance += v
692         c, u = self.get_balance()
693
694         if balance != c+u:
695             v_str = format_satoshis( c+u - balance, True, self.num_zeros)
696             result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
697
698         balance = c + u - balance
699         for tx in history:
700             tx_hash = tx['tx_hash']
701             timestamp = tx.get('timestamp')
702             conf = self.verifier.get_confirmations(tx_hash) if self.verifier else None
703             is_mine, value, fee = self.get_tx_value(tx_hash)
704             if value is not None:
705                 balance += value
706
707             result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
708
709         return result
710
711     def get_transactions_at_height(self, height):
712         with self.lock:
713             values = self.transactions.values()[:]
714
715         out = []
716         for tx in values:
717             if tx['height'] == height:
718                 out.append(tx['tx_hash'])
719         return out
720
721
722     def get_label(self, tx_hash):
723         label = self.labels.get(tx_hash)
724         is_default = (label == '') or (label is None)
725         if is_default: label = self.get_default_label(tx_hash)
726         return label, is_default
727
728     def get_default_label(self, tx_hash):
729         tx = self.transactions.get(tx_hash)
730         default_label = ''
731         if tx:
732             is_mine, _, _ = self.get_tx_value(tx_hash)
733             if is_mine:
734                 for o in tx['outputs']:
735                     o_addr = o.get('address')
736                     if not self.is_mine(o_addr):
737                         try:
738                             default_label = self.labels[o_addr]
739                         except KeyError:
740                             default_label = o_addr
741             else:
742                 for o in tx['outputs']:
743                     o_addr = o.get('address')
744                     if self.is_mine(o_addr) and not self.is_change(o_addr):
745                         break
746                 else:
747                     for o in tx['outputs']:
748                         o_addr = o.get('address')
749                         if self.is_mine(o_addr):
750                             break
751                     else:
752                         o_addr = None
753
754                 if o_addr:
755                     dest_label = self.labels.get(o_addr)
756                     try:
757                         default_label = self.labels[o_addr]
758                     except KeyError:
759                         default_label = o_addr
760
761         return default_label
762
763
764     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
765         if not self.is_valid(to_address):
766             raise ValueError("Invalid address")
767         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
768         if not inputs:
769             raise ValueError("Not enough funds")
770
771         if not self.use_change and not change_addr:
772             change_addr = inputs[-1][0]
773             print_error( "Sending change to", change_addr )
774
775         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
776         s_inputs = self.sign_inputs( inputs, outputs, password )
777
778         tx = filter( raw_tx( s_inputs, outputs ) )
779         if to_address not in self.addressbook:
780             self.addressbook.append(to_address)
781         if label: 
782             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
783             self.labels[tx_hash] = label
784
785         return tx
786
787     def sendtx(self, tx):
788         # synchronous
789         h = self.send_tx(tx)
790         self.tx_event.wait()
791         return self.receive_tx(h)
792
793     def send_tx(self, tx):
794         # asynchronous
795         self.tx_event.clear()
796         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
797         self.interface.send([('blockchain.transaction.broadcast', [tx])], 'synchronizer')
798         return tx_hash
799
800     def receive_tx(self,tx_hash):
801         out = self.tx_result 
802         if out != tx_hash:
803             return False, "error: " + out
804         if self.receipt:
805             self.receipts[tx_hash] = self.receipt
806             self.receipt = None
807         return True, out
808
809
810     def read_alias(self, alias):
811         # this might not be the right place for this function.
812         import urllib
813
814         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
815         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
816         if m1:
817             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
818         elif m2:
819             url = 'https://' + alias + '/bitcoin.id'
820         else:
821             return ''
822         try:
823             lines = urllib.urlopen(url).readlines()
824         except:
825             return ''
826
827         # line 0
828         line = lines[0].strip().split(':')
829         if len(line) == 1:
830             auth_name = None
831             target = signing_addr = line[0]
832         else:
833             target, auth_name, signing_addr, signature = line
834             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
835             print msg, signature
836             self.verify_message(signing_addr, signature, msg)
837         
838         # other lines are signed updates
839         for line in lines[1:]:
840             line = line.strip()
841             if not line: continue
842             line = line.split(':')
843             previous = target
844             print repr(line)
845             target, signature = line
846             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
847
848         if not self.is_valid(target):
849             raise ValueError("Invalid bitcoin address")
850
851         return target, signing_addr, auth_name
852
853     def update_password(self, seed, old_password, new_password):
854         if new_password == '': new_password = None
855         self.use_encryption = (new_password != None)
856         self.seed = self.pw_encode( seed, new_password)
857         for k in self.imported_keys.keys():
858             a = self.imported_keys[k]
859             b = self.pw_decode(a, old_password)
860             c = self.pw_encode(b, new_password)
861             self.imported_keys[k] = c
862         self.save()
863
864     def get_alias(self, alias, interactive = False, show_message=None, question = None):
865         try:
866             target, signing_address, auth_name = self.read_alias(alias)
867         except BaseException, e:
868             # raise exception if verify fails (verify the chain)
869             if interactive:
870                 show_message("Alias error: " + str(e))
871             return
872
873         print target, signing_address, auth_name
874
875         if auth_name is None:
876             a = self.aliases.get(alias)
877             if not a:
878                 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)
879                 if interactive and question( msg ):
880                     self.aliases[alias] = (signing_address, target)
881                 else:
882                     target = None
883             else:
884                 if signing_address != a[0]:
885                     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
886                     if interactive and question( msg ):
887                         self.aliases[alias] = (signing_address, target)
888                     else:
889                         target = None
890         else:
891             if signing_address not in self.authorities.keys():
892                 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)
893                 if interactive and question( msg ):
894                     self.authorities[signing_address] = auth_name
895                 else:
896                     target = None
897
898         if target:
899             self.aliases[alias] = (signing_address, target)
900             
901         return target
902
903
904     def parse_url(self, url, show_message, question):
905         o = url[8:].split('?')
906         address = o[0]
907         if len(o)>1:
908             params = o[1].split('&')
909         else:
910             params = []
911
912         amount = label = message = signature = identity = ''
913         for p in params:
914             k,v = p.split('=')
915             uv = urldecode(v)
916             if k == 'amount': amount = uv
917             elif k == 'message': message = uv
918             elif k == 'label': label = uv
919             elif k == 'signature':
920                 identity, signature = uv.split(':')
921                 url = url.replace('&%s=%s'%(k,v),'')
922             else: 
923                 print k,v
924
925         if label and self.labels.get(address) != label:
926             if question('Give label "%s" to address %s ?'%(label,address)):
927                 if address not in self.addressbook and address not in self.all_addresses(): 
928                     self.addressbook.append(address)
929                 self.labels[address] = label
930
931         if signature:
932             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
933                 signing_address = self.get_alias(identity, True, show_message, question)
934             elif self.is_valid(identity):
935                 signing_address = identity
936             else:
937                 signing_address = None
938             if not signing_address:
939                 return
940             try:
941                 self.verify_message(signing_address, signature, url )
942                 self.receipt = (signing_address, signature, url)
943             except:
944                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
945                 address = amount = label = identity = message = ''
946
947         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
948             payto_address = self.get_alias(address, True, show_message, question)
949             if payto_address:
950                 address = address + ' <' + payto_address + '>'
951
952         return address, amount, label, message, signature, identity, url
953
954
955
956     def freeze(self,addr):
957         if addr in self.all_addresses() and addr not in self.frozen_addresses:
958             self.unprioritize(addr)
959             self.frozen_addresses.append(addr)
960             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
961             return True
962         else:
963             return False
964
965     def unfreeze(self,addr):
966         if addr in self.all_addresses() and addr in self.frozen_addresses:
967             self.frozen_addresses.remove(addr)
968             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
969             return True
970         else:
971             return False
972
973     def prioritize(self,addr):
974         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
975             self.unfreeze(addr)
976             self.prioritized_addresses.append(addr)
977             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
978             return True
979         else:
980             return False
981
982     def unprioritize(self,addr):
983         if addr in self.all_addresses() and addr in self.prioritized_addresses:
984             self.prioritized_addresses.remove(addr)
985             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
986             return True
987         else:
988             return False
989
990     def save(self):
991         s = {
992             'seed_version': self.seed_version,
993             'use_encryption': self.use_encryption,
994             'use_change': self.use_change,
995             'master_public_key': self.master_public_key,
996             'fee': self.fee,
997             'seed': self.seed,
998             'addresses': self.addresses,
999             'change_addresses': self.change_addresses,
1000             'addr_history': self.history, 
1001             'labels': self.labels,
1002             'contacts': self.addressbook,
1003             'imported_keys': self.imported_keys,
1004             'aliases': self.aliases,
1005             'authorities': self.authorities,
1006             'receipts': self.receipts,
1007             'num_zeros': self.num_zeros,
1008             'frozen_addresses': self.frozen_addresses,
1009             'prioritized_addresses': self.prioritized_addresses,
1010             'gap_limit': self.gap_limit,
1011             'transactions': self.transactions,
1012         }
1013         for k, v in s.items():
1014             self.config.set_key(k,v)
1015         self.config.save()
1016
1017     def set_verifier(self, verifier):
1018         self.verifier = verifier
1019
1020         # review stored transactions and send them to the verifier
1021         # (they are not necessarily in the history, because history items might have have been pruned)
1022         for tx_hash, tx in self.transactions.items():
1023             tx_height = tx.get('height')
1024             if tx_height <1:
1025                 print_error( "skipping", tx_hash, tx_height )
1026                 continue
1027             
1028             if tx_height>0:
1029                 self.verifier.add(tx_hash, tx_height)
1030
1031             # set the timestamp for transactions that need it
1032             if tx and not tx.get('timestamp'):
1033                 timestamp = self.verifier.get_timestamp(tx_height)
1034                 self.set_tx_timestamp(tx_hash, timestamp)
1035
1036         # review transactions that are in the history
1037         for addr, hist in self.history.items():
1038             if hist == ['*']: continue
1039             for tx_hash, tx_height in hist:
1040                 if tx_height>0:
1041                     # add it in case it was previously unconfirmed
1042                     self.verifier.add(tx_hash, tx_height)
1043                     # set the height in case it changed
1044                     tx = self.transactions.get(tx_hash)
1045                     if tx:
1046                         if tx.get('height') != tx_height:
1047                             print_error( "changing height for tx", tx_hash )
1048                             tx['height'] = tx_height
1049
1050
1051
1052     def set_tx_timestamp(self, tx_hash, timestamp):
1053         with self.lock:
1054             self.transactions[tx_hash]['timestamp'] = timestamp
1055
1056
1057
1058     def is_addr_in_tx(self, addr, tx):
1059         found = False
1060         for txin in tx.get('inputs'):
1061             if addr == txin.get('address'): 
1062                 found = True
1063                 break
1064         for txout in tx.get('outputs'):
1065             if addr == txout.get('address'): 
1066                 found = True
1067                 break
1068         return found
1069
1070
1071     def check_new_history(self, addr, hist):
1072         
1073         # check that all tx in hist are relevant
1074         if hist != ['*']:
1075             for tx_hash, height in hist:
1076                 tx = self.transactions.get(tx_hash)
1077                 if not tx: continue
1078                 if not self.is_addr_in_tx(addr,tx):
1079                     return False
1080
1081         # check that we are not "orphaning" a transaction
1082         old_hist = self.history.get(addr,[])
1083         if old_hist == ['*']: return True
1084
1085         for tx_hash, height in old_hist:
1086             if tx_hash in map(lambda x:x[0], hist): continue
1087             found = False
1088             for _addr, _hist in self.history.items():
1089                 if _addr == addr: continue
1090                 if _hist == ['*']: continue
1091                 _tx_hist = map(lambda x:x[0], _hist)
1092                 if tx_hash in _tx_hist:
1093                     found = True
1094                     break
1095
1096             if not found:
1097                 tx = self.transactions.get(tx_hash)
1098                 # already verified?
1099                 if tx.get('height'):
1100                     continue
1101                 # unconfirmed tx
1102                 print_error("new history is orphaning transaction:", tx_hash)
1103                 # check that all outputs are not mine, request histories
1104                 ext_requests = []
1105                 for o in tx.get('outputs'):
1106                     _addr = o.get('address')
1107                     # assert not self.is_mine(_addr)
1108                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1109
1110                 ext_h = self.interface.synchronous_get(ext_requests)
1111                 height = None
1112                 for h in ext_h:
1113                     if h == ['*']: continue
1114                     for item in h:
1115                         if item.get('tx_hash') == tx_hash:
1116                             height = item.get('height')
1117                             tx['height'] = height
1118                 if height:
1119                     print_error("found height for", tx_hash, height)
1120                     self.verifier.add(tx_hash, height)
1121                 else:
1122                     print_error("removing orphaned tx from history", tx_hash)
1123                     self.transactions.pop(tx_hash)
1124
1125         return True
1126
1127
1128
1129     def check_new_tx(self, tx_hash, tx):
1130         # 1 check that tx is referenced in addr_history. 
1131         addresses = []
1132         for addr, hist in self.history.items():
1133             if hist == ['*']:continue
1134             for txh, height in hist:
1135                 if txh == tx_hash: 
1136                     addresses.append(addr)
1137
1138         if not addresses:
1139             return False
1140
1141         # 2 check that referencing addresses are in the tx
1142         for addr in addresses:
1143             if not self.is_addr_in_tx(addr, tx):
1144                 return False
1145
1146         return True
1147
1148
1149
1150
1151 class WalletSynchronizer(threading.Thread):
1152
1153
1154     def __init__(self, wallet, config):
1155         threading.Thread.__init__(self)
1156         # self.daemon = True
1157         self.wallet = wallet
1158         self.interface = self.wallet.interface
1159         self.interface.register_channel('synchronizer')
1160         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1161         self.wallet.interface.register_callback('connected', lambda: self.interface.send([('server.banner',[])],'synchronizer') )
1162         self.was_updated = True
1163         self.running = False
1164         self.lock = threading.Lock()
1165
1166     def stop(self):
1167         with self.lock: self.running = False
1168         self.interface.poke('synchronizer')
1169
1170     def is_running(self):
1171         with self.lock: return self.running
1172
1173     def synchronize_wallet(self):
1174         new_addresses = self.wallet.synchronize()
1175         if new_addresses:
1176             self.subscribe_to_addresses(new_addresses)
1177             self.wallet.up_to_date = False
1178             return
1179             
1180         if not self.interface.is_up_to_date('synchronizer'):
1181             if self.wallet.is_up_to_date():
1182                 self.wallet.set_up_to_date(False)
1183                 self.was_updated = True
1184             return
1185
1186         self.wallet.set_up_to_date(True)
1187         self.was_updated = True
1188
1189     
1190     def subscribe_to_addresses(self, addresses):
1191         messages = []
1192         for addr in addresses:
1193             messages.append(('blockchain.address.subscribe', [addr]))
1194         self.interface.send( messages, 'synchronizer')
1195
1196
1197     def run(self):
1198         with self.lock: self.running = True
1199
1200         requested_tx = []
1201         missing_tx = []
1202         requested_histories = {}
1203
1204         # request any missing transactions
1205         for history in self.wallet.history.values():
1206             if history == ['*']: continue
1207             for tx_hash, tx_height in history:
1208                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1209                     missing_tx.append( (tx_hash, tx_height) )
1210         print_error("missing tx", missing_tx)
1211
1212         # wait until we are connected, in case the user is not connected
1213         while not self.interface.is_connected:
1214             time.sleep(1)
1215         
1216         # request banner, because 'connected' event happens before this thread is started
1217         self.interface.send([('server.banner',[])],'synchronizer')
1218
1219         # subscriptions
1220         self.subscribe_to_addresses(self.wallet.all_addresses())
1221
1222         while self.is_running():
1223             # 1. send new requests
1224             self.synchronize_wallet()
1225
1226             for tx_hash, tx_height in missing_tx:
1227                 if (tx_hash, tx_height) not in requested_tx:
1228                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1229                     requested_tx.append( (tx_hash, tx_height) )
1230             missing_tx = []
1231
1232             if self.was_updated:
1233                 self.interface.trigger_callback('updated')
1234                 self.was_updated = False
1235
1236             # 2. get a response
1237             r = self.interface.get_response('synchronizer')
1238
1239             # poke sends None. (needed during stop)
1240             if not r: continue
1241
1242             # 3. handle response
1243             method = r['method']
1244             params = r['params']
1245             result = r.get('result')
1246             error = r.get('error')
1247             if error:
1248                 print "error", r
1249                 continue
1250
1251             if method == 'blockchain.address.subscribe':
1252                 addr = params[0]
1253                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1254                     self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1255                     requested_histories[addr] = result
1256
1257             elif method == 'blockchain.address.get_history':
1258                 addr = params[0]
1259                 print_error("receiving history", addr, result)
1260                 if result == ['*']:
1261                     assert requested_histories.pop(addr) == '*'
1262                     self.wallet.receive_history_callback(addr, result)
1263                 else:
1264                     hist = []
1265                     # check that txids are unique
1266                     txids = []
1267                     for item in result:
1268                         tx_hash = item['tx_hash']
1269                         if tx_hash not in txids:
1270                             txids.append(tx_hash)
1271                             hist.append( (tx_hash, item['height']) )
1272
1273                     if len(hist) != len(result):
1274                         raise BaseException("error: server sent history with non-unique txid", result)
1275
1276                     # check that the status corresponds to what was announced
1277                     rs = requested_histories.pop(addr)
1278                     if self.wallet.get_status(hist) != rs:
1279                         raise BaseException("error: status mismatch: %s"%addr)
1280                 
1281                     # store received history
1282                     self.wallet.receive_history_callback(addr, hist)
1283
1284                     # request transactions that we don't have 
1285                     for tx_hash, tx_height in hist:
1286                         if self.wallet.transactions.get(tx_hash) is None:
1287                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1288                                 missing_tx.append( (tx_hash, tx_height) )
1289                         else:
1290                             if self.wallet.verifier:
1291                                 timestamp = self.wallet.verifier.get_timestamp(tx_height)
1292                                 self.wallet.set_tx_timestamp(tx_hash, timestamp)
1293
1294             elif method == 'blockchain.transaction.get':
1295                 tx_hash = params[0]
1296                 tx_height = params[1]
1297                 d = self.deserialize_tx(tx_hash, tx_height, result)
1298                 self.wallet.receive_tx_callback(tx_hash, d)
1299                 self.was_updated = True
1300                 requested_tx.remove( (tx_hash, tx_height) )
1301                 print_error("received tx:", d)
1302
1303             elif method == 'blockchain.transaction.broadcast':
1304                 self.wallet.tx_result = result
1305                 self.wallet.tx_event.set()
1306
1307             elif method == 'server.banner':
1308                 self.wallet.banner = result
1309                 self.was_updated = True
1310
1311             else:
1312                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1313
1314             if self.was_updated and not requested_tx:
1315                 self.interface.trigger_callback('updated')
1316                 self.was_updated = False
1317
1318
1319     def deserialize_tx(self, tx_hash, tx_height, raw_tx):
1320
1321         assert tx_hash == hash_encode(Hash(raw_tx.decode('hex')))
1322         import deserialize
1323         vds = deserialize.BCDataStream()
1324         vds.write(raw_tx.decode('hex'))
1325         d = deserialize.parse_Transaction(vds)
1326         d['height'] = tx_height
1327         d['tx_hash'] = tx_hash
1328         if self.wallet.verifier: d['timestamp'] = self.wallet.verifier.get_timestamp(tx_height)
1329         return d
1330