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