move get_keyID to accounts
[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 v.get('xpub3'):
453                 self.accounts[k] = BIP32_Account_2of3(v)
454             elif v.get('xpub2'):
455                 self.accounts[k] = BIP32_Account_2of2(v)
456             elif v.get('xpub'):
457                 self.accounts[k] = BIP32_Account(v)
458             else:
459                 raise
460
461         self.pending_accounts = self.storage.get('pending_accounts',{})
462
463
464     def delete_pending_account(self, k):
465         self.pending_accounts.pop(k)
466         self.storage.put('pending_accounts', self.pending_accounts)
467
468     def account_is_pending(self, k):
469         return k in self.pending_accounts
470
471     def create_pending_account(self, acct_type, name, password):
472         account_id, addr = self.next_account_address(acct_type, password)
473         self.set_label(account_id, name)
474         self.pending_accounts[account_id] = addr
475         self.storage.put('pending_accounts', self.pending_accounts)
476
477     def get_pending_accounts(self):
478         return self.pending_accounts.items()
479
480
481     def addresses(self, include_change = True, _next=True):
482         o = self.get_account_addresses(-1, include_change)
483         for a in self.accounts.keys():
484             o += self.get_account_addresses(a, include_change)
485
486         if _next:
487             for addr in self.next_addresses.values():
488                 if addr not in o:
489                     o += [addr]
490         return o
491
492
493     def is_mine(self, address):
494         return address in self.addresses(True)
495
496
497     def is_change(self, address):
498         if not self.is_mine(address): return False
499         if address in self.imported_keys.keys(): return False
500         acct, s = self.get_address_index(address)
501         if s is None: return False
502         return s[0] == 1
503
504     def get_master_public_key(self):
505         return self.storage.get("master_public_keys")["m/"]
506
507     def get_master_private_key(self, account, password):
508         k = self.master_private_keys.get(account)
509         if not k: return
510         xpriv = pw_decode( k, password)
511         return xpriv
512
513
514     def get_address_index(self, address):
515         if address in self.imported_keys.keys():
516             return -1, None
517
518         for account in self.accounts.keys():
519             for for_change in [0,1]:
520                 addresses = self.accounts[account].get_addresses(for_change)
521                 for addr in addresses:
522                     if address == addr:
523                         return account, (for_change, addresses.index(addr))
524
525         for k,v in self.next_addresses.items():
526             if v == address:
527                 return k, (0,0)
528
529         raise Exception("Address not found", address)
530
531
532     def getpubkeys(self, addr):
533         assert is_valid(addr) and self.is_mine(addr)
534         account, sequence = self.get_address_index(addr)
535         if account != -1:
536             a = self.accounts[account]
537             return a.get_pubkeys( sequence )
538
539
540     def get_roots(self, account):
541         roots = []
542         for a in account.split('&'):
543             s = a.strip()
544             m = re.match("m/(\d+')", s)
545             roots.append( m.group(1) )
546         return roots
547
548
549     def is_seeded(self, account):
550         return True
551
552
553         for root in self.get_roots(account):
554             if root not in self.master_private_keys.keys(): 
555                 return False
556         return True
557
558     def rebase_sequence(self, account, sequence):
559         # account has one or more xpub
560         # sequence is a sequence of public derivations
561         c, i = sequence
562         dd = []
563         for a in account.split('&'):
564             s = a.strip()
565             m = re.match("m/(\d+)'", s)
566             root = "m/"
567             num = int(m.group(1))
568             dd.append( (root, [num,c,i] ) )
569         return dd
570         
571
572
573
574
575     def get_seed(self, password):
576         s = pw_decode(self.seed, password)
577         seed = mnemonic_to_seed(s,'').encode('hex')
578         return seed
579
580
581     def get_mnemonic(self, password):
582         return pw_decode(self.seed, password)
583         
584
585     def get_private_key(self, address, password):
586         if self.is_watching_only():
587             return []
588
589         # first check the provided password
590         seed = self.get_seed(password)
591
592         out = []
593         if address in self.imported_keys.keys():
594             out.append( pw_decode( self.imported_keys[address], password ) )
595         else:
596             account_id, sequence = self.get_address_index(address)
597             account = self.accounts[account_id]
598             xpubs = account.get_master_pubkeys()
599             roots = [k for k, v in self.master_public_keys.iteritems() if v in xpubs]
600             for root in roots:
601                 xpriv = self.get_master_private_key(root, password)
602                 if not xpriv:
603                     continue
604                 _, _, _, c, k = deserialize_xkey(xpriv)
605                 pk = bip32_private_key( sequence, k, c )
606                 out.append(pk)
607                     
608         return out
609
610
611     def add_keypairs_from_wallet(self, tx, keypairs, password):
612         for txin in tx.inputs:
613             address = txin['address']
614             if not self.is_mine(address):
615                 continue
616             private_keys = self.get_private_key(address, password)
617             for sec in private_keys:
618                 pubkey = public_key_from_private_key(sec)
619                 keypairs[ pubkey ] = sec
620                 if address in self.imported_keys.keys():
621                     txin['redeemPubkey'] = pubkey
622
623
624     def add_keypairs_from_KeyID(self, tx, keypairs, password):
625         # first check the provided password
626         seed = self.get_seed(password)
627
628         for txin in tx.inputs:
629             keyid = txin.get('KeyID')
630             if keyid:
631                 roots = []
632                 for s in keyid.split('&'):
633                     m = re.match("bip32\((.*),(/\d+/\d+)\)", s)
634                     if not m: continue
635                     xpub = m.group(1)
636                     sequence = m.group(2)
637                     root = self.find_root_by_master_key(xpub)
638                     if not root: continue
639                     sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
640                     root = root + '%d'%sequence[0]
641                     sequence = sequence[1:]
642                     roots.append((root,sequence)) 
643
644                 account_id = " & ".join( map(lambda x:x[0], roots) )
645                 account = self.accounts.get(account_id)
646                 if not account: continue
647                 addr = account.get_address(*sequence)
648                 txin['address'] = addr # fixme: side effect
649                 pk = self.get_private_key(addr, password)
650                 for sec in pk:
651                     pubkey = public_key_from_private_key(sec)
652                     keypairs[pubkey] = sec
653
654
655
656     def signrawtransaction(self, tx, input_info, private_keys, password):
657
658         # check that the password is correct
659         seed = self.get_seed(password)
660
661         # add input info
662         tx.add_input_info(input_info)
663
664         # add redeem script for coins that are in the wallet
665         # FIXME: add redeemPubkey too!
666
667         try:
668             unspent_coins = self.get_unspent_coins()
669         except:
670             # an exception may be raised is the wallet is not synchronized
671             unspent_coins = []
672
673         for txin in tx.inputs:
674             for item in unspent_coins:
675                 if txin['prevout_hash'] == item['prevout_hash'] and txin['prevout_n'] == item['prevout_n']:
676                     print_error( "tx input is in unspent coins" )
677                     txin['scriptPubKey'] = item['scriptPubKey']
678                     account, sequence = self.get_address_index(item['address'])
679                     if account != -1:
680                         txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
681                         print_error("added redeemScript", txin['redeemScript'])
682                     break
683
684
685         # build a list of public/private keys
686         keypairs = {}
687
688         # add private keys from parameter
689         for sec in private_keys:
690             pubkey = public_key_from_private_key(sec)
691             keypairs[ pubkey ] = sec
692
693         # add private_keys from KeyID
694         self.add_keypairs_from_KeyID(tx, keypairs, password)
695
696         # add private keys from wallet
697         self.add_keypairs_from_wallet(tx, keypairs, password)
698         self.sign_transaction(tx, keypairs, password)
699
700
701     def sign_message(self, address, message, password):
702         keys = self.get_private_key(address, password)
703         assert len(keys) == 1
704         sec = keys[0]
705         key = regenerate_key(sec)
706         compressed = is_compressed(sec)
707         return key.sign_message(message, compressed, address)
708
709
710
711     def decrypt_message(self, pubkey, message, password):
712         address = public_key_to_bc_address(pubkey.decode('hex'))
713         keys = self.get_private_key(address, password)
714         secret = keys[0]
715         ec = regenerate_key(secret)
716         decrypted = ec.decrypt_message(message)
717         return decrypted[0]
718
719
720     def change_gap_limit(self, value):
721         if value >= self.gap_limit:
722             self.gap_limit = value
723             self.storage.put('gap_limit', self.gap_limit, True)
724             #self.interface.poke('synchronizer')
725             return True
726
727         elif value >= self.min_acceptable_gap():
728             for key, account in self.accounts.items():
729                 addresses = account[0]
730                 k = self.num_unused_trailing_addresses(addresses)
731                 n = len(addresses) - k + value
732                 addresses = addresses[0:n]
733                 self.accounts[key][0] = addresses
734
735             self.gap_limit = value
736             self.storage.put('gap_limit', self.gap_limit, True)
737             self.save_accounts()
738             return True
739         else:
740             return False
741
742     def num_unused_trailing_addresses(self, addresses):
743         k = 0
744         for a in addresses[::-1]:
745             if self.history.get(a):break
746             k = k + 1
747         return k
748
749     def min_acceptable_gap(self):
750         # fixme: this assumes wallet is synchronized
751         n = 0
752         nmax = 0
753
754         for account in self.accounts.values():
755             addresses = account.get_addresses(0)
756             k = self.num_unused_trailing_addresses(addresses)
757             for a in addresses[0:-k]:
758                 if self.history.get(a):
759                     n = 0
760                 else:
761                     n += 1
762                     if n > nmax: nmax = n
763         return nmax + 1
764
765
766     def address_is_old(self, address):
767         age = -1
768         h = self.history.get(address, [])
769         if h == ['*']:
770             return True
771         for tx_hash, tx_height in h:
772             if tx_height == 0:
773                 tx_age = 0
774             else:
775                 tx_age = self.network.get_local_height() - tx_height + 1
776             if tx_age > age:
777                 age = tx_age
778         return age > 2
779
780
781     def synchronize_sequence(self, account, for_change):
782         limit = self.gap_limit_for_change if for_change else self.gap_limit
783         new_addresses = []
784         while True:
785             addresses = account.get_addresses(for_change)
786             if len(addresses) < limit:
787                 address = account.create_new_address(for_change)
788                 self.history[address] = []
789                 new_addresses.append( address )
790                 continue
791
792             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
793                 break
794             else:
795                 address = account.create_new_address(for_change)
796                 self.history[address] = []
797                 new_addresses.append( address )
798
799         return new_addresses
800         
801
802     def check_pending_accounts(self):
803         for account_id, addr in self.next_addresses.items():
804             if self.address_is_old(addr):
805                 print_error( "creating account", account_id )
806                 xpub = self.master_public_keys[account_id]
807                 account = BIP32_Account({'xpub':xpub})
808                 self.add_account(account_id, account)
809                 self.next_addresses.pop(account_id)
810
811
812     def synchronize_account(self, account):
813         new = []
814         new += self.synchronize_sequence(account, 0)
815         new += self.synchronize_sequence(account, 1)
816         return new
817
818
819     def synchronize(self):
820         self.check_pending_accounts()
821         new = []
822         for account in self.accounts.values():
823             new += self.synchronize_account(account)
824         if new:
825             self.save_accounts()
826             self.storage.put('addr_history', self.history, True)
827         return new
828
829
830     def is_found(self):
831         return self.history.values() != [[]] * len(self.history) 
832
833
834     def add_contact(self, address, label=None):
835         self.addressbook.append(address)
836         self.storage.put('contacts', self.addressbook, True)
837         if label:  
838             self.set_label(address, label)
839
840
841     def delete_contact(self, addr):
842         if addr in self.addressbook:
843             self.addressbook.remove(addr)
844             self.storage.put('addressbook', self.addressbook, True)
845
846
847     def fill_addressbook(self):
848         for tx_hash, tx in self.transactions.items():
849             is_relevant, is_send, _, _ = self.get_tx_value(tx)
850             if is_send:
851                 for addr, v in tx.outputs:
852                     if not self.is_mine(addr) and addr not in self.addressbook:
853                         self.addressbook.append(addr)
854         # redo labels
855         # self.update_tx_labels()
856
857     def get_num_tx(self, address):
858         n = 0 
859         for tx in self.transactions.values():
860             if address in map(lambda x:x[0], tx.outputs): n += 1
861         return n
862
863
864     def get_address_flags(self, addr):
865         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
866         flags += "F" if addr in self.frozen_addresses else "-"
867         return flags
868         
869
870     def get_tx_value(self, tx, account=None):
871         domain = self.get_account_addresses(account)
872         return tx.get_value(domain, self.prevout_values)
873
874     
875     def update_tx_outputs(self, tx_hash):
876         tx = self.transactions.get(tx_hash)
877
878         for i, (addr, value) in enumerate(tx.outputs):
879             key = tx_hash+ ':%d'%i
880             self.prevout_values[key] = value
881
882         for item in tx.inputs:
883             if self.is_mine(item.get('address')):
884                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
885                 self.spent_outputs.append(key)
886
887
888     def get_addr_balance(self, address):
889         assert self.is_mine(address)
890         h = self.history.get(address,[])
891         if h == ['*']: return 0,0
892         c = u = 0
893         received_coins = []   # list of coins received at address
894
895         for tx_hash, tx_height in h:
896             tx = self.transactions.get(tx_hash)
897             if not tx: continue
898
899             for i, (addr, value) in enumerate(tx.outputs):
900                 if addr == address:
901                     key = tx_hash + ':%d'%i
902                     received_coins.append(key)
903
904         for tx_hash, tx_height in h:
905             tx = self.transactions.get(tx_hash)
906             if not tx: continue
907             v = 0
908
909             for item in tx.inputs:
910                 addr = item.get('address')
911                 if addr == address:
912                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
913                     value = self.prevout_values.get( key )
914                     if key in received_coins: 
915                         v -= value
916
917             for i, (addr, value) in enumerate(tx.outputs):
918                 key = tx_hash + ':%d'%i
919                 if addr == address:
920                     v += value
921
922             if tx_height:
923                 c += v
924             else:
925                 u += v
926         return c, u
927
928
929     def get_account_name(self, k):
930         default = "Unnamed account"
931         m = re.match("m/0'/(\d+)", k)
932         if m:
933             num = m.group(1)
934             if num == '0':
935                 default = "Main account"
936             else:
937                 default = "Account %s"%num
938                     
939         m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
940         if m:
941             num = m.group(1)
942             default = "2of2 account %s"%num
943         name = self.labels.get(k, default)
944         return name
945
946
947     def get_account_names(self):
948         accounts = {}
949         for k, account in self.accounts.items():
950             accounts[k] = self.get_account_name(k)
951         if self.imported_keys:
952             accounts[-1] = 'Imported keys'
953         return accounts
954
955
956     def get_account_addresses(self, a, include_change=True):
957         if a is None:
958             o = self.addresses(True)
959         elif a == -1:
960             o = self.imported_keys.keys()
961         else:
962             ac = self.accounts[a]
963             o = ac.get_addresses(0)
964             if include_change: o += ac.get_addresses(1)
965         return o
966
967     def get_imported_balance(self):
968         return self.get_balance(self.imported_keys.keys())
969
970     def get_account_balance(self, account):
971         return self.get_balance(self.get_account_addresses(account))
972
973     def get_frozen_balance(self):
974         return self.get_balance(self.frozen_addresses)
975         
976     def get_balance(self, domain=None):
977         if domain is None: domain = self.addresses(True)
978         cc = uu = 0
979         for addr in domain:
980             c, u = self.get_addr_balance(addr)
981             cc += c
982             uu += u
983         return cc, uu
984
985
986     def get_unspent_coins(self, domain=None):
987         coins = []
988         if domain is None: domain = self.addresses(True)
989         for addr in domain:
990             h = self.history.get(addr, [])
991             if h == ['*']: continue
992             for tx_hash, tx_height in h:
993                 tx = self.transactions.get(tx_hash)
994                 if tx is None: raise Exception("Wallet not synchronized")
995                 is_coinbase = tx.inputs[0].get('prevout_hash') == '0'*64
996                 for o in tx.d.get('outputs'):
997                     output = o.copy()
998                     if output.get('address') != addr: continue
999                     key = tx_hash + ":%d" % output.get('prevout_n')
1000                     if key in self.spent_outputs: continue
1001                     output['prevout_hash'] = tx_hash
1002                     output['height'] = tx_height
1003                     output['coinbase'] = is_coinbase
1004                     coins.append((tx_height, output))
1005
1006         # sort by age
1007         if coins:
1008             coins = sorted(coins)
1009             if coins[-1][0] != 0:
1010                 while coins[0][0] == 0: 
1011                     coins = coins[1:] + [ coins[0] ]
1012         return [x[1] for x in coins]
1013
1014
1015     def choose_tx_inputs( self, amount, fixed_fee, num_outputs, domain = None ):
1016         """ todo: minimize tx size """
1017         total = 0
1018         fee = self.fee if fixed_fee is None else fixed_fee
1019         if domain is None:
1020             domain = self.addresses(True)
1021
1022         for i in self.frozen_addresses:
1023             if i in domain: domain.remove(i)
1024
1025         coins = self.get_unspent_coins(domain)
1026         inputs = []
1027
1028         for item in coins:
1029             if item.get('coinbase') and item.get('height') + COINBASE_MATURITY > self.network.get_local_height():
1030                 continue
1031             addr = item.get('address')
1032             v = item.get('value')
1033             total += v
1034             inputs.append(item)
1035             fee = self.estimated_fee(inputs, num_outputs) if fixed_fee is None else fixed_fee
1036             if total >= amount + fee: break
1037         else:
1038             inputs = []
1039
1040         return inputs, total, fee
1041
1042
1043     def set_fee(self, fee):
1044         if self.fee != fee:
1045             self.fee = fee
1046             self.storage.put('fee_per_kb', self.fee, True)
1047         
1048     def estimated_fee(self, inputs, num_outputs):
1049         estimated_size =  len(inputs) * 180 + num_outputs * 34    # this assumes non-compressed keys
1050         fee = self.fee * int(math.ceil(estimated_size/1000.))
1051         return fee
1052
1053
1054     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None):
1055         "add change to a transaction"
1056         change_amount = total - ( amount + fee )
1057         if change_amount > DUST_THRESHOLD:
1058             if not change_addr:
1059
1060                 # send change to one of the accounts involved in the tx
1061                 address = inputs[0].get('address')
1062                 account, _ = self.get_address_index(address)
1063
1064                 if not self.use_change or account == -1:
1065                     change_addr = inputs[-1]['address']
1066                 else:
1067                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
1068
1069             # Insert the change output at a random position in the outputs
1070             posn = random.randint(0, len(outputs))
1071             outputs[posn:posn] = [( change_addr,  change_amount)]
1072         return outputs
1073
1074
1075     def get_history(self, address):
1076         with self.lock:
1077             return self.history.get(address)
1078
1079
1080     def get_status(self, h):
1081         if not h: return None
1082         if h == ['*']: return '*'
1083         status = ''
1084         for tx_hash, height in h:
1085             status += tx_hash + ':%d:' % height
1086         return hashlib.sha256( status ).digest().encode('hex')
1087
1088
1089     def receive_tx_callback(self, tx_hash, tx, tx_height):
1090
1091         with self.transaction_lock:
1092             self.add_extra_addresses(tx)
1093             if not self.check_new_tx(tx_hash, tx):
1094                 # may happen due to pruning
1095                 print_error("received transaction that is no longer referenced in history", tx_hash)
1096                 return
1097             self.transactions[tx_hash] = tx
1098             self.network.interface.pending_transactions_for_notifications.append(tx)
1099             self.save_transactions()
1100             if self.verifier and tx_height>0: 
1101                 self.verifier.add(tx_hash, tx_height)
1102             self.update_tx_outputs(tx_hash)
1103
1104
1105     def save_transactions(self):
1106         tx = {}
1107         for k,v in self.transactions.items():
1108             tx[k] = str(v)
1109         self.storage.put('transactions', tx, True)
1110
1111     def receive_history_callback(self, addr, hist):
1112
1113         if not self.check_new_history(addr, hist):
1114             raise Exception("error: received history for %s is not consistent with known transactions"%addr)
1115             
1116         with self.lock:
1117             self.history[addr] = hist
1118             self.storage.put('addr_history', self.history, True)
1119
1120         if hist != ['*']:
1121             for tx_hash, tx_height in hist:
1122                 if tx_height>0:
1123                     # add it in case it was previously unconfirmed
1124                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1125
1126
1127     def get_tx_history(self, account=None):
1128         if not self.verifier:
1129             return []
1130
1131         with self.transaction_lock:
1132             history = self.transactions.items()
1133             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1134             result = []
1135     
1136             balance = 0
1137             for tx_hash, tx in history:
1138                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1139                 if v is not None: balance += v
1140
1141             c, u = self.get_account_balance(account)
1142
1143             if balance != c+u:
1144                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1145
1146             balance = c + u - balance
1147             for tx_hash, tx in history:
1148                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1149                 if not is_relevant:
1150                     continue
1151                 if value is not None:
1152                     balance += value
1153
1154                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1155                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1156
1157         return result
1158
1159
1160     def get_label(self, tx_hash):
1161         label = self.labels.get(tx_hash)
1162         is_default = (label == '') or (label is None)
1163         if is_default: label = self.get_default_label(tx_hash)
1164         return label, is_default
1165
1166
1167     def get_default_label(self, tx_hash):
1168         tx = self.transactions.get(tx_hash)
1169         default_label = ''
1170         if tx:
1171             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1172             if is_mine:
1173                 for o in tx.outputs:
1174                     o_addr, _ = o
1175                     if not self.is_mine(o_addr):
1176                         try:
1177                             default_label = self.labels[o_addr]
1178                         except KeyError:
1179                             default_label = '>' + o_addr
1180                         break
1181                 else:
1182                     default_label = '(internal)'
1183             else:
1184                 for o in tx.outputs:
1185                     o_addr, _ = o
1186                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1187                         break
1188                 else:
1189                     for o in tx.outputs:
1190                         o_addr, _ = o
1191                         if self.is_mine(o_addr):
1192                             break
1193                     else:
1194                         o_addr = None
1195
1196                 if o_addr:
1197                     dest_label = self.labels.get(o_addr)
1198                     try:
1199                         default_label = self.labels[o_addr]
1200                     except KeyError:
1201                         default_label = '<' + o_addr
1202
1203         return default_label
1204
1205
1206     def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None ):
1207         for address, x in outputs:
1208             assert is_valid(address), "Address " + address + " is invalid!"
1209         amount = sum( map(lambda x:x[1], outputs) )
1210         inputs, total, fee = self.choose_tx_inputs( amount, fee, len(outputs), domain )
1211         if not inputs:
1212             raise ValueError("Not enough funds")
1213         self.add_input_info(inputs)
1214         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr)
1215         return Transaction.from_io(inputs, outputs)
1216
1217
1218     def mktx(self, outputs, password, fee=None, change_addr=None, domain= None ):
1219         tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain)
1220         keypairs = {}
1221         self.add_keypairs_from_wallet(tx, keypairs, password)
1222         if keypairs:
1223             self.sign_transaction(tx, keypairs, password)
1224         return tx
1225
1226
1227     def add_input_info(self, inputs):
1228         for txin in inputs:
1229             address = txin['address']
1230             if address in self.imported_keys.keys():
1231                 continue
1232             account_id, sequence = self.get_address_index(address)
1233             account = self.accounts[account_id]
1234             txin['KeyID'] = account.get_keyID(sequence)
1235             redeemScript = account.redeem_script(sequence)
1236             if redeemScript: 
1237                 txin['redeemScript'] = redeemScript
1238             else:
1239                 txin['redeemPubkey'] = account.get_pubkey(*sequence)
1240
1241
1242     def sign_transaction(self, tx, keypairs, password):
1243         tx.sign(keypairs)
1244         run_hook('sign_transaction', tx, password)
1245
1246
1247     def sendtx(self, tx):
1248         # synchronous
1249         h = self.send_tx(tx)
1250         self.tx_event.wait()
1251         return self.receive_tx(h, tx)
1252
1253     def send_tx(self, tx):
1254         # asynchronous
1255         self.tx_event.clear()
1256         self.network.interface.send([('blockchain.transaction.broadcast', [str(tx)])], self.on_broadcast)
1257         return tx.hash()
1258
1259     def on_broadcast(self, i, r):
1260         self.tx_result = r.get('result')
1261         self.tx_event.set()
1262
1263     def receive_tx(self, tx_hash, tx):
1264         out = self.tx_result 
1265         if out != tx_hash:
1266             return False, "error: " + out
1267         run_hook('receive_tx', tx, self)
1268         return True, out
1269
1270
1271
1272     def update_password(self, old_password, new_password):
1273         if new_password == '': new_password = None
1274         decoded = self.get_seed(old_password)
1275         self.seed = pw_encode( decoded, new_password)
1276         self.storage.put('seed', self.seed, True)
1277         self.use_encryption = (new_password != None)
1278         self.storage.put('use_encryption', self.use_encryption,True)
1279         for k in self.imported_keys.keys():
1280             a = self.imported_keys[k]
1281             b = pw_decode(a, old_password)
1282             c = pw_encode(b, new_password)
1283             self.imported_keys[k] = c
1284         self.storage.put('imported_keys', self.imported_keys, True)
1285
1286         for k, v in self.master_private_keys.items():
1287             b = pw_decode(v, old_password)
1288             c = pw_encode(b, new_password)
1289             self.master_private_keys[k] = c
1290         self.storage.put('master_private_keys', self.master_private_keys, True)
1291
1292
1293     def freeze(self,addr):
1294         if self.is_mine(addr) and addr not in self.frozen_addresses:
1295             self.frozen_addresses.append(addr)
1296             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1297             return True
1298         else:
1299             return False
1300
1301     def unfreeze(self,addr):
1302         if self.is_mine(addr) and addr in self.frozen_addresses:
1303             self.frozen_addresses.remove(addr)
1304             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1305             return True
1306         else:
1307             return False
1308
1309
1310     def set_verifier(self, verifier):
1311         self.verifier = verifier
1312
1313         # review transactions that are in the history
1314         for addr, hist in self.history.items():
1315             if hist == ['*']: continue
1316             for tx_hash, tx_height in hist:
1317                 if tx_height>0:
1318                     # add it in case it was previously unconfirmed
1319                     self.verifier.add(tx_hash, tx_height)
1320
1321
1322         # if we are on a pruning server, remove unverified transactions
1323         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1324         for tx_hash in self.transactions.keys():
1325             if tx_hash not in vr:
1326                 self.transactions.pop(tx_hash)
1327
1328
1329
1330     def check_new_history(self, addr, hist):
1331         
1332         # check that all tx in hist are relevant
1333         if hist != ['*']:
1334             for tx_hash, height in hist:
1335                 tx = self.transactions.get(tx_hash)
1336                 if not tx: continue
1337                 if not tx.has_address(addr):
1338                     return False
1339
1340         # check that we are not "orphaning" a transaction
1341         old_hist = self.history.get(addr,[])
1342         if old_hist == ['*']: return True
1343
1344         for tx_hash, height in old_hist:
1345             if tx_hash in map(lambda x:x[0], hist): continue
1346             found = False
1347             for _addr, _hist in self.history.items():
1348                 if _addr == addr: continue
1349                 if _hist == ['*']: continue
1350                 _tx_hist = map(lambda x:x[0], _hist)
1351                 if tx_hash in _tx_hist:
1352                     found = True
1353                     break
1354
1355             if not found:
1356                 tx = self.transactions.get(tx_hash)
1357                 # tx might not be there
1358                 if not tx: continue
1359                 
1360                 # already verified?
1361                 if self.verifier.get_height(tx_hash):
1362                     continue
1363                 # unconfirmed tx
1364                 print_error("new history is orphaning transaction:", tx_hash)
1365                 # check that all outputs are not mine, request histories
1366                 ext_requests = []
1367                 for _addr, _v in tx.outputs:
1368                     # assert not self.is_mine(_addr)
1369                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1370
1371                 ext_h = self.network.synchronous_get(ext_requests)
1372                 print_error("sync:", ext_requests, ext_h)
1373                 height = None
1374                 for h in ext_h:
1375                     if h == ['*']: continue
1376                     for item in h:
1377                         if item.get('tx_hash') == tx_hash:
1378                             height = item.get('height')
1379                 if height:
1380                     print_error("found height for", tx_hash, height)
1381                     self.verifier.add(tx_hash, height)
1382                 else:
1383                     print_error("removing orphaned tx from history", tx_hash)
1384                     self.transactions.pop(tx_hash)
1385
1386         return True
1387
1388
1389
1390     def check_new_tx(self, tx_hash, tx):
1391         # 1 check that tx is referenced in addr_history. 
1392         addresses = []
1393         for addr, hist in self.history.items():
1394             if hist == ['*']:continue
1395             for txh, height in hist:
1396                 if txh == tx_hash: 
1397                     addresses.append(addr)
1398
1399         if not addresses:
1400             return False
1401
1402         # 2 check that referencing addresses are in the tx
1403         for addr in addresses:
1404             if not tx.has_address(addr):
1405                 return False
1406
1407         return True
1408
1409
1410     def start_threads(self, network):
1411         from verifier import TxVerifier
1412         self.network = network
1413         if self.network is not None:
1414             self.verifier = TxVerifier(self.network, self.storage)
1415             self.verifier.start()
1416             self.set_verifier(self.verifier)
1417             self.synchronizer = WalletSynchronizer(self, network)
1418             self.synchronizer.start()
1419         else:
1420             self.verifier = None
1421             self.synchronizer =None
1422
1423     def stop_threads(self):
1424         if self.network:
1425             self.verifier.stop()
1426             self.synchronizer.stop()
1427
1428
1429     def restore(self, callback):
1430         from i18n import _
1431         def wait_for_wallet():
1432             self.set_up_to_date(False)
1433             while not self.is_up_to_date():
1434                 msg = "%s\n%s %d\n%s %.1f"%(
1435                     _("Please wait..."),
1436                     _("Addresses generated:"),
1437                     len(self.addresses(True)), 
1438                     _("Kilobytes received:"), 
1439                     self.network.interface.bytes_received/1024.)
1440
1441                 apply(callback, (msg,))
1442                 time.sleep(0.1)
1443
1444         def wait_for_network():
1445             while not self.network.is_connected():
1446                 msg = "%s \n" % (_("Connecting..."))
1447                 apply(callback, (msg,))
1448                 time.sleep(0.1)
1449
1450         # wait until we are connected, because the user might have selected another server
1451         if self.network:
1452             wait_for_network()
1453             wait_for_wallet()
1454         else:
1455             self.synchronize()
1456             
1457         self.fill_addressbook()
1458
1459
1460
1461
1462 class WalletSynchronizer(threading.Thread):
1463
1464
1465     def __init__(self, wallet, network):
1466         threading.Thread.__init__(self)
1467         self.daemon = True
1468         self.wallet = wallet
1469         self.network = network
1470         self.was_updated = True
1471         self.running = False
1472         self.lock = threading.Lock()
1473         self.queue = Queue.Queue()
1474
1475     def stop(self):
1476         with self.lock: self.running = False
1477
1478     def is_running(self):
1479         with self.lock: return self.running
1480
1481     
1482     def subscribe_to_addresses(self, addresses):
1483         messages = []
1484         for addr in addresses:
1485             messages.append(('blockchain.address.subscribe', [addr]))
1486         self.network.subscribe( messages, lambda i,r: self.queue.put(r))
1487
1488
1489     def run(self):
1490         with self.lock:
1491             self.running = True
1492
1493         while self.is_running():
1494
1495             if not self.network.is_connected():
1496                 self.network.wait_until_connected()
1497                 
1498             self.run_interface()
1499
1500
1501     def run_interface(self):
1502
1503         print_error("synchronizer: connected to", self.network.main_server())
1504
1505         requested_tx = []
1506         missing_tx = []
1507         requested_histories = {}
1508
1509         # request any missing transactions
1510         for history in self.wallet.history.values():
1511             if history == ['*']: continue
1512             for tx_hash, tx_height in history:
1513                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1514                     missing_tx.append( (tx_hash, tx_height) )
1515
1516         if missing_tx:
1517             print_error("missing tx", missing_tx)
1518
1519         # subscriptions
1520         self.subscribe_to_addresses(self.wallet.addresses(True))
1521
1522         while self.is_running():
1523             # 1. create new addresses
1524             new_addresses = self.wallet.synchronize()
1525
1526             # request missing addresses
1527             if new_addresses:
1528                 self.subscribe_to_addresses(new_addresses)
1529
1530             # request missing transactions
1531             for tx_hash, tx_height in missing_tx:
1532                 if (tx_hash, tx_height) not in requested_tx:
1533                     self.network.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
1534                     requested_tx.append( (tx_hash, tx_height) )
1535             missing_tx = []
1536
1537             # detect if situation has changed
1538             if self.network.is_up_to_date() and self.queue.empty():
1539                 if not self.wallet.is_up_to_date():
1540                     self.wallet.set_up_to_date(True)
1541                     self.was_updated = True
1542             else:
1543                 if self.wallet.is_up_to_date():
1544                     self.wallet.set_up_to_date(False)
1545                     self.was_updated = True
1546
1547             if self.was_updated:
1548                 self.network.trigger_callback('updated')
1549                 self.was_updated = False
1550
1551             # 2. get a response
1552             try:
1553                 r = self.queue.get(block=True, timeout=1)
1554             except Queue.Empty:
1555                 continue
1556
1557             # see if it changed
1558             #if interface != self.network.interface:
1559             #    break
1560             
1561             if not r:
1562                 continue
1563
1564             # 3. handle response
1565             method = r['method']
1566             params = r['params']
1567             result = r.get('result')
1568             error = r.get('error')
1569             if error:
1570                 print "error", r
1571                 continue
1572
1573             if method == 'blockchain.address.subscribe':
1574                 addr = params[0]
1575                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1576                     if requested_histories.get(addr) is None:
1577                         self.network.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
1578                         requested_histories[addr] = result
1579
1580             elif method == 'blockchain.address.get_history':
1581                 addr = params[0]
1582                 print_error("receiving history", addr, result)
1583                 if result == ['*']:
1584                     assert requested_histories.pop(addr) == '*'
1585                     self.wallet.receive_history_callback(addr, result)
1586                 else:
1587                     hist = []
1588                     # check that txids are unique
1589                     txids = []
1590                     for item in result:
1591                         tx_hash = item['tx_hash']
1592                         if tx_hash not in txids:
1593                             txids.append(tx_hash)
1594                             hist.append( (tx_hash, item['height']) )
1595
1596                     if len(hist) != len(result):
1597                         raise Exception("error: server sent history with non-unique txid", result)
1598
1599                     # check that the status corresponds to what was announced
1600                     rs = requested_histories.pop(addr)
1601                     if self.wallet.get_status(hist) != rs:
1602                         raise Exception("error: status mismatch: %s"%addr)
1603                 
1604                     # store received history
1605                     self.wallet.receive_history_callback(addr, hist)
1606
1607                     # request transactions that we don't have 
1608                     for tx_hash, tx_height in hist:
1609                         if self.wallet.transactions.get(tx_hash) is None:
1610                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1611                                 missing_tx.append( (tx_hash, tx_height) )
1612
1613             elif method == 'blockchain.transaction.get':
1614                 tx_hash = params[0]
1615                 tx_height = params[1]
1616                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1617                 tx = Transaction(result)
1618                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1619                 self.was_updated = True
1620                 requested_tx.remove( (tx_hash, tx_height) )
1621                 print_error("received tx:", tx_hash, len(tx.raw))
1622
1623             else:
1624                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1625
1626             if self.was_updated and not requested_tx:
1627                 self.network.trigger_callback('updated')
1628                 # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
1629                 self.network.trigger_callback("new_transaction") 
1630                 self.was_updated = False
1631
1632
1633
1634
1635 class OldWallet(NewWallet):
1636
1637     def init_seed(self, seed):
1638         import mnemonic
1639         
1640         if self.seed: 
1641             raise Exception("a seed exists")
1642
1643         if not seed:
1644             seed = random_seed(128)
1645
1646         self.seed_version = OLD_SEED_VERSION
1647
1648         # see if seed was entered as hex
1649         seed = seed.strip()
1650         try:
1651             assert seed
1652             seed.decode('hex')
1653             self.seed = str(seed)
1654             return
1655         except Exception:
1656             pass
1657
1658         words = seed.split()
1659         try:
1660             mnemonic.mn_decode(words)
1661         except Exception:
1662             raise
1663
1664         self.seed = mnemonic.mn_decode(words)
1665
1666         if not self.seed:
1667             raise Exception("Invalid seed")
1668             
1669
1670
1671     def get_master_public_key(self):
1672         return self.storage.get("master_public_key")
1673
1674     def create_accounts(self, password):
1675         seed = pw_decode(self.seed, password)
1676         mpk = OldAccount.mpk_from_seed(seed)
1677         self.create_account(mpk)
1678
1679     def create_account(self, mpk):
1680         self.storage.put('master_public_key', mpk, True)
1681         self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
1682         self.save_accounts()
1683
1684     def create_watching_only_wallet(self, mpk):
1685         self.seed_version = OLD_SEED_VERSION
1686         self.storage.put('seed_version', self.seed_version, True)
1687         self.create_account(mpk)
1688
1689     def get_seed(self, password):
1690         seed = pw_decode(self.seed, password)
1691         self.accounts[0].check_seed(seed)
1692         return seed
1693
1694     def get_mnemonic(self, password):
1695         import mnemonic
1696         s = pw_decode(self.seed, password)
1697         return ' '.join(mnemonic.mn_encode(s))
1698
1699
1700     def add_keypairs_from_KeyID(self, tx, keypairs, password):
1701         # first check the provided password
1702         seed = self.get_seed(password)
1703         for txin in tx.inputs:
1704             keyid = txin.get('KeyID')
1705             if keyid:
1706                 m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
1707                 if not m: continue
1708                 mpk = m.group(1)
1709                 if mpk != self.storage.get('master_public_key'): continue 
1710                 for_change = int(m.group(2))
1711                 num = int(m.group(3))
1712                 account = self.accounts[0]
1713                 addr = account.get_address(for_change, num)
1714                 txin['address'] = addr # fixme: side effect
1715                 pk = account.get_private_key(seed, (for_change, num))
1716                 pubkey = public_key_from_private_key(pk)
1717                 keypairs[pubkey] = pk
1718
1719
1720     def get_account_name(self, k):
1721         assert k == 0
1722         return 'Main account'
1723
1724     def is_seeded(self, account):
1725         return self.seed is not None
1726
1727     def get_private_key(self, address, password):
1728         if self.is_watching_only():
1729             return []
1730
1731         # first check the provided password
1732         seed = self.get_seed(password)
1733         
1734         out = []
1735         if address in self.imported_keys.keys():
1736             out.append( pw_decode( self.imported_keys[address], password ) )
1737         else:
1738             account_id, sequence = self.get_address_index(address)
1739             pk = self.accounts[0].get_private_key(seed, sequence)
1740             out.append(pk)
1741         return out
1742
1743     def check_pending_accounts(self):
1744         pass
1745
1746
1747 # former WalletFactory
1748 class Wallet(object):
1749
1750     def __new__(self, storage):
1751         config = storage.config
1752         if config.get('bitkey', False):
1753             # if user requested support for Bitkey device,
1754             # import Bitkey driver
1755             from wallet_bitkey import WalletBitkey
1756             return WalletBitkey(config)
1757
1758         if not storage.file_exists:
1759             seed_version = NEW_SEED_VERSION if config.get('bip32') is True else OLD_SEED_VERSION
1760         else:
1761             seed_version = storage.get('seed_version')
1762             if not seed_version:
1763                 seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key')) == 128 else NEW_SEED_VERSION
1764
1765         if seed_version == OLD_SEED_VERSION:
1766             return OldWallet(storage)
1767         elif seed_version == NEW_SEED_VERSION:
1768             return NewWallet(storage)
1769         else:
1770             msg = "This wallet seed is not supported."
1771             if seed_version in [5]:
1772                 msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
1773             print msg
1774             sys.exit(1)
1775
1776
1777
1778     @classmethod
1779     def from_seed(self, seed, storage):
1780         import mnemonic
1781         if not seed:
1782             return 
1783
1784         words = seed.strip().split()
1785         try:
1786             mnemonic.mn_decode(words)
1787             uses_electrum_words = True
1788         except Exception:
1789             uses_electrum_words = False
1790
1791         try:
1792             seed.decode('hex')
1793             is_hex = True
1794         except Exception:
1795             is_hex = False
1796          
1797         if is_hex or (uses_electrum_words and len(words) != 13):
1798             #print "old style wallet", len(words), words
1799             w = OldWallet(storage)
1800             w.init_seed(seed) #hex
1801         else:
1802             #assert is_seed(seed)
1803             w = NewWallet(storage)
1804             w.init_seed(seed)
1805
1806         return w
1807
1808
1809     @classmethod
1810     def from_mpk(self, mpk, storage):
1811
1812         try:
1813             int(mpk, 16)
1814             old = True
1815         except:
1816             old = False
1817
1818         if old:
1819             w = OldWallet(storage)
1820             w.seed = ''
1821             w.create_watching_only_wallet(mpk)
1822         else:
1823             w = NewWallet(storage)
1824             w.create_watching_only_wallet(mpk)
1825
1826         return w