3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
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.
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.
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/>.
24 from lib.util import print_error
26 from electrum.util import print_error
31 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
36 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
39 from lib import Wallet, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
41 from electrum import Wallet, Interface, WalletSynchronizer, WalletVerifier, format_satoshis, mnemonic, SimpleConfig, pick_random_server
43 from decimal import Decimal
46 'help':'Prints this help',
47 'validateaddress':'Check that the address is valid',
48 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
49 'contacts': "Show your list of contacts",
50 'create':'Create a wallet',
51 'restore':'Restore a wallet',
52 'payto':"""Create and broadcast a transaction.
53 Syntax: payto <recipient> <amount> [label]
54 <recipient> can be a bitcoin address or a label
55 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
58 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
60 "Changes your password",
62 """Shows your list of addresses.
64 -a: show all addresses, including change addresses
66 -b: show the balance of addresses""",
68 'history':"Shows the transaction history",
69 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
71 """Create a signed transaction, password protected.
72 Syntax: mktx <recipient> <amount> [label]
73 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
76 "Print the generation seed of your wallet.",
78 'Imports a key pair\nSyntax: import <address>:<privatekey>',
80 '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 '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 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
86 "Set wallet parameter. (gui)",
88 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
90 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
99 offline_commands = [ 'password', 'mktx',
101 'help', 'validateaddress',
102 'signmessage', 'verifymessage',
103 'eval', 'set', 'create', 'addresses',
107 'prioritize','unprioritize']
110 protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ]
112 # get password routine
113 def prompt_password(prompt, confirm=True):
115 if sys.stdin.isatty():
116 password = getpass.getpass(prompt)
117 if password and confirm:
118 password2 = getpass.getpass("Confirm: ")
119 if password != password2:
120 sys.exit("Error: Passwords do not match.")
122 password = raw_input(prompt)
129 if __name__ == '__main__':
131 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
132 parser = optparse.OptionParser(prog=usage)
133 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
134 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
135 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
136 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
137 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
138 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
139 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
140 parser.add_option("-F", "--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.")
141 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")
142 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
143 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
144 options, args = parser.parse_args()
146 # config is an object passed to the various constructors (wallet, interface, gui)
147 config = SimpleConfig(options)
148 wallet = Wallet(config)
153 elif len(args)==1 and re.match('^bitcoin:', args[0]):
158 firstarg = args[1] if len(args) > 1 else ''
160 #this entire if/else block is just concerned with importing the
161 #right GUI toolkit based the GUI command line option given
163 pref_gui = config.get('gui','classic')
164 if pref_gui == 'gtk':
166 import lib.gui as gui
168 import electrum.gui as gui
169 elif pref_gui in ['classic', 'qt']:
171 import lib.gui_qt as gui
173 import electrum.gui_qt as gui
174 elif pref_gui == 'lite':
176 import lib.gui_lite as gui
178 import electrum.gui_lite as gui
179 elif pref_gui == 'text':
181 import lib.gui_text as gui
183 import electrum.gui_text as gui
185 sys.exit("Error: Unknown GUI: " + pref_gui )
187 interface = Interface(config, True)
189 wallet.interface = interface
191 gui = gui.ElectrumGui(wallet, config)
192 interface.register_callback('peers', gui.server_list_changed)
193 # need to wait until interface is connected..
194 WalletSynchronizer(wallet, config).start()
195 WalletVerifier(wallet, config).start()
199 found = config.wallet_file_exists
201 found = gui.restore_or_create()
202 except SystemExit, e:
204 except BaseException, e:
206 traceback.print_exc(file=sys.stdout)
207 #gui.show_message(e.message)
216 if cmd not in known_commands:
219 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
220 print "Error: Wallet file not found."
221 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
224 if cmd in ['create', 'restore']:
225 if config.wallet_file_exists:
226 sys.exit("Error: Remove the existing wallet first!")
227 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
229 server = config.get('server')
230 if not server: server = pick_random_server()
231 w_host, w_port, w_protocol = server.split(':')
232 host = raw_input("server (default:%s):"%w_host)
233 port = raw_input("port (default:%s):"%w_port)
234 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
235 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
236 gap = raw_input("gap limit (default 5):")
237 if host: w_host = host
238 if port: w_port = port
239 if protocol: w_protocol = protocol
240 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
241 if fee: wallet.fee = float(fee)
242 if gap: wallet.gap_limit = int(gap)
245 seed = raw_input("seed:")
249 print_error("Warning: Not hex, trying decode.")
250 seed = mnemonic.mn_decode( seed.split(' ') )
252 sys.exit("Error: No seed")
254 wallet.seed = str(seed)
255 wallet.init_mpk( wallet.seed )
256 if not options.offline:
257 WalletSynchronizer(wallet, config).start()
258 print "Recovering wallet..."
259 wallet.up_to_date_event.clear()
260 wallet.up_to_date = False
262 if wallet.is_found():
263 print "Recovery successful"
265 print_error("Warning: Found no history for this wallet")
268 wallet.fill_addressbook()
270 print_error("Wallet saved in '" + wallet.path)
272 wallet.new_seed(None)
273 wallet.init_mpk( wallet.seed )
274 wallet.synchronize() # there is no wallet thread
276 print "Your wallet generation seed is: " + wallet.seed
277 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
278 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
279 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
280 print "Wallet saved in '%s'"%wallet.config.path
283 wallet.update_password(wallet.seed, None, password)
286 if cmd in ['payto', 'mktx']:
289 amount = int( 100000000 * Decimal(args[2]) )
291 label = ' '.join(args[3:])
293 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
299 if cmd not in offline_commands and not options.offline:
300 interface = Interface(config)
301 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
303 wallet.interface = interface
304 WalletSynchronizer(wallet, config).start()
308 # check if --from_addr not in wallet (for mktx/payto)
311 if options.from_addr:
312 from_addr = options.from_addr
313 if from_addr not in wallet.all_addresses():
317 if cmd=='addresses' and options.show_keys:
318 print "WARNING: ALL your private keys are secret."
319 print "Exposing a single private key can compromise your entire wallet!"
320 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
322 # commands needing password
323 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
324 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
327 wallet.pw_decode( wallet.seed, password)
329 print_error("Error: This password does not decode this wallet.")
333 # See if they specificed a key on the cmd line, if not prompt
337 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
339 wallet.import_key(keypair,password)
341 print "Keypair imported"
342 except BaseException, e:
343 print_error("Error: Keypair import failed: " + str(e))
347 if cmd2 not in known_commands:
349 print "Type 'electrum help <command>' to see the help for a specific command"
350 print "Type 'electrum --help' to see the list of options"
351 print "List of commands:", ', '.join(known_commands)
353 print known_commands[cmd2]
356 seed = wallet.pw_decode( wallet.seed, password)
357 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
359 elif cmd == 'deseed':
361 print_error("Error: This wallet has no seed")
362 elif wallet.use_encryption:
363 print_error("Error: This wallet is encrypted")
365 ns = wallet.path + '.seed'
366 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
367 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
369 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
372 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
376 print_error("Action canceled.")
378 elif cmd == 'reseed':
380 print "Warning: This wallet already has a seed", wallet.seed
382 ns = wallet.path + '.seed'
388 sys.exit("Error: Seed file not found")
391 d = ast.literal_eval( data )
393 imported_keys = d.get('imported_keys',{})
395 sys.exit("Error: Error with seed file")
397 mpk = wallet.master_public_key
399 wallet.imported_keys = imported_keys
400 wallet.use_encryption = False
401 wallet.init_mpk(seed)
402 if mpk == wallet.master_public_key:
404 print "Done: " + wallet.path
406 print_error("Error: Master public key does not match")
408 elif cmd == 'validateaddress':
410 print wallet.is_valid(addr)
412 elif cmd == 'balance':
418 c, u = wallet.get_balance()
420 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
422 print Decimal( c ) / 100000000
425 c, u = wallet.get_addr_balance(addr)
427 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
429 print "%s %s" % (addr, str(Decimal(c)/100000000))
431 elif cmd in [ 'contacts']:
432 for addr in wallet.addressbook:
433 print addr, " ", wallet.labels.get(addr)
440 key, value = args[1:3]
441 if key in ['gui', 'server', 'proxy', 'fee', 'gap_limit', 'use_change']:
442 wallet.config.set_key(key, value, True)
447 elif cmd in [ 'addresses']:
448 for addr in wallet.all_addresses():
449 if options.show_all or not wallet.is_change(addr):
451 flags = wallet.get_address_flags(addr)
452 label = wallet.labels.get(addr,'')
454 if label: label = "\"%s\""%label
456 if options.show_balance:
457 h = wallet.history.get(addr,[])
460 # if item['is_input']: ni += 1
462 b = format_satoshis(wallet.get_addr_balance(addr)[0])
465 if options.show_keys:
466 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
467 print flags, m_addr, b, label
470 lines = wallet.get_tx_history()
477 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
479 print line['timestamp']
481 label = line.get('label')
482 if not label: label = line['tx_hash']
483 else: label = label + ' '*(64 - len(label) )
485 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
486 print "# balance: ", format_satoshis(b)
491 label = ' '.join(args[2:])
493 print_error("Error. Syntax: label <tx_hash> <text>")
495 wallet.labels[tx] = label
498 elif cmd in ['payto', 'mktx']:
499 if from_addr and is_temporary:
500 if from_addr.find(":") == -1:
501 keypair = from_addr + ":" + prompt_password('Private key:', False)
504 from_addr = keypair.split(':')[0]
505 if not wallet.import_key(keypair,password):
506 print_error("Error: Invalid key pair")
508 wallet.history[from_addr] = interface.retrieve_history(from_addr)
509 wallet.update_tx_history()
510 change_addr = from_addr
512 if options.change_addr:
513 change_addr = options.change_addr
515 for k, v in wallet.labels.items():
518 print "alias", to_address
520 if change_addr and v == change_addr:
523 tx = wallet.mktx( to_address, amount, label, password,
524 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
527 traceback.print_exc(file=sys.stdout)
530 if tx and cmd=='payto':
531 r, h = wallet.sendtx( tx )
537 wallet.imported_keys.pop(from_addr)
538 del(wallet.history[from_addr])
541 elif cmd == 'sendtx':
543 r, h = wallet.sendtx( tx )
546 elif cmd == 'password':
548 seed = wallet.pw_decode( wallet.seed, password)
550 sys.exit("Error: Password does not decrypt this wallet.")
552 new_password = prompt_password('New password:')
553 wallet.update_password(seed, password, new_password)
555 elif cmd == 'signmessage':
557 print_error("Error: Invalid usage of signmessage.")
558 print known_commands[cmd]
561 message = ' '.join(args[2:])
563 print "Warning: Message was reconstructed from several arguments:", repr(message)
564 print wallet.sign_message(address, message, password)
566 elif cmd == 'verifymessage':
570 message = ' '.join(args[3:])
572 print_error("Error: Not all parameters were given, displaying help instead.")
573 print known_commands[cmd]
576 print "Warning: Message was reconstructed from several arguments:", repr(message)
578 wallet.verify_message(address, signature, message)
580 except BaseException as e:
581 print "Verification error: {0}".format(e)
584 elif cmd == 'freeze':
586 print wallet.freeze(addr)
588 elif cmd == 'unfreeze':
590 print wallet.unfreeze(addr)
592 elif cmd == 'prioritize':
594 print wallet.prioritize(addr)
596 elif cmd == 'unprioritize':
598 print wallet.unprioritize(addr)