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