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