account names
[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         mpk = OldAccount.mpk_from_seed(self.seed)
374         self.storage.put('master_public_key', mpk, True)
375         self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
376         self.save_accounts()
377
378
379     def save_accounts(self):
380         d = {}
381         for k, v in self.accounts.items():
382             d[k] = v.dump()
383         self.storage.put('accounts', d, True)
384
385     
386
387     def load_accounts(self):
388         d = self.storage.get('accounts', {})
389         self.accounts = {}
390         for k, v in d.items():
391             if k == 0:
392                 v['mpk'] = self.storage.get('master_public_key')
393                 self.accounts[k] = OldAccount(v)
394             elif '&' in k:
395                 self.accounts[k] = BIP32_Account_2of2(v)
396             else:
397                 self.accounts[k] = BIP32_Account(v)
398
399
400     def addresses(self, include_change = True):
401         o = self.get_account_addresses(-1, include_change)
402         for a in self.accounts.keys():
403             o += self.get_account_addresses(a, include_change)
404         o += self.first_addresses.values()
405         return o
406
407
408     def is_mine(self, address):
409         return address in self.addresses(True)
410
411
412     def is_change(self, address):
413         if not self.is_mine(address): return False
414         if address in self.imported_keys.keys(): return False
415         acct, s = self.get_address_index(address)
416         return s[0] == 1
417
418     def get_master_public_key(self):
419         raise
420         return self.storage.get("master_public_key")
421
422     def get_master_private_key(self, account, password):
423         master_k = pw_decode( self.master_private_keys[account], password)
424         master_c, master_K, master_Kc = self.master_public_keys[account]
425         try:
426             K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
427             assert K.encode('hex') == master_K
428         except:
429             raise BaseException("Invalid password")
430         return master_k
431
432
433     def get_address_index(self, address):
434         if address in self.imported_keys.keys():
435             return -1, None
436
437         for account in self.accounts.keys():
438             for for_change in [0,1]:
439                 addresses = self.accounts[account].get_addresses(for_change)
440                 for addr in addresses:
441                     if address == addr:
442                         return account, (for_change, addresses.index(addr))
443
444         raise BaseException("not found")
445
446
447     def rebase_sequence(self, account, sequence):
448         c, i = sequence
449         dd = []
450         for a in account.split('&'):
451             s = a.strip()
452             m = re.match("(m/\d+'/)(\d+)", s)
453             root = m.group(1)
454             num = int(m.group(2))
455             dd.append( (root, [num,c,i] ) )
456         return dd
457         
458
459     def get_keyID(self, account, sequence):
460         if account == 0:
461             return 'old'
462
463         rs = self.rebase_sequence(account, sequence)
464         dd = []
465         for root, public_sequence in rs:
466             c, K, _ = self.master_public_keys[root]
467             s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
468             dd.append( 'bip32(%s,%s,%s)'%(c,K, s) )
469         return '&'.join(dd)
470
471
472     def get_public_key(self, address):
473         account, sequence = self.get_address_index(address)
474         return self.accounts[account].get_pubkey( *sequence )
475
476
477     def decode_seed(self, password):
478         seed = pw_decode(self.seed, password)
479         #todo:  #self.sequences[0].check_seed(seed)
480         return seed
481         
482
483     def get_private_key(self, address, password):
484         out = []
485         if address in self.imported_keys.keys():
486             out.append( pw_decode( self.imported_keys[address], password ) )
487         else:
488             account, sequence = self.get_address_index(address)
489             if account == 0:
490                 seed = self.decode_seed(password)
491                 pk = self.accounts[account].get_private_key(seed, sequence)
492                 out.append(pk)
493                 return out
494
495             # assert address == self.accounts[account].get_address(*sequence)
496             rs = self.rebase_sequence( account, sequence)
497             for root, public_sequence in rs:
498
499                 if root not in self.master_private_keys.keys(): continue
500                 master_k = self.get_master_private_key(root, password)
501                 master_c, _, _ = self.master_public_keys[root]
502                 pk = bip32_private_key( public_sequence, master_k.decode('hex'), master_c.decode('hex'))
503                 out.append(pk)
504                     
505         return out
506
507
508
509
510     def signrawtransaction(self, tx, input_info, private_keys, password):
511         import deserialize
512         unspent_coins = self.get_unspent_coins()
513         seed = self.decode_seed(password)
514
515         # build a list of public/private keys
516         keypairs = {}
517         for sec in private_keys:
518             pubkey = public_key_from_private_key(sec)
519             keypairs[ pubkey ] = sec
520
521
522         for txin in tx.inputs:
523             # convert to own format
524             txin['tx_hash'] = txin['prevout_hash']
525             txin['index'] = txin['prevout_n']
526
527             for item in input_info:
528                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
529                     txin['raw_output_script'] = item['scriptPubKey']
530                     txin['redeemScript'] = item.get('redeemScript')
531                     txin['KeyID'] = item.get('KeyID')
532                     break
533             else:
534                 for item in unspent_coins:
535                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
536                         print_error( "tx input is in unspent coins" )
537                         txin['raw_output_script'] = item['raw_output_script']
538                         account, sequence = self.get_address_index(item['address'])
539                         if account != -1:
540                             txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
541                         break
542                 else:
543                     raise BaseException("Unknown transaction input. Please provide the 'input_info' parameter, or synchronize this wallet")
544
545             # if available, derive private_keys from KeyID
546             keyid = txin.get('KeyID')
547             if keyid:
548                 roots = []
549                 for s in keyid.split('&'):
550                     m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
551                     if not m: continue
552                     c = m.group(1)
553                     K = m.group(2)
554                     sequence = m.group(3)
555                     root = self.find_root_by_master_key(c,K)
556                     if not root: continue
557                     sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
558                     root = root + '%d'%sequence[0]
559                     sequence = sequence[1:]
560                     roots.append((root,sequence)) 
561
562                 account_id = " & ".join( map(lambda x:x[0], roots) )
563                 account = self.accounts.get(account_id)
564                 if not account: continue
565                 addr = account.get_address(*sequence)
566                 txin['address'] = addr
567                 pk = self.get_private_key(addr, password)
568                 for sec in pk:
569                     pubkey = public_key_from_private_key(sec)
570                     keypairs[pubkey] = sec
571
572             redeem_script = txin.get("redeemScript")
573             print_error( "p2sh:", "yes" if redeem_script else "no")
574             if redeem_script:
575                 addr = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
576             else:
577                 addr = deserialize.get_address_from_output_script(txin["raw_output_script"].decode('hex'))
578             txin['address'] = addr
579
580             # add private keys that are in the wallet
581             pk = self.get_private_key(addr, password)
582             for sec in pk:
583                 pubkey = public_key_from_private_key(sec)
584                 keypairs[pubkey] = sec
585                 if not redeem_script:
586                     txin['redeemPubkey'] = pubkey
587
588             print txin
589
590         tx.sign( keypairs )
591
592     def sign_message(self, address, message, password):
593         sec = self.get_private_key(address, password)
594         key = regenerate_key(sec)
595         compressed = is_compressed(sec)
596         return key.sign_message(message, compressed, address)
597
598     def verify_message(self, address, signature, message):
599         try:
600             EC_KEY.verify_message(address, signature, message)
601             return True
602         except BaseException as e:
603             print_error("Verification error: {0}".format(e))
604             return False
605
606
607     def change_gap_limit(self, value):
608         if value >= self.gap_limit:
609             self.gap_limit = value
610             self.storage.put('gap_limit', self.gap_limit, True)
611             self.interface.poke('synchronizer')
612             return True
613
614         elif value >= self.min_acceptable_gap():
615             for key, account in self.accounts.items():
616                 addresses = account[0]
617                 k = self.num_unused_trailing_addresses(addresses)
618                 n = len(addresses) - k + value
619                 addresses = addresses[0:n]
620                 self.accounts[key][0] = addresses
621
622             self.gap_limit = value
623             self.storage.put('gap_limit', self.gap_limit, True)
624             self.save_accounts()
625             return True
626         else:
627             return False
628
629     def num_unused_trailing_addresses(self, addresses):
630         k = 0
631         for a in addresses[::-1]:
632             if self.history.get(a):break
633             k = k + 1
634         return k
635
636     def min_acceptable_gap(self):
637         # fixme: this assumes wallet is synchronized
638         n = 0
639         nmax = 0
640
641         for account in self.accounts.values():
642             addresses = account.get_addresses(0)
643             k = self.num_unused_trailing_addresses(addresses)
644             for a in addresses[0:-k]:
645                 if self.history.get(a):
646                     n = 0
647                 else:
648                     n += 1
649                     if n > nmax: nmax = n
650         return nmax + 1
651
652
653     def address_is_old(self, address):
654         age = -1
655         h = self.history.get(address, [])
656         if h == ['*']:
657             return True
658         for tx_hash, tx_height in h:
659             if tx_height == 0:
660                 tx_age = 0
661             else: 
662                 tx_age = self.verifier.blockchain.height - tx_height + 1
663             if tx_age > age:
664                 age = tx_age
665         return age > 2
666
667
668     def synchronize_sequence(self, account, for_change):
669         limit = self.gap_limit_for_change if for_change else self.gap_limit
670         new_addresses = []
671         while True:
672             addresses = account.get_addresses(for_change)
673             if len(addresses) < limit:
674                 address = account.create_new_address(for_change)
675                 self.history[address] = []
676                 new_addresses.append( address )
677                 continue
678
679             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
680                 break
681             else:
682                 address = account.create_new_address(for_change)
683                 self.history[address] = []
684                 new_addresses.append( address )
685
686         return new_addresses
687         
688
689
690     def create_pending_accounts(self):
691         for account_type in ['1','2of2','2of3']:
692             a = self.new_account_address(account_type)
693             if self.address_is_old(a):
694                 print "creating account", a
695                 self.create_account(account_type)
696
697
698     def synchronize_account(self, account):
699         new = []
700         new += self.synchronize_sequence(account, 0)
701         new += self.synchronize_sequence(account, 1)
702         return new
703
704
705     def synchronize(self):
706         if self.master_public_keys:
707             self.create_pending_accounts()
708         new = []
709         for account in self.accounts.values():
710             new += self.synchronize_account(account)
711         if new:
712             self.save_accounts()
713             self.storage.put('addr_history', self.history, True)
714         return new
715
716
717     def is_found(self):
718         return self.history.values() != [[]] * len(self.history) 
719
720
721     def add_contact(self, address, label=None):
722         self.addressbook.append(address)
723         self.storage.put('contacts', self.addressbook, True)
724         if label:  
725             self.labels[address] = label
726             self.storage.put('labels', self.labels, True)
727
728     def delete_contact(self, addr):
729         if addr in self.addressbook:
730             self.addressbook.remove(addr)
731             self.storage.put('addressbook', self.addressbook, True)
732
733
734     def fill_addressbook(self):
735         for tx_hash, tx in self.transactions.items():
736             is_relevant, is_send, _, _ = self.get_tx_value(tx)
737             if is_send:
738                 for addr, v in tx.outputs:
739                     if not self.is_mine(addr) and addr not in self.addressbook:
740                         self.addressbook.append(addr)
741         # redo labels
742         # self.update_tx_labels()
743
744     def get_num_tx(self, address):
745         n = 0 
746         for tx in self.transactions.values():
747             if address in map(lambda x:x[0], tx.outputs): n += 1
748         return n
749
750
751     def get_address_flags(self, addr):
752         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
753         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
754         return flags
755         
756
757     def get_tx_value(self, tx, account=None):
758         domain = self.get_account_addresses(account)
759         return tx.get_value(domain, self.prevout_values)
760
761     
762     def update_tx_outputs(self, tx_hash):
763         tx = self.transactions.get(tx_hash)
764
765         for i, (addr, value) in enumerate(tx.outputs):
766             key = tx_hash+ ':%d'%i
767             self.prevout_values[key] = value
768
769         for item in tx.inputs:
770             if self.is_mine(item.get('address')):
771                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
772                 self.spent_outputs.append(key)
773
774
775     def get_addr_balance(self, address):
776         assert self.is_mine(address)
777         h = self.history.get(address,[])
778         if h == ['*']: return 0,0
779         c = u = 0
780         received_coins = []   # list of coins received at address
781
782         for tx_hash, tx_height in h:
783             tx = self.transactions.get(tx_hash)
784             if not tx: continue
785
786             for i, (addr, value) in enumerate(tx.outputs):
787                 if addr == address:
788                     key = tx_hash + ':%d'%i
789                     received_coins.append(key)
790
791         for tx_hash, tx_height in h:
792             tx = self.transactions.get(tx_hash)
793             if not tx: continue
794             v = 0
795
796             for item in tx.inputs:
797                 addr = item.get('address')
798                 if addr == address:
799                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
800                     value = self.prevout_values.get( key )
801                     if key in received_coins: 
802                         v -= value
803
804             for i, (addr, value) in enumerate(tx.outputs):
805                 key = tx_hash + ':%d'%i
806                 if addr == address:
807                     v += value
808
809             if tx_height:
810                 c += v
811             else:
812                 u += v
813         return c, u
814
815
816     def get_account_name(self, k):
817         if k == 0:
818             if self.seed_version == 4: 
819                 name = 'Main account'
820             else:
821                 name = 'Old account'
822         else:
823             name = self.labels.get(k, 'Unnamed account')
824         return name
825
826     def get_account_names(self):
827         accounts = {}
828         for k, account in self.accounts.items():
829             accounts[k] = self.get_account_name(k)
830         if self.imported_keys:
831             accounts[-1] = 'Imported keys'
832         return accounts
833
834     def get_account_addresses(self, a, include_change=True):
835         if a is None:
836             o = self.addresses(True)
837         elif a == -1:
838             o = self.imported_keys.keys()
839         else:
840             ac = self.accounts[a]
841             o = ac.get_addresses(0)
842             if include_change: o += ac.get_addresses(1)
843         return o
844
845     def get_imported_balance(self):
846         cc = uu = 0
847         for addr in self.imported_keys.keys():
848             c, u = self.get_addr_balance(addr)
849             cc += c
850             uu += u
851         return cc, uu
852
853     def get_account_balance(self, account):
854         if account is None:
855             return self.get_balance()
856         elif account == -1:
857             return self.get_imported_balance()
858         
859         conf = unconf = 0
860         for addr in self.get_account_addresses(account): 
861             c, u = self.get_addr_balance(addr)
862             conf += c
863             unconf += u
864         return conf, unconf
865
866     def get_frozen_balance(self):
867         conf = unconf = 0
868         for addr in self.frozen_addresses:
869             c, u = self.get_addr_balance(addr)
870             conf += c
871             unconf += u
872         return conf, unconf
873
874         
875     def get_balance(self):
876         cc = uu = 0
877         for a in self.accounts.keys():
878             c, u = self.get_account_balance(a)
879             cc += c
880             uu += u
881         c, u = self.get_imported_balance()
882         cc += c
883         uu += u
884         return cc, uu
885
886
887     def get_unspent_coins(self, domain=None):
888         coins = []
889         if domain is None: domain = self.addresses(True)
890         for addr in domain:
891             h = self.history.get(addr, [])
892             if h == ['*']: continue
893             for tx_hash, tx_height in h:
894                 tx = self.transactions.get(tx_hash)
895                 if tx is None: raise BaseException("Wallet not synchronized")
896                 for output in tx.d.get('outputs'):
897                     if output.get('address') != addr: continue
898                     key = tx_hash + ":%d" % output.get('index')
899                     if key in self.spent_outputs: continue
900                     output['tx_hash'] = tx_hash
901                     coins.append(output)
902         return coins
903
904
905
906     def choose_tx_inputs( self, amount, fixed_fee, account = None ):
907         """ todo: minimize tx size """
908         total = 0
909         fee = self.fee if fixed_fee is None else fixed_fee
910         domain = self.get_account_addresses(account)
911         coins = []
912         prioritized_coins = []
913         for i in self.frozen_addresses:
914             if i in domain: domain.remove(i)
915
916         for i in self.prioritized_addresses:
917             if i in domain: domain.remove(i)
918
919         coins = self.get_unspent_coins(domain)
920         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
921
922         inputs = []
923         coins = prioritized_coins + coins
924
925         for item in coins: 
926             addr = item.get('address')
927             v = item.get('value')
928             total += v
929             inputs.append( item )
930             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
931             if total >= amount + fee: break
932         else:
933             inputs = []
934
935         return inputs, total, fee
936
937
938     def set_fee(self, fee):
939         if self.fee != fee:
940             self.fee = fee
941             self.storage.put('fee_per_kb', self.fee, True)
942         
943     def estimated_fee(self, inputs):
944         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
945         fee = self.fee * int(round(estimated_size/1024.))
946         if fee == 0: fee = self.fee
947         return fee
948
949
950     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None, account=0 ):
951         "add change to a transaction"
952         change_amount = total - ( amount + fee )
953         if change_amount != 0:
954             if not change_addr:
955                 if account is None: 
956                     # send change to one of the accounts involved in the tx
957                     address = inputs[0].get('address')
958                     account, _ = self.get_address_index(address)
959
960                 if not self.use_change or account == -1:
961                     change_addr = inputs[-1]['address']
962                 else:
963                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
964
965             # Insert the change output at a random position in the outputs
966             posn = random.randint(0, len(outputs))
967             outputs[posn:posn] = [( change_addr,  change_amount)]
968         return outputs
969
970
971     def get_history(self, address):
972         with self.lock:
973             return self.history.get(address)
974
975
976     def get_status(self, h):
977         if not h: return None
978         if h == ['*']: return '*'
979         status = ''
980         for tx_hash, height in h:
981             status += tx_hash + ':%d:' % height
982         return hashlib.sha256( status ).digest().encode('hex')
983
984
985     def receive_tx_callback(self, tx_hash, tx, tx_height):
986         if not self.check_new_tx(tx_hash, tx):
987             # may happen due to pruning
988             print_error("received transaction that is no longer referenced in history", tx_hash)
989             return
990
991         with self.transaction_lock:
992             self.transactions[tx_hash] = tx
993
994             self.interface.pending_transactions_for_notifications.append(tx)
995
996             self.save_transactions()
997             if self.verifier and tx_height>0: 
998                 self.verifier.add(tx_hash, tx_height)
999             self.update_tx_outputs(tx_hash)
1000
1001
1002     def save_transactions(self):
1003         tx = {}
1004         for k,v in self.transactions.items():
1005             tx[k] = str(v)
1006         self.storage.put('transactions', tx, True)
1007
1008     def receive_history_callback(self, addr, hist):
1009
1010         if not self.check_new_history(addr, hist):
1011             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
1012             
1013         with self.lock:
1014             self.history[addr] = hist
1015             self.storage.put('addr_history', self.history, True)
1016
1017         if hist != ['*']:
1018             for tx_hash, tx_height in hist:
1019                 if tx_height>0:
1020                     # add it in case it was previously unconfirmed
1021                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1022
1023
1024     def get_tx_history(self, account=None):
1025         with self.transaction_lock:
1026             history = self.transactions.items()
1027             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1028             result = []
1029     
1030             balance = 0
1031             for tx_hash, tx in history:
1032                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1033                 if v is not None: balance += v
1034
1035             c, u = self.get_account_balance(account)
1036
1037             if balance != c+u:
1038                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1039
1040             balance = c + u - balance
1041             for tx_hash, tx in history:
1042                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1043                 if not is_relevant:
1044                     continue
1045                 if value is not None:
1046                     balance += value
1047
1048                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1049                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1050
1051         return result
1052
1053
1054     def get_label(self, tx_hash):
1055         label = self.labels.get(tx_hash)
1056         is_default = (label == '') or (label is None)
1057         if is_default: label = self.get_default_label(tx_hash)
1058         return label, is_default
1059
1060
1061     def get_default_label(self, tx_hash):
1062         tx = self.transactions.get(tx_hash)
1063         default_label = ''
1064         if tx:
1065             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1066             if is_mine:
1067                 for o in tx.outputs:
1068                     o_addr, _ = o
1069                     if not self.is_mine(o_addr):
1070                         try:
1071                             default_label = self.labels[o_addr]
1072                         except KeyError:
1073                             default_label = o_addr
1074                         break
1075                 else:
1076                     default_label = '(internal)'
1077             else:
1078                 for o in tx.outputs:
1079                     o_addr, _ = o
1080                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1081                         break
1082                 else:
1083                     for o in tx.outputs:
1084                         o_addr, _ = o
1085                         if self.is_mine(o_addr):
1086                             break
1087                     else:
1088                         o_addr = None
1089
1090                 if o_addr:
1091                     dest_label = self.labels.get(o_addr)
1092                     try:
1093                         default_label = self.labels[o_addr]
1094                     except KeyError:
1095                         default_label = o_addr
1096
1097         return default_label
1098
1099
1100     def mktx(self, outputs, password, fee=None, change_addr=None, account=None ):
1101         
1102         for address, x in outputs:
1103             assert is_valid(address)
1104
1105         amount = sum( map(lambda x:x[1], outputs) )
1106
1107         inputs, total, fee = self.choose_tx_inputs( amount, fee, account )
1108         if not inputs:
1109             raise ValueError("Not enough funds")
1110
1111         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account)
1112         tx = Transaction.from_io(inputs, outputs)
1113
1114         keypairs = {}
1115         for i, txin in enumerate(tx.inputs):
1116             address = txin['address']
1117
1118             account, sequence = self.get_address_index(address)
1119             txin['KeyID'] = self.get_keyID(account, sequence)
1120
1121             redeemScript = self.accounts[account].redeem_script(sequence)
1122             if redeemScript: 
1123                 txin['redeemScript'] = redeemScript
1124             else:
1125                 txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
1126
1127             private_keys = self.get_private_key(address, password)
1128
1129             for sec in private_keys:
1130                 pubkey = public_key_from_private_key(sec)
1131                 keypairs[ pubkey ] = sec
1132
1133         tx.sign(keypairs)
1134         for address, x in outputs:
1135             if address not in self.addressbook and not self.is_mine(address):
1136                 self.addressbook.append(address)
1137
1138         return tx
1139
1140
1141
1142     def sendtx(self, tx):
1143         # synchronous
1144         h = self.send_tx(tx)
1145         self.tx_event.wait()
1146         return self.receive_tx(h)
1147
1148     def send_tx(self, tx):
1149         # asynchronous
1150         self.tx_event.clear()
1151         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
1152         return tx.hash()
1153
1154     def receive_tx(self,tx_hash):
1155         out = self.tx_result 
1156         if out != tx_hash:
1157             return False, "error: " + out
1158         return True, out
1159
1160
1161
1162     def update_password(self, seed, old_password, new_password):
1163         if new_password == '': new_password = None
1164         # this will throw an exception if unicode cannot be converted
1165         self.seed = pw_encode( seed, new_password)
1166         self.storage.put('seed', self.seed, True)
1167         self.use_encryption = (new_password != None)
1168         self.storage.put('use_encryption', self.use_encryption,True)
1169         for k in self.imported_keys.keys():
1170             a = self.imported_keys[k]
1171             b = pw_decode(a, old_password)
1172             c = pw_encode(b, new_password)
1173             self.imported_keys[k] = c
1174         self.storage.put('imported_keys', self.imported_keys, True)
1175
1176         for k, v in self.master_private_keys.items():
1177             b = pw_decode(v, old_password)
1178             c = pw_encode(b, new_password)
1179             self.master_private_keys[k] = c
1180         self.storage.put('master_private_keys', self.master_private_keys, True)
1181
1182
1183     def freeze(self,addr):
1184         if self.is_mine(addr) and addr not in self.frozen_addresses:
1185             self.unprioritize(addr)
1186             self.frozen_addresses.append(addr)
1187             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1188             return True
1189         else:
1190             return False
1191
1192     def unfreeze(self,addr):
1193         if self.is_mine(addr) and addr in self.frozen_addresses:
1194             self.frozen_addresses.remove(addr)
1195             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1196             return True
1197         else:
1198             return False
1199
1200     def prioritize(self,addr):
1201         if self.is_mine(addr) and addr not in self.prioritized_addresses:
1202             self.unfreeze(addr)
1203             self.prioritized_addresses.append(addr)
1204             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1205             return True
1206         else:
1207             return False
1208
1209     def unprioritize(self,addr):
1210         if self.is_mine(addr) and addr in self.prioritized_addresses:
1211             self.prioritized_addresses.remove(addr)
1212             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1213             return True
1214         else:
1215             return False
1216
1217
1218     def set_verifier(self, verifier):
1219         self.verifier = verifier
1220
1221         # review transactions that are in the history
1222         for addr, hist in self.history.items():
1223             if hist == ['*']: continue
1224             for tx_hash, tx_height in hist:
1225                 if tx_height>0:
1226                     # add it in case it was previously unconfirmed
1227                     self.verifier.add(tx_hash, tx_height)
1228
1229
1230         # if we are on a pruning server, remove unverified transactions
1231         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1232         for tx_hash in self.transactions.keys():
1233             if tx_hash not in vr:
1234                 self.transactions.pop(tx_hash)
1235
1236
1237
1238     def check_new_history(self, addr, hist):
1239         
1240         # check that all tx in hist are relevant
1241         if hist != ['*']:
1242             for tx_hash, height in hist:
1243                 tx = self.transactions.get(tx_hash)
1244                 if not tx: continue
1245                 if not tx.has_address(addr):
1246                     return False
1247
1248         # check that we are not "orphaning" a transaction
1249         old_hist = self.history.get(addr,[])
1250         if old_hist == ['*']: return True
1251
1252         for tx_hash, height in old_hist:
1253             if tx_hash in map(lambda x:x[0], hist): continue
1254             found = False
1255             for _addr, _hist in self.history.items():
1256                 if _addr == addr: continue
1257                 if _hist == ['*']: continue
1258                 _tx_hist = map(lambda x:x[0], _hist)
1259                 if tx_hash in _tx_hist:
1260                     found = True
1261                     break
1262
1263             if not found:
1264                 tx = self.transactions.get(tx_hash)
1265                 # tx might not be there
1266                 if not tx: continue
1267                 
1268                 # already verified?
1269                 if self.verifier.get_height(tx_hash):
1270                     continue
1271                 # unconfirmed tx
1272                 print_error("new history is orphaning transaction:", tx_hash)
1273                 # check that all outputs are not mine, request histories
1274                 ext_requests = []
1275                 for _addr, _v in tx.outputs:
1276                     # assert not self.is_mine(_addr)
1277                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1278
1279                 ext_h = self.interface.synchronous_get(ext_requests)
1280                 print_error("sync:", ext_requests, ext_h)
1281                 height = None
1282                 for h in ext_h:
1283                     if h == ['*']: continue
1284                     for item in h:
1285                         if item.get('tx_hash') == tx_hash:
1286                             height = item.get('height')
1287                 if height:
1288                     print_error("found height for", tx_hash, height)
1289                     self.verifier.add(tx_hash, height)
1290                 else:
1291                     print_error("removing orphaned tx from history", tx_hash)
1292                     self.transactions.pop(tx_hash)
1293
1294         return True
1295
1296
1297
1298     def check_new_tx(self, tx_hash, tx):
1299         # 1 check that tx is referenced in addr_history. 
1300         addresses = []
1301         for addr, hist in self.history.items():
1302             if hist == ['*']:continue
1303             for txh, height in hist:
1304                 if txh == tx_hash: 
1305                     addresses.append(addr)
1306
1307         if not addresses:
1308             return False
1309
1310         # 2 check that referencing addresses are in the tx
1311         for addr in addresses:
1312             if not tx.has_address(addr):
1313                 return False
1314
1315         return True
1316
1317
1318     def start_threads(self, interface, blockchain):
1319         from verifier import TxVerifier
1320         self.interface = interface
1321         self.verifier = TxVerifier(interface, blockchain, self.storage)
1322         self.verifier.start()
1323         self.set_verifier(self.verifier)
1324         self.synchronizer = WalletSynchronizer(self)
1325         self.synchronizer.start()
1326
1327     def stop_threads(self):
1328         self.verifier.stop()
1329         self.synchronizer.stop()
1330
1331
1332
1333
1334 class WalletSynchronizer(threading.Thread):
1335
1336
1337     def __init__(self, wallet):
1338         threading.Thread.__init__(self)
1339         self.daemon = True
1340         self.wallet = wallet
1341         wallet.synchronizer = self
1342         self.interface = self.wallet.interface
1343         self.interface.register_channel('synchronizer')
1344         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1345         self.was_updated = True
1346         self.running = False
1347         self.lock = threading.Lock()
1348
1349     def stop(self):
1350         with self.lock: self.running = False
1351         self.interface.poke('synchronizer')
1352
1353     def is_running(self):
1354         with self.lock: return self.running
1355
1356     
1357     def subscribe_to_addresses(self, addresses):
1358         messages = []
1359         for addr in addresses:
1360             messages.append(('blockchain.address.subscribe', [addr]))
1361         self.interface.send( messages, 'synchronizer')
1362
1363
1364     def run(self):
1365         if not self.interface.is_connected:
1366             print_error( "synchronizer: waiting for interface")
1367             self.interface.connect_event.wait()
1368
1369         with self.lock: self.running = True
1370
1371         requested_tx = []
1372         missing_tx = []
1373         requested_histories = {}
1374
1375         # request any missing transactions
1376         for history in self.wallet.history.values():
1377             if history == ['*']: continue
1378             for tx_hash, tx_height in history:
1379                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1380                     missing_tx.append( (tx_hash, tx_height) )
1381         print_error("missing tx", missing_tx)
1382
1383         # wait until we are connected, in case the user is not connected
1384         while not self.interface.is_connected:
1385             time.sleep(1)
1386         
1387         # subscriptions
1388         self.subscribe_to_addresses(self.wallet.addresses(True))
1389
1390         while self.is_running():
1391             # 1. create new addresses
1392             new_addresses = self.wallet.synchronize()
1393
1394             # request missing addresses
1395             if new_addresses:
1396                 self.subscribe_to_addresses(new_addresses)
1397
1398             # request missing transactions
1399             for tx_hash, tx_height in missing_tx:
1400                 if (tx_hash, tx_height) not in requested_tx:
1401                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1402                     requested_tx.append( (tx_hash, tx_height) )
1403             missing_tx = []
1404
1405             # detect if situation has changed
1406             if not self.interface.is_up_to_date('synchronizer'):
1407                 if self.wallet.is_up_to_date():
1408                     self.wallet.set_up_to_date(False)
1409                     self.was_updated = True
1410             else:
1411                 if not self.wallet.is_up_to_date():
1412                     self.wallet.set_up_to_date(True)
1413                     self.was_updated = True
1414
1415             if self.was_updated:
1416                 self.interface.trigger_callback('updated')
1417                 self.was_updated = False
1418
1419             # 2. get a response
1420             r = self.interface.get_response('synchronizer')
1421
1422             # poke sends None. (needed during stop)
1423             if not r: continue
1424
1425             # 3. handle response
1426             method = r['method']
1427             params = r['params']
1428             result = r.get('result')
1429             error = r.get('error')
1430             if error:
1431                 print "error", r
1432                 continue
1433
1434             if method == 'blockchain.address.subscribe':
1435                 addr = params[0]
1436                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1437                     if requested_histories.get(addr) is None:
1438                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1439                         requested_histories[addr] = result
1440
1441             elif method == 'blockchain.address.get_history':
1442                 addr = params[0]
1443                 print_error("receiving history", addr, result)
1444                 if result == ['*']:
1445                     assert requested_histories.pop(addr) == '*'
1446                     self.wallet.receive_history_callback(addr, result)
1447                 else:
1448                     hist = []
1449                     # check that txids are unique
1450                     txids = []
1451                     for item in result:
1452                         tx_hash = item['tx_hash']
1453                         if tx_hash not in txids:
1454                             txids.append(tx_hash)
1455                             hist.append( (tx_hash, item['height']) )
1456
1457                     if len(hist) != len(result):
1458                         raise BaseException("error: server sent history with non-unique txid", result)
1459
1460                     # check that the status corresponds to what was announced
1461                     rs = requested_histories.pop(addr)
1462                     if self.wallet.get_status(hist) != rs:
1463                         raise BaseException("error: status mismatch: %s"%addr)
1464                 
1465                     # store received history
1466                     self.wallet.receive_history_callback(addr, hist)
1467
1468                     # request transactions that we don't have 
1469                     for tx_hash, tx_height in hist:
1470                         if self.wallet.transactions.get(tx_hash) is None:
1471                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1472                                 missing_tx.append( (tx_hash, tx_height) )
1473
1474             elif method == 'blockchain.transaction.get':
1475                 tx_hash = params[0]
1476                 tx_height = params[1]
1477                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1478                 tx = Transaction(result)
1479                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1480                 self.was_updated = True
1481                 requested_tx.remove( (tx_hash, tx_height) )
1482                 print_error("received tx:", tx_hash, len(tx.raw))
1483
1484             elif method == 'blockchain.transaction.broadcast':
1485                 self.wallet.tx_result = result
1486                 self.wallet.tx_event.set()
1487
1488             else:
1489                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1490
1491             if self.was_updated and not requested_tx:
1492                 self.interface.trigger_callback('updated')
1493                 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
1494                 
1495
1496                 self.was_updated = False