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