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