RAII open of wallet file.
[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 getpass
30 import aes
31 import ecdsa
32
33 from ecdsa.util import string_to_number, number_to_string
34 from util import print_error
35
36 ############ functions from pywallet ##################### 
37
38 addrtype = 0
39
40 def hash_160(public_key):
41     try:
42         md = hashlib.new('ripemd160')
43         md.update(hashlib.sha256(public_key).digest())
44         return md.digest()
45     except:
46         import ripemd
47         md = ripemd.new(hashlib.sha256(public_key).digest())
48         return md.digest()
49
50
51 def public_key_to_bc_address(public_key):
52     h160 = hash_160(public_key)
53     return hash_160_to_bc_address(h160)
54
55 def hash_160_to_bc_address(h160):
56     vh160 = chr(addrtype) + h160
57     h = Hash(vh160)
58     addr = vh160 + h[0:4]
59     return b58encode(addr)
60
61 def bc_address_to_hash_160(addr):
62     bytes = b58decode(addr, 25)
63     return bytes[1:21]
64
65 def encode_point(pubkey, compressed=False):
66     order = generator_secp256k1.order()
67     p = pubkey.pubkey.point
68     x_str = ecdsa.util.number_to_string(p.x(), order)
69     y_str = ecdsa.util.number_to_string(p.y(), order)
70     if compressed:
71         return chr(2 + (p.y() & 1)) + x_str
72     else:
73         return chr(4) + pubkey.to_string() #x_str + y_str
74
75 __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
76 __b58base = len(__b58chars)
77
78 def b58encode(v):
79     """ encode v, which is a string of bytes, to base58."""
80
81     long_value = 0L
82     for (i, c) in enumerate(v[::-1]):
83         long_value += (256**i) * ord(c)
84
85     result = ''
86     while long_value >= __b58base:
87         div, mod = divmod(long_value, __b58base)
88         result = __b58chars[mod] + result
89         long_value = div
90     result = __b58chars[long_value] + result
91
92     # Bitcoin does a little leading-zero-compression:
93     # leading 0-bytes in the input become leading-1s
94     nPad = 0
95     for c in v:
96         if c == '\0': nPad += 1
97         else: break
98
99     return (__b58chars[0]*nPad) + result
100
101 def b58decode(v, length):
102     """ decode v into a string of len bytes."""
103     long_value = 0L
104     for (i, c) in enumerate(v[::-1]):
105         long_value += __b58chars.find(c) * (__b58base**i)
106
107     result = ''
108     while long_value >= 256:
109         div, mod = divmod(long_value, 256)
110         result = chr(mod) + result
111         long_value = div
112     result = chr(long_value) + result
113
114     nPad = 0
115     for c in v:
116         if c == __b58chars[0]: nPad += 1
117         else: break
118
119     result = chr(0)*nPad + result
120     if length is not None and len(result) != length:
121         return None
122
123     return result
124
125
126 def Hash(data):
127     return hashlib.sha256(hashlib.sha256(data).digest()).digest()
128
129 def EncodeBase58Check(vchIn):
130     hash = Hash(vchIn)
131     return b58encode(vchIn + hash[0:4])
132
133 def DecodeBase58Check(psz):
134     vchRet = b58decode(psz, None)
135     key = vchRet[0:-4]
136     csum = vchRet[-4:]
137     hash = Hash(key)
138     cs32 = hash[0:4]
139     if cs32 != csum:
140         return None
141     else:
142         return key
143
144 def PrivKeyToSecret(privkey):
145     return privkey[9:9+32]
146
147 def SecretToASecret(secret):
148     vchIn = chr(addrtype+128) + secret
149     return EncodeBase58Check(vchIn)
150
151 def ASecretToSecret(key):
152     vch = DecodeBase58Check(key)
153     if vch and vch[0] == chr(addrtype+128):
154         return vch[1:]
155     else:
156         return False
157
158 ########### end pywallet functions #######################
159
160 # get password routine
161 def prompt_password(prompt, confirm=True):
162     if sys.stdin.isatty():
163         password = getpass.getpass(prompt)
164
165         if password and confirm:
166             password2 = getpass.getpass("Confirm: ")
167
168             if password != password2:
169                 sys.exit("Error: Passwords do not match.")
170
171     else:
172         password = raw_input(prompt)
173
174     if not password:
175         password = None
176
177     return password
178
179 # URL decode
180 _ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
181 urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
182
183
184 def int_to_hex(i, length=1):
185     s = hex(i)[2:].rstrip('L')
186     s = "0"*(2*length - len(s)) + s
187     return s.decode('hex')[::-1].encode('hex')
188
189
190 # AES
191 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
192 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
193
194
195
196 # secp256k1, http://www.oid-info.com/get/1.3.132.0.10
197 _p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
198 _r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
199 _b = 0x0000000000000000000000000000000000000000000000000000000000000007L
200 _a = 0x0000000000000000000000000000000000000000000000000000000000000000L
201 _Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
202 _Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
203 curve_secp256k1 = ecdsa.ellipticcurve.CurveFp( _p, _a, _b )
204 generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
205 oid_secp256k1 = (1,3,132,0,10)
206 SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) 
207
208
209 def filter(s): 
210     out = re.sub('( [^\n]*|)\n','',s)
211     out = out.replace(' ','')
212     out = out.replace('\n','')
213     return out
214
215 def raw_tx( inputs, outputs, for_sig = None ):
216     s  = int_to_hex(1,4)                                     +   '     version\n' 
217     s += int_to_hex( len(inputs) )                           +   '     number of inputs\n'
218     for i in range(len(inputs)):
219         _, _, p_hash, p_index, p_script, pubkey, sig = inputs[i]
220         s += p_hash.decode('hex')[::-1].encode('hex')        +  '     prev hash\n'
221         s += int_to_hex(p_index,4)                           +  '     prev index\n'
222         if for_sig is None:
223             sig = sig + chr(1)                               # hashtype
224             script  = int_to_hex( len(sig))                  +  '     push %d bytes\n'%len(sig)
225             script += sig.encode('hex')                      +  '     sig\n'
226             pubkey = chr(4) + pubkey
227             script += int_to_hex( len(pubkey))               +  '     push %d bytes\n'%len(pubkey)
228             script += pubkey.encode('hex')                   +  '     pubkey\n'
229         elif for_sig==i:
230             script = p_script                                +  '     scriptsig \n'
231         else:
232             script=''
233         s += int_to_hex( len(filter(script))/2 )             +  '     script length \n'
234         s += script
235         s += "ffffffff"                                      +  '     sequence\n'
236     s += int_to_hex( len(outputs) )                          +  '     number of outputs\n'
237     for output in outputs:
238         addr, amount = output
239         s += int_to_hex( amount, 8)                          +  '     amount: %d\n'%amount 
240         script = '76a9'                                      # op_dup, op_hash_160
241         script += '14'                                       # push 0x14 bytes
242         script += bc_address_to_hash_160(addr).encode('hex')
243         script += '88ac'                                     # op_equalverify, op_checksig
244         s += int_to_hex( len(filter(script))/2 )             +  '     script length \n'
245         s += script                                          +  '     script \n'
246     s += int_to_hex(0,4)                                     # lock time
247     if for_sig is not None: s += int_to_hex(1, 4)            # hash type
248     return s
249
250
251
252
253 def format_satoshis(x, is_diff=False, num_zeros = 0):
254     from decimal import Decimal
255     s = Decimal(x)
256     sign, digits, exp = s.as_tuple()
257     digits = map(str, digits)
258     while len(digits) < 9:
259         digits.insert(0,'0')
260     digits.insert(-8,'.')
261     s = ''.join(digits).rstrip('0')
262     if sign: 
263         s = '-' + s
264     elif is_diff:
265         s = "+" + s
266
267     p = s.find('.')
268     s += "0"*( 1 + num_zeros - ( len(s) - p ))
269     s += " "*( 9 - ( len(s) - p ))
270     s = " "*( 5 - ( p )) + s
271     return s
272
273
274 from version import ELECTRUM_VERSION, SEED_VERSION
275 from interface import DEFAULT_SERVERS
276
277
278
279
280 class Wallet:
281     def __init__(self):
282
283         self.electrum_version = ELECTRUM_VERSION
284         self.seed_version = SEED_VERSION
285         self.update_callbacks = []
286
287         self.gap_limit = 5           # configuration
288         self.use_change = True
289         self.fee = 100000
290         self.num_zeros = 0
291         self.master_public_key = ''
292         self.conversion_currency = None
293         self.theme = None
294
295         # saved fields
296         self.use_encryption = False
297         self.addresses = []          # receiving addresses visible for user
298         self.change_addresses = []   # addresses used as change
299         self.seed = ''               # encrypted
300         self.history = {}
301         self.labels = {}             # labels for addresses and transactions
302         self.aliases = {}            # aliases for addresses
303         self.authorities = {}        # trusted addresses
304         self.frozen_addresses = []
305         self.prioritized_addresses = []
306         self.expert_mode = False
307         
308         self.receipts = {}           # signed URIs
309         self.receipt = None          # next receipt
310         self.addressbook = []        # outgoing addresses, for payments
311         self.debug_server = False    # write server communication debug info to stdout
312
313         # not saved
314         self.tx_history = {}
315
316         self.imported_keys = {}
317         self.remote_url = None
318
319         self.was_updated = True
320         self.blocks = -1
321         self.banner = ''
322
323         # there is a difference between self.up_to_date and self.is_up_to_date()
324         # self.is_up_to_date() returns true when all requests have been answered and processed
325         # self.up_to_date is true when the wallet is synchronized (stronger requirement)
326         self.up_to_date_event = threading.Event()
327         self.up_to_date_event.clear()
328         self.up_to_date = False
329         self.lock = threading.Lock()
330         self.tx_event = threading.Event()
331
332         self.pick_random_server()
333
334     def register_callback(self, update_callback):
335         with self.lock:
336             self.update_callbacks.append(update_callback)
337
338     def trigger_callbacks(self):
339         with self.lock:
340             callbacks = self.update_callbacks[:]
341         [update() for update in callbacks]
342
343     def pick_random_server(self):
344         self.server = random.choice( DEFAULT_SERVERS )         # random choice when the wallet is created
345
346     def is_up_to_date(self):
347         return self.interface.responses.empty() and not self.interface.unanswered_requests
348
349     def set_server(self, server):
350         # raise an error if the format isnt correct
351         a,b,c = server.split(':')
352         b = int(b)
353         assert c in ['t','h','n']
354         # set the server
355         if server != self.server:
356             self.server = server
357             self.save()
358             self.interface.is_connected = False  # this exits the polling loop
359             self.interface.poke()
360
361     def set_path(self, wallet_path):
362         """Set the path of the wallet."""
363         if wallet_path is not None:
364             self.path = wallet_path
365             return
366         # Look for wallet file in the default data directory.
367         # Keeps backwards compatibility.
368         if "HOME" in os.environ:
369             wallet_dir = os.path.join(os.environ["HOME"], ".electrum")
370         elif "LOCALAPPDATA" in os.environ:
371             wallet_dir = os.path.join(os.environ["LOCALAPPDATA"], "Electrum")
372         elif "APPDATA" in os.environ:
373             wallet_dir = os.path.join(os.environ["APPDATA"], "Electrum")
374         else:
375             raise BaseException("No home directory found in environment variables.")
376         # Make wallet directory if it does not yet exist.
377         if not os.path.exists(wallet_dir):
378             os.mkdir(wallet_dir)
379         self.path = os.path.join(wallet_dir, "electrum.dat")
380
381     def import_key(self, keypair, password):
382         address, key = keypair.split(':')
383         if not self.is_valid(address):
384             raise BaseException('Invalid Bitcoin address')
385         if address in self.all_addresses():
386             raise BaseException('Address already in wallet')
387         b = ASecretToSecret( key )
388         if not b: 
389             raise BaseException('Unsupported key format')
390         secexp = int( b.encode('hex'), 16)
391         private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve=SECP256k1 )
392         # sanity check
393         public_key = private_key.get_verifying_key()
394         if not address == public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() ):
395             raise BaseException('Address does not match private key')
396         self.imported_keys[address] = self.pw_encode( key, password )
397
398     def new_seed(self, password):
399         seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
400         #self.init_mpk(seed)
401         # encrypt
402         self.seed = self.pw_encode( seed, password )
403
404
405     def init_mpk(self,seed):
406         # public key
407         curve = SECP256k1
408         secexp = self.stretch_key(seed)
409         master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
410         self.master_public_key = master_private_key.get_verifying_key().to_string()
411
412     def all_addresses(self):
413         return self.addresses + self.change_addresses + self.imported_keys.keys()
414
415     def is_mine(self, address):
416         return address in self.all_addresses()
417
418     def is_change(self, address):
419         return address in self.change_addresses
420
421     def is_valid(self,addr):
422         ADDRESS_RE = re.compile('[1-9A-HJ-NP-Za-km-z]{26,}\\Z')
423         if not ADDRESS_RE.match(addr): return False
424         try:
425             h = bc_address_to_hash_160(addr)
426         except:
427             return False
428         return addr == hash_160_to_bc_address(h)
429
430     def stretch_key(self,seed):
431         oldseed = seed
432         for i in range(100000):
433             seed = hashlib.sha256(seed + oldseed).digest()
434         return string_to_number( seed )
435
436     def get_sequence(self,n,for_change):
437         return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) )
438
439     def get_private_key_base58(self, address, password):
440         pk = self.get_private_key(address, password)
441         if pk is None: return None
442         return SecretToASecret( pk )
443
444     def get_private_key(self, address, password):
445         """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
446         order = generator_secp256k1.order()
447         
448         if address in self.imported_keys.keys():
449             b = self.pw_decode( self.imported_keys[address], password )
450             if not b: return None
451             b = ASecretToSecret( b )
452             secexp = int( b.encode('hex'), 16)
453         else:
454             if address in self.addresses:
455                 n = self.addresses.index(address)
456                 for_change = False
457             elif address in self.change_addresses:
458                 n = self.change_addresses.index(address)
459                 for_change = True
460             else:
461                 raise BaseException("unknown address")
462             try:
463                 seed = self.pw_decode( self.seed, password)
464             except:
465                 raise BaseException("Invalid password")
466             if not seed: return None
467             secexp = self.stretch_key(seed)
468             secexp = ( secexp + self.get_sequence(n,for_change) ) % order
469
470         pk = number_to_string(secexp,order)
471         return pk
472
473     def msg_magic(self, message):
474         return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
475
476     def sign_message(self, address, message, password):
477         private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 )
478         public_key = private_key.get_verifying_key()
479         signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
480         assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
481         for i in range(4):
482             sig = base64.b64encode( chr(27+i) + signature )
483             try:
484                 self.verify_message( address, sig, message)
485                 return sig
486             except:
487                 continue
488         else:
489             raise BaseException("error: cannot sign message")
490
491
492     def verify_message(self, address, signature, message):
493         """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
494         from ecdsa import numbertheory, ellipticcurve, util
495         import msqr
496         curve = curve_secp256k1
497         G = generator_secp256k1
498         order = G.order()
499         # extract r,s from signature
500         sig = base64.b64decode(signature)
501         if len(sig) != 65: raise BaseException("Wrong encoding")
502         r,s = util.sigdecode_string(sig[1:], order)
503         nV = ord(sig[0])
504         if nV < 27 or nV >= 35:
505             raise BaseException("Bad encoding")
506         if nV >= 31:
507             compressed = True
508             nV -= 4
509         else:
510             compressed = False
511
512         recid = nV - 27
513         # 1.1
514         x = r + (recid/2) * order
515         # 1.3
516         alpha = ( x * x * x  + curve.a() * x + curve.b() ) % curve.p()
517         beta = msqr.modular_sqrt(alpha, curve.p())
518         y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
519         # 1.4 the constructor checks that nR is at infinity
520         R = ellipticcurve.Point(curve, x, y, order)
521         # 1.5 compute e from message:
522         h = Hash( self.msg_magic( message ) )
523         e = string_to_number(h)
524         minus_e = -e % order
525         # 1.6 compute Q = r^-1 (sR - eG)
526         inv_r = numbertheory.inverse_mod(r,order)
527         Q = inv_r * ( s * R + minus_e * G )
528         public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
529         # check that Q is the public key
530         public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
531         # check that we get the original signing address
532         addr = public_key_to_bc_address( encode_point(public_key, compressed) )
533         if address != addr:
534             raise BaseException("Bad signature")
535     
536
537     def create_new_address(self, for_change):
538         """   Publickey(type,n) = Master_public_key + H(n|S|type)*point  """
539         curve = SECP256k1
540         n = len(self.change_addresses) if for_change else len(self.addresses)
541         z = self.get_sequence(n,for_change)
542         master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key, curve = SECP256k1 )
543         pubkey_point = master_public_key.pubkey.point + z*curve.generator
544         public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
545         address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() )
546         if for_change:
547             self.change_addresses.append(address)
548         else:
549             self.addresses.append(address)
550
551         self.history[address] = []
552         print address
553         return address
554
555
556     def change_gap_limit(self, value):
557         if value >= self.gap_limit:
558             self.gap_limit = value
559             self.save()
560             self.interface.poke()
561             return True
562
563         elif value >= self.min_acceptable_gap():
564             k = self.num_unused_trailing_addresses()
565             n = len(self.addresses) - k + value
566             self.addresses = self.addresses[0:n]
567             self.gap_limit = value
568             self.save()
569             return True
570         else:
571             return False
572
573     def num_unused_trailing_addresses(self):
574         k = 0
575         for a in self.addresses[::-1]:
576             if self.history.get(a):break
577             k = k + 1
578         return k
579
580     def min_acceptable_gap(self):
581         # fixme: this assumes wallet is synchronized
582         n = 0
583         nmax = 0
584         k = self.num_unused_trailing_addresses()
585         for a in self.addresses[0:-k]:
586             if self.history.get(a):
587                 n = 0
588             else:
589                 n += 1
590                 if n > nmax: nmax = n
591         return nmax + 1
592
593
594     def synchronize(self):
595         if not self.master_public_key:
596             return []
597
598         new_addresses = []
599         while True:
600             if self.change_addresses == []:
601                 new_addresses.append( self.create_new_address(True) )
602                 continue
603             a = self.change_addresses[-1]
604             if self.history.get(a):
605                 new_addresses.append( self.create_new_address(True) )
606             else:
607                 break
608
609         n = self.gap_limit
610         while True:
611             if len(self.addresses) < n:
612                 new_addresses.append( self.create_new_address(False) )
613                 continue
614             if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
615                 break
616             else:
617                 new_addresses.append( self.create_new_address(False) )
618
619         if self.remote_url:
620             num = self.get_remote_number()
621             while len(self.addresses)<num:
622                 new_addresses.append( self.create_new_address(False) )
623
624         return new_addresses
625
626
627     def get_remote_number(self):
628         import jsonrpclib
629         server = jsonrpclib.Server(self.remote_url)
630         out = server.getnum()
631         return out
632
633     def get_remote_mpk(self):
634         import jsonrpclib
635         server = jsonrpclib.Server(self.remote_url)
636         out = server.getkey()
637         return out
638
639     def is_found(self):
640         return (len(self.change_addresses) > 1 ) or ( len(self.addresses) > self.gap_limit )
641
642     def fill_addressbook(self):
643         for tx in self.tx_history.values():
644             if tx['value']<0:
645                 for i in tx['outputs']:
646                     if not self.is_mine(i) and i not in self.addressbook:
647                         self.addressbook.append(i)
648         # redo labels
649         self.update_tx_labels()
650
651
652     def save(self):
653         # TODO: Need special config storage class. Should not be mixed
654         # up with the wallet.
655         # Settings should maybe be stored in a flat ini file.
656         s = {
657             'seed_version': self.seed_version,
658             'use_encryption': self.use_encryption,
659             'use_change': self.use_change,
660             'master_public_key': self.master_public_key.encode('hex'),
661             'fee': self.fee,
662             'server': self.server,
663             'seed': self.seed,
664             'addresses': self.addresses,
665             'change_addresses': self.change_addresses,
666             'history': self.history, 
667             'labels': self.labels,
668             'contacts': self.addressbook,
669             'imported_keys': self.imported_keys,
670             'aliases': self.aliases,
671             'authorities': self.authorities,
672             'receipts': self.receipts,
673             'num_zeros': self.num_zeros,
674             'frozen_addresses': self.frozen_addresses,
675             'prioritized_addresses': self.prioritized_addresses,
676             'expert_mode': self.expert_mode,
677             'gap_limit': self.gap_limit,
678             'debug_server': self.debug_server,
679             'conversion_currency': self.conversion_currency,
680             'theme': self.theme
681         }
682         f = open(self.path,"w")
683         f.write( repr(s) )
684         f.close()
685         import stat
686         os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
687
688     def read(self):
689         """Read the contents of the wallet file."""
690         import interface
691
692         self.file_exists = False
693         try:
694             with open(self.path, "r") as f:
695                 data = f.read()
696         except IOError:
697             return
698         try:
699             d = ast.literal_eval( data )  #parse raw data from reading wallet file
700             interface.old_to_new(d)
701             
702             self.seed_version = d.get('seed_version')
703             self.master_public_key = d.get('master_public_key').decode('hex')
704             self.use_encryption = d.get('use_encryption')
705             self.use_change = bool(d.get('use_change', True))
706             self.fee = int(d.get('fee'))
707             self.seed = d.get('seed')
708             self.server = d.get('server')
709             self.addresses = d.get('addresses')
710             self.change_addresses = d.get('change_addresses')
711             self.history = d.get('history')
712             self.labels = d.get('labels')
713             self.addressbook = d.get('contacts')
714             self.imported_keys = d.get('imported_keys', {})
715             self.aliases = d.get('aliases', {})
716             self.authorities = d.get('authorities', {})
717             self.receipts = d.get('receipts', {})
718             self.num_zeros = d.get('num_zeros', 0)
719             self.frozen_addresses = d.get('frozen_addresses', [])
720             self.prioritized_addresses = d.get('prioritized_addresses', [])
721             self.expert_mode = d.get('expert_mode', False)
722             self.gap_limit = d.get('gap_limit', 5)
723             self.debug_server = d.get('debug_server', False)
724             self.conversion_currency = d.get('conversion_currency', 'USD')
725             self.theme = d.get('theme', 'Cleanlook')
726         except:
727             raise IOError("Cannot read wallet file.")
728
729         self.update_tx_history()
730
731         if self.seed_version != SEED_VERSION:
732             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
733
734         if self.remote_url: assert self.master_public_key.encode('hex') == self.get_remote_mpk()
735
736         self.file_exists = True
737
738
739     def get_address_flags(self, addr):
740         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
741         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
742         return flags
743         
744
745     def get_addr_balance(self, addr):
746         assert self.is_mine(addr)
747         h = self.history.get(addr,[])
748         c = u = 0
749         for item in h:
750             v = item['value']
751             if item['height']:
752                 c += v
753             else:
754                 u += v
755         return c, u
756
757     def get_balance(self):
758         conf = unconf = 0
759         for addr in self.all_addresses(): 
760             c, u = self.get_addr_balance(addr)
761             conf += c
762             unconf += u
763         return conf, unconf
764
765
766     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
767         """ todo: minimize tx size """
768         total = 0
769         fee = self.fee if fixed_fee is None else fixed_fee
770
771         coins = []
772         prioritized_coins = []
773         domain = [from_addr] if from_addr else self.all_addresses()
774         for i in self.frozen_addresses:
775             if i in domain: domain.remove(i)
776
777         for i in self.prioritized_addresses:
778             if i in domain: domain.remove(i)
779
780         for addr in domain:
781             h = self.history.get(addr)
782             if h is None: continue
783             for item in h:
784                 if item.get('raw_output_script'):
785                     coins.append( (addr,item))
786
787         coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
788
789         for addr in self.prioritized_addresses:
790             h = self.history.get(addr)
791             if h is None: continue
792             for item in h:
793                 if item.get('raw_output_script'):
794                     prioritized_coins.append( (addr,item))
795
796         prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
797
798         inputs = []
799         coins = prioritized_coins + coins
800
801         for c in coins: 
802             addr, item = c
803             v = item.get('value')
804             total += v
805             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
806             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
807             if total >= amount + fee: break
808         else:
809             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
810             inputs = []
811         return inputs, total, fee
812
813     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
814         outputs = [ (to_addr, amount) ]
815         change_amount = total - ( amount + fee )
816         if change_amount != 0:
817             # normally, the update thread should ensure that the last change address is unused
818             if not change_addr:
819                 change_addr = self.change_addresses[-1]
820             outputs.append( ( change_addr,  change_amount) )
821         return outputs
822
823     def sign_inputs( self, inputs, outputs, password ):
824         s_inputs = []
825         for i in range(len(inputs)):
826             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
827             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
828             public_key = private_key.get_verifying_key()
829             pubkey = public_key.to_string()
830             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
831             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
832             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
833             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
834         return s_inputs
835
836     def pw_encode(self, s, password):
837         if password:
838             secret = Hash(password)
839             return EncodeAES(secret, s)
840         else:
841             return s
842
843     def pw_decode(self, s, password):
844         if password is not None:
845             secret = Hash(password)
846             d = DecodeAES(secret, s)
847             if s == self.seed:
848                 try:
849                     d.decode('hex')
850                 except:
851                     raise ValueError("Invalid password")
852             return d
853         else:
854             return s
855
856     def get_status(self, address):
857         h = self.history.get(address)
858         if not h:
859             status = None
860         else:
861             lastpoint = h[-1]
862             status = lastpoint['block_hash']
863             if status == 'mempool': 
864                 status = status + ':%d'% len(h)
865         return status
866
867     def receive_status_callback(self, addr, status):
868         with self.lock:
869             if self.get_status(addr) != status:
870                 #print "updating status for", addr, status
871                 self.interface.get_history(addr)
872
873     def receive_history_callback(self, addr, data): 
874         #print "updating history for", addr
875         with self.lock:
876             self.history[addr] = data
877             self.update_tx_history()
878             self.save()
879
880     def get_tx_history(self):
881         lines = self.tx_history.values()
882         lines = sorted(lines, key=operator.itemgetter("timestamp"))
883         return lines
884
885     def update_tx_history(self):
886         self.tx_history= {}
887         for addr in self.all_addresses():
888             h = self.history.get(addr)
889             if h is None: continue
890             for tx in h:
891                 tx_hash = tx['tx_hash']
892                 line = self.tx_history.get(tx_hash)
893                 if not line:
894                     self.tx_history[tx_hash] = copy.copy(tx)
895                     line = self.tx_history.get(tx_hash)
896                 else:
897                     line['value'] += tx['value']
898                 if line['height'] == 0:
899                     line['timestamp'] = 1e12
900         self.update_tx_labels()
901
902     def update_tx_labels(self):
903         for tx in self.tx_history.values():
904             default_label = ''
905             if tx['value']<0:
906                 for o_addr in tx['outputs']:
907                     if not self.is_mine(o_addr):
908                         try:
909                             default_label = self.labels[o_addr]
910                         except KeyError:
911                             default_label = o_addr
912             else:
913                 for o_addr in tx['outputs']:
914                     if self.is_mine(o_addr) and not self.is_change(o_addr):
915                         break
916                 else:
917                     for o_addr in tx['outputs']:
918                         if self.is_mine(o_addr):
919                             break
920                     else:
921                         o_addr = None
922
923                 if o_addr:
924                     dest_label = self.labels.get(o_addr)
925                     try:
926                         default_label = self.labels[o_addr]
927                     except KeyError:
928                         default_label = o_addr
929
930             tx['default_label'] = default_label
931
932     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
933         if not self.is_valid(to_address):
934             raise ValueError("Invalid address")
935         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
936         if not inputs:
937             raise ValueError("Not enough funds")
938
939         if not self.use_change and not change_addr:
940             change_addr = inputs[0][0]
941             print "Sending change to", change_addr
942
943         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
944         s_inputs = self.sign_inputs( inputs, outputs, password )
945
946         tx = filter( raw_tx( s_inputs, outputs ) )
947         if to_address not in self.addressbook:
948             self.addressbook.append(to_address)
949         if label: 
950             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
951             self.labels[tx_hash] = label
952
953         return tx
954
955     def sendtx(self, tx):
956         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
957         self.tx_event.clear()
958         self.interface.send([('blockchain.transaction.broadcast', [tx])])
959         self.tx_event.wait()
960         out = self.tx_result 
961         if out != tx_hash:
962             return False, "error: " + out
963         if self.receipt:
964             self.receipts[tx_hash] = self.receipt
965             self.receipt = None
966         return True, out
967
968
969     def read_alias(self, alias):
970         # this might not be the right place for this function.
971         import urllib
972
973         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
974         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
975         if m1:
976             url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
977         elif m2:
978             url = 'http://' + alias + '/bitcoin.id'
979         else:
980             return ''
981         try:
982             lines = urllib.urlopen(url).readlines()
983         except:
984             return ''
985
986         # line 0
987         line = lines[0].strip().split(':')
988         if len(line) == 1:
989             auth_name = None
990             target = signing_addr = line[0]
991         else:
992             target, auth_name, signing_addr, signature = line
993             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
994             print msg, signature
995             self.verify_message(signing_addr, signature, msg)
996         
997         # other lines are signed updates
998         for line in lines[1:]:
999             line = line.strip()
1000             if not line: continue
1001             line = line.split(':')
1002             previous = target
1003             print repr(line)
1004             target, signature = line
1005             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
1006
1007         if not self.is_valid(target):
1008             raise ValueError("Invalid bitcoin address")
1009
1010         return target, signing_addr, auth_name
1011
1012     def update_password(self, seed, old_password, new_password):
1013         if new_password == '': new_password = None
1014         self.use_encryption = (new_password != None)
1015         self.seed = self.pw_encode( seed, new_password)
1016         for k in self.imported_keys.keys():
1017             a = self.imported_keys[k]
1018             b = self.pw_decode(a, old_password)
1019             c = self.pw_encode(b, new_password)
1020             self.imported_keys[k] = c
1021         self.save()
1022
1023     def get_alias(self, alias, interactive = False, show_message=None, question = None):
1024         try:
1025             target, signing_address, auth_name = self.read_alias(alias)
1026         except BaseException, e:
1027             # raise exception if verify fails (verify the chain)
1028             if interactive:
1029                 show_message("Alias error: " + str(e))
1030             return
1031
1032         print target, signing_address, auth_name
1033
1034         if auth_name is None:
1035             a = self.aliases.get(alias)
1036             if not a:
1037                 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)
1038                 if interactive and question( msg ):
1039                     self.aliases[alias] = (signing_address, target)
1040                 else:
1041                     target = None
1042             else:
1043                 if signing_address != a[0]:
1044                     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
1045                     if interactive and question( msg ):
1046                         self.aliases[alias] = (signing_address, target)
1047                     else:
1048                         target = None
1049         else:
1050             if signing_address not in self.authorities.keys():
1051                 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)
1052                 if interactive and question( msg ):
1053                     self.authorities[signing_address] = auth_name
1054                 else:
1055                     target = None
1056
1057         if target:
1058             self.aliases[alias] = (signing_address, target)
1059             
1060         return target
1061
1062
1063     def parse_url(self, url, show_message, question):
1064         o = url[8:].split('?')
1065         address = o[0]
1066         if len(o)>1:
1067             params = o[1].split('&')
1068         else:
1069             params = []
1070
1071         amount = label = message = signature = identity = ''
1072         for p in params:
1073             k,v = p.split('=')
1074             uv = urldecode(v)
1075             if k == 'amount': amount = uv
1076             elif k == 'message': message = uv
1077             elif k == 'label': label = uv
1078             elif k == 'signature':
1079                 identity, signature = uv.split(':')
1080                 url = url.replace('&%s=%s'%(k,v),'')
1081             else: 
1082                 print k,v
1083
1084         if label and self.labels.get(address) != label:
1085             if question('Give label "%s" to address %s ?'%(label,address)):
1086                 if address not in self.addressbook and address not in self.all_addresses(): 
1087                     self.addressbook.append(address)
1088                 self.labels[address] = label
1089
1090         if signature:
1091             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
1092                 signing_address = self.get_alias(identity, True, show_message, question)
1093             elif self.is_valid(identity):
1094                 signing_address = identity
1095             else:
1096                 signing_address = None
1097             if not signing_address:
1098                 return
1099             try:
1100                 self.verify_message(signing_address, signature, url )
1101                 self.receipt = (signing_address, signature, url)
1102             except:
1103                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
1104                 address = amount = label = identity = message = ''
1105
1106         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
1107             payto_address = self.get_alias(address, True, show_message, question)
1108             if payto_address:
1109                 address = address + ' <' + payto_address + '>'
1110
1111         return address, amount, label, message, signature, identity, url
1112
1113
1114     def update(self):
1115         self.interface.poke()
1116         self.up_to_date_event.wait(10000000000)
1117
1118
1119     def start_session(self, interface):
1120         self.interface = interface
1121         self.interface.send([('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])])
1122         self.interface.subscribe(self.all_addresses())
1123
1124
1125     def freeze(self,addr):
1126         if addr in self.all_addresses() and addr not in self.frozen_addresses:
1127             self.unprioritize(addr)
1128             self.frozen_addresses.append(addr)
1129             self.save()
1130             return True
1131         else:
1132             return False
1133
1134     def unfreeze(self,addr):
1135         if addr in self.all_addresses() and addr in self.frozen_addresses:
1136             self.frozen_addresses.remove(addr)
1137             self.save()
1138             return True
1139         else:
1140             return False
1141
1142     def prioritize(self,addr):
1143         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
1144             self.unfreeze(addr)
1145             self.prioritized_addresses.append(addr)
1146             self.save()
1147             return True
1148         else:
1149             return False
1150
1151     def unprioritize(self,addr):
1152         if addr in self.all_addresses() and addr in self.prioritized_addresses:
1153             self.prioritized_addresses.remove(addr)
1154             self.save()
1155             return True
1156         else:
1157             return False