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