df1332df39266bc60082ab5687d41d471bcfa169
[electrum-nvc.git] / electrum
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 re
20 import sys
21 # import argparse
22 import optparse
23
24 try:
25     from lib.util import print_error
26 except ImportError:
27     from electrum.util import print_error
28
29 try:
30     import ecdsa  
31 except ImportError:
32     sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
33
34 try:
35     import aes
36 except ImportError:
37     sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
38
39 try:
40     from lib import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
41 except ImportError:
42     from electrum import Wallet, WalletSynchronizer, format_satoshis, mnemonic, prompt_password, parse_proxy_options, SimpleConfig
43
44 from decimal import Decimal
45
46 known_commands = {
47     'help':'Prints this help',
48     'validateaddress':'Check that the address is valid', 
49     'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]", 
50     'contacts': "Show your list of contacts", 
51     'create':'Create a wallet', 
52     'restore':'Restore a wallet', 
53     'payto':"""Create and broadcast a transaction.
54 Syntax: payto <recipient> <amount> [label]
55 <recipient> can be a bitcoin address or a label
56 options:\n  --fee, -f: set transaction fee\n  --fromaddr, -s: send from address -\n  --changeaddr, -c: send change to address
57             """,
58     'sendtx':
59             'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
60     'password': 
61             "Changes your password",
62     'addresses':  
63             """Shows your list of addresses.
64 options:
65   -a: show all addresses, including change addresses
66   -k: show private keys
67   -b: show the balance of addresses""",
68
69     'history':"Shows the transaction history",
70     'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
71     'mktx':
72         """Create a signed transaction, password protected.
73 Syntax: mktx <recipient> <amount> [label]
74 options:\n  --fee, -f: set transaction fee\n  --fromaddr, -s: send from address -\n  --changeaddr, -c: send change to address
75         """,
76     'seed':
77             "Print the generation seed of your wallet.",
78     'import': 
79             'Imports a key pair\nSyntax: import <address>:<privatekey>',
80     'signmessage':
81             'Signs a message with a key\nSyntax: signmessage <address> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello  This is a weird String "',
82     'verifymessage':
83              'Verifies a signature\nSyntax: verifymessage <address> <signature> <message>\nIf you want to lead or end a message with spaces, or want double spaces inside the message make sure you quote the string. I.e. " Hello  This is a weird String "',
84     'eval':  
85              "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86     'deseed':
87             "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
88     'reseed':
89             "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
90     'freeze':'',
91     'unfreeze':'',
92     'prioritize':'',
93     'unprioritize':'',
94     }
95
96
97
98 offline_commands = [ 'password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed','freeze','unfreeze','prioritize','unprioritize']
99
100 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
101
102 if __name__ == '__main__':
103
104     # Load simple config class
105     simple_config = SimpleConfig()
106
107     usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
108     parser = optparse.OptionParser(prog=usage)
109     parser.add_option("-g", "--gui", dest="gui", default=simple_config.config["gui"], help="gui")
110     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
111     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
112     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
113     parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
114     parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
115     parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
116     parser.add_option("-s", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.")
117     parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet")
118     parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
119     options, args = parser.parse_args()
120
121     proxy = parse_proxy_options(options.proxy) if options.proxy else simple_config.config["proxy"]
122     wallet = Wallet()
123     wallet.set_path(options.wallet_path)
124     wallet.read()
125
126     if len(args)==0:
127         url = None
128         cmd = 'gui'
129     elif len(args)==1 and re.match('^bitcoin:', args[0]):
130         url = args[0]
131         cmd = 'gui'
132     else:
133         cmd = args[0]
134         firstarg = args[1] if len(args) > 1 else ''
135        
136     #this entire if/else block is just concerned with importing the 
137     #right GUI toolkit based the GUI command line option given 
138     if cmd == 'gui':
139         
140         if options.gui=='gtk':
141             try:
142                 import lib.gui as gui
143             except ImportError:
144                 import electrum.gui as gui
145         elif options.gui=='qt':
146             try:
147                 import lib.gui_qt as gui
148             except ImportError:
149                 import electrum.gui_qt as gui
150         elif options.gui == 'lite':
151               try:
152                   import lib.gui_lite as gui
153               except ImportError:
154                   import electrum.gui_lite as gui
155         else:
156             sys.exit("Error: Unknown GUI: " + options.gui)
157
158         gui = gui.ElectrumGui(wallet)
159         interface = WalletSynchronizer(wallet, True, gui.server_list_changed, proxy)
160         interface.start()
161
162         try:
163             found = wallet.file_exists
164             if not found:
165                 found = gui.restore_or_create()
166         except SystemExit, e:
167             exit(e)
168         except BaseException, e:
169             import traceback
170             traceback.print_exc(file=sys.stdout)
171             #gui.show_message(e.message)
172             exit(1)
173
174         if not found:
175             exit(1)
176         gui.main(url)
177         wallet.save()
178         sys.exit(0)
179
180     if cmd not in known_commands:
181         cmd = 'help'
182
183     if not wallet.file_exists and cmd not in ['help','create','restore']:
184         print "Error: Wallet file not found."
185         print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
186         sys.exit(0)
187     
188     if cmd in ['create', 'restore']:
189         if wallet.file_exists:
190             sys.exit("Error: Remove the existing wallet first!")
191         password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
192
193         w_host, w_port, w_protocol = wallet.server.split(':')
194         host = raw_input("server (default:%s):"%w_host)
195         port = raw_input("port (default:%s):"%w_port)
196         protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
197         fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
198         gap = raw_input("gap limit (default 5):")
199         if host: w_host = host
200         if port: w_port = port
201         if protocol: w_protocol = protocol
202         wallet.server = w_host + ':' + w_port + ':' +w_protocol
203         if fee: wallet.fee = float(fee)
204         if gap: wallet.gap_limit = int(gap)
205
206         if cmd == 'restore':
207             seed = raw_input("seed:")
208             try:
209                 seed.decode('hex')
210             except:
211                 print_error("Warning: Not hex, trying decode.")
212                 seed = mnemonic.mn_decode( seed.split(' ') )
213             if not seed:
214                 sys.exit("Error: No seed")
215
216             wallet.seed = str(seed)
217             wallet.init_mpk( wallet.seed )
218             if not options.offline:
219                 WalletSynchronizer(wallet, proxy=proxy).start()
220                 print "Recovering wallet..."
221                 wallet.up_to_date_event.clear()
222                 wallet.up_to_date = False
223                 wallet.update()
224                 if wallet.is_found():
225                     print "Recovery successful"
226                 else:
227                     print_error("Warning: Found no history for this wallet")
228             else:
229                 wallet.synchronize()
230             wallet.fill_addressbook()
231             wallet.save()
232             print_error("Wallet saved in '" + wallet.path)
233         else:
234             wallet.new_seed(None)
235             wallet.init_mpk( wallet.seed )
236             wallet.synchronize() # there is no wallet thread 
237             wallet.save()
238             print "Your wallet generation seed is: " + wallet.seed
239             print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
240             print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
241             print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
242             print "Wallet saved in '%s'"%wallet.path
243             
244         if password:
245             wallet.update_password(wallet.seed, None, password)
246
247     # check syntax
248     if cmd in ['payto', 'mktx']:
249         try:
250             to_address = args[1]
251             amount = int( 100000000 * Decimal(args[2]) )
252             change_addr = None
253             label = ' '.join(args[3:])
254             if options.tx_fee: 
255                 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
256         except:
257             firstarg = cmd
258             cmd = 'help'
259
260     # open session
261     if cmd not in offline_commands and not options.offline:
262         WalletSynchronizer(wallet, proxy=proxy).start()
263         wallet.update()
264         wallet.save()
265
266     # check if --from_addr not in wallet (for mktx/payto)
267     is_temporary = False
268     from_addr = None
269     if options.from_addr:
270         from_addr = options.from_addr
271         if from_addr not in wallet.all_addresses():
272             is_temporary = True
273                 
274     # important warning
275     if cmd=='addresses' and options.show_keys:
276         print "WARNING: ALL your private keys are secret."
277         print "Exposing a single private key can compromise your entire wallet!"
278         print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
279
280     # commands needing password
281     if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
282         password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
283         # check password
284         try:
285             wallet.pw_decode( wallet.seed, password)
286         except:
287             print_error("Error: This password does not decode this wallet.")
288             exit(1)
289
290     if cmd == 'import':
291         # See if they specificed a key on the cmd line, if not prompt
292         if len(args) > 1:
293             keypair = args[1]
294         else:
295             keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
296         try:
297             wallet.import_key(keypair,password)
298             wallet.save()
299             print "Keypair imported"
300         except BaseException, e:
301             print_error("Error: Keypair import failed: " + str(e))
302
303     if cmd == 'help':
304         cmd2 = firstarg
305         if cmd2 not in known_commands:
306             parser.print_help()
307             print "Type 'electrum help <command>' to see the help for a specific command"
308             print "Type 'electrum --help' to see the list of options"
309             print "List of commands:", ', '.join(known_commands)
310         else:
311             print known_commands[cmd2]
312
313     elif cmd == 'seed':
314         seed = wallet.pw_decode( wallet.seed, password)
315         print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
316
317     elif cmd == 'deseed':
318         if not wallet.seed:
319             print_error("Error: This wallet has no seed")
320         elif wallet.use_encryption:
321             print_error("Error: This wallet is encrypted")
322         else:
323             ns = wallet.path + '.seed'
324             print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
325             if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
326                 f = open(ns,'w')
327                 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
328                 f.close()
329                 wallet.seed = ''
330                 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
331                 wallet.save()
332                 print "Done."
333             else:
334                 print_error("Action canceled.")
335
336     elif cmd == 'reseed':
337         if wallet.seed:
338             print "Warning: This wallet already has a seed", wallet.seed
339         else:
340             ns = wallet.path + '.seed'
341             try:
342                 f = open(ns,'r')
343                 data = f.read()
344                 f.close()
345             except IOError:
346                 sys.exit("Error: Seed file not found")
347             try:
348                 import ast
349                 d = ast.literal_eval( data )
350                 seed = d['seed']
351                 imported_keys = d.get('imported_keys',{})
352             except:
353                 sys.exit("Error: Error with seed file")
354
355             mpk = wallet.master_public_key
356             wallet.seed = seed
357             wallet.imported_keys = imported_keys
358             wallet.use_encryption = False
359             wallet.init_mpk(seed)
360             if mpk == wallet.master_public_key:
361                 wallet.save()
362                 print "Done: " + wallet.path
363             else:
364                 print_error("Error: Master public key does not match")
365
366     elif cmd == 'validateaddress':
367         addr = args[1]
368         print wallet.is_valid(addr)
369
370     elif cmd == 'balance':
371         try:
372             addrs = args[1:]
373         except:
374             pass
375         if addrs == []:
376             c, u = wallet.get_balance()
377             if u:
378                 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
379             else:
380                 print Decimal( c ) / 100000000
381         else:
382             for addr in addrs:
383                 c, u = wallet.get_addr_balance(addr)
384                 if u:
385                     print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
386                 else:
387                     print "%s %s" % (addr, str(Decimal(c)/100000000))
388
389     elif cmd in [ 'contacts']:
390         for addr in wallet.addressbook:
391             print addr, "   ", wallet.labels.get(addr)
392
393     elif cmd == 'eval':
394         print eval(args[1])
395         wallet.save()
396
397     elif cmd in [ 'addresses']:
398         for addr in wallet.all_addresses():
399             if options.show_all or not wallet.is_change(addr):
400
401                 flags = wallet.get_address_flags(addr)
402                 label = wallet.labels.get(addr,'')
403                 
404                 if label: label = "\"%s\""%label
405
406                 if options.show_balance:
407                     h = wallet.history.get(addr,[])
408                     #ni = no = 0
409                     #for item in h:
410                     #    if item['is_input']:  ni += 1
411                     #    else:              no += 1
412                     b = format_satoshis(wallet.get_addr_balance(addr)[0])
413                 else: b=''
414                 m_addr = "%34s"%addr
415                 if options.show_keys:
416                     m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
417                 print flags, m_addr, b, label
418
419     if cmd == 'history':
420         lines = wallet.get_tx_history()
421         b = 0 
422         for line in lines:
423             import datetime
424             v = line['value'] 
425             b += v
426             try:
427                 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
428             except:
429                 print line['timestamp']
430                 time_str = 'pending'
431             label = line.get('label')
432             if not label: label = line['tx_hash']
433             else: label = label + ' '*(64 - len(label) )
434
435             print time_str , "  " + label + "  " + format_satoshis(v)+ "  "+ format_satoshis(b)
436         print "# balance: ", format_satoshis(b)
437
438     elif cmd == 'label':
439         try:
440             tx = args[1]
441             label = ' '.join(args[2:])
442         except:
443             print_error("Error. Syntax:  label <tx_hash> <text>")
444             sys.exit(1)
445         wallet.labels[tx] = label
446         wallet.save()
447             
448     elif cmd in ['payto', 'mktx']:
449         if from_addr and is_temporary:
450             if from_addr.find(":") == -1:
451                 keypair = from_addr + ":" + prompt_password('Private key:', False)
452             else:
453                 keypair = from_addr
454                 from_addr = keypair.split(':')[0]
455             if not wallet.import_key(keypair,password):
456                 print_error("Error: Invalid key pair")
457                 exit(1)
458             wallet.history[from_addr] = interface.retrieve_history(from_addr)
459             wallet.update_tx_history()
460             change_addr = from_addr
461
462         if options.change_addr:
463             change_addr = options.change_addr
464
465         for k, v in wallet.labels.items():
466             if v == to_address:
467                 to_address = k
468                 print "alias", to_address
469                 break
470             if change_addr and v == change_addr:
471                 change_addr = k
472         try:
473             tx = wallet.mktx( to_address, amount, label, password,
474                 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
475         except:
476             import traceback
477             traceback.print_exc(file=sys.stdout)
478             tx = None
479
480         if tx and cmd=='payto': 
481             r, h = wallet.sendtx( tx )
482             print h
483         else:
484             print tx
485
486         if is_temporary:
487             wallet.imported_keys.pop(from_addr)
488             del(wallet.history[from_addr])
489         wallet.save()
490
491     elif cmd == 'sendtx':
492         tx = args[1]
493         r, h = wallet.sendtx( tx )
494         print h
495
496     elif cmd == 'password':
497         try:
498             seed = wallet.pw_decode( wallet.seed, password)
499         except ValueError:
500             sys.exit("Error: Password does not decrypt this wallet.")
501
502         new_password = prompt_password('New password:')
503         wallet.update_password(seed, password, new_password)
504
505     elif cmd == 'signmessage':
506         if len(args) < 3:
507             print_error("Error: Invalid usage of signmessage.")
508             print known_commands[cmd]
509             sys.exit(1)
510         address = args[1]
511         message = ' '.join(args[2:])
512         if len(args) > 3:
513             print "Warning: Message was reconstructed from several arguments:", repr(message)
514         print wallet.sign_message(address, message, password)
515
516     elif cmd == 'verifymessage':
517         try:
518             address = args[1]
519             signature = args[2]
520             message = ' '.join(args[3:])
521         except:
522             print_error("Error: Not all parameters were given, displaying help instead.")
523             print known_commands[cmd]
524             sys.exit(1)
525         if len(args) > 4:
526             print "Warning: Message was reconstructed from several arguments:", repr(message)
527         try:
528             wallet.verify_message(address, signature, message)
529             print True
530         except BaseException as e:
531             print "Verification error: {0}".format(e)
532             print False
533
534     elif cmd == 'freeze':
535         addr = args[1]
536         print self.wallet.freeze(addr)
537         
538     elif cmd == 'unfreeze':
539         addr = args[1]
540         print self.wallet.unfreeze(addr)
541
542     elif cmd == 'prioritize':
543         addr = args[1]
544         print self.wallet.prioritize(addr)
545
546     elif cmd == 'unprioritize':
547         addr = args[1]
548         print self.wallet.unprioritize(addr)
549