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