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