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