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)
190 wallet.interface = interface
191 WalletSynchronizer(wallet, config).start()
193 gui = gui.ElectrumGui(wallet, config)
194 interface.register_callback('peers', gui.server_list_changed)
196 found = config.wallet_file_exists
198 found = gui.restore_or_create()
199 except SystemExit, e:
201 except BaseException, e:
203 traceback.print_exc(file=sys.stdout)
204 #gui.show_message(e.message)
210 verifier = WalletVerifier(interface, config)
211 wallet.set_verifier(verifier)
218 if cmd not in known_commands:
221 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
222 print "Error: Wallet file not found."
223 print "Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option"
226 if cmd in ['create', 'restore']:
227 if config.wallet_file_exists:
228 sys.exit("Error: Remove the existing wallet first!")
229 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
231 server = config.get('server')
232 if not server: server = pick_random_server()
233 w_host, w_port, w_protocol = server.split(':')
234 host = raw_input("server (default:%s):"%w_host)
235 port = raw_input("port (default:%s):"%w_port)
236 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
237 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
238 gap = raw_input("gap limit (default 5):")
239 if host: w_host = host
240 if port: w_port = port
241 if protocol: w_protocol = protocol
242 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
243 if fee: wallet.fee = float(fee)
244 if gap: wallet.gap_limit = int(gap)
247 seed = raw_input("seed:")
251 print_error("Warning: Not hex, trying decode.")
252 seed = mnemonic.mn_decode( seed.split(' ') )
254 sys.exit("Error: No seed")
256 wallet.seed = str(seed)
257 wallet.init_mpk( wallet.seed )
258 if not options.offline:
259 WalletSynchronizer(wallet, config).start()
260 print "Recovering wallet..."
261 wallet.up_to_date_event.clear()
262 wallet.up_to_date = False
264 if wallet.is_found():
265 print "Recovery successful"
267 print_error("Warning: Found no history for this wallet")
270 wallet.fill_addressbook()
272 print_error("Wallet saved in '" + wallet.path)
274 wallet.new_seed(None)
275 wallet.init_mpk( wallet.seed )
276 wallet.synchronize() # there is no wallet thread
278 print "Your wallet generation seed is: " + wallet.seed
279 print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet."
280 print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:"
281 print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\""
282 print "Wallet saved in '%s'"%wallet.config.path
285 wallet.update_password(wallet.seed, None, password)
288 if cmd in ['payto', 'mktx']:
291 amount = int( 100000000 * Decimal(args[2]) )
293 label = ' '.join(args[3:])
295 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
301 if cmd not in offline_commands and not options.offline:
302 interface = Interface(config)
303 interface.register_callback('connected', lambda: print_error("Connected to " + interface.connection_msg))
305 wallet.interface = interface
306 WalletSynchronizer(wallet, config).start()
310 # check if --from_addr not in wallet (for mktx/payto)
313 if options.from_addr:
314 from_addr = options.from_addr
315 if from_addr not in wallet.all_addresses():
319 if cmd=='addresses' and options.show_keys:
320 print "WARNING: ALL your private keys are secret."
321 print "Exposing a single private key can compromise your entire wallet!"
322 print "In particular, DO NOT use 'redeem private key' services proposed by third parties."
324 # commands needing password
325 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
326 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
329 wallet.pw_decode( wallet.seed, password)
331 print_error("Error: This password does not decode this wallet.")
335 # See if they specificed a key on the cmd line, if not prompt
339 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
341 wallet.import_key(keypair,password)
343 print "Keypair imported"
344 except BaseException, e:
345 print_error("Error: Keypair import failed: " + str(e))
349 if cmd2 not in known_commands:
351 print "Type 'electrum help <command>' to see the help for a specific command"
352 print "Type 'electrum --help' to see the list of options"
353 print "List of commands:", ', '.join(known_commands)
355 print known_commands[cmd2]
358 seed = wallet.pw_decode( wallet.seed, password)
359 print seed + ' "' + ' '.join(mnemonic.mn_encode(seed)) + '"'
361 elif cmd == 'deseed':
363 print_error("Error: This wallet has no seed")
364 elif wallet.use_encryption:
365 print_error("Error: This wallet is encrypted")
367 ns = wallet.path + '.seed'
368 print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.path,ns)
369 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
371 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
374 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
378 print_error("Action canceled.")
380 elif cmd == 'reseed':
382 print "Warning: This wallet already has a seed", wallet.seed
384 ns = wallet.path + '.seed'
390 sys.exit("Error: Seed file not found")
393 d = ast.literal_eval( data )
395 imported_keys = d.get('imported_keys',{})
397 sys.exit("Error: Error with seed file")
399 mpk = wallet.master_public_key
401 wallet.imported_keys = imported_keys
402 wallet.use_encryption = False
403 wallet.init_mpk(seed)
404 if mpk == wallet.master_public_key:
406 print "Done: " + wallet.path
408 print_error("Error: Master public key does not match")
410 elif cmd == 'validateaddress':
412 print wallet.is_valid(addr)
414 elif cmd == 'balance':
420 c, u = wallet.get_balance()
422 print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
424 print Decimal( c ) / 100000000
427 c, u = wallet.get_addr_balance(addr)
429 print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
431 print "%s %s" % (addr, str(Decimal(c)/100000000))
433 elif cmd in [ 'contacts']:
434 for addr in wallet.addressbook:
435 print addr, " ", wallet.labels.get(addr)
442 key, value = args[1:3]
443 if key not in ['seed_version', 'master_public_key', 'use_encryption']:
444 wallet.config.set_key(key, value, True)
449 elif cmd in [ 'addresses']:
450 for addr in wallet.all_addresses():
451 if options.show_all or not wallet.is_change(addr):
453 flags = wallet.get_address_flags(addr)
454 label = wallet.labels.get(addr,'')
456 if label: label = "\"%s\""%label
458 if options.show_balance:
459 h = wallet.history.get(addr,[])
462 # if item['is_input']: ni += 1
464 b = format_satoshis(wallet.get_addr_balance(addr)[0])
467 if options.show_keys:
468 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
469 print flags, m_addr, b, label
472 lines = wallet.get_tx_history()
479 time_str = str( datetime.datetime.fromtimestamp( line['timestamp']))
481 print line['timestamp']
483 label = line.get('label')
484 if not label: label = line['tx_hash']
485 else: label = label + ' '*(64 - len(label) )
487 print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
488 print "# balance: ", format_satoshis(b)
493 label = ' '.join(args[2:])
495 print_error("Error. Syntax: label <tx_hash> <text>")
497 wallet.labels[tx] = label
500 elif cmd in ['payto', 'mktx']:
501 if from_addr and is_temporary:
502 if from_addr.find(":") == -1:
503 keypair = from_addr + ":" + prompt_password('Private key:', False)
506 from_addr = keypair.split(':')[0]
507 if not wallet.import_key(keypair,password):
508 print_error("Error: Invalid key pair")
510 wallet.history[from_addr] = interface.retrieve_history(from_addr)
511 wallet.update_tx_history()
512 change_addr = from_addr
514 if options.change_addr:
515 change_addr = options.change_addr
517 for k, v in wallet.labels.items():
520 print "alias", to_address
522 if change_addr and v == change_addr:
525 tx = wallet.mktx( to_address, amount, label, password,
526 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
529 traceback.print_exc(file=sys.stdout)
532 if tx and cmd=='payto':
533 r, h = wallet.sendtx( tx )
539 wallet.imported_keys.pop(from_addr)
540 del(wallet.history[from_addr])
543 elif cmd == 'sendtx':
545 r, h = wallet.sendtx( tx )
548 elif cmd == 'password':
550 seed = wallet.pw_decode( wallet.seed, password)
552 sys.exit("Error: Password does not decrypt this wallet.")
554 new_password = prompt_password('New password:')
555 wallet.update_password(seed, password, new_password)
557 elif cmd == 'signmessage':
559 print_error("Error: Invalid usage of signmessage.")
560 print known_commands[cmd]
563 message = ' '.join(args[2:])
565 print "Warning: Message was reconstructed from several arguments:", repr(message)
566 print wallet.sign_message(address, message, password)
568 elif cmd == 'verifymessage':
572 message = ' '.join(args[3:])
574 print_error("Error: Not all parameters were given, displaying help instead.")
575 print known_commands[cmd]
578 print "Warning: Message was reconstructed from several arguments:", repr(message)
580 wallet.verify_message(address, signature, message)
582 except BaseException as e:
583 print "Verification error: {0}".format(e)
586 elif cmd == 'freeze':
588 print wallet.freeze(addr)
590 elif cmd == 'unfreeze':
592 print wallet.unfreeze(addr)
594 elif cmd == 'prioritize':
596 print wallet.prioritize(addr)
598 elif cmd == 'unprioritize':
600 print wallet.unprioritize(addr)