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