add class BIP32Sequence
[electrum-nvc.git] / lib / wallet.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import sys
20 import base64
21 import os
22 import re
23 import hashlib
24 import copy
25 import operator
26 import ast
27 import threading
28 import random
29 import aes
30 import Queue
31 import time
32
33 from util import print_msg, print_error, user_dir, format_satoshis
34 from bitcoin import *
35
36 # URL decode
37 _ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
38 urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
39
40 # AES encryption
41 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
42 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
43
44 def pw_encode(s, password):
45     if password:
46         secret = Hash(password)
47         return EncodeAES(secret, s)
48     else:
49         return s
50
51 def pw_decode(s, password):
52     if password is not None:
53         secret = Hash(password)
54         try:
55             d = DecodeAES(secret, s)
56         except:
57             raise BaseException('Invalid password')
58         return d
59     else:
60         return s
61
62
63
64
65
66 from version import ELECTRUM_VERSION, SEED_VERSION
67
68
69 class Wallet:
70     def __init__(self, config={}):
71
72         self.config = config
73         self.electrum_version = ELECTRUM_VERSION
74         self.gap_limit_for_change = 3 # constant
75
76         # saved fields
77         self.seed_version          = config.get('seed_version', SEED_VERSION)
78         self.gap_limit             = config.get('gap_limit', 5)
79         self.use_change            = config.get('use_change',True)
80         self.fee                   = int(config.get('fee',100000))
81         self.num_zeros             = int(config.get('num_zeros',0))
82         self.use_encryption        = config.get('use_encryption', False)
83         self.seed                  = config.get('seed', '')               # encrypted
84         self.labels                = config.get('labels', {})
85         self.aliases               = config.get('aliases', {})            # aliases for addresses
86         self.authorities           = config.get('authorities', {})        # trusted addresses
87         self.frozen_addresses      = config.get('frozen_addresses',[])
88         self.prioritized_addresses = config.get('prioritized_addresses',[])
89         self.receipts              = config.get('receipts',{})            # signed URIs
90         self.addressbook           = config.get('contacts', [])
91         self.imported_keys         = config.get('imported_keys',{})
92         self.history               = config.get('addr_history',{})        # address -> list(txid, height)
93         self.tx_height             = config.get('tx_height',{})
94         self.requested_amounts     = config.get('requested_amounts',{}) 
95         self.accounts              = config.get('accounts', {})   # this should not include public keys
96
97         self.sequences = {}
98         self.sequences[0] = DeterministicSequence(self.config.get('master_public_key'))
99
100         if self.accounts.get(0) is None:
101             self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
102
103         self.transactions = {}
104         tx = config.get('transactions',{})
105         try:
106             for k,v in tx.items(): self.transactions[k] = Transaction(v)
107         except:
108             print_msg("Warning: Cannot deserialize transactions. skipping")
109         
110         # plugins
111         self.plugins = []
112         self.plugin_hooks = {}
113
114         # not saved
115         self.prevout_values = {}     # my own transaction outputs
116         self.spent_outputs = []
117         self.receipt = None          # next receipt
118         self.banner = ''
119
120         # spv
121         self.verifier = None
122
123         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
124         # interface.is_up_to_date() returns true when all requests have been answered and processed
125         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
126         
127         self.up_to_date = False
128         self.lock = threading.Lock()
129         self.tx_event = threading.Event()
130
131         if self.seed_version != SEED_VERSION:
132             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
133
134         for tx_hash in self.transactions.keys():
135             self.update_tx_outputs(tx_hash)
136
137
138     # plugins 
139     def set_hook(self, name, callback):
140         h = self.plugin_hooks.get(name, [])
141         h.append(callback)
142         self.plugin_hooks[name] = h
143
144     def unset_hook(self, name, callback):
145         h = self.plugin_hooks.get(name,[])
146         if callback in h: h.remove(callback)
147         self.plugin_hooks[name] = h
148
149     def init_plugins(self, plugins):
150         self.plugins = plugins
151         for p in plugins:
152             try:
153                 p.init(self)
154             except:
155                 import traceback
156                 print_msg("Error:cannot initialize plugin",p)
157                 traceback.print_exc(file=sys.stdout)
158
159
160     def set_up_to_date(self,b):
161         with self.lock: self.up_to_date = b
162
163     def is_up_to_date(self):
164         with self.lock: return self.up_to_date
165
166     def update(self):
167         self.up_to_date = False
168         self.interface.poke('synchronizer')
169         while not self.is_up_to_date(): time.sleep(0.1)
170
171     def import_key(self, sec, password):
172         # check password
173         seed = self.decode_seed(password)
174         address = address_from_private_key(sec)
175
176         if self.is_mine(address):
177             raise BaseException('Address already in wallet')
178         
179         # store the originally requested keypair into the imported keys table
180         self.imported_keys[address] = pw_encode(sec, password )
181         return address
182         
183
184     def init_seed(self, seed):
185         if self.seed: raise BaseException("a seed exists")
186         if not seed: 
187             seed = random_seed(128)
188         self.seed = seed 
189         self.config.set_key('seed', self.seed, True)
190         self.config.set_key('seed_version', self.seed_version, True)
191
192         mpk = DeterministicSequence.mpk_from_seed(self.seed)
193         self.config.set_key('master_public_key', mpk, True)
194         self.sequences[0] = DeterministicSequence(mpk)
195
196         self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
197         self.config.set_key('accounts', self.accounts, True)
198
199
200
201     def addresses(self, include_change = False):
202         o = self.imported_keys.keys()
203         for a in self.accounts.values():
204             o += a[0]
205             if include_change: o += a[1]
206         return o
207
208
209     def is_mine(self, address):
210         return address in self.addresses(True)
211
212     def is_change(self, address):
213         #return address in self.change_addresses
214         return False
215
216     def get_master_public_key(self):
217         return self.sequence.master_public_key
218
219     def get_address_index(self, address):
220         if address in self.imported_keys.keys():
221             raise BaseException("imported key")
222         for account in self.accounts.keys():
223             for for_change in [0,1]:
224                 addresses = self.accounts[account][for_change]
225                 for addr in addresses:
226                     if address == addr:
227                         return account, for_change, addresses.index(addr)
228         raise BaseException("not found")
229         
230
231     def get_public_key(self, address):
232         account, n, for_change = self.get_address_index(address)
233         return self.sequences[account].get_pubkey(n, for_change)
234
235
236     def decode_seed(self, password):
237         seed = pw_decode(self.seed, password)
238         self.sequences[0].check_seed(seed)
239         return seed
240         
241     def get_private_key(self, address, password):
242         return self.get_private_keys([address], password).get(address)
243
244     def get_private_keys(self, addresses, password):
245         # decode seed in any case, in order to test the password
246         seed = self.decode_seed(password)
247         secexp = self.sequences[0].stretch_key(seed)
248         out = {}
249         for address in addresses:
250             if address in self.imported_keys.keys():
251                 out[address] = pw_decode( self.imported_keys[address], password )
252             else:
253                 account, for_change, n = self.get_address_index(address)
254                 if account == 0:
255                     out[address] = self.sequences[0].get_private_key_from_stretched_exponent(n, for_change, secexp)
256         return out
257
258
259     def signrawtransaction(self, tx, input_info, private_keys, password):
260         unspent_coins = self.get_unspent_coins()
261         seed = self.decode_seed(password)
262
263         # convert private_keys to dict 
264         pk = {}
265         for sec in private_keys:
266             address = address_from_private_key(sec)
267             pk[address] = sec
268         private_keys = pk
269
270         for txin in tx.inputs:
271             # convert to own format
272             txin['tx_hash'] = txin['prevout_hash']
273             txin['index'] = txin['prevout_n']
274
275             for item in input_info:
276                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
277                     txin['raw_output_script'] = item['scriptPubKey']
278                     txin['redeemScript'] = item.get('redeemScript')
279                     txin['electrumKeyID'] = item.get('electrumKeyID')
280                     break
281             else:
282                 for item in unspent_coins:
283                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
284                         txin['raw_output_script'] = item['raw_output_script']
285                         break
286                 else:
287                     # if neither, we might want to get it from the server..
288                     raise
289
290             # find the address:
291             if txin.get('electrumKeyID'):
292                 account, for_change, n = txin.get('electrumKeyID')
293                 sec = self.sequences[account].get_private_key(n, for_change, seed)
294                 addr = self.sequences[account].get_address(n, for_change)
295                 txin['address'] = addr
296                 private_keys[addr] = sec
297
298             elif txin.get("redeemScript"):
299                 txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
300
301             elif txin.get("raw_output_script"):
302                 import deserialize
303                 addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
304                 sec = self.get_private_key(addr, password)
305                 if sec: 
306                     private_keys[addr] = sec
307                     txin['address'] = addr
308
309         tx.sign( private_keys )
310
311     def sign_message(self, address, message, password):
312         sec = self.get_private_key(address, password)
313         key = regenerate_key(sec)
314         compressed = is_compressed(sec)
315         return key.sign_message(message, compressed, address)
316
317
318     def create_new_address(self, account, for_change):
319         addresses = self.accounts[account][for_change]
320         n = len(addresses)
321         address = self.get_new_address( account, for_change, n)
322         self.accounts[account][for_change].append(address)
323         self.history[address] = []
324         return address
325         
326
327     def get_new_address(self, account, for_change, n):
328         return self.sequences[account].get_address(for_change, n)
329         print address
330         return address
331
332     def change_gap_limit(self, value):
333         if value >= self.gap_limit:
334             self.gap_limit = value
335             self.save()
336             self.interface.poke('synchronizer')
337             return True
338
339         elif value >= self.min_acceptable_gap():
340             for key, account in self.accounts.items():
341                 addresses = account[0]
342                 k = self.num_unused_trailing_addresses(addresses)
343                 n = len(addresses) - k + value
344                 addresses = addresses[0:n]
345                 self.accounts[key][0] = addresses
346
347             self.gap_limit = value
348             self.save()
349             return True
350         else:
351             return False
352
353     def num_unused_trailing_addresses(self, addresses):
354         k = 0
355         for a in addresses[::-1]:
356             if self.history.get(a):break
357             k = k + 1
358         return k
359
360     def min_acceptable_gap(self):
361         # fixme: this assumes wallet is synchronized
362         n = 0
363         nmax = 0
364
365         for account in self.accounts.values():
366             addresses = account[0]
367             k = self.num_unused_trailing_addresses(addresses)
368             for a in addresses[0:-k]:
369                 if self.history.get(a):
370                     n = 0
371                 else:
372                     n += 1
373                     if n > nmax: nmax = n
374         return nmax + 1
375
376
377     def address_is_old(self, address):
378         age = -1
379         h = self.history.get(address, [])
380         if h == ['*']:
381             return True
382         for tx_hash, tx_height in h:
383             if tx_height == 0:
384                 tx_age = 0
385             else: 
386                 tx_age = self.verifier.height - tx_height + 1
387             if tx_age > age:
388                 age = tx_age
389         return age > 2
390
391
392     def synchronize_sequence(self, account, for_change):
393         limit = self.gap_limit_for_change if for_change else self.gap_limit
394         addresses = self.accounts[account][for_change]
395         new_addresses = []
396         while True:
397             if len(addresses) < limit:
398                 new_addresses.append( self.create_new_address(account, for_change) )
399                 continue
400             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
401                 break
402             else:
403                 new_addresses.append( self.create_new_address(account, for_change) )
404         return new_addresses
405         
406
407     def synchronize_account(self, account):
408         new = []
409         new += self.synchronize_sequence(account, 0)
410         new += self.synchronize_sequence(account, 1)
411         return new
412
413     def synchronize(self):
414         new = []
415         for account in self.accounts.keys():
416             new += self.synchronize_account(account)
417         return new
418
419
420     def is_found(self):
421         return self.history.values() != [[]] * len(self.history) 
422
423
424     def fill_addressbook(self):
425         for tx_hash, tx in self.transactions.items():
426             is_send, _, _ = self.get_tx_value(tx)
427             if is_send:
428                 for addr, v in tx.outputs:
429                     if not self.is_mine(addr) and addr not in self.addressbook:
430                         self.addressbook.append(addr)
431         # redo labels
432         # self.update_tx_labels()
433
434
435     def get_address_flags(self, addr):
436         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
437         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
438         return flags
439         
440
441     def get_tx_value(self, tx, addresses=None):
442         if addresses is None: addresses = self.addresses(True)
443         return tx.get_value(addresses, self.prevout_values)
444
445
446     def get_tx_details(self, tx_hash):
447         import datetime
448         if not tx_hash: return ''
449         tx = self.transactions.get(tx_hash)
450         is_mine, v, fee = self.get_tx_value(tx)
451         conf, timestamp = self.verifier.get_confirmations(tx_hash)
452
453         if timestamp:
454             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
455         else:
456             time_str = 'pending'
457
458         inputs = map(lambda x: x.get('address'), tx.inputs)
459         outputs = map(lambda x: x.get('address'), tx.d['outputs'])
460         tx_details = "Transaction Details" +"\n\n" \
461             + "Transaction ID:\n" + tx_hash + "\n\n" \
462             + "Status: %d confirmations\n"%conf
463         if is_mine:
464             if fee: 
465                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
466                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
467             else:
468                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
469                               + "Transaction fee: unknown\n"
470         else:
471             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
472
473         tx_details += "Date: %s\n\n"%time_str \
474             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
475             + "Outputs:\n-"+ '\n-'.join(outputs)
476
477         r = self.receipts.get(tx_hash)
478         if r:
479             tx_details += "\n_______________________________________" \
480                 + '\n\nSigned URI: ' + r[2] \
481                 + "\n\nSigned by: " + r[0] \
482                 + '\n\nSignature: ' + r[1]
483
484         return tx_details
485
486     
487     def update_tx_outputs(self, tx_hash):
488         tx = self.transactions.get(tx_hash)
489         i = 0
490         for item in tx.outputs:
491             addr, value = item
492             key = tx_hash+ ':%d'%i
493             with self.lock:
494                 self.prevout_values[key] = value
495             i += 1
496
497         for item in tx.inputs:
498             if self.is_mine(item.get('address')):
499                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
500                 self.spent_outputs.append(key)
501
502
503     def get_addr_balance(self, address):
504         assert self.is_mine(address)
505         h = self.history.get(address,[])
506         if h == ['*']: return 0,0
507         c = u = 0
508         received_coins = []   # list of coins received at address
509
510         for tx_hash, tx_height in h:
511             tx = self.transactions.get(tx_hash)
512             if not tx: continue
513             i = 0
514             for item in tx.outputs:
515                 addr, value = item
516                 if addr == address:
517                     key = tx_hash + ':%d'%i
518                     received_coins.append(key)
519                 i +=1
520
521         for tx_hash, tx_height in h:
522             tx = self.transactions.get(tx_hash)
523             if not tx: continue
524             v = 0
525
526             for item in tx.inputs:
527                 addr = item.get('address')
528                 if addr == address:
529                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
530                     value = self.prevout_values.get( key )
531                     if key in received_coins: 
532                         v -= value
533
534             i = 0
535             for item in tx.outputs:
536                 addr, value = item
537                 key = tx_hash + ':%d'%i
538                 if addr == address:
539                     v += value
540                 i += 1
541
542             if tx_height:
543                 c += v
544             else:
545                 u += v
546         return c, u
547
548     def get_account_addresses(self, a):
549         ac = self.accounts[a]
550         return ac[0] + ac[1]
551
552     def get_imported_balance(self):
553         cc = uu = 0
554         for addr in self.imported_keys.keys():
555             c, u = self.get_addr_balance(addr)
556             cc += c
557             uu += u
558         return cc, uu
559
560     def get_account_balance(self, account):
561         conf = unconf = 0
562         for addr in self.get_account_addresses(account): 
563             c, u = self.get_addr_balance(addr)
564             conf += c
565             unconf += u
566         return conf, unconf
567
568     def get_balance(self):
569         cc = uu = 0
570         for a in self.accounts.keys():
571             c, u = self.get_account_balance(a)
572             cc += c
573             uu += u
574         c, u = self.get_imported_balance()
575         cc += c
576         uu += u
577         return cc, uu
578
579
580     def get_unspent_coins(self, domain=None):
581         coins = []
582         if domain is None: domain = self.addresses(True)
583         for addr in domain:
584             h = self.history.get(addr, [])
585             if h == ['*']: continue
586             for tx_hash, tx_height in h:
587                 tx = self.transactions.get(tx_hash)
588                 for output in tx.d.get('outputs'):
589                     if output.get('address') != addr: continue
590                     key = tx_hash + ":%d" % output.get('index')
591                     if key in self.spent_outputs: continue
592                     output['tx_hash'] = tx_hash
593                     coins.append(output)
594         return coins
595
596
597
598     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
599         """ todo: minimize tx size """
600         total = 0
601         fee = self.fee if fixed_fee is None else fixed_fee
602
603         coins = []
604         prioritized_coins = []
605         domain = [from_addr] if from_addr else self.addresses(True)
606         for i in self.frozen_addresses:
607             if i in domain: domain.remove(i)
608
609         for i in self.prioritized_addresses:
610             if i in domain: domain.remove(i)
611
612         coins = self.get_unspent_coins(domain)
613         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
614
615         inputs = []
616         coins = prioritized_coins + coins
617
618         for item in coins: 
619             addr = item.get('address')
620             v = item.get('value')
621             total += v
622
623             inputs.append( item )
624             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
625             if total >= amount + fee: break
626         else:
627             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
628             inputs = []
629         return inputs, total, fee
630
631     def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
632         change_amount = total - ( amount + fee )
633         if change_amount != 0:
634             # normally, the update thread should ensure that the last change address is unused
635             if not change_addr:
636                 change_addresses = self.accounts[0][1]
637                 change_addr = change_addresses[-self.gap_limit_for_change]
638             # Insert the change output at a random position in the outputs
639             posn = random.randint(0, len(outputs))
640             outputs[posn:posn] = [( change_addr,  change_amount)]
641         return outputs
642
643
644     def get_history(self, address):
645         with self.lock:
646             return self.history.get(address)
647
648     def get_status(self, h):
649         if not h: return None
650         if h == ['*']: return '*'
651         status = ''
652         for tx_hash, height in h:
653             status += tx_hash + ':%d:' % height
654         return hashlib.sha256( status ).digest().encode('hex')
655
656
657
658     def receive_tx_callback(self, tx_hash, tx, tx_height):
659
660         if not self.check_new_tx(tx_hash, tx):
661             raise BaseException("error: received transaction is not consistent with history"%tx_hash)
662
663         with self.lock:
664             self.transactions[tx_hash] = tx
665             self.tx_height[tx_hash] = tx_height
666
667         #tx_height = tx.get('height')
668         if self.verifier and tx_height>0: 
669             self.verifier.add(tx_hash, tx_height)
670
671         self.update_tx_outputs(tx_hash)
672
673         self.save()
674
675
676     def receive_history_callback(self, addr, hist):
677
678         if not self.check_new_history(addr, hist):
679             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
680             
681         with self.lock:
682             self.history[addr] = hist
683             self.save()
684
685         if hist != ['*']:
686             for tx_hash, tx_height in hist:
687                 if tx_height>0:
688                     # add it in case it was previously unconfirmed
689                     if self.verifier: self.verifier.add(tx_hash, tx_height)
690                     # set the height in case it changed
691                     txh = self.tx_height.get(tx_hash)
692                     if txh is not None and txh != tx_height:
693                         print_error( "changing height for tx", tx_hash )
694                         self.tx_height[tx_hash] = tx_height
695
696
697     def get_tx_history(self):
698         with self.lock:
699             history = self.transactions.items()
700         history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
701         result = []
702     
703         balance = 0
704         for tx_hash, tx in history:
705             is_mine, v, fee = self.get_tx_value(tx)
706             if v is not None: balance += v
707         c, u = self.get_balance()
708
709         if balance != c+u:
710             v_str = format_satoshis( c+u - balance, True, self.num_zeros)
711             result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
712
713         balance = c + u - balance
714         for tx_hash, tx in history:
715             conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
716             is_mine, value, fee = self.get_tx_value(tx)
717             if value is not None:
718                 balance += value
719
720             result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
721
722         return result
723
724
725     def get_label(self, tx_hash):
726         label = self.labels.get(tx_hash)
727         is_default = (label == '') or (label is None)
728         if is_default: label = self.get_default_label(tx_hash)
729         return label, is_default
730
731
732     def get_default_label(self, tx_hash):
733         tx = self.transactions.get(tx_hash)
734         default_label = ''
735         if tx:
736             is_mine, _, _ = self.get_tx_value(tx)
737             if is_mine:
738                 for o in tx.outputs:
739                     o_addr, _ = o
740                     if not self.is_mine(o_addr):
741                         try:
742                             default_label = self.labels[o_addr]
743                         except KeyError:
744                             default_label = o_addr
745             else:
746                 for o in tx.outputs:
747                     o_addr, _ = o
748                     if self.is_mine(o_addr) and not self.is_change(o_addr):
749                         break
750                 else:
751                     for o in tx.outputs:
752                         o_addr, _ = o
753                         if self.is_mine(o_addr):
754                             break
755                     else:
756                         o_addr = None
757
758                 if o_addr:
759                     dest_label = self.labels.get(o_addr)
760                     try:
761                         default_label = self.labels[o_addr]
762                     except KeyError:
763                         default_label = o_addr
764
765         return default_label
766
767
768     def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None):
769
770         for address, x in outputs:
771             assert is_valid(address)
772
773         amount = sum( map(lambda x:x[1], outputs) )
774         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
775         if not inputs:
776             raise ValueError("Not enough funds")
777
778         if not self.use_change and not change_addr:
779             change_addr = inputs[-1]['address']
780             print_error( "Sending change to", change_addr )
781         outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
782
783         tx = Transaction.from_io(inputs, outputs)
784
785         pk_addresses = []
786         for i in range(len(tx.inputs)):
787             txin = tx.inputs[i]
788             account, is_change, n = self.get_address_index(txin['address'])
789             pk_addr, redeemScript = self.sequences[account].get_input_info(is_change, n)
790             txin['redeemScript'] = redeemScript
791             txin['electrumKeyID'] = (account, is_change, n) # used by the server to find the key
792             pk_addresses.append(pk_addr)
793
794         # get all private keys at once.
795         private_keys = self.get_private_keys(pk_addresses, password)
796         tx.sign(private_keys)
797
798         for address, x in outputs:
799             if address not in self.addressbook and not self.is_mine(address):
800                 self.addressbook.append(address)
801
802         return tx
803
804
805
806     def sendtx(self, tx):
807         # synchronous
808         h = self.send_tx(tx)
809         self.tx_event.wait()
810         return self.receive_tx(h)
811
812     def send_tx(self, tx):
813         # asynchronous
814         self.tx_event.clear()
815         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
816         return tx.hash()
817
818     def receive_tx(self,tx_hash):
819         out = self.tx_result 
820         if out != tx_hash:
821             return False, "error: " + out
822         if self.receipt:
823             self.receipts[tx_hash] = self.receipt
824             self.receipt = None
825         return True, out
826
827
828     def read_alias(self, alias):
829         # this might not be the right place for this function.
830         import urllib
831
832         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
833         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
834         if m1:
835             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
836         elif m2:
837             url = 'https://' + alias + '/bitcoin.id'
838         else:
839             return ''
840         try:
841             lines = urllib.urlopen(url).readlines()
842         except:
843             return ''
844
845         # line 0
846         line = lines[0].strip().split(':')
847         if len(line) == 1:
848             auth_name = None
849             target = signing_addr = line[0]
850         else:
851             target, auth_name, signing_addr, signature = line
852             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
853             print msg, signature
854             EC_KEY.verify_message(signing_addr, signature, msg)
855         
856         # other lines are signed updates
857         for line in lines[1:]:
858             line = line.strip()
859             if not line: continue
860             line = line.split(':')
861             previous = target
862             print repr(line)
863             target, signature = line
864             EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
865
866         if not is_valid(target):
867             raise ValueError("Invalid bitcoin address")
868
869         return target, signing_addr, auth_name
870
871     def update_password(self, seed, old_password, new_password):
872         if new_password == '': new_password = None
873         self.use_encryption = (new_password != None)
874         self.seed = pw_encode( seed, new_password)
875         self.config.set_key('seed', self.seed, True)
876         for k in self.imported_keys.keys():
877             a = self.imported_keys[k]
878             b = pw_decode(a, old_password)
879             c = pw_encode(b, new_password)
880             self.imported_keys[k] = c
881         self.save()
882
883     def get_alias(self, alias, interactive = False, show_message=None, question = None):
884         try:
885             target, signing_address, auth_name = self.read_alias(alias)
886         except BaseException, e:
887             # raise exception if verify fails (verify the chain)
888             if interactive:
889                 show_message("Alias error: " + str(e))
890             return
891
892         print target, signing_address, auth_name
893
894         if auth_name is None:
895             a = self.aliases.get(alias)
896             if not a:
897                 msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
898                 if interactive and question( msg ):
899                     self.aliases[alias] = (signing_address, target)
900                 else:
901                     target = None
902             else:
903                 if signing_address != a[0]:
904                     msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
905                     if interactive and question( msg ):
906                         self.aliases[alias] = (signing_address, target)
907                     else:
908                         target = None
909         else:
910             if signing_address not in self.authorities.keys():
911                 msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
912                 if interactive and question( msg ):
913                     self.authorities[signing_address] = auth_name
914                 else:
915                     target = None
916
917         if target:
918             self.aliases[alias] = (signing_address, target)
919             
920         return target
921
922
923     def parse_url(self, url, show_message, question):
924         o = url[8:].split('?')
925         address = o[0]
926         if len(o)>1:
927             params = o[1].split('&')
928         else:
929             params = []
930
931         amount = label = message = signature = identity = ''
932         for p in params:
933             k,v = p.split('=')
934             uv = urldecode(v)
935             if k == 'amount': amount = uv
936             elif k == 'message': message = uv
937             elif k == 'label': label = uv
938             elif k == 'signature':
939                 identity, signature = uv.split(':')
940                 url = url.replace('&%s=%s'%(k,v),'')
941             else: 
942                 print k,v
943
944         if label and self.labels.get(address) != label:
945             if question('Give label "%s" to address %s ?'%(label,address)):
946                 if address not in self.addressbook and not self.is_mine(address):
947                     self.addressbook.append(address)
948                 self.labels[address] = label
949
950         if signature:
951             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
952                 signing_address = self.get_alias(identity, True, show_message, question)
953             elif is_valid(identity):
954                 signing_address = identity
955             else:
956                 signing_address = None
957             if not signing_address:
958                 return
959             try:
960                 EC_KEY.verify_message(signing_address, signature, url )
961                 self.receipt = (signing_address, signature, url)
962             except:
963                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
964                 address = amount = label = identity = message = ''
965
966         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
967             payto_address = self.get_alias(address, True, show_message, question)
968             if payto_address:
969                 address = address + ' <' + payto_address + '>'
970
971         return address, amount, label, message, signature, identity, url
972
973
974
975     def freeze(self,addr):
976         if self.is_mine(addr) and addr not in self.frozen_addresses:
977             self.unprioritize(addr)
978             self.frozen_addresses.append(addr)
979             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
980             return True
981         else:
982             return False
983
984     def unfreeze(self,addr):
985         if self.is_mine(addr) and addr in self.frozen_addresses:
986             self.frozen_addresses.remove(addr)
987             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
988             return True
989         else:
990             return False
991
992     def prioritize(self,addr):
993         if is_mine(addr) and addr not in self.prioritized_addresses:
994             self.unfreeze(addr)
995             self.prioritized_addresses.append(addr)
996             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
997             return True
998         else:
999             return False
1000
1001     def unprioritize(self,addr):
1002         if is_mine(addr) and addr in self.prioritized_addresses:
1003             self.prioritized_addresses.remove(addr)
1004             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
1005             return True
1006         else:
1007             return False
1008
1009     def save(self):
1010         tx = {}
1011         for k,v in self.transactions.items():
1012             tx[k] = str(v)
1013             
1014         s = {
1015             'use_encryption': self.use_encryption,
1016             'use_change': self.use_change,
1017             'fee': self.fee,
1018             'accounts': self.accounts,
1019             'addr_history': self.history, 
1020             'labels': self.labels,
1021             'contacts': self.addressbook,
1022             'imported_keys': self.imported_keys,
1023             'aliases': self.aliases,
1024             'authorities': self.authorities,
1025             'receipts': self.receipts,
1026             'num_zeros': self.num_zeros,
1027             'frozen_addresses': self.frozen_addresses,
1028             'prioritized_addresses': self.prioritized_addresses,
1029             'gap_limit': self.gap_limit,
1030             'transactions': tx,
1031             'tx_height': self.tx_height,
1032             'requested_amounts': self.requested_amounts,
1033         }
1034         for k, v in s.items():
1035             self.config.set_key(k,v)
1036         self.config.save()
1037
1038     def set_verifier(self, verifier):
1039         self.verifier = verifier
1040
1041         # review stored transactions and send them to the verifier
1042         # (they are not necessarily in the history, because history items might have have been pruned)
1043         for tx_hash, tx in self.transactions.items():
1044             tx_height = self.tx_height[tx_hash]
1045             if tx_height <1:
1046                 print_error( "skipping", tx_hash, tx_height )
1047                 continue
1048             
1049             if tx_height>0:
1050                 self.verifier.add(tx_hash, tx_height)
1051
1052         # review transactions that are in the history
1053         for addr, hist in self.history.items():
1054             if hist == ['*']: continue
1055             for tx_hash, tx_height in hist:
1056                 if tx_height>0:
1057                     # add it in case it was previously unconfirmed
1058                     self.verifier.add(tx_hash, tx_height)
1059                     # set the height in case it changed
1060                     txh = self.tx_height.get(tx_hash)
1061                     if txh is not None and txh != tx_height:
1062                         print_error( "changing height for tx", tx_hash )
1063                         self.tx_height[tx_hash] = tx_height
1064
1065
1066
1067
1068     def check_new_history(self, addr, hist):
1069         
1070         # check that all tx in hist are relevant
1071         if hist != ['*']:
1072             for tx_hash, height in hist:
1073                 tx = self.transactions.get(tx_hash)
1074                 if not tx: continue
1075                 if not tx.has_address(addr):
1076                     return False
1077
1078         # check that we are not "orphaning" a transaction
1079         old_hist = self.history.get(addr,[])
1080         if old_hist == ['*']: return True
1081
1082         for tx_hash, height in old_hist:
1083             if tx_hash in map(lambda x:x[0], hist): continue
1084             found = False
1085             for _addr, _hist in self.history.items():
1086                 if _addr == addr: continue
1087                 if _hist == ['*']: continue
1088                 _tx_hist = map(lambda x:x[0], _hist)
1089                 if tx_hash in _tx_hist:
1090                     found = True
1091                     break
1092
1093             if not found:
1094                 tx = self.transactions.get(tx_hash)
1095                 # tx might not be there
1096                 if not tx: continue
1097                 
1098                 # already verified?
1099                 if self.tx_height.get(tx_hash):
1100                     continue
1101                 # unconfirmed tx
1102                 print_error("new history is orphaning transaction:", tx_hash)
1103                 # check that all outputs are not mine, request histories
1104                 ext_requests = []
1105                 for o in tx.get('outputs'):
1106                     _addr = o.get('address')
1107                     # assert not self.is_mine(_addr)
1108                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1109
1110                 ext_h = self.interface.synchronous_get(ext_requests)
1111                 height = None
1112                 for h in ext_h:
1113                     if h == ['*']: continue
1114                     for item in h:
1115                         if item.get('tx_hash') == tx_hash:
1116                             height = item.get('height')
1117                             self.tx_height[tx_hash] = height
1118                 if height:
1119                     print_error("found height for", tx_hash, height)
1120                     self.verifier.add(tx_hash, height)
1121                 else:
1122                     print_error("removing orphaned tx from history", tx_hash)
1123                     self.transactions.pop(tx_hash)
1124
1125         return True
1126
1127
1128
1129     def check_new_tx(self, tx_hash, tx):
1130         # 1 check that tx is referenced in addr_history. 
1131         addresses = []
1132         for addr, hist in self.history.items():
1133             if hist == ['*']:continue
1134             for txh, height in hist:
1135                 if txh == tx_hash: 
1136                     addresses.append(addr)
1137
1138         if not addresses:
1139             return False
1140
1141         # 2 check that referencing addresses are in the tx
1142         for addr in addresses:
1143             if not tx.has_address(addr):
1144                 return False
1145
1146         return True
1147
1148
1149
1150
1151 class WalletSynchronizer(threading.Thread):
1152
1153
1154     def __init__(self, wallet, config):
1155         threading.Thread.__init__(self)
1156         self.daemon = True
1157         self.wallet = wallet
1158         self.interface = self.wallet.interface
1159         self.interface.register_channel('synchronizer')
1160         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1161         self.wallet.interface.register_callback('connected', lambda: self.interface.send([('server.banner',[])],'synchronizer') )
1162         self.was_updated = True
1163         self.running = False
1164         self.lock = threading.Lock()
1165
1166     def stop(self):
1167         with self.lock: self.running = False
1168         self.interface.poke('synchronizer')
1169
1170     def is_running(self):
1171         with self.lock: return self.running
1172
1173     def synchronize_wallet(self):
1174         new_addresses = self.wallet.synchronize()
1175         if new_addresses:
1176             self.subscribe_to_addresses(new_addresses)
1177             self.wallet.up_to_date = False
1178             return
1179             
1180         if not self.interface.is_up_to_date('synchronizer'):
1181             if self.wallet.is_up_to_date():
1182                 self.wallet.set_up_to_date(False)
1183                 self.was_updated = True
1184             return
1185
1186         self.wallet.set_up_to_date(True)
1187         self.was_updated = True
1188
1189     
1190     def subscribe_to_addresses(self, addresses):
1191         messages = []
1192         for addr in addresses:
1193             messages.append(('blockchain.address.subscribe', [addr]))
1194         self.interface.send( messages, 'synchronizer')
1195
1196
1197     def run(self):
1198         with self.lock: self.running = True
1199
1200         requested_tx = []
1201         missing_tx = []
1202         requested_histories = {}
1203
1204         # request any missing transactions
1205         for history in self.wallet.history.values():
1206             if history == ['*']: continue
1207             for tx_hash, tx_height in history:
1208                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1209                     missing_tx.append( (tx_hash, tx_height) )
1210         print_error("missing tx", missing_tx)
1211
1212         # wait until we are connected, in case the user is not connected
1213         while not self.interface.is_connected:
1214             time.sleep(1)
1215         
1216         # request banner, because 'connected' event happens before this thread is started
1217         self.interface.send([('server.banner',[])],'synchronizer')
1218
1219         # subscriptions
1220         self.subscribe_to_addresses(self.wallet.addresses(True))
1221
1222         while self.is_running():
1223             # 1. send new requests
1224             self.synchronize_wallet()
1225
1226             for tx_hash, tx_height in missing_tx:
1227                 if (tx_hash, tx_height) not in requested_tx:
1228                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1229                     requested_tx.append( (tx_hash, tx_height) )
1230             missing_tx = []
1231
1232             if self.was_updated:
1233                 self.interface.trigger_callback('updated')
1234                 self.was_updated = False
1235
1236             # 2. get a response
1237             r = self.interface.get_response('synchronizer')
1238
1239             # poke sends None. (needed during stop)
1240             if not r: continue
1241
1242             # 3. handle response
1243             method = r['method']
1244             params = r['params']
1245             result = r.get('result')
1246             error = r.get('error')
1247             if error:
1248                 print "error", r
1249                 continue
1250
1251             if method == 'blockchain.address.subscribe':
1252                 addr = params[0]
1253                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1254                     if requested_histories.get(addr) is None:
1255                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1256                         requested_histories[addr] = result
1257
1258             elif method == 'blockchain.address.get_history':
1259                 addr = params[0]
1260                 print_error("receiving history", addr, result)
1261                 if result == ['*']:
1262                     assert requested_histories.pop(addr) == '*'
1263                     self.wallet.receive_history_callback(addr, result)
1264                 else:
1265                     hist = []
1266                     # check that txids are unique
1267                     txids = []
1268                     for item in result:
1269                         tx_hash = item['tx_hash']
1270                         if tx_hash not in txids:
1271                             txids.append(tx_hash)
1272                             hist.append( (tx_hash, item['height']) )
1273
1274                     if len(hist) != len(result):
1275                         raise BaseException("error: server sent history with non-unique txid", result)
1276
1277                     # check that the status corresponds to what was announced
1278                     rs = requested_histories.pop(addr)
1279                     if self.wallet.get_status(hist) != rs:
1280                         raise BaseException("error: status mismatch: %s"%addr)
1281                 
1282                     # store received history
1283                     self.wallet.receive_history_callback(addr, hist)
1284
1285                     # request transactions that we don't have 
1286                     for tx_hash, tx_height in hist:
1287                         if self.wallet.transactions.get(tx_hash) is None:
1288                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1289                                 missing_tx.append( (tx_hash, tx_height) )
1290
1291             elif method == 'blockchain.transaction.get':
1292                 tx_hash = params[0]
1293                 tx_height = params[1]
1294                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1295                 tx = Transaction(result)
1296                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1297                 self.was_updated = True
1298                 requested_tx.remove( (tx_hash, tx_height) )
1299                 print_error("received tx:", tx)
1300
1301             elif method == 'blockchain.transaction.broadcast':
1302                 self.wallet.tx_result = result
1303                 self.wallet.tx_event.set()
1304
1305             elif method == 'server.banner':
1306                 self.wallet.banner = result
1307                 self.interface.trigger_callback('banner')
1308             else:
1309                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1310
1311             if self.was_updated and not requested_tx:
1312                 self.interface.trigger_callback('updated')
1313                 self.was_updated = False
1314
1315