f82dd374027985cc0b03ce0653802c2d2adc1442
[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, sys, getpass
20
21 try:
22     import ecdsa  
23 except:
24     print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'"
25     sys.exit(1)
26
27 try:
28     import aes
29 except:
30     print "AES does not seem to be installed. Try 'sudo pip install slowaes'"
31     sys.exit(1)
32
33
34 import electrum
35 from optparse import OptionParser
36 from decimal import Decimal
37
38 from electrum import Wallet, WalletSynchronizer, format_satoshis
39
40 known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed']
41 offline_commands = ['password', 'mktx', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed']
42 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
43
44 if __name__ == '__main__':
45
46     usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
47     parser = OptionParser(usage=usage)
48     parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui")
49     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
50     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
51     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
52     parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
53     parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
54     parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
55     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.")
56     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")
57     parser.add_option("-r", "--remote", dest="remote_url", default=None, help="URL of a remote wallet")
58     options, args = parser.parse_args()
59
60     wallet = Wallet()
61     wallet.set_path(options.wallet_path)
62     wallet.read()
63     wallet.remote_url = options.remote_url
64
65     if len(args)==0:
66         url = None
67         cmd = 'gui'
68     elif len(args)==1 and re.match('^bitcoin:', args[0]):
69         url = args[0]
70         cmd = 'gui'
71     else:
72         cmd = args[0]
73         firstarg = args[1] if len(args) > 1 else ''
74         
75     if cmd == 'gui':
76         if options.gui=='gtk':
77             import electrum.gui as gui
78         elif options.gui=='qt':
79             import electrum.gui_qt as gui
80         else:
81             print "unknown gui", options.gui
82             exit(1)
83
84         gui = gui.ElectrumGui(wallet)
85         WalletSynchronizer(wallet,True).start()
86
87         try:
88             found = wallet.file_exists
89             if not found:
90                 found = gui.restore_or_create()
91         except SystemExit, e:
92             exit(e)
93         except BaseException, e:
94             import traceback
95             traceback.print_exc(file=sys.stdout)
96             #gui.show_message(e.message)
97             exit(1)
98
99         if not found: exit(1)
100         gui.main(url)
101         wallet.save()
102         sys.exit(0)
103
104     if cmd not in known_commands:
105         cmd = 'help'
106
107     if not wallet.file_exists and cmd not in ['help','create','restore']:
108         print "Wallet file not found."
109         print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
110         sys.exit(0)
111     
112     if cmd in ['create', 'restore']:
113         from electrum import mnemonic
114         if wallet.file_exists:
115             print "remove the existing wallet first!"
116             sys.exit(0)
117         password = getpass.getpass("Password (hit return if you do not wish to encrypt your wallet):")
118         if password:
119             password2 = getpass.getpass("Confirm password:")
120             if password != password2:
121                 print "error"
122                 sys.exit(1)
123         else:
124             password = None
125
126         w_host, w_port, w_protocol = wallet.server.split(':')
127         host = raw_input("server (default:%s):"%w_host)
128         port = raw_input("port (default:%s):"%w_port)
129         protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
130         fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
131         gap = raw_input("gap limit (default 5):")
132         if host: w_host = host
133         if port: w_port = port
134         if protocol: w_protocol = protocol
135         wallet.server = w_host + ':' + w_port + ':' +w_protocol
136         if fee: wallet.fee = float(fee)
137         if gap: wallet.gap_limit = int(gap)
138
139         if cmd == 'restore':
140             seed = raw_input("seed:")
141             try:
142                 seed.decode('hex')
143             except:
144                 print "not hex, trying decode"
145                 seed = mnemonic.mn_decode( seed.split(' ') )
146             if not seed:
147                 print "no seed"
148                 sys.exit(1)
149
150             wallet.seed = str(seed)
151             wallet.init_mpk( wallet.seed )
152             if not options.offline:
153                 WalletSynchronizer(wallet).start()
154                 print "recovering wallet..."
155                 wallet.up_to_date_event.clear()
156                 wallet.up_to_date = False
157                 wallet.update()
158                 if wallet.is_found():
159                     print "recovery successful"
160                 else:
161                     print "found no history for this wallet"
162             wallet.fill_addressbook()
163             wallet.save()
164             print "Wallet saved in '%s'"%wallet.path
165         else:
166             wallet.new_seed(None)
167             wallet.init_mpk( wallet.seed )
168             wallet.synchronize() # there is no wallet thread 
169             wallet.save()
170             print "Your wallet generation seed is: " + wallet.seed
171             print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
172             print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
173             print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
174             print "Wallet saved in '%s'"%wallet.path
175             
176         if password:
177             wallet.update_password(wallet.seed, None, password)
178
179     # check syntax
180     if cmd in ['payto', 'mktx']:
181         try:
182             to_address = args[1]
183             amount = int( 100000000 * Decimal(args[2]) )
184             change_addr = None
185             label = ' '.join(args[3:])
186             if options.tx_fee: 
187                 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
188         except:
189             firstarg = cmd
190             cmd = 'help'
191
192     # open session
193     if cmd not in offline_commands and not options.offline:
194         WalletSynchronizer(wallet).start()
195         wallet.update()
196         wallet.save()
197
198     # check if --from_addr not in wallet (for mktx/payto)
199     is_temporary = False
200     from_addr = None
201     if options.from_addr:
202         from_addr = options.from_addr
203         if from_addr not in wallet.all_addresses():
204             is_temporary = True
205                 
206     # commands needing password
207     if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
208         password = getpass.getpass('Password:') if wallet.use_encryption and not is_temporary else None
209         # check password
210         try:
211             wallet.pw_decode( wallet.seed, password)
212         except:
213             print "invalid password"
214             exit(1)
215
216     if cmd == 'import':
217         keypair = args[1]
218         if wallet.import_key(keypair,password):
219             print "keypair imported"
220         else:
221             print "error"
222         wallet.save()
223
224     if cmd=='help':
225         cmd2 = firstarg
226         if cmd2 not in known_commands:
227             print "type 'electrum help <command>' to see the help for a specific command"
228             print "type 'electrum --help' to see the list of options"
229             print "list of commands:", ', '.join(known_commands)
230         elif cmd2 == 'balance':
231             print "Display the balance of your wallet or of an address."
232             print "syntax: balance [<address>]"
233         elif cmd2 == 'contacts':
234             print "show your list of contacts"
235         elif cmd2 == 'payto':
236             print "payto <recipient> <amount> [label]"
237             print "create and broadcast a transaction."
238             print "<recipient> can be a bitcoin address or a label"
239             print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
240         elif cmd2== 'sendtx':
241             print "sendtx <tx>"
242             print "broadcast a transaction to the network. <tx> must be in hexadecimal"
243         elif cmd2 == 'password':
244             print "change your password"
245         elif cmd2 == 'addresses':
246             print "show your list of addresses."
247             print "options:\n -a: show all addresses, including change addresses\n-k: show private keys\n-b: show the balance of addresses"
248         elif cmd2 == 'history':
249             print "show the transaction history"
250         elif cmd2 == 'label':
251             print "assign a label to an item"
252         elif cmd2 == 'gtk':
253             print "start the GUI"
254         elif cmd2 == 'mktx':
255             print "create a signed transaction. password protected"
256             print "syntax: mktx <recipient> <amount> [label]"
257             print "options:\n--fee, -f: set transaction fee\n--fromaddr, -s: send from address -\n--changeaddr, -c: send change to address"
258         elif cmd2 == 'seed':
259             print "print the generation seed of your wallet."
260         elif cmd2 == 'deseed':
261             print "remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'"
262         elif cmd2 == 'reseed':
263             print "restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key."
264         elif cmd2 == 'eval':
265             print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\""
266
267     elif cmd == 'seed':
268         from electrum import mnemonic
269         seed = wallet.pw_decode( wallet.seed, password)
270         print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
271
272     elif cmd == 'deseed':
273         if not wallet.seed:
274             print "Error: This wallet has no seed"
275         elif wallet.use_encryption:
276             print "Error: This wallet is encrypted"
277         else:
278             ns = wallet.path + '.seed'
279             print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
280             if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
281                 f = open(ns,'w')
282                 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
283                 f.close()
284                 wallet.seed = ''
285                 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
286                 wallet.save()
287                 print "Done."
288             else:
289                 print "Action canceled."
290
291     elif cmd == 'reseed':
292         if wallet.seed:
293             print "This wallet already has a seed", wallet.seed
294         else:
295             ns = wallet.path + '.seed'
296             try:
297                 f = open(ns,'r')
298                 data = f.read()
299                 f.close()
300             except:
301                 print "seed file not found"
302                 sys.exit()
303             try:
304                 import ast
305                 d = ast.literal_eval( data )
306                 seed = d['seed']
307                 imported_keys = d.get('imported_keys',{})
308             except:
309                 print "error with seed file"
310                 sys.exit(1)
311
312             mpk = wallet.master_public_key
313             wallet.seed = seed
314             wallet.imported_keys = imported_keys
315             wallet.use_encryption = False
316             wallet.init_mpk(seed)
317             if mpk == wallet.master_public_key:
318                 wallet.save()
319                 print "Done: " + wallet.path
320             else:
321                 print "error: master public key does not match"
322
323     elif cmd == 'validateaddress':
324         addr = args[1]
325         print wallet.is_valid(addr)
326
327     elif cmd == 'balance':
328         try:
329             addrs = args[1:]
330         except:
331             pass
332         if addrs == []:
333             c, u = wallet.get_balance()
334             if u:
335                 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
336             else:
337                 print Decimal( c ) / 100000000
338         else:
339             for addr in addrs:
340                 c, u = wallet.get_addr_balance(addr)
341                 if u:
342                     print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
343                 else:
344                     print "%s %s" % (addr, str(Decimal(c)/100000000))
345
346     elif cmd in [ 'contacts']:
347         for addr in wallet.addressbook:
348             print addr, "   ", wallet.labels.get(addr)
349
350     elif cmd == 'eval':
351         print eval(args[1])
352         wallet.save()
353
354     elif cmd in [ 'addresses']:
355         for addr in wallet.all_addresses():
356             if options.show_all or not wallet.is_change(addr):
357                 label = wallet.labels.get(addr)
358                 _type = ''
359                 if wallet.is_change(addr): _type = "[change]"
360                 if addr in wallet.imported_keys.keys(): _type = "[imported]"
361                 if label is None: label = ''
362                 if options.show_balance:
363                     h = wallet.history.get(addr,[])
364                     ni = no = 0
365                     for item in h:
366                         if item['is_input']:  ni += 1
367                         else:              no += 1
368                     b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
369                 else: b=''
370                 if options.show_keys:
371                     addr += ':' + str(wallet.get_private_key_base58(addr, password))
372                 print addr, b, _type, label
373
374     if cmd == 'history':
375         lines = wallet.get_tx_history()
376         b = 0 
377         for line in lines:
378             import datetime
379             v = line['value'] 
380             b += v
381             try:
382                 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
383             except:
384                 print line['timestamp']
385                 time_str = 'pending'
386             label = line.get('label')
387             if not label: label = line['tx_hash']
388             else: label = label + ' '*(64 - len(label) )
389
390             print time_str , "  " + label + "  " + format_satoshis(v)+ "  "+ format_satoshis(b)
391         print "# balance: ", format_satoshis(b)
392
393     elif cmd == 'label':
394         try:
395             tx = args[1]
396             label = ' '.join(args[2:])
397         except:
398             print "syntax:  label <tx_hash> <text>"
399             sys.exit(1)
400         wallet.labels[tx] = label
401         wallet.save()
402             
403     elif cmd in ['payto', 'mktx']:
404         if from_addr and is_temporary:
405             if from_addr.find(":") == -1:
406                 keypair = from_addr + ":" + getpass.getpass('Private key:')
407             else:
408                 keypair = from_addr
409                 from_addr = keypair.split(':')[0]
410             if not wallet.import_key(keypair,password):
411                 print "invalid key pair"
412                 exit(1)
413             wallet.history[from_addr] = interface.retrieve_history(from_addr)
414             wallet.update_tx_history()
415             change_addr = from_addr
416
417         if options.change_addr:
418             change_addr = options.change_addr
419
420         for k, v in wallet.labels.items():
421             if v == to_address:
422                 to_address = k
423                 print "alias", to_address
424                 break
425             if change_addr and v == change_addr:
426                 change_addr = k
427         try:
428             tx = wallet.mktx( to_address, amount, label, password,
429                 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
430         except:
431             import traceback
432             traceback.print_exc(file=sys.stdout)
433             tx = None
434
435         if tx and cmd=='payto': 
436             r, h = wallet.sendtx( tx )
437             print h
438         else:
439             print tx
440
441         if is_temporary:
442             wallet.imported_keys.pop(from_addr)
443             del(wallet.history[from_addr])
444         wallet.save()
445
446     elif cmd == 'sendtx':
447         tx = args[1]
448         r, h = wallet.sendtx( tx )
449         print h
450
451     elif cmd == 'password':
452         try:
453             seed = wallet.pw_decode( wallet.seed, password)
454         except:
455             print "sorry"
456             sys.exit(1)
457         new_password = getpass.getpass('New password:')
458         if new_password == getpass.getpass('Confirm new password:'):
459             wallet.update_password(seed, password, new_password)
460         else:
461             print "error: mismatch"
462
463     elif cmd == 'signmessage':
464         address, message = args[1:3]
465         print wallet.sign_message(address, message, password)
466
467     elif cmd == 'verifymessage':
468         address, signature, message = args[1:4]
469         try:
470             wallet.verify_message(address, signature, message)
471             print True
472         except:
473             print False
474         
475