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