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