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