d478eac03db0eb4357746109ec1033226695443d
[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.gui_detailed_view = 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
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, proxy=None):
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 or proxy != self.interface.proxy:
356             self.server = server
357             self.save()
358             self.interface.proxy = proxy
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         n = len(self.change_addresses) if for_change else len(self.addresses)
534         address = self.get_new_address(n, for_change)
535         if for_change:
536             self.change_addresses.append(address)
537         else:
538             self.addresses.append(address)
539         self.history[address] = []
540         return address
541         
542     def get_new_address(self, n, for_change):
543         """   Publickey(type,n) = Master_public_key + H(n|S|type)*point  """
544         curve = SECP256k1
545         z = self.get_sequence(n, for_change)
546         master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key, curve = SECP256k1 )
547         pubkey_point = master_public_key.pubkey.point + z*curve.generator
548         public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
549         address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() )
550         print address
551         return address
552                                                                       
553
554     def change_gap_limit(self, value):
555         if value >= self.gap_limit:
556             self.gap_limit = value
557             self.save()
558             self.interface.poke()
559             return True
560
561         elif value >= self.min_acceptable_gap():
562             k = self.num_unused_trailing_addresses()
563             n = len(self.addresses) - k + value
564             self.addresses = self.addresses[0:n]
565             self.gap_limit = value
566             self.save()
567             return True
568         else:
569             return False
570
571     def num_unused_trailing_addresses(self):
572         k = 0
573         for a in self.addresses[::-1]:
574             if self.history.get(a):break
575             k = k + 1
576         return k
577
578     def min_acceptable_gap(self):
579         # fixme: this assumes wallet is synchronized
580         n = 0
581         nmax = 0
582         k = self.num_unused_trailing_addresses()
583         for a in self.addresses[0:-k]:
584             if self.history.get(a):
585                 n = 0
586             else:
587                 n += 1
588                 if n > nmax: nmax = n
589         return nmax + 1
590
591
592     def synchronize(self):
593         if not self.master_public_key:
594             return []
595
596         new_addresses = []
597         while True:
598             if self.change_addresses == []:
599                 new_addresses.append( self.create_new_address(True) )
600                 continue
601             a = self.change_addresses[-1]
602             if self.history.get(a):
603                 new_addresses.append( self.create_new_address(True) )
604             else:
605                 break
606
607         n = self.gap_limit
608         while True:
609             if len(self.addresses) < n:
610                 new_addresses.append( self.create_new_address(False) )
611                 continue
612             if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
613                 break
614             else:
615                 new_addresses.append( self.create_new_address(False) )
616
617         return new_addresses
618
619
620     def is_found(self):
621         return (len(self.change_addresses) > 1 ) or ( len(self.addresses) > self.gap_limit )
622
623     def fill_addressbook(self):
624         for tx in self.tx_history.values():
625             if tx['value']<0:
626                 for i in tx['outputs']:
627                     if not self.is_mine(i) and i not in self.addressbook:
628                         self.addressbook.append(i)
629         # redo labels
630         self.update_tx_labels()
631
632
633     def save(self):
634         # TODO: Need special config storage class. Should not be mixed
635         # up with the wallet.
636         # Settings should maybe be stored in a flat ini file.
637         s = {
638             'seed_version': self.seed_version,
639             'use_encryption': self.use_encryption,
640             'use_change': self.use_change,
641             'master_public_key': self.master_public_key.encode('hex'),
642             'fee': self.fee,
643             'server': self.server,
644             'seed': self.seed,
645             'addresses': self.addresses,
646             'change_addresses': self.change_addresses,
647             'history': self.history, 
648             'labels': self.labels,
649             'contacts': self.addressbook,
650             'imported_keys': self.imported_keys,
651             'aliases': self.aliases,
652             'authorities': self.authorities,
653             'receipts': self.receipts,
654             'num_zeros': self.num_zeros,
655             'frozen_addresses': self.frozen_addresses,
656             'prioritized_addresses': self.prioritized_addresses,
657             'gui_detailed_view': self.gui_detailed_view,
658             'gap_limit': self.gap_limit,
659             'debug_server': self.debug_server,
660             'conversion_currency': self.conversion_currency,
661             'theme': self.theme
662         }
663         f = open(self.path,"w")
664         f.write( repr(s) )
665         f.close()
666         import stat
667         os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
668
669     def read(self):
670         """Read the contents of the wallet file."""
671         import interface
672
673         self.file_exists = False
674         try:
675             with open(self.path, "r") as f:
676                 data = f.read()
677         except IOError:
678             return
679         try:
680             d = ast.literal_eval( data )  #parse raw data from reading wallet file
681             interface.old_to_new(d)
682             
683             self.seed_version = d.get('seed_version')
684             self.master_public_key = d.get('master_public_key').decode('hex')
685             self.use_encryption = d.get('use_encryption')
686             self.use_change = bool(d.get('use_change', True))
687             self.fee = int(d.get('fee'))
688             self.seed = d.get('seed')
689             self.server = d.get('server')
690             self.addresses = d.get('addresses')
691             self.change_addresses = d.get('change_addresses')
692             self.history = d.get('history')
693             self.labels = d.get('labels')
694             self.addressbook = d.get('contacts')
695             self.imported_keys = d.get('imported_keys', {})
696             self.aliases = d.get('aliases', {})
697             self.authorities = d.get('authorities', {})
698             self.receipts = d.get('receipts', {})
699             self.num_zeros = d.get('num_zeros', 0)
700             self.frozen_addresses = d.get('frozen_addresses', [])
701             self.prioritized_addresses = d.get('prioritized_addresses', [])
702             self.gui_detailed_view = d.get('gui_detailed_view', False)
703             self.gap_limit = d.get('gap_limit', 5)
704             self.debug_server = d.get('debug_server', False)
705             self.conversion_currency = d.get('conversion_currency', 'USD')
706             self.theme = d.get('theme', 'Cleanlook')
707         except:
708             raise IOError("Cannot read wallet file.")
709
710         self.update_tx_history()
711
712         if self.seed_version != SEED_VERSION:
713             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
714
715         self.file_exists = True
716
717
718     def get_address_flags(self, addr):
719         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
720         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
721         return flags
722         
723
724     def get_addr_balance(self, addr):
725         assert self.is_mine(addr)
726         h = self.history.get(addr,[])
727         c = u = 0
728         for item in h:
729             v = item['value']
730             if item['height']:
731                 c += v
732             else:
733                 u += v
734         return c, u
735
736     def get_balance(self):
737         conf = unconf = 0
738         for addr in self.all_addresses(): 
739             c, u = self.get_addr_balance(addr)
740             conf += c
741             unconf += u
742         return conf, unconf
743
744
745     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
746         """ todo: minimize tx size """
747         total = 0
748         fee = self.fee if fixed_fee is None else fixed_fee
749
750         coins = []
751         prioritized_coins = []
752         domain = [from_addr] if from_addr else self.all_addresses()
753         for i in self.frozen_addresses:
754             if i in domain: domain.remove(i)
755
756         for i in self.prioritized_addresses:
757             if i in domain: domain.remove(i)
758
759         for addr in domain:
760             h = self.history.get(addr)
761             if h is None: continue
762             for item in h:
763                 if item.get('raw_output_script'):
764                     coins.append( (addr,item))
765
766         coins = sorted( coins, key = lambda x: x[1]['timestamp'] )
767
768         for addr in self.prioritized_addresses:
769             h = self.history.get(addr)
770             if h is None: continue
771             for item in h:
772                 if item.get('raw_output_script'):
773                     prioritized_coins.append( (addr,item))
774
775         prioritized_coins = sorted( prioritized_coins, key = lambda x: x[1]['timestamp'] )
776
777         inputs = []
778         coins = prioritized_coins + coins
779
780         for c in coins: 
781             addr, item = c
782             v = item.get('value')
783             total += v
784             inputs.append((addr, v, item['tx_hash'], item['index'], item['raw_output_script'], None, None) )
785             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
786             if total >= amount + fee: break
787         else:
788             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
789             inputs = []
790         return inputs, total, fee
791
792     def choose_tx_outputs( self, to_addr, amount, fee, total, change_addr=None ):
793         outputs = [ (to_addr, amount) ]
794         change_amount = total - ( amount + fee )
795         if change_amount != 0:
796             # normally, the update thread should ensure that the last change address is unused
797             if not change_addr:
798                 change_addr = self.change_addresses[-1]
799             outputs.append( ( change_addr,  change_amount) )
800         return outputs
801
802     def sign_inputs( self, inputs, outputs, password ):
803         s_inputs = []
804         for i in range(len(inputs)):
805             addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
806             private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
807             public_key = private_key.get_verifying_key()
808             pubkey = public_key.to_string()
809             tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
810             sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
811             assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
812             s_inputs.append( (addr, v, p_hash, p_pos, p_scriptPubKey, pubkey, sig) )
813         return s_inputs
814
815     def pw_encode(self, s, password):
816         if password:
817             secret = Hash(password)
818             return EncodeAES(secret, s)
819         else:
820             return s
821
822     def pw_decode(self, s, password):
823         if password is not None:
824             secret = Hash(password)
825             d = DecodeAES(secret, s)
826             if s == self.seed:
827                 try:
828                     d.decode('hex')
829                 except:
830                     raise ValueError("Invalid password")
831             return d
832         else:
833             return s
834
835     def get_status(self, address):
836         h = self.history.get(address)
837         if not h:
838             status = None
839         else:
840             lastpoint = h[-1]
841             status = lastpoint['block_hash']
842             if status == 'mempool': 
843                 status = status + ':%d'% len(h)
844         return status
845
846     def receive_status_callback(self, addr, status):
847         with self.lock:
848             if self.get_status(addr) != status:
849                 #print "updating status for", addr, status
850                 self.interface.get_history(addr)
851
852     def receive_history_callback(self, addr, data): 
853         #print "updating history for", addr
854         with self.lock:
855             self.history[addr] = data
856             self.update_tx_history()
857             self.save()
858
859     def get_tx_history(self):
860         lines = self.tx_history.values()
861         lines = sorted(lines, key=operator.itemgetter("timestamp"))
862         return lines
863
864     def update_tx_history(self):
865         self.tx_history= {}
866         for addr in self.all_addresses():
867             h = self.history.get(addr)
868             if h is None: continue
869             for tx in h:
870                 tx_hash = tx['tx_hash']
871                 line = self.tx_history.get(tx_hash)
872                 if not line:
873                     self.tx_history[tx_hash] = copy.copy(tx)
874                     line = self.tx_history.get(tx_hash)
875                 else:
876                     line['value'] += tx['value']
877                 if line['height'] == 0:
878                     line['timestamp'] = 1e12
879         self.update_tx_labels()
880
881     def update_tx_labels(self):
882         for tx in self.tx_history.values():
883             default_label = ''
884             if tx['value']<0:
885                 for o_addr in tx['outputs']:
886                     if not self.is_mine(o_addr):
887                         try:
888                             default_label = self.labels[o_addr]
889                         except KeyError:
890                             default_label = o_addr
891             else:
892                 for o_addr in tx['outputs']:
893                     if self.is_mine(o_addr) and not self.is_change(o_addr):
894                         break
895                 else:
896                     for o_addr in tx['outputs']:
897                         if self.is_mine(o_addr):
898                             break
899                     else:
900                         o_addr = None
901
902                 if o_addr:
903                     dest_label = self.labels.get(o_addr)
904                     try:
905                         default_label = self.labels[o_addr]
906                     except KeyError:
907                         default_label = o_addr
908
909             tx['default_label'] = default_label
910
911     def mktx(self, to_address, amount, label, password, fee=None, change_addr=None, from_addr= None):
912         if not self.is_valid(to_address):
913             raise ValueError("Invalid address")
914         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
915         if not inputs:
916             raise ValueError("Not enough funds")
917
918         if not self.use_change and not change_addr:
919             change_addr = inputs[0][0]
920             print "Sending change to", change_addr
921
922         outputs = self.choose_tx_outputs( to_address, amount, fee, total, change_addr )
923         s_inputs = self.sign_inputs( inputs, outputs, password )
924
925         tx = filter( raw_tx( s_inputs, outputs ) )
926         if to_address not in self.addressbook:
927             self.addressbook.append(to_address)
928         if label: 
929             tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
930             self.labels[tx_hash] = label
931
932         return tx
933
934     def sendtx(self, tx):
935         tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
936         self.tx_event.clear()
937         self.interface.send([('blockchain.transaction.broadcast', [tx])])
938         self.tx_event.wait()
939         out = self.tx_result 
940         if out != tx_hash:
941             return False, "error: " + out
942         if self.receipt:
943             self.receipts[tx_hash] = self.receipt
944             self.receipt = None
945         return True, out
946
947
948     def read_alias(self, alias):
949         # this might not be the right place for this function.
950         import urllib
951
952         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
953         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
954         if m1:
955             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
956         elif m2:
957             url = 'https://' + alias + '/bitcoin.id'
958         else:
959             return ''
960         try:
961             lines = urllib.urlopen(url).readlines()
962         except:
963             return ''
964
965         # line 0
966         line = lines[0].strip().split(':')
967         if len(line) == 1:
968             auth_name = None
969             target = signing_addr = line[0]
970         else:
971             target, auth_name, signing_addr, signature = line
972             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
973             print msg, signature
974             self.verify_message(signing_addr, signature, msg)
975         
976         # other lines are signed updates
977         for line in lines[1:]:
978             line = line.strip()
979             if not line: continue
980             line = line.split(':')
981             previous = target
982             print repr(line)
983             target, signature = line
984             self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
985
986         if not self.is_valid(target):
987             raise ValueError("Invalid bitcoin address")
988
989         return target, signing_addr, auth_name
990
991     def update_password(self, seed, old_password, new_password):
992         if new_password == '': new_password = None
993         self.use_encryption = (new_password != None)
994         self.seed = self.pw_encode( seed, new_password)
995         for k in self.imported_keys.keys():
996             a = self.imported_keys[k]
997             b = self.pw_decode(a, old_password)
998             c = self.pw_encode(b, new_password)
999             self.imported_keys[k] = c
1000         self.save()
1001
1002     def get_alias(self, alias, interactive = False, show_message=None, question = None):
1003         try:
1004             target, signing_address, auth_name = self.read_alias(alias)
1005         except BaseException, e:
1006             # raise exception if verify fails (verify the chain)
1007             if interactive:
1008                 show_message("Alias error: " + str(e))
1009             return
1010
1011         print target, signing_address, auth_name
1012
1013         if auth_name is None:
1014             a = self.aliases.get(alias)
1015             if not a:
1016                 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)
1017                 if interactive and question( msg ):
1018                     self.aliases[alias] = (signing_address, target)
1019                 else:
1020                     target = None
1021             else:
1022                 if signing_address != a[0]:
1023                     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
1024                     if interactive and question( msg ):
1025                         self.aliases[alias] = (signing_address, target)
1026                     else:
1027                         target = None
1028         else:
1029             if signing_address not in self.authorities.keys():
1030                 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)
1031                 if interactive and question( msg ):
1032                     self.authorities[signing_address] = auth_name
1033                 else:
1034                     target = None
1035
1036         if target:
1037             self.aliases[alias] = (signing_address, target)
1038             
1039         return target
1040
1041
1042     def parse_url(self, url, show_message, question):
1043         o = url[8:].split('?')
1044         address = o[0]
1045         if len(o)>1:
1046             params = o[1].split('&')
1047         else:
1048             params = []
1049
1050         amount = label = message = signature = identity = ''
1051         for p in params:
1052             k,v = p.split('=')
1053             uv = urldecode(v)
1054             if k == 'amount': amount = uv
1055             elif k == 'message': message = uv
1056             elif k == 'label': label = uv
1057             elif k == 'signature':
1058                 identity, signature = uv.split(':')
1059                 url = url.replace('&%s=%s'%(k,v),'')
1060             else: 
1061                 print k,v
1062
1063         if label and self.labels.get(address) != label:
1064             if question('Give label "%s" to address %s ?'%(label,address)):
1065                 if address not in self.addressbook and address not in self.all_addresses(): 
1066                     self.addressbook.append(address)
1067                 self.labels[address] = label
1068
1069         if signature:
1070             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
1071                 signing_address = self.get_alias(identity, True, show_message, question)
1072             elif self.is_valid(identity):
1073                 signing_address = identity
1074             else:
1075                 signing_address = None
1076             if not signing_address:
1077                 return
1078             try:
1079                 self.verify_message(signing_address, signature, url )
1080                 self.receipt = (signing_address, signature, url)
1081             except:
1082                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
1083                 address = amount = label = identity = message = ''
1084
1085         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
1086             payto_address = self.get_alias(address, True, show_message, question)
1087             if payto_address:
1088                 address = address + ' <' + payto_address + '>'
1089
1090         return address, amount, label, message, signature, identity, url
1091
1092
1093     def update(self):
1094         self.interface.poke()
1095         self.up_to_date_event.wait(10000000000)
1096
1097
1098     def start_session(self, interface):
1099         self.interface = interface
1100         self.interface.send([('server.banner',[]), ('blockchain.numblocks.subscribe',[]), ('server.peers.subscribe',[])])
1101         self.interface.subscribe(self.all_addresses())
1102
1103
1104     def freeze(self,addr):
1105         if addr in self.all_addresses() and addr not in self.frozen_addresses:
1106             self.unprioritize(addr)
1107             self.frozen_addresses.append(addr)
1108             self.save()
1109             return True
1110         else:
1111             return False
1112
1113     def unfreeze(self,addr):
1114         if addr in self.all_addresses() and addr in self.frozen_addresses:
1115             self.frozen_addresses.remove(addr)
1116             self.save()
1117             return True
1118         else:
1119             return False
1120
1121     def prioritize(self,addr):
1122         if addr in self.all_addresses() and addr not in self.prioritized_addresses:
1123             self.unfreeze(addr)
1124             self.prioritized_addresses.append(addr)
1125             self.save()
1126             return True
1127         else:
1128             return False
1129
1130     def unprioritize(self,addr):
1131         if addr in self.all_addresses() and addr in self.prioritized_addresses:
1132             self.prioritized_addresses.remove(addr)
1133             self.save()
1134             return True
1135         else:
1136             return False