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