method get_private_keys for sequence
[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, sequence = self.get_address_index(address)
233         return self.sequences[account].get_pubkey( sequence )
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         out = {}
248         l_sequences = []
249         l_addresses = []
250         for address in addresses:
251             if address in self.imported_keys.keys():
252                 out[address] = pw_decode( self.imported_keys[address], password )
253             else:
254                 account, sequence = self.get_address_index(address)
255                 if account == 0:
256                     l_sequences.append(sequence)
257                     l_addresses.append(address)
258
259         pk = self.sequences[0].get_private_keys(l_sequences, seed)
260         for i, address in enumerate(l_addresses): out[address] = pk[i]                     
261         return out
262
263
264     def signrawtransaction(self, tx, input_info, private_keys, password):
265         unspent_coins = self.get_unspent_coins()
266         seed = self.decode_seed(password)
267
268         # convert private_keys to dict 
269         pk = {}
270         for sec in private_keys:
271             address = address_from_private_key(sec)
272             pk[address] = sec
273         private_keys = pk
274
275         for txin in tx.inputs:
276             # convert to own format
277             txin['tx_hash'] = txin['prevout_hash']
278             txin['index'] = txin['prevout_n']
279
280             for item in input_info:
281                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
282                     txin['raw_output_script'] = item['scriptPubKey']
283                     txin['redeemScript'] = item.get('redeemScript')
284                     txin['electrumKeyID'] = item.get('electrumKeyID')
285                     break
286             else:
287                 for item in unspent_coins:
288                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
289                         txin['raw_output_script'] = item['raw_output_script']
290                         break
291                 else:
292                     # if neither, we might want to get it from the server..
293                     raise
294
295             # find the address:
296             if txin.get('electrumKeyID'):
297                 account, sequence = txin.get('electrumKeyID')
298                 sec = self.sequences[account].get_private_key(sequence, seed)
299                 addr = self.sequences[account].get_address(sequence)
300                 txin['address'] = addr
301                 private_keys[addr] = sec
302
303             elif txin.get("redeemScript"):
304                 txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
305
306             elif txin.get("raw_output_script"):
307                 import deserialize
308                 addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
309                 sec = self.get_private_key(addr, password)
310                 if sec: 
311                     private_keys[addr] = sec
312                     txin['address'] = addr
313
314         tx.sign( private_keys )
315
316     def sign_message(self, address, message, password):
317         sec = self.get_private_key(address, password)
318         key = regenerate_key(sec)
319         compressed = is_compressed(sec)
320         return key.sign_message(message, compressed, address)
321
322
323     def create_new_address(self, account, for_change):
324         addresses = self.accounts[account][for_change]
325         n = len(addresses)
326         address = self.get_new_address( account, for_change, n)
327         self.accounts[account][for_change].append(address)
328         self.history[address] = []
329         return address
330         
331
332     def get_new_address(self, account, for_change, n):
333         return self.sequences[account].get_address((for_change, n))
334         print address
335         return address
336
337     def change_gap_limit(self, value):
338         if value >= self.gap_limit:
339             self.gap_limit = value
340             self.save()
341             self.interface.poke('synchronizer')
342             return True
343
344         elif value >= self.min_acceptable_gap():
345             for key, account in self.accounts.items():
346                 addresses = account[0]
347                 k = self.num_unused_trailing_addresses(addresses)
348                 n = len(addresses) - k + value
349                 addresses = addresses[0:n]
350                 self.accounts[key][0] = addresses
351
352             self.gap_limit = value
353             self.save()
354             return True
355         else:
356             return False
357
358     def num_unused_trailing_addresses(self, addresses):
359         k = 0
360         for a in addresses[::-1]:
361             if self.history.get(a):break
362             k = k + 1
363         return k
364
365     def min_acceptable_gap(self):
366         # fixme: this assumes wallet is synchronized
367         n = 0
368         nmax = 0
369
370         for account in self.accounts.values():
371             addresses = account[0]
372             k = self.num_unused_trailing_addresses(addresses)
373             for a in addresses[0:-k]:
374                 if self.history.get(a):
375                     n = 0
376                 else:
377                     n += 1
378                     if n > nmax: nmax = n
379         return nmax + 1
380
381
382     def address_is_old(self, address):
383         age = -1
384         h = self.history.get(address, [])
385         if h == ['*']:
386             return True
387         for tx_hash, tx_height in h:
388             if tx_height == 0:
389                 tx_age = 0
390             else: 
391                 tx_age = self.verifier.height - tx_height + 1
392             if tx_age > age:
393                 age = tx_age
394         return age > 2
395
396
397     def synchronize_sequence(self, account, for_change):
398         limit = self.gap_limit_for_change if for_change else self.gap_limit
399         addresses = self.accounts[account][for_change]
400         new_addresses = []
401         while True:
402             if len(addresses) < limit:
403                 new_addresses.append( self.create_new_address(account, for_change) )
404                 continue
405             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
406                 break
407             else:
408                 new_addresses.append( self.create_new_address(account, for_change) )
409         return new_addresses
410         
411
412     def synchronize_account(self, account):
413         new = []
414         new += self.synchronize_sequence(account, 0)
415         new += self.synchronize_sequence(account, 1)
416         return new
417
418     def synchronize(self):
419         new = []
420         for account in self.accounts.keys():
421             new += self.synchronize_account(account)
422         return new
423
424
425     def is_found(self):
426         return self.history.values() != [[]] * len(self.history) 
427
428
429     def fill_addressbook(self):
430         for tx_hash, tx in self.transactions.items():
431             is_send, _, _ = self.get_tx_value(tx)
432             if is_send:
433                 for addr, v in tx.outputs:
434                     if not self.is_mine(addr) and addr not in self.addressbook:
435                         self.addressbook.append(addr)
436         # redo labels
437         # self.update_tx_labels()
438
439
440     def get_address_flags(self, addr):
441         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
442         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
443         return flags
444         
445
446     def get_tx_value(self, tx, addresses=None):
447         if addresses is None: addresses = self.addresses(True)
448         return tx.get_value(addresses, self.prevout_values)
449
450
451     def get_tx_details(self, tx_hash):
452         import datetime
453         if not tx_hash: return ''
454         tx = self.transactions.get(tx_hash)
455         is_mine, v, fee = self.get_tx_value(tx)
456         conf, timestamp = self.verifier.get_confirmations(tx_hash)
457
458         if timestamp:
459             time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
460         else:
461             time_str = 'pending'
462
463         inputs = map(lambda x: x.get('address'), tx.inputs)
464         outputs = map(lambda x: x.get('address'), tx.d['outputs'])
465         tx_details = "Transaction Details" +"\n\n" \
466             + "Transaction ID:\n" + tx_hash + "\n\n" \
467             + "Status: %d confirmations\n"%conf
468         if is_mine:
469             if fee: 
470                 tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
471                               + "Transaction fee: %s\n"% format_satoshis(fee, False)
472             else:
473                 tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
474                               + "Transaction fee: unknown\n"
475         else:
476             tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
477
478         tx_details += "Date: %s\n\n"%time_str \
479             + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
480             + "Outputs:\n-"+ '\n-'.join(outputs)
481
482         r = self.receipts.get(tx_hash)
483         if r:
484             tx_details += "\n_______________________________________" \
485                 + '\n\nSigned URI: ' + r[2] \
486                 + "\n\nSigned by: " + r[0] \
487                 + '\n\nSignature: ' + r[1]
488
489         return tx_details
490
491     
492     def update_tx_outputs(self, tx_hash):
493         tx = self.transactions.get(tx_hash)
494         i = 0
495         for item in tx.outputs:
496             addr, value = item
497             key = tx_hash+ ':%d'%i
498             with self.lock:
499                 self.prevout_values[key] = value
500             i += 1
501
502         for item in tx.inputs:
503             if self.is_mine(item.get('address')):
504                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
505                 self.spent_outputs.append(key)
506
507
508     def get_addr_balance(self, address):
509         assert self.is_mine(address)
510         h = self.history.get(address,[])
511         if h == ['*']: return 0,0
512         c = u = 0
513         received_coins = []   # list of coins received at address
514
515         for tx_hash, tx_height in h:
516             tx = self.transactions.get(tx_hash)
517             if not tx: continue
518             i = 0
519             for item in tx.outputs:
520                 addr, value = item
521                 if addr == address:
522                     key = tx_hash + ':%d'%i
523                     received_coins.append(key)
524                 i +=1
525
526         for tx_hash, tx_height in h:
527             tx = self.transactions.get(tx_hash)
528             if not tx: continue
529             v = 0
530
531             for item in tx.inputs:
532                 addr = item.get('address')
533                 if addr == address:
534                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
535                     value = self.prevout_values.get( key )
536                     if key in received_coins: 
537                         v -= value
538
539             i = 0
540             for item in tx.outputs:
541                 addr, value = item
542                 key = tx_hash + ':%d'%i
543                 if addr == address:
544                     v += value
545                 i += 1
546
547             if tx_height:
548                 c += v
549             else:
550                 u += v
551         return c, u
552
553     def get_account_addresses(self, a):
554         ac = self.accounts[a]
555         return ac[0] + ac[1]
556
557     def get_imported_balance(self):
558         cc = uu = 0
559         for addr in self.imported_keys.keys():
560             c, u = self.get_addr_balance(addr)
561             cc += c
562             uu += u
563         return cc, uu
564
565     def get_account_balance(self, account):
566         conf = unconf = 0
567         for addr in self.get_account_addresses(account): 
568             c, u = self.get_addr_balance(addr)
569             conf += c
570             unconf += u
571         return conf, unconf
572
573     def get_balance(self):
574         cc = uu = 0
575         for a in self.accounts.keys():
576             c, u = self.get_account_balance(a)
577             cc += c
578             uu += u
579         c, u = self.get_imported_balance()
580         cc += c
581         uu += u
582         return cc, uu
583
584
585     def get_unspent_coins(self, domain=None):
586         coins = []
587         if domain is None: domain = self.addresses(True)
588         for addr in domain:
589             h = self.history.get(addr, [])
590             if h == ['*']: continue
591             for tx_hash, tx_height in h:
592                 tx = self.transactions.get(tx_hash)
593                 for output in tx.d.get('outputs'):
594                     if output.get('address') != addr: continue
595                     key = tx_hash + ":%d" % output.get('index')
596                     if key in self.spent_outputs: continue
597                     output['tx_hash'] = tx_hash
598                     coins.append(output)
599         return coins
600
601
602
603     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
604         """ todo: minimize tx size """
605         total = 0
606         fee = self.fee if fixed_fee is None else fixed_fee
607
608         coins = []
609         prioritized_coins = []
610         domain = [from_addr] if from_addr else self.addresses(True)
611         for i in self.frozen_addresses:
612             if i in domain: domain.remove(i)
613
614         for i in self.prioritized_addresses:
615             if i in domain: domain.remove(i)
616
617         coins = self.get_unspent_coins(domain)
618         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
619
620         inputs = []
621         coins = prioritized_coins + coins
622
623         for item in coins: 
624             addr = item.get('address')
625             v = item.get('value')
626             total += v
627
628             inputs.append( item )
629             fee = self.fee*len(inputs) if fixed_fee is None else fixed_fee
630             if total >= amount + fee: break
631         else:
632             #print "not enough funds: %s %s"%(format_satoshis(total), format_satoshis(fee))
633             inputs = []
634
635         return inputs, total, fee
636
637     def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
638         change_amount = total - ( amount + fee )
639         if change_amount != 0:
640             # normally, the update thread should ensure that the last change address is unused
641             if not change_addr:
642                 change_addresses = self.accounts[0][1]
643                 change_addr = change_addresses[-self.gap_limit_for_change]
644             # Insert the change output at a random position in the outputs
645             posn = random.randint(0, len(outputs))
646             outputs[posn:posn] = [( change_addr,  change_amount)]
647         return outputs
648
649
650     def get_history(self, address):
651         with self.lock:
652             return self.history.get(address)
653
654     def get_status(self, h):
655         if not h: return None
656         if h == ['*']: return '*'
657         status = ''
658         for tx_hash, height in h:
659             status += tx_hash + ':%d:' % height
660         return hashlib.sha256( status ).digest().encode('hex')
661
662
663
664     def receive_tx_callback(self, tx_hash, tx, tx_height):
665
666         if not self.check_new_tx(tx_hash, tx):
667             raise BaseException("error: received transaction is not consistent with history"%tx_hash)
668
669         with self.lock:
670             self.transactions[tx_hash] = tx
671             self.tx_height[tx_hash] = tx_height
672
673         #tx_height = tx.get('height')
674         if self.verifier and tx_height>0: 
675             self.verifier.add(tx_hash, tx_height)
676
677         self.update_tx_outputs(tx_hash)
678
679         self.save()
680
681
682     def receive_history_callback(self, addr, hist):
683
684         if not self.check_new_history(addr, hist):
685             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
686             
687         with self.lock:
688             self.history[addr] = hist
689             self.save()
690
691         if hist != ['*']:
692             for tx_hash, tx_height in hist:
693                 if tx_height>0:
694                     # add it in case it was previously unconfirmed
695                     if self.verifier: self.verifier.add(tx_hash, tx_height)
696                     # set the height in case it changed
697                     txh = self.tx_height.get(tx_hash)
698                     if txh is not None and txh != tx_height:
699                         print_error( "changing height for tx", tx_hash )
700                         self.tx_height[tx_hash] = tx_height
701
702
703     def get_tx_history(self):
704         with self.lock:
705             history = self.transactions.items()
706         history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
707         result = []
708     
709         balance = 0
710         for tx_hash, tx in history:
711             is_mine, v, fee = self.get_tx_value(tx)
712             if v is not None: balance += v
713         c, u = self.get_balance()
714
715         if balance != c+u:
716             v_str = format_satoshis( c+u - balance, True, self.num_zeros)
717             result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
718
719         balance = c + u - balance
720         for tx_hash, tx in history:
721             conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
722             is_mine, value, fee = self.get_tx_value(tx)
723             if value is not None:
724                 balance += value
725
726             result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
727
728         return result
729
730
731     def get_label(self, tx_hash):
732         label = self.labels.get(tx_hash)
733         is_default = (label == '') or (label is None)
734         if is_default: label = self.get_default_label(tx_hash)
735         return label, is_default
736
737
738     def get_default_label(self, tx_hash):
739         tx = self.transactions.get(tx_hash)
740         default_label = ''
741         if tx:
742             is_mine, _, _ = self.get_tx_value(tx)
743             if is_mine:
744                 for o in tx.outputs:
745                     o_addr, _ = o
746                     if not self.is_mine(o_addr):
747                         try:
748                             default_label = self.labels[o_addr]
749                         except KeyError:
750                             default_label = o_addr
751             else:
752                 for o in tx.outputs:
753                     o_addr, _ = o
754                     if self.is_mine(o_addr) and not self.is_change(o_addr):
755                         break
756                 else:
757                     for o in tx.outputs:
758                         o_addr, _ = o
759                         if self.is_mine(o_addr):
760                             break
761                     else:
762                         o_addr = None
763
764                 if o_addr:
765                     dest_label = self.labels.get(o_addr)
766                     try:
767                         default_label = self.labels[o_addr]
768                     except KeyError:
769                         default_label = o_addr
770
771         return default_label
772
773
774     def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None):
775
776         for address, x in outputs:
777             assert is_valid(address)
778
779         amount = sum( map(lambda x:x[1], outputs) )
780         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
781         if not inputs:
782             raise ValueError("Not enough funds")
783
784         if not self.use_change and not change_addr:
785             change_addr = inputs[-1]['address']
786             print_error( "Sending change to", change_addr )
787         outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
788
789         tx = Transaction.from_io(inputs, outputs)
790
791         pk_addresses = []
792         for i in range(len(tx.inputs)):
793             txin = tx.inputs[i]
794             address = txin['address']
795             if address in self.imported_keys.keys(): 
796                 pk_addresses.append(address)
797                 continue
798             account, sequence = self.get_address_index(address)
799             txin['electrumKeyID'] = (account, sequence) # used by the server to find the key
800             pk_addr, redeemScript = self.sequences[account].get_input_info(sequence)
801             if redeemScript: txin['redeemScript'] = redeemScript
802             pk_addresses.append(pk_addr)
803
804         # get all private keys at once.
805         private_keys = self.get_private_keys(pk_addresses, password)
806         tx.sign(private_keys)
807
808         for address, x in outputs:
809             if address not in self.addressbook and not self.is_mine(address):
810                 self.addressbook.append(address)
811
812         return tx
813
814
815
816     def sendtx(self, tx):
817         # synchronous
818         h = self.send_tx(tx)
819         self.tx_event.wait()
820         return self.receive_tx(h)
821
822     def send_tx(self, tx):
823         # asynchronous
824         self.tx_event.clear()
825         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
826         return tx.hash()
827
828     def receive_tx(self,tx_hash):
829         out = self.tx_result 
830         if out != tx_hash:
831             return False, "error: " + out
832         if self.receipt:
833             self.receipts[tx_hash] = self.receipt
834             self.receipt = None
835         return True, out
836
837
838     def read_alias(self, alias):
839         # this might not be the right place for this function.
840         import urllib
841
842         m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
843         m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
844         if m1:
845             url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
846         elif m2:
847             url = 'https://' + alias + '/bitcoin.id'
848         else:
849             return ''
850         try:
851             lines = urllib.urlopen(url).readlines()
852         except:
853             return ''
854
855         # line 0
856         line = lines[0].strip().split(':')
857         if len(line) == 1:
858             auth_name = None
859             target = signing_addr = line[0]
860         else:
861             target, auth_name, signing_addr, signature = line
862             msg = "alias:%s:%s:%s"%(alias,target,auth_name)
863             print msg, signature
864             EC_KEY.verify_message(signing_addr, signature, msg)
865         
866         # other lines are signed updates
867         for line in lines[1:]:
868             line = line.strip()
869             if not line: continue
870             line = line.split(':')
871             previous = target
872             print repr(line)
873             target, signature = line
874             EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
875
876         if not is_valid(target):
877             raise ValueError("Invalid bitcoin address")
878
879         return target, signing_addr, auth_name
880
881     def update_password(self, seed, old_password, new_password):
882         if new_password == '': new_password = None
883         self.use_encryption = (new_password != None)
884         self.seed = pw_encode( seed, new_password)
885         self.config.set_key('seed', self.seed, True)
886         for k in self.imported_keys.keys():
887             a = self.imported_keys[k]
888             b = pw_decode(a, old_password)
889             c = pw_encode(b, new_password)
890             self.imported_keys[k] = c
891         self.save()
892
893     def get_alias(self, alias, interactive = False, show_message=None, question = None):
894         try:
895             target, signing_address, auth_name = self.read_alias(alias)
896         except BaseException, e:
897             # raise exception if verify fails (verify the chain)
898             if interactive:
899                 show_message("Alias error: " + str(e))
900             return
901
902         print target, signing_address, auth_name
903
904         if auth_name is None:
905             a = self.aliases.get(alias)
906             if not a:
907                 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)
908                 if interactive and question( msg ):
909                     self.aliases[alias] = (signing_address, target)
910                 else:
911                     target = None
912             else:
913                 if signing_address != a[0]:
914                     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
915                     if interactive and question( msg ):
916                         self.aliases[alias] = (signing_address, target)
917                     else:
918                         target = None
919         else:
920             if signing_address not in self.authorities.keys():
921                 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)
922                 if interactive and question( msg ):
923                     self.authorities[signing_address] = auth_name
924                 else:
925                     target = None
926
927         if target:
928             self.aliases[alias] = (signing_address, target)
929             
930         return target
931
932
933     def parse_url(self, url, show_message, question):
934         o = url[8:].split('?')
935         address = o[0]
936         if len(o)>1:
937             params = o[1].split('&')
938         else:
939             params = []
940
941         amount = label = message = signature = identity = ''
942         for p in params:
943             k,v = p.split('=')
944             uv = urldecode(v)
945             if k == 'amount': amount = uv
946             elif k == 'message': message = uv
947             elif k == 'label': label = uv
948             elif k == 'signature':
949                 identity, signature = uv.split(':')
950                 url = url.replace('&%s=%s'%(k,v),'')
951             else: 
952                 print k,v
953
954         if label and self.labels.get(address) != label:
955             if question('Give label "%s" to address %s ?'%(label,address)):
956                 if address not in self.addressbook and not self.is_mine(address):
957                     self.addressbook.append(address)
958                 self.labels[address] = label
959
960         if signature:
961             if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
962                 signing_address = self.get_alias(identity, True, show_message, question)
963             elif is_valid(identity):
964                 signing_address = identity
965             else:
966                 signing_address = None
967             if not signing_address:
968                 return
969             try:
970                 EC_KEY.verify_message(signing_address, signature, url )
971                 self.receipt = (signing_address, signature, url)
972             except:
973                 show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
974                 address = amount = label = identity = message = ''
975
976         if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
977             payto_address = self.get_alias(address, True, show_message, question)
978             if payto_address:
979                 address = address + ' <' + payto_address + '>'
980
981         return address, amount, label, message, signature, identity, url
982
983
984
985     def freeze(self,addr):
986         if self.is_mine(addr) and addr not in self.frozen_addresses:
987             self.unprioritize(addr)
988             self.frozen_addresses.append(addr)
989             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
990             return True
991         else:
992             return False
993
994     def unfreeze(self,addr):
995         if self.is_mine(addr) and addr in self.frozen_addresses:
996             self.frozen_addresses.remove(addr)
997             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
998             return True
999         else:
1000             return False
1001
1002     def prioritize(self,addr):
1003         if is_mine(addr) and addr not in self.prioritized_addresses:
1004             self.unfreeze(addr)
1005             self.prioritized_addresses.append(addr)
1006             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
1007             return True
1008         else:
1009             return False
1010
1011     def unprioritize(self,addr):
1012         if is_mine(addr) and addr in self.prioritized_addresses:
1013             self.prioritized_addresses.remove(addr)
1014             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
1015             return True
1016         else:
1017             return False
1018
1019     def save(self):
1020         tx = {}
1021         for k,v in self.transactions.items():
1022             tx[k] = str(v)
1023             
1024         s = {
1025             'use_encryption': self.use_encryption,
1026             'use_change': self.use_change,
1027             'fee': self.fee,
1028             'accounts': self.accounts,
1029             'addr_history': self.history, 
1030             'labels': self.labels,
1031             'contacts': self.addressbook,
1032             'imported_keys': self.imported_keys,
1033             'aliases': self.aliases,
1034             'authorities': self.authorities,
1035             'receipts': self.receipts,
1036             'num_zeros': self.num_zeros,
1037             'frozen_addresses': self.frozen_addresses,
1038             'prioritized_addresses': self.prioritized_addresses,
1039             'gap_limit': self.gap_limit,
1040             'transactions': tx,
1041             'tx_height': self.tx_height,
1042             'requested_amounts': self.requested_amounts,
1043         }
1044         for k, v in s.items():
1045             self.config.set_key(k,v)
1046         self.config.save()
1047
1048     def set_verifier(self, verifier):
1049         self.verifier = verifier
1050
1051         # review stored transactions and send them to the verifier
1052         # (they are not necessarily in the history, because history items might have have been pruned)
1053         for tx_hash, tx in self.transactions.items():
1054             tx_height = self.tx_height[tx_hash]
1055             if tx_height <1:
1056                 print_error( "skipping", tx_hash, tx_height )
1057                 continue
1058             
1059             if tx_height>0:
1060                 self.verifier.add(tx_hash, tx_height)
1061
1062         # review transactions that are in the history
1063         for addr, hist in self.history.items():
1064             if hist == ['*']: continue
1065             for tx_hash, tx_height in hist:
1066                 if tx_height>0:
1067                     # add it in case it was previously unconfirmed
1068                     self.verifier.add(tx_hash, tx_height)
1069                     # set the height in case it changed
1070                     txh = self.tx_height.get(tx_hash)
1071                     if txh is not None and txh != tx_height:
1072                         print_error( "changing height for tx", tx_hash )
1073                         self.tx_height[tx_hash] = tx_height
1074
1075
1076
1077
1078     def check_new_history(self, addr, hist):
1079         
1080         # check that all tx in hist are relevant
1081         if hist != ['*']:
1082             for tx_hash, height in hist:
1083                 tx = self.transactions.get(tx_hash)
1084                 if not tx: continue
1085                 if not tx.has_address(addr):
1086                     return False
1087
1088         # check that we are not "orphaning" a transaction
1089         old_hist = self.history.get(addr,[])
1090         if old_hist == ['*']: return True
1091
1092         for tx_hash, height in old_hist:
1093             if tx_hash in map(lambda x:x[0], hist): continue
1094             found = False
1095             for _addr, _hist in self.history.items():
1096                 if _addr == addr: continue
1097                 if _hist == ['*']: continue
1098                 _tx_hist = map(lambda x:x[0], _hist)
1099                 if tx_hash in _tx_hist:
1100                     found = True
1101                     break
1102
1103             if not found:
1104                 tx = self.transactions.get(tx_hash)
1105                 # tx might not be there
1106                 if not tx: continue
1107                 
1108                 # already verified?
1109                 if self.tx_height.get(tx_hash):
1110                     continue
1111                 # unconfirmed tx
1112                 print_error("new history is orphaning transaction:", tx_hash)
1113                 # check that all outputs are not mine, request histories
1114                 ext_requests = []
1115                 for o in tx.get('outputs'):
1116                     _addr = o.get('address')
1117                     # assert not self.is_mine(_addr)
1118                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1119
1120                 ext_h = self.interface.synchronous_get(ext_requests)
1121                 height = None
1122                 for h in ext_h:
1123                     if h == ['*']: continue
1124                     for item in h:
1125                         if item.get('tx_hash') == tx_hash:
1126                             height = item.get('height')
1127                             self.tx_height[tx_hash] = height
1128                 if height:
1129                     print_error("found height for", tx_hash, height)
1130                     self.verifier.add(tx_hash, height)
1131                 else:
1132                     print_error("removing orphaned tx from history", tx_hash)
1133                     self.transactions.pop(tx_hash)
1134
1135         return True
1136
1137
1138
1139     def check_new_tx(self, tx_hash, tx):
1140         # 1 check that tx is referenced in addr_history. 
1141         addresses = []
1142         for addr, hist in self.history.items():
1143             if hist == ['*']:continue
1144             for txh, height in hist:
1145                 if txh == tx_hash: 
1146                     addresses.append(addr)
1147
1148         if not addresses:
1149             return False
1150
1151         # 2 check that referencing addresses are in the tx
1152         for addr in addresses:
1153             if not tx.has_address(addr):
1154                 return False
1155
1156         return True
1157
1158
1159
1160
1161 class WalletSynchronizer(threading.Thread):
1162
1163
1164     def __init__(self, wallet, config):
1165         threading.Thread.__init__(self)
1166         self.daemon = True
1167         self.wallet = wallet
1168         self.interface = self.wallet.interface
1169         self.interface.register_channel('synchronizer')
1170         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1171         self.wallet.interface.register_callback('connected', lambda: self.interface.send([('server.banner',[])],'synchronizer') )
1172         self.was_updated = True
1173         self.running = False
1174         self.lock = threading.Lock()
1175
1176     def stop(self):
1177         with self.lock: self.running = False
1178         self.interface.poke('synchronizer')
1179
1180     def is_running(self):
1181         with self.lock: return self.running
1182
1183     def synchronize_wallet(self):
1184         new_addresses = self.wallet.synchronize()
1185         if new_addresses:
1186             self.subscribe_to_addresses(new_addresses)
1187             self.wallet.up_to_date = False
1188             return
1189             
1190         if not self.interface.is_up_to_date('synchronizer'):
1191             if self.wallet.is_up_to_date():
1192                 self.wallet.set_up_to_date(False)
1193                 self.was_updated = True
1194             return
1195
1196         self.wallet.set_up_to_date(True)
1197         self.was_updated = True
1198
1199     
1200     def subscribe_to_addresses(self, addresses):
1201         messages = []
1202         for addr in addresses:
1203             messages.append(('blockchain.address.subscribe', [addr]))
1204         self.interface.send( messages, 'synchronizer')
1205
1206
1207     def run(self):
1208         with self.lock: self.running = True
1209
1210         requested_tx = []
1211         missing_tx = []
1212         requested_histories = {}
1213
1214         # request any missing transactions
1215         for history in self.wallet.history.values():
1216             if history == ['*']: continue
1217             for tx_hash, tx_height in history:
1218                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1219                     missing_tx.append( (tx_hash, tx_height) )
1220         print_error("missing tx", missing_tx)
1221
1222         # wait until we are connected, in case the user is not connected
1223         while not self.interface.is_connected:
1224             time.sleep(1)
1225         
1226         # request banner, because 'connected' event happens before this thread is started
1227         self.interface.send([('server.banner',[])],'synchronizer')
1228
1229         # subscriptions
1230         self.subscribe_to_addresses(self.wallet.addresses(True))
1231
1232         while self.is_running():
1233             # 1. send new requests
1234             self.synchronize_wallet()
1235
1236             for tx_hash, tx_height in missing_tx:
1237                 if (tx_hash, tx_height) not in requested_tx:
1238                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1239                     requested_tx.append( (tx_hash, tx_height) )
1240             missing_tx = []
1241
1242             if self.was_updated:
1243                 self.interface.trigger_callback('updated')
1244                 self.was_updated = False
1245
1246             # 2. get a response
1247             r = self.interface.get_response('synchronizer')
1248
1249             # poke sends None. (needed during stop)
1250             if not r: continue
1251
1252             # 3. handle response
1253             method = r['method']
1254             params = r['params']
1255             result = r.get('result')
1256             error = r.get('error')
1257             if error:
1258                 print "error", r
1259                 continue
1260
1261             if method == 'blockchain.address.subscribe':
1262                 addr = params[0]
1263                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1264                     if requested_histories.get(addr) is None:
1265                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1266                         requested_histories[addr] = result
1267
1268             elif method == 'blockchain.address.get_history':
1269                 addr = params[0]
1270                 print_error("receiving history", addr, result)
1271                 if result == ['*']:
1272                     assert requested_histories.pop(addr) == '*'
1273                     self.wallet.receive_history_callback(addr, result)
1274                 else:
1275                     hist = []
1276                     # check that txids are unique
1277                     txids = []
1278                     for item in result:
1279                         tx_hash = item['tx_hash']
1280                         if tx_hash not in txids:
1281                             txids.append(tx_hash)
1282                             hist.append( (tx_hash, item['height']) )
1283
1284                     if len(hist) != len(result):
1285                         raise BaseException("error: server sent history with non-unique txid", result)
1286
1287                     # check that the status corresponds to what was announced
1288                     rs = requested_histories.pop(addr)
1289                     if self.wallet.get_status(hist) != rs:
1290                         raise BaseException("error: status mismatch: %s"%addr)
1291                 
1292                     # store received history
1293                     self.wallet.receive_history_callback(addr, hist)
1294
1295                     # request transactions that we don't have 
1296                     for tx_hash, tx_height in hist:
1297                         if self.wallet.transactions.get(tx_hash) is None:
1298                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1299                                 missing_tx.append( (tx_hash, tx_height) )
1300
1301             elif method == 'blockchain.transaction.get':
1302                 tx_hash = params[0]
1303                 tx_height = params[1]
1304                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1305                 tx = Transaction(result)
1306                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1307                 self.was_updated = True
1308                 requested_tx.remove( (tx_hash, tx_height) )
1309                 print_error("received tx:", tx)
1310
1311             elif method == 'blockchain.transaction.broadcast':
1312                 self.wallet.tx_result = result
1313                 self.wallet.tx_event.set()
1314
1315             elif method == 'server.banner':
1316                 self.wallet.banner = result
1317                 self.interface.trigger_callback('banner')
1318             else:
1319                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1320
1321             if self.was_updated and not requested_tx:
1322                 self.interface.trigger_callback('updated')
1323                 self.was_updated = False
1324
1325