Better looking set_path(wallet_path) method. Flattened function that's easier to...
[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             f = open(self.path,"r")
695             data = f.read()
696             f.close()
697             
698             d = ast.literal_eval( data )  #parse raw data from reading wallet file
699             interface.old_to_new(d)
700             
701             self.seed_version = d.get('seed_version')
702             self.master_public_key = d.get('master_public_key').decode('hex')
703             self.use_encryption = d.get('use_encryption')
704             self.use_change = bool(d.get('use_change', True))
705             self.fee = int(d.get('fee'))
706             self.seed = d.get('seed')
707             self.server = d.get('server')
708             self.addresses = d.get('addresses')
709             self.change_addresses = d.get('change_addresses')
710             self.history = d.get('history')
711             self.labels = d.get('labels')
712             self.addressbook = d.get('contacts')
713             self.imported_keys = d.get('imported_keys', {})
714             self.aliases = d.get('aliases', {})
715             self.authorities = d.get('authorities', {})
716             self.receipts = d.get('receipts', {})
717             self.num_zeros = d.get('num_zeros', 0)
718             self.frozen_addresses = d.get('frozen_addresses', [])
719             self.prioritized_addresses = d.get('prioritized_addresses', [])
720             self.expert_mode = d.get('expert_mode', False)
721             self.gap_limit = d.get('gap_limit', 5)
722             self.debug_server = d.get('debug_server', False)
723             self.conversion_currency = d.get('conversion_currency', 'USD')
724             self.theme = d.get('theme', 'Cleanlook')
725         except:
726             raise IOError("Cannot read wallet file.")
727
728         self.update_tx_history()
729
730         if self.seed_version != SEED_VERSION:
731             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
732
733         if self.remote_url: assert self.master_public_key.encode('hex') == self.get_remote_mpk()
734
735         self.file_exists = True
736
737
738     def get_address_flags(self, addr):
739         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
740         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
741         return flags
742         
743
744     def get_addr_balance(self, addr):
745         assert self.is_mine(addr)
746         h = self.history.get(addr,[])
747         c = u = 0
748         for item in h:
749             v = item['value']
750             if item['height']:
751                 c += v
752             else:
753                 u += v
754         return c, u
755
756     def get_balance(self):
757         conf = unconf = 0
758         for addr in self.all_addresses(): 
759             c, u = self.get_addr_balance(addr)
760             conf += c
761             unconf += u
762         return conf, unconf
763
764
765     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
766         """ todo: minimize tx size """
767         total = 0
768         fee = self.fee if fixed_fee is None else fixed_fee
769
770         coins = []
771         prioritized_coins = []
772         domain = [from_addr] if from_addr else self.all_addresses()
773         for i in self.frozen_addresses:
774             if i in domain: domain.remove(i)
775
776         for i in self.prioritized_addresses:
777             if i in domain: domain.remove(i)
778
779         for addr in domain:
780             h = self.history.get(addr)
781             if h is None: continue
782             for item in h:
783                 if item.get('raw_output_script'):
784                     coins.append( (addr,item))
785
786         coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
787
788         for addr in self.prioritized_addresses:
789             h = self.history.get(addr)
790             if h is None: continue
791             for item in h:
792                 if item.get('raw_output_script'):
793                     prioritized_coins.append( (addr,item))
794
795         prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
796
797         inputs = []
798         coins = prioritized_coins + coins
799
800         for c in coins: 
801             addr, item = c
802             v = item.get('value')
803             total += v
804             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
805             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
806             if total >= amount + fee: break
807         else:
808             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
809             inputs = []
810         return inputs, total, fee
811
812     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
813         outputs = [ (to_addr, amount) ]
814         change_amount = total - ( amount + fee )
815         if change_amount != 0:
816             # normally, the update thread should ensure that the last change address is unused
817             if not change_addr:
818                 change_addr = self.change_addresses[-1]
819             outputs.append( ( change_addr,  change_amount) )
820         return outputs
821
822     def sign_inputs( self, inputs, outputs, password ):
823         s_inputs = []
824         for i in range(len(inputs)):
825             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
826             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
827             public_key = private_key.get_verifying_key()
828             pubkey = public_key.to_string()
829             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
830             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
831             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
832             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
833         return s_inputs
834
835     def pw_encode(self, s, password):
836         if password:
837             secret = Hash(password)
838             return EncodeAES(secret, s)
839         else:
840             return s
841
842     def pw_decode(self, s, password):
843         if password is not None:
844             secret = Hash(password)
845             d = DecodeAES(secret, s)
846             if s == self.seed:
847                 try:
848                     d.decode('hex')
849                 except:
850                     raise ValueError("Invalid password")
851             return d
852         else:
853             return s
854
855     def get_status(self, address):
856         h = self.history.get(address)
857         if not h:
858             status = None
859         else:
860             lastpoint = h[-1]
861             status = lastpoint['block_hash']
862             if status == 'mempool': 
863                 status = status + ':%d'% len(h)
864         return status
865
866     def receive_status_callback(self, addr, status):
867         with self.lock:
868             if self.get_status(addr) != status:
869                 #print "updating status for", addr, status
870                 self.interface.get_history(addr)
871
872     def receive_history_callback(self, addr, data): 
873         #print "updating history for", addr
874         with self.lock:
875             self.history[addr] = data
876             self.update_tx_history()
877             self.save()
878
879     def get_tx_history(self):
880         lines = self.tx_history.values()
881         lines = sorted(lines, key=operator.itemgetter("timestamp"))
882         return lines
883
884     def update_tx_history(self):
885         self.tx_history= {}
886         for addr in self.all_addresses():
887             h = self.history.get(addr)
888             if h is None: continue
889             for tx in h:
890                 tx_hash = tx['tx_hash']
891                 line = self.tx_history.get(tx_hash)
892                 if not line:
893                     self.tx_history[tx_hash] = copy.copy(tx)
894                     line = self.tx_history.get(tx_hash)
895                 else:
896                     line['value'] += tx['value']
897                 if line['height'] == 0:
898                     line['timestamp'] = 1e12
899         self.update_tx_labels()
900
901     def update_tx_labels(self):
902         for tx in self.tx_history.values():
903             default_label = ''
904             if tx['value']<0:
905                 for o_addr in tx['outputs']:
906                     if not self.is_mine(o_addr):
907                         try:
908                             default_label = self.labels[o_addr]
909                         except KeyError:
910                             default_label = o_addr
911             else:
912                 for o_addr in tx['outputs']:
913                     if self.is_mine(o_addr) and not self.is_change(o_addr):
914                         break
915                 else:
916                     for o_addr in tx['outputs']:
917                         if self.is_mine(o_addr):
918                             break
919                     else:
920                         o_addr = None
921
922                 if o_addr:
923                     dest_label = self.labels.get(o_addr)
924                     try:
925                         default_label = self.labels[o_addr]
926                     except KeyError:
927                         default_label = o_addr
928
929             tx['default_label'] = default_label
930
931     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
932         if not self.is_valid(to_address):
933             raise ValueError("Invalid address")
934         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
935         if not inputs:
936             raise ValueError("Not enough funds")
937
938         if not self.use_change and not change_addr:
939             change_addr = inputs[0][0]
940             print "Sending change to", change_addr
941
942         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
943         s_inputs = self.sign_inputs( inputs, outputs, password )
944
945         tx = filter( raw_tx( s_inputs, outputs ) )
946         if to_address not in self.addressbook:
947             self.addressbook.append(to_address)
948         if label: 
949             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
950             self.labels[tx_hash] = label
951
952         return tx
953
954     def sendtx(self, tx):
955         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
956         self.tx_event.clear()
957         self.interface.send([('blockchain.transaction.broadcast', [tx])])
958         self.tx_event.wait()
959         out = self.tx_result 
960         if out != tx_hash:
961             return False, "error: " + out
962         if self.receipt:
963             self.receipts[tx_hash] = self.receipt
964             self.receipt = None
965         return True, out
966
967
968     def read_alias(self, alias):
969         # this might not be the right place for this function.
970         import urllib
971
972         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
973         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
974         if m1:
975             url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
976         elif m2:
977             url = 'http://' + alias + '/bitcoin.id'
978         else:
979             return ''
980         try:
981             lines = urllib.urlopen(url).readlines()
982         except:
983             return ''
984
985         # line 0
986         line = lines[0].strip().split(':')
987         if len(line) == 1:
988             auth_name = None
989             target = signing_addr = line[0]
990         else:
991             target, auth_name, signing_addr, signature = line
992             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
993             print msg, signature
994             self.verify_message(signing_addr, signature, msg)
995         
996         # other lines are signed updates
997         for line in lines[1:]:
998             line = line.strip()
999             if not line: continue
1000             line = line.split(':')
1001             previous = target
1002             print repr(line)
1003             target, signature = line
1004             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
1005
1006         if not self.is_valid(target):
1007             raise ValueError("Invalid bitcoin address")
1008
1009         return target, signing_addr, auth_name
1010
1011     def update_password(self, seed, old_password, new_password):
1012         if new_password == '': new_password = None
1013         self.use_encryption = (new_password != None)
1014         self.seed = self.pw_encode( seed, new_password)
1015         for k in self.imported_keys.keys():
1016             a = self.imported_keys[k]
1017             b = self.pw_decode(a, old_password)
1018             c = self.pw_encode(b, new_password)
1019             self.imported_keys[k] = c
1020         self.save()
1021
1022     def get_alias(self, alias, interactive = False, show_message=None, question = None):
1023         try:
1024             target, signing_address, auth_name = self.read_alias(alias)
1025         except BaseException, e:
1026             # raise exception if verify fails (verify the chain)
1027             if interactive:
1028                 show_message("Alias error: " + str(e))
1029             return
1030
1031         print target, signing_address, auth_name
1032
1033         if auth_name is None:
1034             a = self.aliases.get(alias)
1035             if not a:
1036                 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)
1037                 if interactive and question( msg ):
1038                     self.aliases[alias] = (signing_address, target)
1039                 else:
1040                     target = None
1041             else:
1042                 if signing_address != a[0]:
1043                     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
1044                     if interactive and question( msg ):
1045                         self.aliases[alias] = (signing_address, target)
1046                     else:
1047                         target = None
1048         else:
1049             if signing_address not in self.authorities.keys():
1050                 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)
1051                 if interactive and question( msg ):
1052                     self.authorities[signing_address] = auth_name
1053                 else:
1054                     target = None
1055
1056         if target:
1057             self.aliases[alias] = (signing_address, target)
1058             
1059         return target
1060
1061
1062     def parse_url(self, url, show_message, question):
1063         o = url[8:].split('?')
1064         address = o[0]
1065         if len(o)>1:
1066             params = o[1].split('&')
1067         else:
1068             params = []
1069
1070         amount = label = message = signature = identity = ''
1071         for p in params:
1072             k,v = p.split('=')
1073             uv = urldecode(v)
1074             if k == 'amount': amount = uv
1075             elif k == 'message': message = uv
1076             elif k == 'label': label = uv
1077             elif k == 'signature':
1078                 identity, signature = uv.split(':')
1079                 url = url.replace('&%s=%s'%(k,v),'')
1080             else: 
1081                 print k,v
1082
1083         if label and self.labels.get(address) != label:
1084             if question('Give label "%s" to address %s ?'%(label,address)):
1085                 if address not in self.addressbook and address not in self.all_addresses(): 
1086                     self.addressbook.append(address)
1087                 self.labels[address] = label
1088
1089         if signature:
1090             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
1091                 signing_address = self.get_alias(identity, True, show_message, question)
1092             elif self.is_valid(identity):
1093                 signing_address = identity
1094             else:
1095                 signing_address = None
1096             if not signing_address:
1097                 return
1098             try:
1099                 self.verify_message(signing_address, signature, url )
1100                 self.receipt = (signing_address, signature, url)
1101             except:
1102                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
1103                 address = amount = label = identity = message = ''
1104
1105         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
1106             payto_address = self.get_alias(address, True, show_message, question)
1107             if payto_address:
1108                 address = address + ' <' + payto_address + '>'
1109
1110         return address, amount, label, message, signature, identity, url
1111
1112
1113     def update(self):
1114         self.interface.poke()
1115         self.up_to_date_event.wait(10000000000)
1116
1117
1118     def start_session(self, interface):
1119         self.interface = interface
1120         self.interface.send([('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])])
1121         self.interface.subscribe(self.all_addresses())
1122
1123
1124     def freeze(self,addr):
1125         if addr in self.all_addresses() and addr not in self.frozen_addresses:
1126             self.unprioritize(addr)
1127             self.frozen_addresses.append(addr)
1128             self.save()
1129             return True
1130         else:
1131             return False
1132
1133     def unfreeze(self,addr):
1134         if addr in self.all_addresses() and addr in self.frozen_addresses:
1135             self.frozen_addresses.remove(addr)
1136             self.save()
1137             return True
1138         else:
1139             return False
1140
1141     def prioritize(self,addr):
1142         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
1143             self.unfreeze(addr)
1144             self.prioritized_addresses.append(addr)
1145             self.save()
1146             return True
1147         else:
1148             return False
1149
1150     def unprioritize(self,addr):
1151         if addr in self.all_addresses() and addr in self.prioritized_addresses:
1152             self.prioritized_addresses.remove(addr)
1153             self.save()
1154             return True
1155         else:
1156             return False