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