update bip32 accounts and wallet
[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 aes
30 import Queue
31 import time
32 import math
33
34 from util import print_msg, print_error, format_satoshis
35 from bitcoin import *
36 from account import *
37 from transaction import Transaction
38 from plugins import run_hook
39
40 COINBASE_MATURITY = 100
41 DUST_THRESHOLD = 5430
42
43 # AES encryption
44 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
45 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
46
47 def pw_encode(s, password):
48     if password:
49         secret = Hash(password)
50         return EncodeAES(secret, s)
51     else:
52         return s
53
54 def pw_decode(s, password):
55     if password is not None:
56         secret = Hash(password)
57         try:
58             d = DecodeAES(secret, s)
59         except Exception:
60             raise Exception('Invalid password')
61         return d
62     else:
63         return s
64
65
66
67
68
69 from version import *
70
71
72 class WalletStorage:
73
74     def __init__(self, config):
75         self.lock = threading.Lock()
76         self.config = config
77         self.data = {}
78         self.file_exists = False
79         self.path = self.init_path(config)
80         print_error( "wallet path", self.path )
81         if self.path:
82             self.read(self.path)
83
84
85     def init_path(self, config):
86         """Set the path of the wallet."""
87
88         # command line -w option
89         path = config.get('wallet_path')
90         if path:
91             return path
92
93         # path in config file
94         path = config.get('default_wallet_path')
95         if path:
96             return path
97
98         # default path
99         dirpath = os.path.join(config.path, "wallets")
100         if not os.path.exists(dirpath):
101             os.mkdir(dirpath)
102
103         new_path = os.path.join(config.path, "wallets", "default_wallet")
104
105         # default path in pre 1.9 versions
106         old_path = os.path.join(config.path, "electrum.dat")
107         if os.path.exists(old_path) and not os.path.exists(new_path):
108             os.rename(old_path, new_path)
109
110         return new_path
111
112
113     def read(self, path):
114         """Read the contents of the wallet file."""
115         try:
116             with open(self.path, "r") as f:
117                 data = f.read()
118         except IOError:
119             return
120         try:
121             d = ast.literal_eval( data )  #parse raw data from reading wallet file
122         except Exception:
123             raise IOError("Cannot read wallet file.")
124
125         self.data = d
126         self.file_exists = True
127
128
129     def get(self, key, default=None):
130         v = self.data.get(key)
131         if v is None: 
132             v = default
133         return v
134
135     def put(self, key, value, save = True):
136
137         with self.lock:
138             if value is not None:
139                 self.data[key] = value
140             else:
141                 self.data.pop(key)
142             if save: 
143                 self.write()
144
145     def write(self):
146         s = repr(self.data)
147         f = open(self.path,"w")
148         f.write( s )
149         f.close()
150         if 'ANDROID_DATA' not in os.environ:
151             import stat
152             os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
153
154
155
156     
157
158 class NewWallet:
159
160     def __init__(self, storage):
161
162         self.storage = storage
163         self.electrum_version = ELECTRUM_VERSION
164         self.gap_limit_for_change = 3 # constant
165
166         # saved fields
167         self.seed_version          = storage.get('seed_version', NEW_SEED_VERSION)
168
169         self.gap_limit             = storage.get('gap_limit', 5)
170         self.use_change            = storage.get('use_change',True)
171         self.use_encryption        = storage.get('use_encryption', False)
172         self.seed                  = storage.get('seed', '')               # encrypted
173         self.labels                = storage.get('labels', {})
174         self.frozen_addresses      = storage.get('frozen_addresses',[])
175         self.addressbook           = storage.get('contacts', [])
176
177         self.imported_keys         = storage.get('imported_keys',{})
178         self.history               = storage.get('addr_history',{})        # address -> list(txid, height)
179
180         self.fee                   = int(storage.get('fee_per_kb',20000))
181
182         self.master_public_keys = storage.get('master_public_keys',{})
183         self.master_private_keys = storage.get('master_private_keys', {})
184
185         self.next_addresses = storage.get('next_addresses',{})
186
187
188         # This attribute is set when wallet.start_threads is called.
189         self.synchronizer = None
190
191         self.load_accounts()
192
193         self.transactions = {}
194         tx_list = self.storage.get('transactions',{})
195         for k,v in tx_list.items():
196             try:
197                 tx = Transaction(v)
198             except Exception:
199                 print_msg("Warning: Cannot deserialize transactions. skipping")
200                 continue
201
202             self.add_extra_addresses(tx)
203             self.transactions[k] = tx
204
205         for h,tx in self.transactions.items():
206             if not self.check_new_tx(h, tx):
207                 print_error("removing unreferenced tx", h)
208                 self.transactions.pop(h)
209
210
211         # not saved
212         self.prevout_values = {}     # my own transaction outputs
213         self.spent_outputs = []
214
215         # spv
216         self.verifier = None
217
218         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
219         # interface.is_up_to_date() returns true when all requests have been answered and processed
220         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
221         
222         self.up_to_date = False
223         self.lock = threading.Lock()
224         self.transaction_lock = threading.Lock()
225         self.tx_event = threading.Event()
226
227         for tx_hash, tx in self.transactions.items():
228             self.update_tx_outputs(tx_hash)
229
230
231     def add_extra_addresses(self, tx):
232         h = tx.hash()
233         # find the address corresponding to pay-to-pubkey inputs
234         tx.add_extra_addresses(self.transactions)
235         for o in tx.d.get('outputs'):
236             if o.get('is_pubkey'):
237                 for tx2 in self.transactions.values():
238                     tx2.add_extra_addresses({h:tx})
239
240             
241
242
243     def set_up_to_date(self,b):
244         with self.lock: self.up_to_date = b
245
246
247     def is_up_to_date(self):
248         with self.lock: return self.up_to_date
249
250
251     def update(self):
252         self.up_to_date = False
253         while not self.is_up_to_date(): 
254             time.sleep(0.1)
255
256
257     def import_key(self, sec, password):
258         # check password
259         seed = self.get_seed(password)
260         try:
261             address = address_from_private_key(sec)
262         except Exception:
263             raise Exception('Invalid private key')
264
265         if self.is_mine(address):
266             raise Exception('Address already in wallet')
267         
268         # store the originally requested keypair into the imported keys table
269         self.imported_keys[address] = pw_encode(sec, password )
270         self.storage.put('imported_keys', self.imported_keys, True)
271         if self.synchronizer:
272             self.synchronizer.subscribe_to_addresses([address])
273         return address
274         
275     def delete_imported_key(self, addr):
276         if addr in self.imported_keys:
277             self.imported_keys.pop(addr)
278             self.storage.put('imported_keys', self.imported_keys, True)
279
280
281     def make_seed(self):
282         import mnemonic, ecdsa
283         entropy = ecdsa.util.randrange( pow(2,160) )
284         nonce = 0
285         while True:
286             ss = "%040x"%(entropy+nonce)
287             s = hashlib.sha256(ss.decode('hex')).digest().encode('hex')
288             # we keep only 13 words, that's approximately 139 bits of entropy
289             words = mnemonic.mn_encode(s)[0:13] 
290             seed = ' '.join(words)
291             if is_seed(seed):
292                 break  # this will remove 8 bits of entropy
293             nonce += 1
294
295         return seed
296
297
298     def init_seed(self, seed):
299         import mnemonic, unicodedata
300         
301         if self.seed: 
302             raise Exception("a seed exists")
303
304         self.seed_version = NEW_SEED_VERSION
305
306         if not seed:
307             self.seed = self.make_seed()
308             return
309
310         self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
311
312             
313
314     def save_seed(self, password):
315         if password: 
316             self.seed = pw_encode( self.seed, password)
317             self.use_encryption = True
318         self.storage.put('seed', self.seed, True)
319         self.storage.put('seed_version', self.seed_version, True)
320         self.storage.put('use_encryption', self.use_encryption,True)
321         self.create_accounts(password)
322
323
324     def create_watching_only_wallet(self, xpub):
325         self.master_public_keys = { "m/": xpub }
326         self.storage.put('master_public_keys', self.master_public_keys, True)
327         self.storage.put('seed_version', self.seed_version, True)
328         account = BIP32_Account({'xpub':xpub})
329         self.add_account("m/", account)
330
331
332     def create_accounts(self, password):
333         seed = pw_decode(self.seed, password)
334         # create default account
335         self.create_master_keys(password)
336         self.create_account('Main account', password)
337
338
339     def create_master_keys(self, password):
340         xpriv, xpub = bip32_root(self.get_seed(password))
341         self.master_public_keys["m/"] = xpub
342         self.master_private_keys["m/"] = pw_encode(xpriv, password)
343         self.storage.put('master_public_keys', self.master_public_keys, True)
344         self.storage.put('master_private_keys', self.master_private_keys, True)
345
346
347     def find_root_by_master_key(self, xpub):
348         for key, xpub2 in self.master_public_keys.items():
349             if key == "m/":continue
350             if xpub == xpub2:
351                 return key
352
353     def is_watching_only(self):
354         return (self.seed == '') and (self.master_private_keys == {})
355
356
357     def num_accounts(self, account_type = '1of1'):
358         keys = self.accounts.keys()
359         i = 0
360         while True:
361             account_id = self.account_id(account_type, i)
362             if account_id not in keys: break
363             i += 1
364         return i
365
366
367     def next_account_address(self, account_type, password):
368         i = self.num_accounts(account_type)
369         account_id = self.account_id(account_type, i)
370
371         addr = self.next_addresses.get(account_id)
372         if not addr: 
373             account = self.make_account(account_id, password)
374             addr = account.first_address()
375             self.next_addresses[account_id] = addr
376             self.storage.put('next_addresses', self.next_addresses)
377
378         return account_id, addr
379
380     def account_id(self, account_type, i):
381         if account_type == '1of1':
382             return "m/%d'"%i
383         else:
384             raise
385
386     def make_account(self, account_id, password):
387         """Creates and saves the master keys, but does not save the account"""
388         master_xpriv = pw_decode( self.master_private_keys["m/"] , password )
389         xpriv, xpub = bip32_private_derivation(master_xpriv, "m/", account_id)
390         self.master_private_keys[account_id] = pw_encode(xpriv, password)
391         self.master_public_keys[account_id] = xpub
392         self.storage.put('master_public_keys', self.master_public_keys, True)
393         self.storage.put('master_private_keys', self.master_private_keys, True)
394         account = BIP32_Account({'xpub':xpub})
395         return account
396
397
398     def set_label(self, name, text = None):
399         changed = False
400         old_text = self.labels.get(name)
401         if text:
402             if old_text != text:
403                 self.labels[name] = text
404                 changed = True
405         else:
406             if old_text:
407                 self.labels.pop(name)
408                 changed = True
409
410         if changed:
411             self.storage.put('labels', self.labels, True)
412
413         run_hook('set_label', name, text, changed)
414         return changed
415
416
417     def create_account(self, name, password):
418         i = self.num_accounts('1of1')
419         account_id = self.account_id('1of1', i)
420         account = self.make_account(account_id, password)
421         self.add_account(account_id, account)
422         if name:
423             self.set_label(account_id, name)
424
425         # add address of the next account
426         _, _ = self.next_account_address('1of1', password)
427
428
429     def add_account(self, account_id, account):
430         self.accounts[account_id] = account
431         if account_id in self.pending_accounts:
432             self.pending_accounts.pop(account_id)
433             self.storage.put('pending_accounts', self.pending_accounts)
434         self.save_accounts()
435
436
437     def save_accounts(self):
438         d = {}
439         for k, v in self.accounts.items():
440             d[k] = v.dump()
441         self.storage.put('accounts', d, True)
442
443     
444
445     def load_accounts(self):
446         d = self.storage.get('accounts', {})
447         self.accounts = {}
448         for k, v in d.items():
449             if k == 0:
450                 v['mpk'] = self.storage.get('master_public_key')
451                 self.accounts[k] = OldAccount(v)
452             elif '&' in k:
453                 self.accounts[k] = BIP32_Account_2of2(v)
454             else:
455                 self.accounts[k] = BIP32_Account(v)
456
457         self.pending_accounts = self.storage.get('pending_accounts',{})
458
459
460     def delete_pending_account(self, k):
461         self.pending_accounts.pop(k)
462         self.storage.put('pending_accounts', self.pending_accounts)
463
464     def account_is_pending(self, k):
465         return k in self.pending_accounts
466
467     def create_pending_account(self, acct_type, name, password):
468         account_id, addr = self.next_account_address(acct_type, password)
469         self.set_label(account_id, name)
470         self.pending_accounts[account_id] = addr
471         self.storage.put('pending_accounts', self.pending_accounts)
472
473     def get_pending_accounts(self):
474         return self.pending_accounts.items()
475
476
477     def addresses(self, include_change = True, _next=True):
478         o = self.get_account_addresses(-1, include_change)
479         for a in self.accounts.keys():
480             o += self.get_account_addresses(a, include_change)
481
482         if _next:
483             for addr in self.next_addresses.values():
484                 if addr not in o:
485                     o += [addr]
486         return o
487
488
489     def is_mine(self, address):
490         return address in self.addresses(True)
491
492
493     def is_change(self, address):
494         if not self.is_mine(address): return False
495         if address in self.imported_keys.keys(): return False
496         acct, s = self.get_address_index(address)
497         if s is None: return False
498         return s[0] == 1
499
500     def get_master_public_key(self):
501         return self.storage.get("master_public_keys")["m/"]
502
503     def get_master_private_key(self, account, password):
504         k = self.master_private_keys.get(account)
505         if not k: return
506         xpriv = pw_decode( k, password)
507         return xpriv
508
509
510     def get_address_index(self, address):
511         if address in self.imported_keys.keys():
512             return -1, None
513
514         for account in self.accounts.keys():
515             for for_change in [0,1]:
516                 addresses = self.accounts[account].get_addresses(for_change)
517                 for addr in addresses:
518                     if address == addr:
519                         return account, (for_change, addresses.index(addr))
520
521         for k,v in self.next_addresses.items():
522             if v == address:
523                 return k, (0,0)
524
525         raise Exception("Address not found", address)
526
527
528     def getpubkeys(self, addr):
529         assert is_valid(addr) and self.is_mine(addr)
530         account, sequence = self.get_address_index(addr)
531         if account != -1:
532             a = self.accounts[account]
533             return a.get_pubkeys( sequence )
534
535
536     def get_roots(self, account):
537         roots = []
538         for a in account.split('&'):
539             s = a.strip()
540             m = re.match("m/(\d+')", s)
541             roots.append( m.group(1) )
542         return roots
543
544
545     def is_seeded(self, account):
546         return True
547
548
549         for root in self.get_roots(account):
550             if root not in self.master_private_keys.keys(): 
551                 return False
552         return True
553
554     def rebase_sequence(self, account, sequence):
555         # account has one or more xpub
556         # sequence is a sequence of public derivations
557         c, i = sequence
558         dd = []
559         for a in account.split('&'):
560             s = a.strip()
561             m = re.match("m/(\d+)'", s)
562             root = "m/"
563             num = int(m.group(1))
564             dd.append( (root, [num,c,i] ) )
565         return dd
566         
567
568
569     def get_keyID(self, account, sequence):
570         rs = self.rebase_sequence(account, sequence)
571         dd = []
572         for root, public_sequence in rs:
573             xpub = self.master_public_keys[root]
574             s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
575             dd.append( 'bip32(%s,%s)'%(xpub, s) )
576         return '&'.join(dd)
577
578
579     def get_seed(self, password):
580         s = pw_decode(self.seed, password)
581         seed = mnemonic_to_seed(s,'').encode('hex')
582         return seed
583
584
585     def get_mnemonic(self, password):
586         return pw_decode(self.seed, password)
587         
588
589     def get_private_key(self, address, password):
590         if self.is_watching_only():
591             return []
592
593         # first check the provided password
594         seed = self.get_seed(password)
595         
596         out = []
597         if address in self.imported_keys.keys():
598             out.append( pw_decode( self.imported_keys[address], password ) )
599         else:
600             account_id, sequence = self.get_address_index(address)
601             #rs = self.rebase_sequence( account, sequence)
602             rs = [(account_id, sequence)]
603             for root, public_sequence in rs:
604                 xpriv = self.get_master_private_key(root, password)
605                 if not xpriv: continue
606                 _, _, _, c, k = deserialize_xkey(xpriv)
607                 pk = bip32_private_key( public_sequence, k, c )
608                 out.append(pk)
609                     
610         return out
611
612
613     def add_keypairs_from_wallet(self, tx, keypairs, password):
614         for txin in tx.inputs:
615             address = txin['address']
616             if not self.is_mine(address):
617                 continue
618             private_keys = self.get_private_key(address, password)
619             for sec in private_keys:
620                 pubkey = public_key_from_private_key(sec)
621                 keypairs[ pubkey ] = sec
622                 if address in self.imported_keys.keys():
623                     txin['redeemPubkey'] = pubkey
624
625
626     def add_keypairs_from_KeyID(self, tx, keypairs, password):
627         # first check the provided password
628         seed = self.get_seed(password)
629
630         for txin in tx.inputs:
631             keyid = txin.get('KeyID')
632             if keyid:
633                 roots = []
634                 for s in keyid.split('&'):
635                     m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
636                     if not m: continue
637                     c = m.group(1)
638                     K = m.group(2)
639                     sequence = m.group(3)
640                     root = self.find_root_by_master_key(c,K)
641                     if not root: continue
642                     sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
643                     root = root + '%d'%sequence[0]
644                     sequence = sequence[1:]
645                     roots.append((root,sequence)) 
646
647                 account_id = " & ".join( map(lambda x:x[0], roots) )
648                 account = self.accounts.get(account_id)
649                 if not account: continue
650                 addr = account.get_address(*sequence)
651                 txin['address'] = addr # fixme: side effect
652                 pk = self.get_private_key(addr, password)
653                 for sec in pk:
654                     pubkey = public_key_from_private_key(sec)
655                     keypairs[pubkey] = sec
656
657
658
659     def signrawtransaction(self, tx, input_info, private_keys, password):
660
661         # check that the password is correct
662         seed = self.get_seed(password)
663
664         # add input info
665         tx.add_input_info(input_info)
666
667         # add redeem script for coins that are in the wallet
668         # FIXME: add redeemPubkey too!
669
670         try:
671             unspent_coins = self.get_unspent_coins()
672         except:
673             # an exception may be raised is the wallet is not synchronized
674             unspent_coins = []
675
676         for txin in tx.inputs:
677             for item in unspent_coins:
678                 if txin['prevout_hash'] == item['prevout_hash'] and txin['prevout_n'] == item['prevout_n']:
679                     print_error( "tx input is in unspent coins" )
680                     txin['scriptPubKey'] = item['scriptPubKey']
681                     account, sequence = self.get_address_index(item['address'])
682                     if account != -1:
683                         txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
684                         print_error("added redeemScript", txin['redeemScript'])
685                     break
686
687
688         # build a list of public/private keys
689         keypairs = {}
690
691         # add private keys from parameter
692         for sec in private_keys:
693             pubkey = public_key_from_private_key(sec)
694             keypairs[ pubkey ] = sec
695
696         # add private_keys from KeyID
697         self.add_keypairs_from_KeyID(tx, keypairs, password)
698
699         # add private keys from wallet
700         self.add_keypairs_from_wallet(tx, keypairs, password)
701         self.sign_transaction(tx, keypairs, password)
702
703
704     def sign_message(self, address, message, password):
705         keys = self.get_private_key(address, password)
706         assert len(keys) == 1
707         sec = keys[0]
708         key = regenerate_key(sec)
709         compressed = is_compressed(sec)
710         return key.sign_message(message, compressed, address)
711
712
713
714     def decrypt_message(self, pubkey, message, password):
715         address = public_key_to_bc_address(pubkey.decode('hex'))
716         keys = self.get_private_key(address, password)
717         secret = keys[0]
718         ec = regenerate_key(secret)
719         decrypted = ec.decrypt_message(message)
720         return decrypted[0]
721
722
723     def change_gap_limit(self, value):
724         if value >= self.gap_limit:
725             self.gap_limit = value
726             self.storage.put('gap_limit', self.gap_limit, True)
727             #self.interface.poke('synchronizer')
728             return True
729
730         elif value >= self.min_acceptable_gap():
731             for key, account in self.accounts.items():
732                 addresses = account[0]
733                 k = self.num_unused_trailing_addresses(addresses)
734                 n = len(addresses) - k + value
735                 addresses = addresses[0:n]
736                 self.accounts[key][0] = addresses
737
738             self.gap_limit = value
739             self.storage.put('gap_limit', self.gap_limit, True)
740             self.save_accounts()
741             return True
742         else:
743             return False
744
745     def num_unused_trailing_addresses(self, addresses):
746         k = 0
747         for a in addresses[::-1]:
748             if self.history.get(a):break
749             k = k + 1
750         return k
751
752     def min_acceptable_gap(self):
753         # fixme: this assumes wallet is synchronized
754         n = 0
755         nmax = 0
756
757         for account in self.accounts.values():
758             addresses = account.get_addresses(0)
759             k = self.num_unused_trailing_addresses(addresses)
760             for a in addresses[0:-k]:
761                 if self.history.get(a):
762                     n = 0
763                 else:
764                     n += 1
765                     if n > nmax: nmax = n
766         return nmax + 1
767
768
769     def address_is_old(self, address):
770         age = -1
771         h = self.history.get(address, [])
772         if h == ['*']:
773             return True
774         for tx_hash, tx_height in h:
775             if tx_height == 0:
776                 tx_age = 0
777             else:
778                 tx_age = self.network.get_local_height() - tx_height + 1
779             if tx_age > age:
780                 age = tx_age
781         return age > 2
782
783
784     def synchronize_sequence(self, account, for_change):
785         limit = self.gap_limit_for_change if for_change else self.gap_limit
786         new_addresses = []
787         while True:
788             addresses = account.get_addresses(for_change)
789             if len(addresses) < limit:
790                 address = account.create_new_address(for_change)
791                 self.history[address] = []
792                 new_addresses.append( address )
793                 continue
794
795             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
796                 break
797             else:
798                 address = account.create_new_address(for_change)
799                 self.history[address] = []
800                 new_addresses.append( address )
801
802         return new_addresses
803         
804
805     def check_pending_accounts(self):
806         for account_id, addr in self.next_addresses.items():
807             if self.address_is_old(addr):
808                 print_error( "creating account", account_id )
809                 xpub = self.master_public_keys[account_id]
810                 account = BIP32_Account({'xpub':xpub})
811                 self.add_account(account_id, account)
812                 self.next_addresses.pop(account_id)
813
814
815     def synchronize_account(self, account):
816         new = []
817         new += self.synchronize_sequence(account, 0)
818         new += self.synchronize_sequence(account, 1)
819         return new
820
821
822     def synchronize(self):
823         self.check_pending_accounts()
824         new = []
825         for account in self.accounts.values():
826             new += self.synchronize_account(account)
827         if new:
828             self.save_accounts()
829             self.storage.put('addr_history', self.history, True)
830         return new
831
832
833     def is_found(self):
834         return self.history.values() != [[]] * len(self.history) 
835
836
837     def add_contact(self, address, label=None):
838         self.addressbook.append(address)
839         self.storage.put('contacts', self.addressbook, True)
840         if label:  
841             self.set_label(address, label)
842
843
844     def delete_contact(self, addr):
845         if addr in self.addressbook:
846             self.addressbook.remove(addr)
847             self.storage.put('addressbook', self.addressbook, True)
848
849
850     def fill_addressbook(self):
851         for tx_hash, tx in self.transactions.items():
852             is_relevant, is_send, _, _ = self.get_tx_value(tx)
853             if is_send:
854                 for addr, v in tx.outputs:
855                     if not self.is_mine(addr) and addr not in self.addressbook:
856                         self.addressbook.append(addr)
857         # redo labels
858         # self.update_tx_labels()
859
860     def get_num_tx(self, address):
861         n = 0 
862         for tx in self.transactions.values():
863             if address in map(lambda x:x[0], tx.outputs): n += 1
864         return n
865
866
867     def get_address_flags(self, addr):
868         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
869         flags += "F" if addr in self.frozen_addresses else "-"
870         return flags
871         
872
873     def get_tx_value(self, tx, account=None):
874         domain = self.get_account_addresses(account)
875         return tx.get_value(domain, self.prevout_values)
876
877     
878     def update_tx_outputs(self, tx_hash):
879         tx = self.transactions.get(tx_hash)
880
881         for i, (addr, value) in enumerate(tx.outputs):
882             key = tx_hash+ ':%d'%i
883             self.prevout_values[key] = value
884
885         for item in tx.inputs:
886             if self.is_mine(item.get('address')):
887                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
888                 self.spent_outputs.append(key)
889
890
891     def get_addr_balance(self, address):
892         assert self.is_mine(address)
893         h = self.history.get(address,[])
894         if h == ['*']: return 0,0
895         c = u = 0
896         received_coins = []   # list of coins received at address
897
898         for tx_hash, tx_height in h:
899             tx = self.transactions.get(tx_hash)
900             if not tx: continue
901
902             for i, (addr, value) in enumerate(tx.outputs):
903                 if addr == address:
904                     key = tx_hash + ':%d'%i
905                     received_coins.append(key)
906
907         for tx_hash, tx_height in h:
908             tx = self.transactions.get(tx_hash)
909             if not tx: continue
910             v = 0
911
912             for item in tx.inputs:
913                 addr = item.get('address')
914                 if addr == address:
915                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
916                     value = self.prevout_values.get( key )
917                     if key in received_coins: 
918                         v -= value
919
920             for i, (addr, value) in enumerate(tx.outputs):
921                 key = tx_hash + ':%d'%i
922                 if addr == address:
923                     v += value
924
925             if tx_height:
926                 c += v
927             else:
928                 u += v
929         return c, u
930
931
932     def get_account_name(self, k):
933         default = "Unnamed account"
934         m = re.match("m/0'/(\d+)", k)
935         if m:
936             num = m.group(1)
937             if num == '0':
938                 default = "Main account"
939             else:
940                 default = "Account %s"%num
941                     
942         m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
943         if m:
944             num = m.group(1)
945             default = "2of2 account %s"%num
946         name = self.labels.get(k, default)
947         return name
948
949
950     def get_account_names(self):
951         accounts = {}
952         for k, account in self.accounts.items():
953             accounts[k] = self.get_account_name(k)
954         if self.imported_keys:
955             accounts[-1] = 'Imported keys'
956         return accounts
957
958
959     def get_account_addresses(self, a, include_change=True):
960         if a is None:
961             o = self.addresses(True)
962         elif a == -1:
963             o = self.imported_keys.keys()
964         else:
965             ac = self.accounts[a]
966             o = ac.get_addresses(0)
967             if include_change: o += ac.get_addresses(1)
968         return o
969
970     def get_imported_balance(self):
971         return self.get_balance(self.imported_keys.keys())
972
973     def get_account_balance(self, account):
974         return self.get_balance(self.get_account_addresses(account))
975
976     def get_frozen_balance(self):
977         return self.get_balance(self.frozen_addresses)
978         
979     def get_balance(self, domain=None):
980         if domain is None: domain = self.addresses(True)
981         cc = uu = 0
982         for addr in domain:
983             c, u = self.get_addr_balance(addr)
984             cc += c
985             uu += u
986         return cc, uu
987
988
989     def get_unspent_coins(self, domain=None):
990         coins = []
991         if domain is None: domain = self.addresses(True)
992         for addr in domain:
993             h = self.history.get(addr, [])
994             if h == ['*']: continue
995             for tx_hash, tx_height in h:
996                 tx = self.transactions.get(tx_hash)
997                 if tx is None: raise Exception("Wallet not synchronized")
998                 is_coinbase = tx.inputs[0].get('prevout_hash') == '0'*64
999                 for o in tx.d.get('outputs'):
1000                     output = o.copy()
1001                     if output.get('address') != addr: continue
1002                     key = tx_hash + ":%d" % output.get('prevout_n')
1003                     if key in self.spent_outputs: continue
1004                     output['prevout_hash'] = tx_hash
1005                     output['height'] = tx_height
1006                     output['coinbase'] = is_coinbase
1007                     coins.append((tx_height, output))
1008
1009         # sort by age
1010         if coins:
1011             coins = sorted(coins)
1012             if coins[-1][0] != 0:
1013                 while coins[0][0] == 0: 
1014                     coins = coins[1:] + [ coins[0] ]
1015         return [x[1] for x in coins]
1016
1017
1018     def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None ):
1019         """ todo: minimize tx size """
1020         total = 0
1021         fee = self.fee if fixed_fee is None else fixed_fee
1022         if domain is None:
1023             domain = self.addresses(True)
1024
1025         for i in self.frozen_addresses:
1026             if i in domain: domain.remove(i)
1027
1028         coins = self.get_unspent_coins(domain)
1029         inputs = []
1030
1031         for item in coins:
1032             if item.get('coinbase') and item.get('height') + COINBASE_MATURITY > self.network.get_local_height():
1033                 continue
1034             addr = item.get('address')
1035             v = item.get('value')
1036             total += v
1037             inputs.append(item)
1038             fee = self.estimated_fee(inputs, num_outputs) if fixed_fee is None else fixed_fee
1039             if total >= amount + fee: break
1040         else:
1041             inputs = []
1042
1043         return inputs, total, fee
1044
1045
1046     def set_fee(self, fee):
1047         if self.fee != fee:
1048             self.fee = fee
1049             self.storage.put('fee_per_kb', self.fee, True)
1050         
1051     def estimated_fee(self, inputs, num_outputs):
1052         estimated_size =  len(inputs) * 180 + num_outputs * 34    # this assumes non-compressed keys
1053         fee = self.fee * int(math.ceil(estimated_size/1000.))
1054         return fee
1055
1056
1057     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None):
1058         "add change to a transaction"
1059         change_amount = total - ( amount + fee )
1060         if change_amount > DUST_THRESHOLD:
1061             if not change_addr:
1062
1063                 # send change to one of the accounts involved in the tx
1064                 address = inputs[0].get('address')
1065                 account, _ = self.get_address_index(address)
1066
1067                 if not self.use_change or account == -1:
1068                     change_addr = inputs[-1]['address']
1069                 else:
1070                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
1071
1072             # Insert the change output at a random position in the outputs
1073             posn = random.randint(0, len(outputs))
1074             outputs[posn:posn] = [( change_addr,  change_amount)]
1075         return outputs
1076
1077
1078     def get_history(self, address):
1079         with self.lock:
1080             return self.history.get(address)
1081
1082
1083     def get_status(self, h):
1084         if not h: return None
1085         if h == ['*']: return '*'
1086         status = ''
1087         for tx_hash, height in h:
1088             status += tx_hash + ':%d:' % height
1089         return hashlib.sha256( status ).digest().encode('hex')
1090
1091
1092     def receive_tx_callback(self, tx_hash, tx, tx_height):
1093
1094         with self.transaction_lock:
1095             self.add_extra_addresses(tx)
1096             if not self.check_new_tx(tx_hash, tx):
1097                 # may happen due to pruning
1098                 print_error("received transaction that is no longer referenced in history", tx_hash)
1099                 return
1100             self.transactions[tx_hash] = tx
1101             self.network.interface.pending_transactions_for_notifications.append(tx)
1102             self.save_transactions()
1103             if self.verifier and tx_height>0: 
1104                 self.verifier.add(tx_hash, tx_height)
1105             self.update_tx_outputs(tx_hash)
1106
1107
1108     def save_transactions(self):
1109         tx = {}
1110         for k,v in self.transactions.items():
1111             tx[k] = str(v)
1112         self.storage.put('transactions', tx, True)
1113
1114     def receive_history_callback(self, addr, hist):
1115
1116         if not self.check_new_history(addr, hist):
1117             raise Exception("error: received history for %s is not consistent with known transactions"%addr)
1118             
1119         with self.lock:
1120             self.history[addr] = hist
1121             self.storage.put('addr_history', self.history, True)
1122
1123         if hist != ['*']:
1124             for tx_hash, tx_height in hist:
1125                 if tx_height>0:
1126                     # add it in case it was previously unconfirmed
1127                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1128
1129
1130     def get_tx_history(self, account=None):
1131         if not self.verifier:
1132             return []
1133
1134         with self.transaction_lock:
1135             history = self.transactions.items()
1136             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1137             result = []
1138     
1139             balance = 0
1140             for tx_hash, tx in history:
1141                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1142                 if v is not None: balance += v
1143
1144             c, u = self.get_account_balance(account)
1145
1146             if balance != c+u:
1147                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1148
1149             balance = c + u - balance
1150             for tx_hash, tx in history:
1151                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1152                 if not is_relevant:
1153                     continue
1154                 if value is not None:
1155                     balance += value
1156
1157                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1158                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1159
1160         return result
1161
1162
1163     def get_label(self, tx_hash):
1164         label = self.labels.get(tx_hash)
1165         is_default = (label == '') or (label is None)
1166         if is_default: label = self.get_default_label(tx_hash)
1167         return label, is_default
1168
1169
1170     def get_default_label(self, tx_hash):
1171         tx = self.transactions.get(tx_hash)
1172         default_label = ''
1173         if tx:
1174             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1175             if is_mine:
1176                 for o in tx.outputs:
1177                     o_addr, _ = o
1178                     if not self.is_mine(o_addr):
1179                         try:
1180                             default_label = self.labels[o_addr]
1181                         except KeyError:
1182                             default_label = '>' + o_addr
1183                         break
1184                 else:
1185                     default_label = '(internal)'
1186             else:
1187                 for o in tx.outputs:
1188                     o_addr, _ = o
1189                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1190                         break
1191                 else:
1192                     for o in tx.outputs:
1193                         o_addr, _ = o
1194                         if self.is_mine(o_addr):
1195                             break
1196                     else:
1197                         o_addr = None
1198
1199                 if o_addr:
1200                     dest_label = self.labels.get(o_addr)
1201                     try:
1202                         default_label = self.labels[o_addr]
1203                     except KeyError:
1204                         default_label = '<' + o_addr
1205
1206         return default_label
1207
1208
1209     def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None ):
1210         for address, x in outputs:
1211             assert is_valid(address), "Address " + address + " is invalid!"
1212         amount = sum( map(lambda x:x[1], outputs) )
1213         inputs, total, fee = self.choose_tx_inputs( amount, fee, len(outputs), domain )
1214         if not inputs:
1215             raise ValueError("Not enough funds")
1216         self.add_input_info(inputs)
1217         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr)
1218         return Transaction.from_io(inputs, outputs)
1219
1220
1221     def mktx(self, outputs, password, fee=None, change_addr=None, domain= None ):
1222         tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain)
1223         keypairs = {}
1224         self.add_keypairs_from_wallet(tx, keypairs, password)
1225         if keypairs:
1226             self.sign_transaction(tx, keypairs, password)
1227         return tx
1228
1229
1230     def add_input_info(self, inputs):
1231         for txin in inputs:
1232             address = txin['address']
1233             if address in self.imported_keys.keys():
1234                 continue
1235             account, sequence = self.get_address_index(address)
1236             txin['KeyID'] = self.get_keyID(account, sequence)
1237             redeemScript = self.accounts[account].redeem_script(sequence)
1238             if redeemScript: 
1239                 txin['redeemScript'] = redeemScript
1240             else:
1241                 txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
1242
1243
1244     def sign_transaction(self, tx, keypairs, password):
1245         tx.sign(keypairs)
1246         run_hook('sign_transaction', tx, password)
1247
1248
1249     def sendtx(self, tx):
1250         # synchronous
1251         h = self.send_tx(tx)
1252         self.tx_event.wait()
1253         return self.receive_tx(h, tx)
1254
1255     def send_tx(self, tx):
1256         # asynchronous
1257         self.tx_event.clear()
1258         self.network.interface.send([('blockchain.transaction.broadcast', [str(tx)])], self.on_broadcast)
1259         return tx.hash()
1260
1261     def on_broadcast(self, i, r):
1262         self.tx_result = r.get('result')
1263         self.tx_event.set()
1264
1265     def receive_tx(self, tx_hash, tx):
1266         out = self.tx_result 
1267         if out != tx_hash:
1268             return False, "error: " + out
1269         run_hook('receive_tx', tx, self)
1270         return True, out
1271
1272
1273
1274     def update_password(self, old_password, new_password):
1275         if new_password == '': new_password = None
1276         decoded = self.get_seed(old_password)
1277         self.seed = pw_encode( decoded, new_password)
1278         self.storage.put('seed', self.seed, True)
1279         self.use_encryption = (new_password != None)
1280         self.storage.put('use_encryption', self.use_encryption,True)
1281         for k in self.imported_keys.keys():
1282             a = self.imported_keys[k]
1283             b = pw_decode(a, old_password)
1284             c = pw_encode(b, new_password)
1285             self.imported_keys[k] = c
1286         self.storage.put('imported_keys', self.imported_keys, True)
1287
1288         for k, v in self.master_private_keys.items():
1289             b = pw_decode(v, old_password)
1290             c = pw_encode(b, new_password)
1291             self.master_private_keys[k] = c
1292         self.storage.put('master_private_keys', self.master_private_keys, True)
1293
1294
1295     def freeze(self,addr):
1296         if self.is_mine(addr) and addr not in self.frozen_addresses:
1297             self.frozen_addresses.append(addr)
1298             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1299             return True
1300         else:
1301             return False
1302
1303     def unfreeze(self,addr):
1304         if self.is_mine(addr) and addr in self.frozen_addresses:
1305             self.frozen_addresses.remove(addr)
1306             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1307             return True
1308         else:
1309             return False
1310
1311
1312     def set_verifier(self, verifier):
1313         self.verifier = verifier
1314
1315         # review transactions that are in the history
1316         for addr, hist in self.history.items():
1317             if hist == ['*']: continue
1318             for tx_hash, tx_height in hist:
1319                 if tx_height>0:
1320                     # add it in case it was previously unconfirmed
1321                     self.verifier.add(tx_hash, tx_height)
1322
1323
1324         # if we are on a pruning server, remove unverified transactions
1325         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1326         for tx_hash in self.transactions.keys():
1327             if tx_hash not in vr:
1328                 self.transactions.pop(tx_hash)
1329
1330
1331
1332     def check_new_history(self, addr, hist):
1333         
1334         # check that all tx in hist are relevant
1335         if hist != ['*']:
1336             for tx_hash, height in hist:
1337                 tx = self.transactions.get(tx_hash)
1338                 if not tx: continue
1339                 if not tx.has_address(addr):
1340                     return False
1341
1342         # check that we are not "orphaning" a transaction
1343         old_hist = self.history.get(addr,[])
1344         if old_hist == ['*']: return True
1345
1346         for tx_hash, height in old_hist:
1347             if tx_hash in map(lambda x:x[0], hist): continue
1348             found = False
1349             for _addr, _hist in self.history.items():
1350                 if _addr == addr: continue
1351                 if _hist == ['*']: continue
1352                 _tx_hist = map(lambda x:x[0], _hist)
1353                 if tx_hash in _tx_hist:
1354                     found = True
1355                     break
1356
1357             if not found:
1358                 tx = self.transactions.get(tx_hash)
1359                 # tx might not be there
1360                 if not tx: continue
1361                 
1362                 # already verified?
1363                 if self.verifier.get_height(tx_hash):
1364                     continue
1365                 # unconfirmed tx
1366                 print_error("new history is orphaning transaction:", tx_hash)
1367                 # check that all outputs are not mine, request histories
1368                 ext_requests = []
1369                 for _addr, _v in tx.outputs:
1370                     # assert not self.is_mine(_addr)
1371                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1372
1373                 ext_h = self.network.synchronous_get(ext_requests)
1374                 print_error("sync:", ext_requests, ext_h)
1375                 height = None
1376                 for h in ext_h:
1377                     if h == ['*']: continue
1378                     for item in h:
1379                         if item.get('tx_hash') == tx_hash:
1380                             height = item.get('height')
1381                 if height:
1382                     print_error("found height for", tx_hash, height)
1383                     self.verifier.add(tx_hash, height)
1384                 else:
1385                     print_error("removing orphaned tx from history", tx_hash)
1386                     self.transactions.pop(tx_hash)
1387
1388         return True
1389
1390
1391
1392     def check_new_tx(self, tx_hash, tx):
1393         # 1 check that tx is referenced in addr_history. 
1394         addresses = []
1395         for addr, hist in self.history.items():
1396             if hist == ['*']:continue
1397             for txh, height in hist:
1398                 if txh == tx_hash: 
1399                     addresses.append(addr)
1400
1401         if not addresses:
1402             return False
1403
1404         # 2 check that referencing addresses are in the tx
1405         for addr in addresses:
1406             if not tx.has_address(addr):
1407                 return False
1408
1409         return True
1410
1411
1412     def start_threads(self, network):
1413         from verifier import TxVerifier
1414         self.network = network
1415         if self.network is not None:
1416             self.verifier = TxVerifier(self.network, self.storage)
1417             self.verifier.start()
1418             self.set_verifier(self.verifier)
1419             self.synchronizer = WalletSynchronizer(self, network)
1420             self.synchronizer.start()
1421         else:
1422             self.verifier = None
1423             self.synchronizer =None
1424
1425     def stop_threads(self):
1426         if self.network:
1427             self.verifier.stop()
1428             self.synchronizer.stop()
1429
1430
1431     def restore(self, callback):
1432         from i18n import _
1433         def wait_for_wallet():
1434             self.set_up_to_date(False)
1435             while not self.is_up_to_date():
1436                 msg = "%s\n%s %d\n%s %.1f"%(
1437                     _("Please wait..."),
1438                     _("Addresses generated:"),
1439                     len(self.addresses(True)), 
1440                     _("Kilobytes received:"), 
1441                     self.network.interface.bytes_received/1024.)
1442
1443                 apply(callback, (msg,))
1444                 time.sleep(0.1)
1445
1446         def wait_for_network():
1447             while not self.network.is_connected():
1448                 msg = "%s \n" % (_("Connecting..."))
1449                 apply(callback, (msg,))
1450                 time.sleep(0.1)
1451
1452         # wait until we are connected, because the user might have selected another server
1453         if self.network:
1454             wait_for_network()
1455             wait_for_wallet()
1456         else:
1457             self.synchronize()
1458             
1459         self.fill_addressbook()
1460
1461
1462
1463
1464 class WalletSynchronizer(threading.Thread):
1465
1466
1467     def __init__(self, wallet, network):
1468         threading.Thread.__init__(self)
1469         self.daemon = True
1470         self.wallet = wallet
1471         self.network = network
1472         self.was_updated = True
1473         self.running = False
1474         self.lock = threading.Lock()
1475         self.queue = Queue.Queue()
1476
1477     def stop(self):
1478         with self.lock: self.running = False
1479
1480     def is_running(self):
1481         with self.lock: return self.running
1482
1483     
1484     def subscribe_to_addresses(self, addresses):
1485         messages = []
1486         for addr in addresses:
1487             messages.append(('blockchain.address.subscribe', [addr]))
1488         self.network.subscribe( messages, lambda i,r: self.queue.put(r))
1489
1490
1491     def run(self):
1492         with self.lock:
1493             self.running = True
1494
1495         while self.is_running():
1496
1497             if not self.network.is_connected():
1498                 self.network.wait_until_connected()
1499                 
1500             self.run_interface()
1501
1502
1503     def run_interface(self):
1504
1505         print_error("synchronizer: connected to", self.network.main_server())
1506
1507         requested_tx = []
1508         missing_tx = []
1509         requested_histories = {}
1510
1511         # request any missing transactions
1512         for history in self.wallet.history.values():
1513             if history == ['*']: continue
1514             for tx_hash, tx_height in history:
1515                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1516                     missing_tx.append( (tx_hash, tx_height) )
1517
1518         if missing_tx:
1519             print_error("missing tx", missing_tx)
1520
1521         # subscriptions
1522         self.subscribe_to_addresses(self.wallet.addresses(True))
1523
1524         while self.is_running():
1525             # 1. create new addresses
1526             new_addresses = self.wallet.synchronize()
1527
1528             # request missing addresses
1529             if new_addresses:
1530                 self.subscribe_to_addresses(new_addresses)
1531
1532             # request missing transactions
1533             for tx_hash, tx_height in missing_tx:
1534                 if (tx_hash, tx_height) not in requested_tx:
1535                     self.network.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
1536                     requested_tx.append( (tx_hash, tx_height) )
1537             missing_tx = []
1538
1539             # detect if situation has changed
1540             if self.network.is_up_to_date() and self.queue.empty():
1541                 if not self.wallet.is_up_to_date():
1542                     self.wallet.set_up_to_date(True)
1543                     self.was_updated = True
1544             else:
1545                 if self.wallet.is_up_to_date():
1546                     self.wallet.set_up_to_date(False)
1547                     self.was_updated = True
1548
1549             if self.was_updated:
1550                 self.network.trigger_callback('updated')
1551                 self.was_updated = False
1552
1553             # 2. get a response
1554             try:
1555                 r = self.queue.get(block=True, timeout=1)
1556             except Queue.Empty:
1557                 continue
1558
1559             # see if it changed
1560             #if interface != self.network.interface:
1561             #    break
1562             
1563             if not r:
1564                 continue
1565
1566             # 3. handle response
1567             method = r['method']
1568             params = r['params']
1569             result = r.get('result')
1570             error = r.get('error')
1571             if error:
1572                 print "error", r
1573                 continue
1574
1575             if method == 'blockchain.address.subscribe':
1576                 addr = params[0]
1577                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1578                     if requested_histories.get(addr) is None:
1579                         self.network.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
1580                         requested_histories[addr] = result
1581
1582             elif method == 'blockchain.address.get_history':
1583                 addr = params[0]
1584                 print_error("receiving history", addr, result)
1585                 if result == ['*']:
1586                     assert requested_histories.pop(addr) == '*'
1587                     self.wallet.receive_history_callback(addr, result)
1588                 else:
1589                     hist = []
1590                     # check that txids are unique
1591                     txids = []
1592                     for item in result:
1593                         tx_hash = item['tx_hash']
1594                         if tx_hash not in txids:
1595                             txids.append(tx_hash)
1596                             hist.append( (tx_hash, item['height']) )
1597
1598                     if len(hist) != len(result):
1599                         raise Exception("error: server sent history with non-unique txid", result)
1600
1601                     # check that the status corresponds to what was announced
1602                     rs = requested_histories.pop(addr)
1603                     if self.wallet.get_status(hist) != rs:
1604                         raise Exception("error: status mismatch: %s"%addr)
1605                 
1606                     # store received history
1607                     self.wallet.receive_history_callback(addr, hist)
1608
1609                     # request transactions that we don't have 
1610                     for tx_hash, tx_height in hist:
1611                         if self.wallet.transactions.get(tx_hash) is None:
1612                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1613                                 missing_tx.append( (tx_hash, tx_height) )
1614
1615             elif method == 'blockchain.transaction.get':
1616                 tx_hash = params[0]
1617                 tx_height = params[1]
1618                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1619                 tx = Transaction(result)
1620                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1621                 self.was_updated = True
1622                 requested_tx.remove( (tx_hash, tx_height) )
1623                 print_error("received tx:", tx_hash, len(tx.raw))
1624
1625             else:
1626                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1627
1628             if self.was_updated and not requested_tx:
1629                 self.network.trigger_callback('updated')
1630                 # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
1631                 self.network.trigger_callback("new_transaction") 
1632                 self.was_updated = False
1633
1634
1635
1636
1637 class OldWallet(NewWallet):
1638
1639     def init_seed(self, seed):
1640         import mnemonic
1641         
1642         if self.seed: 
1643             raise Exception("a seed exists")
1644
1645         if not seed:
1646             seed = random_seed(128)
1647
1648         self.seed_version = OLD_SEED_VERSION
1649
1650         # see if seed was entered as hex
1651         seed = seed.strip()
1652         try:
1653             assert seed
1654             seed.decode('hex')
1655             self.seed = str(seed)
1656             return
1657         except Exception:
1658             pass
1659
1660         words = seed.split()
1661         try:
1662             mnemonic.mn_decode(words)
1663         except Exception:
1664             raise
1665
1666         self.seed = mnemonic.mn_decode(words)
1667
1668         if not self.seed:
1669             raise Exception("Invalid seed")
1670             
1671
1672
1673     def get_master_public_key(self):
1674         return self.storage.get("master_public_key")
1675
1676     def create_accounts(self, password):
1677         seed = pw_decode(self.seed, password)
1678         mpk = OldAccount.mpk_from_seed(seed)
1679         self.create_account(mpk)
1680
1681     def create_account(self, mpk):
1682         self.storage.put('master_public_key', mpk, True)
1683         self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
1684         self.save_accounts()
1685
1686     def create_watching_only_wallet(self, mpk):
1687         self.seed_version = OLD_SEED_VERSION
1688         self.storage.put('seed_version', self.seed_version, True)
1689         self.create_account(mpk)
1690
1691     def get_seed(self, password):
1692         seed = pw_decode(self.seed, password)
1693         self.accounts[0].check_seed(seed)
1694         return seed
1695
1696     def get_mnemonic(self, password):
1697         import mnemonic
1698         s = pw_decode(self.seed, password)
1699         return ' '.join(mnemonic.mn_encode(s))
1700
1701
1702     def add_keypairs_from_KeyID(self, tx, keypairs, password):
1703         # first check the provided password
1704         seed = self.get_seed(password)
1705         for txin in tx.inputs:
1706             keyid = txin.get('KeyID')
1707             if keyid:
1708                 m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
1709                 if not m: continue
1710                 mpk = m.group(1)
1711                 if mpk != self.storage.get('master_public_key'): continue 
1712                 for_change = int(m.group(2))
1713                 num = int(m.group(3))
1714                 account = self.accounts[0]
1715                 addr = account.get_address(for_change, num)
1716                 txin['address'] = addr # fixme: side effect
1717                 pk = account.get_private_key(seed, (for_change, num))
1718                 pubkey = public_key_from_private_key(pk)
1719                 keypairs[pubkey] = pk
1720
1721
1722     def get_account_name(self, k):
1723         assert k == 0
1724         return 'Main account'
1725
1726     def is_seeded(self, account):
1727         return self.seed is not None
1728
1729     def get_private_key(self, address, password):
1730         if self.is_watching_only():
1731             return []
1732
1733         # first check the provided password
1734         seed = self.get_seed(password)
1735         
1736         out = []
1737         if address in self.imported_keys.keys():
1738             out.append( pw_decode( self.imported_keys[address], password ) )
1739         else:
1740             account_id, sequence = self.get_address_index(address)
1741             pk = self.accounts[0].get_private_key(seed, sequence)
1742             out.append(pk)
1743         return out
1744
1745     def get_keyID(self, account, sequence):
1746         a, b = sequence
1747         mpk = self.storage.get('master_public_key')
1748         return 'old(%s,%d,%d)'%(mpk,a,b)
1749
1750     def check_pending_accounts(self):
1751         pass
1752
1753
1754 # former WalletFactory
1755 class Wallet(object):
1756
1757     def __new__(self, storage):
1758         config = storage.config
1759         if config.get('bitkey', False):
1760             # if user requested support for Bitkey device,
1761             # import Bitkey driver
1762             from wallet_bitkey import WalletBitkey
1763             return WalletBitkey(config)
1764
1765         if not storage.file_exists:
1766             seed_version = NEW_SEED_VERSION if config.get('bip32') is True else OLD_SEED_VERSION
1767         else:
1768             seed_version = storage.get('seed_version')
1769             if not seed_version:
1770                 seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key')) == 128 else NEW_SEED_VERSION
1771
1772         if seed_version == OLD_SEED_VERSION:
1773             return OldWallet(storage)
1774         elif seed_version == NEW_SEED_VERSION:
1775             return NewWallet(storage)
1776         else:
1777             msg = "This wallet seed is not supported."
1778             if seed_version in [5]:
1779                 msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
1780             print msg
1781             sys.exit(1)
1782
1783
1784
1785     @classmethod
1786     def from_seed(self, seed, storage):
1787         import mnemonic
1788         if not seed:
1789             return 
1790
1791         words = seed.strip().split()
1792         try:
1793             mnemonic.mn_decode(words)
1794             uses_electrum_words = True
1795         except Exception:
1796             uses_electrum_words = False
1797
1798         try:
1799             seed.decode('hex')
1800             is_hex = True
1801         except Exception:
1802             is_hex = False
1803          
1804         if is_hex or (uses_electrum_words and len(words) != 13):
1805             #print "old style wallet", len(words), words
1806             w = OldWallet(storage)
1807             w.init_seed(seed) #hex
1808         else:
1809             #assert is_seed(seed)
1810             w = NewWallet(storage)
1811             w.init_seed(seed)
1812
1813         return w
1814
1815
1816     @classmethod
1817     def from_mpk(self, mpk, storage):
1818
1819         try:
1820             int(mpk, 16)
1821             old = True
1822         except:
1823             old = False
1824
1825         if old:
1826             w = OldWallet(storage)
1827             w.seed = ''
1828             w.create_watching_only_wallet(mpk)
1829         else:
1830             w = NewWallet(storage)
1831             w.create_watching_only_wallet(mpk)
1832
1833         return w