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