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/>.
27 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
32 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
37 from electrum import *
39 from decimal import Decimal
42 'help':'Prints this help',
43 'validateaddress':'Check that the address is valid',
44 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
45 'contacts': "Show your list of contacts",
46 'create':'Create a wallet',
47 'restore':'Restore a wallet',
48 'payto':"""Create and broadcast a transaction.
49 Syntax: payto <recipient> <amount> [label]
50 <recipient> can be a bitcoin address or a label
51 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
54 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
56 "Changes your password",
58 """Shows your list of addresses.
60 -a: show all addresses, including change addresses
62 -b: show the balance of addresses""",
64 'history':"Shows the transaction history",
65 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
67 """Create a signed transaction, password protected.
68 Syntax: mktx <recipient> <amount> [label]
69 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
71 'signtx':"Sign an unsigned transaction created by a deseeded wallet\nSyntax: signtx <filename>",
73 "Print the generation seed of your wallet.",
75 'Import a private key\nSyntax: importprivkey <privatekey>',
77 '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 "',
79 '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 "',
81 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
83 "Get config parameter.",
85 "Set config parameter.",
87 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
89 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
98 offline_commands = [ 'password', 'mktx', 'signtx',
100 'help', 'validateaddress',
101 'signmessage', 'verifymessage',
102 'eval', 'set', 'get', 'create', 'addresses',
103 'importprivkey', 'seed',
106 'prioritize','unprioritize']
109 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage' ]
111 # get password routine
112 def prompt_password(prompt, confirm=True):
114 if sys.stdin.isatty():
115 password = getpass.getpass(prompt)
116 if password and confirm:
117 password2 = getpass.getpass("Confirm: ")
118 if password != password2:
119 sys.exit("Error: Passwords do not match.")
121 password = raw_input(prompt)
127 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
128 parser = optparse.OptionParser(prog=usage)
129 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
130 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
131 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
132 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
133 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
134 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
135 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
136 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.")
137 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")
138 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
139 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
140 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
141 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
142 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
143 parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
147 if __name__ == '__main__':
149 parser = arg_parser()
150 options, args = parser.parse_args()
151 set_verbosity(options.verbose)
153 # config is an object passed to the various constructors (wallet, interface, gui)
154 if 'ANDROID_DATA' in os.environ:
155 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
157 config_options = eval(str(options))
158 for k, v in config_options.items():
159 if v is None: config_options.pop(k)
161 # Wallet migration on Electrum 1.7
162 # Todo: In time we could remove this again
163 if platform.system() == "Windows":
164 util.check_windows_wallet_migration()
166 config = SimpleConfig(config_options)
167 wallet = Wallet(config)
172 elif len(args)==1 and re.match('^bitcoin:', args[0]):
177 firstarg = args[1] if len(args) > 1 else ''
179 #this entire if/else block is just concerned with importing the
180 #right GUI toolkit based the GUI command line option given
182 pref_gui = config.get('gui','classic')
184 if pref_gui == 'gtk':
186 import lib.gui as gui
188 import electrum.gui as gui
189 elif pref_gui in ['classic', 'qt']:
191 import lib.gui_qt as gui
193 import electrum.gui_qt as gui
194 elif pref_gui == 'lite':
196 import lib.gui_lite as gui
198 import electrum.gui_lite as gui
199 elif pref_gui == 'text':
201 import lib.gui_text as gui
203 import electrum.gui_text as gui
204 elif pref_gui == 'android':
206 import lib.gui_android as gui
208 import electrum.gui_android as gui
210 sys.exit("Error: Unknown GUI: " + pref_gui )
213 interface = Interface(config, True)
214 wallet.interface = interface
216 if interface.is_connected:
217 interface.send([('server.peers.subscribe',[])])
219 set_language(config.get('language'))
220 gui = gui.ElectrumGui(wallet, config)
222 found = config.wallet_file_exists
224 a = gui.restore_or_create()
227 s = gui.network_dialog()
230 wallet.init_seed(None)
232 # ask for seed and gap.
233 sg = gui.seed_dialog()
237 wallet.gap_limit = gap
240 wallet.master_public_key = seed
242 wallet.init_seed(str(seed))
245 # generate the first addresses, in case we are offline
246 if s is None or a == 'create':
252 verifier = WalletVerifier(interface, config)
253 wallet.set_verifier(verifier)
254 synchronizer = WalletSynchronizer(wallet, config)
257 if not found and a == 'restore' and s is not None:
259 keep_it = gui.restore_wallet()
260 wallet.fill_addressbook()
263 traceback.print_exc(file=sys.stdout)
266 if not keep_it: exit()
269 gui.password_dialog()
280 # we use daemon threads, their termination is enforced.
281 # this sleep command gives them time to terminate cleanly.
285 if cmd not in known_commands:
288 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
289 print_msg("Error: Wallet file not found.")
290 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
293 if cmd in ['create', 'restore']:
294 if config.wallet_file_exists:
295 sys.exit("Error: Remove the existing wallet first!")
296 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
298 server = config.get('server')
299 if not server: server = pick_random_server()
300 w_host, w_port, w_protocol = server.split(':')
301 host = raw_input("server (default:%s):"%w_host)
302 port = raw_input("port (default:%s):"%w_port)
303 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
304 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
305 gap = raw_input("gap limit (default 5):")
306 if host: w_host = host
307 if port: w_port = port
308 if protocol: w_protocol = protocol
309 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
310 if fee: wallet.fee = float(fee)
311 if gap: wallet.gap_limit = int(gap)
314 seed = raw_input("seed:")
318 print_error("Warning: Not hex, trying decode.")
319 seed = mnemonic_decode( seed.split(' ') )
321 sys.exit("Error: No seed")
325 wallet.master_public_key = seed
327 wallet.seed = str(seed)
328 wallet.init_mpk( wallet.seed )
330 if not options.offline:
331 interface = Interface(config)
333 wallet.interface = interface
335 verifier = WalletVerifier(interface, config)
336 wallet.set_verifier(verifier)
338 print_msg("Recovering wallet...")
339 WalletSynchronizer(wallet, config).start()
341 if wallet.is_found():
342 print_msg("Recovery successful")
344 print_msg("Warning: Found no history for this wallet")
347 wallet.fill_addressbook()
349 print_msg("Wallet saved in '%s'"%wallet.config.path)
351 wallet.init_seed(None)
352 wallet.synchronize() # there is no wallet thread
354 print_msg("Your wallet generation seed is: " + wallet.seed)
355 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
356 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
357 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
358 print_msg("Wallet saved in '%s'"%wallet.config.path)
361 wallet.update_password(wallet.seed, None, password)
364 if cmd in ['payto', 'mktx']:
367 amount = int( 100000000 * Decimal(args[2]) )
369 label = ' '.join(args[3:])
371 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
377 if cmd not in offline_commands and not options.offline:
378 interface = Interface(config)
379 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
381 wallet.interface = interface
382 synchronizer = WalletSynchronizer(wallet, config)
387 # check if --from_addr not in wallet (for mktx/payto)
390 if options.from_addr:
391 from_addr = options.from_addr
392 if from_addr not in wallet.all_addresses():
396 if cmd=='addresses' and options.show_keys:
397 print_msg("WARNING: ALL your private keys are secret.")
398 print_msg("Exposing a single private key can compromise your entire wallet!")
399 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
401 # commands needing password
402 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
403 if wallet.use_encryption and not is_temporary:
404 password = prompt_password('Password:', False)
406 print_msg("Error: Password required")
410 seed = wallet.decode_seed(password)
412 print_msg("Error: This password does not decode this wallet.")
418 if cmd == 'importprivkey':
419 # See if they specificed a key on the cmd line, if not prompt
423 sec = prompt_password('Enter PrivateKey (will not echo):', False)
425 addr = wallet.import_key(sec,password)
427 print_msg("Keypair imported: ", addr)
428 except BaseException as e:
429 print_msg("Error: Keypair import failed: " + str(e))
433 if cmd2 not in known_commands:
435 print_msg("Type 'electrum help <command>' to see the help for a specific command")
436 print_msg("Type 'electrum --help' to see the list of options")
437 print_msg("List of commands:", ', '.join(known_commands))
439 print_msg(known_commands[cmd2])
442 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
444 elif cmd == 'deseed':
446 print_msg("Error: This wallet has no seed")
447 elif wallet.use_encryption:
448 print_msg("Error: This wallet is encrypted")
450 ns = wallet.config.path + '.seed'
451 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
452 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
454 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
457 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
461 print_msg("Action canceled.")
463 elif cmd == 'reseed':
465 print_msg("Warning: This wallet already has a seed", wallet.seed)
467 ns = wallet.config.path + '.seed'
473 sys.exit("Error: Seed file not found")
476 d = ast.literal_eval( data )
478 imported_keys = d.get('imported_keys',{})
480 sys.exit("Error: Error with seed file")
482 mpk = wallet.master_public_key
484 wallet.imported_keys = imported_keys
485 wallet.use_encryption = False
486 wallet.init_mpk(seed)
487 if mpk == wallet.master_public_key:
489 print_msg("Done: " + wallet.config.path)
491 print_msg("Error: Master public key does not match")
493 elif cmd == 'validateaddress':
495 print_msg(wallet.is_valid(addr))
497 elif cmd == 'balance':
503 c, u = wallet.get_balance()
505 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
507 print_msg(Decimal( c ) / 100000000)
510 c, u = wallet.get_addr_balance(addr)
512 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
514 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
516 elif cmd in [ 'contacts']:
517 for addr in wallet.addressbook:
518 print_msg(addr, " ", wallet.labels.get(addr))
521 print_msg(eval(args[1]))
526 print_msg(wallet.config.get(key))
529 key, value = args[1:3]
530 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
531 wallet.config.set_key(key, value, True)
536 elif cmd in [ 'addresses']:
537 for addr in wallet.all_addresses():
538 if options.show_all or not wallet.is_change(addr):
540 flags = wallet.get_address_flags(addr)
541 label = wallet.labels.get(addr,'')
543 if label: label = "\"%s\""%label
545 if options.show_balance:
546 h = wallet.history.get(addr,[])
549 # if item['is_input']: ni += 1
551 b = format_satoshis(wallet.get_addr_balance(addr)[0])
554 if options.show_keys:
555 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
556 print_msg(flags, m_addr, b, label)
560 for item in wallet.get_tx_history():
561 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
563 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
567 label, is_default_label = wallet.get_label(tx_hash)
568 if not label: label = tx_hash
569 else: label = label + ' '*(64 - len(label) )
571 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
572 print_msg("# balance: ", format_satoshis(balance))
577 label = ' '.join(args[2:])
579 print_msg("Error. Syntax: label <tx_hash> <text>")
581 wallet.labels[tx] = label
584 elif cmd in ['payto', 'mktx']:
585 if from_addr and is_temporary:
586 if from_addr.find(":") == -1:
587 keypair = from_addr + ":" + prompt_password('Private key:', False)
590 from_addr = keypair.split(':')[0]
591 if not wallet.import_key(keypair,password):
592 print_msg("Error: Invalid key pair")
594 wallet.history[from_addr] = interface.retrieve_history(from_addr)
595 wallet.update_tx_history()
596 change_addr = from_addr
598 if options.change_addr:
599 change_addr = options.change_addr
601 for k, v in wallet.labels.items():
604 print_msg("alias", to_address)
606 if change_addr and v == change_addr:
609 tx = wallet.mktx( [(to_address, amount)], label, password,
610 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
613 traceback.print_exc(file=sys.stdout)
616 if tx and cmd=='payto':
617 r, h = wallet.sendtx( tx )
623 wallet.imported_keys.pop(from_addr)
624 del(wallet.history[from_addr])
627 elif cmd == 'signtx':
630 f = open(filename, 'r')
631 d = ast.literal_eval(f.read())
634 outputs = d['outputs']
635 tx = wallet.signed_tx( inputs, outputs, password )
638 elif cmd == 'sendtx':
640 r, h = wallet.sendtx( tx )
643 elif cmd == 'password':
644 new_password = prompt_password('New password:')
645 wallet.update_password(seed, password, new_password)
647 elif cmd == 'signmessage':
649 print_msg("Error: Invalid usage of signmessage.")
650 print_msg(known_commands[cmd])
653 message = ' '.join(args[2:])
655 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
656 print_msg(wallet.sign_message(address, message, password))
658 elif cmd == 'verifymessage':
662 message = ' '.join(args[3:])
664 print_msg("Error: Not all parameters were given, displaying help instead.")
665 print_msg(known_commands[cmd])
668 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
670 wallet.verify_message(address, signature, message)
672 except BaseException as e:
673 print_error("Verification error: {0}".format(e))
676 elif cmd == 'freeze':
678 print_msg(wallet.freeze(addr))
680 elif cmd == 'unfreeze':
682 print_msg(wallet.unfreeze(addr))
684 elif cmd == 'prioritize':
686 print_msg(wallet.prioritize(addr))
688 elif cmd == 'unprioritize':
690 print_msg(wallet.unprioritize(addr))
693 if cmd not in offline_commands and not options.offline: