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.",
94 'createmultisig':'similar to bitcoind\'s command',
95 'createrawtransaction':'similar to bitcoind\'s command',
96 'decoderawtransaction':'similar to bitcoind\'s command',
97 'signrawtransaction':'similar to bitcoind\'s command',
102 offline_commands = [ 'password', 'mktx', 'signtx',
104 'help', 'validateaddress',
105 'signmessage', 'verifymessage',
106 'eval', 'set', 'get', 'create', 'addresses',
107 'importprivkey', 'seed',
110 'prioritize','unprioritize',
111 'createmultisig', 'createrawtransaction', 'decoderawtransaction', 'signrawtransaction'
115 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage', 'signrawtransaction' ]
117 # get password routine
118 def prompt_password(prompt, confirm=True):
120 if sys.stdin.isatty():
121 password = getpass.getpass(prompt)
122 if password and confirm:
123 password2 = getpass.getpass("Confirm: ")
124 if password != password2:
125 sys.exit("Error: Passwords do not match.")
127 password = raw_input(prompt)
133 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
134 parser = optparse.OptionParser(prog=usage)
135 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
136 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
137 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
138 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
139 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
140 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
141 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
142 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.")
143 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")
144 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
145 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
146 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
147 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
148 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
149 parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
153 if __name__ == '__main__':
155 parser = arg_parser()
156 options, args = parser.parse_args()
157 set_verbosity(options.verbose)
159 # config is an object passed to the various constructors (wallet, interface, gui)
160 if 'ANDROID_DATA' in os.environ:
161 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
163 config_options = eval(str(options))
164 for k, v in config_options.items():
165 if v is None: config_options.pop(k)
167 # Wallet migration on Electrum 1.7
168 # Todo: In time we could remove this again
169 if platform.system() == "Windows":
170 util.check_windows_wallet_migration()
172 config = SimpleConfig(config_options)
173 wallet = Wallet(config)
178 elif len(args)==1 and re.match('^bitcoin:', args[0]):
183 firstarg = args[1] if len(args) > 1 else ''
185 #this entire if/else block is just concerned with importing the
186 #right GUI toolkit based the GUI command line option given
188 pref_gui = config.get('gui','classic')
190 if pref_gui == 'gtk':
192 import lib.gui as gui
194 import electrum.gui as gui
195 elif pref_gui in ['classic', 'qt']:
197 import lib.gui_qt as gui
199 import electrum.gui_qt as gui
200 elif pref_gui == 'lite':
202 import lib.gui_lite as gui
204 import electrum.gui_lite as gui
205 elif pref_gui == 'text':
207 import lib.gui_text as gui
209 import electrum.gui_text as gui
210 elif pref_gui == 'android':
212 import lib.gui_android as gui
214 import electrum.gui_android as gui
216 sys.exit("Error: Unknown GUI: " + pref_gui )
219 interface = Interface(config, True)
220 wallet.interface = interface
222 if interface.is_connected:
223 interface.send([('server.peers.subscribe',[])])
225 set_language(config.get('language'))
226 gui = gui.ElectrumGui(wallet, config)
228 found = config.wallet_file_exists
230 a = gui.restore_or_create()
233 s = gui.network_dialog()
236 wallet.init_seed(None)
238 # ask for seed and gap.
239 sg = gui.seed_dialog()
243 wallet.gap_limit = gap
246 wallet.master_public_key = seed
248 wallet.init_seed(str(seed))
251 # generate the first addresses, in case we are offline
252 if s is None or a == 'create':
258 verifier = WalletVerifier(interface, config)
259 wallet.set_verifier(verifier)
260 synchronizer = WalletSynchronizer(wallet, config)
263 if not found and a == 'restore' and s is not None:
265 keep_it = gui.restore_wallet()
266 wallet.fill_addressbook()
269 traceback.print_exc(file=sys.stdout)
272 if not keep_it: exit()
275 gui.password_dialog()
286 # we use daemon threads, their termination is enforced.
287 # this sleep command gives them time to terminate cleanly.
291 if cmd not in known_commands:
294 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
295 print_msg("Error: Wallet file not found.")
296 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
299 if cmd in ['create', 'restore']:
300 if config.wallet_file_exists:
301 sys.exit("Error: Remove the existing wallet first!")
302 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
304 server = config.get('server')
305 if not server: server = pick_random_server()
306 w_host, w_port, w_protocol = server.split(':')
307 host = raw_input("server (default:%s):"%w_host)
308 port = raw_input("port (default:%s):"%w_port)
309 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
310 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
311 gap = raw_input("gap limit (default 5):")
312 if host: w_host = host
313 if port: w_port = port
314 if protocol: w_protocol = protocol
315 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
316 if fee: wallet.fee = float(fee)
317 if gap: wallet.gap_limit = int(gap)
320 seed = raw_input("seed:")
324 print_error("Warning: Not hex, trying decode.")
325 seed = mnemonic_decode( seed.split(' ') )
327 sys.exit("Error: No seed")
331 wallet.master_public_key = seed
333 wallet.seed = str(seed)
334 wallet.init_mpk( wallet.seed )
336 if not options.offline:
337 interface = Interface(config)
339 wallet.interface = interface
341 verifier = WalletVerifier(interface, config)
342 wallet.set_verifier(verifier)
344 print_msg("Recovering wallet...")
345 WalletSynchronizer(wallet, config).start()
347 if wallet.is_found():
348 print_msg("Recovery successful")
350 print_msg("Warning: Found no history for this wallet")
353 wallet.fill_addressbook()
355 print_msg("Wallet saved in '%s'"%wallet.config.path)
357 wallet.init_seed(None)
358 wallet.synchronize() # there is no wallet thread
360 print_msg("Your wallet generation seed is: " + wallet.seed)
361 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
362 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
363 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
364 print_msg("Wallet saved in '%s'"%wallet.config.path)
367 wallet.update_password(wallet.seed, None, password)
370 if cmd in ['payto', 'mktx']:
373 amount = int( 100000000 * Decimal(args[2]) )
375 label = ' '.join(args[3:])
377 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
383 if cmd not in offline_commands and not options.offline:
384 interface = Interface(config)
385 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
387 wallet.interface = interface
388 synchronizer = WalletSynchronizer(wallet, config)
393 # check if --from_addr not in wallet (for mktx/payto)
396 if options.from_addr:
397 from_addr = options.from_addr
398 if from_addr not in wallet.all_addresses():
402 if cmd=='addresses' and options.show_keys:
403 print_msg("WARNING: ALL your private keys are secret.")
404 print_msg("Exposing a single private key can compromise your entire wallet!")
405 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
407 # commands needing password
408 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
409 if wallet.use_encryption and not is_temporary:
410 password = prompt_password('Password:', False)
412 print_msg("Error: Password required")
416 seed = wallet.decode_seed(password)
418 print_msg("Error: This password does not decode this wallet.")
424 if cmd == 'importprivkey':
425 # See if they specificed a key on the cmd line, if not prompt
429 sec = prompt_password('Enter PrivateKey (will not echo):', False)
431 addr = wallet.import_key(sec,password)
433 print_msg("Keypair imported: ", addr)
434 except BaseException as e:
435 print_msg("Error: Keypair import failed: " + str(e))
439 if cmd2 not in known_commands:
441 print_msg("Type 'electrum help <command>' to see the help for a specific command")
442 print_msg("Type 'electrum --help' to see the list of options")
443 print_msg("List of commands:", ', '.join(known_commands))
445 print_msg(known_commands[cmd2])
448 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
450 elif cmd == 'deseed':
452 print_msg("Error: This wallet has no seed")
453 elif wallet.use_encryption:
454 print_msg("Error: This wallet is encrypted")
456 ns = wallet.config.path + '.seed'
457 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
458 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
460 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
463 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
467 print_msg("Action canceled.")
469 elif cmd == 'reseed':
471 print_msg("Warning: This wallet already has a seed", wallet.seed)
473 ns = wallet.config.path + '.seed'
479 sys.exit("Error: Seed file not found")
482 d = ast.literal_eval( data )
484 imported_keys = d.get('imported_keys',{})
486 sys.exit("Error: Error with seed file")
488 mpk = wallet.master_public_key
490 wallet.imported_keys = imported_keys
491 wallet.use_encryption = False
492 wallet.init_mpk(seed)
493 if mpk == wallet.master_public_key:
495 print_msg("Done: " + wallet.config.path)
497 print_msg("Error: Master public key does not match")
499 elif cmd == 'validateaddress':
501 print_msg(wallet.is_valid(addr))
503 elif cmd == 'balance':
509 c, u = wallet.get_balance()
511 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
513 print_msg(Decimal( c ) / 100000000)
516 c, u = wallet.get_addr_balance(addr)
518 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
520 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
522 elif cmd in [ 'contacts']:
523 for addr in wallet.addressbook:
524 print_msg(addr, " ", wallet.labels.get(addr))
527 print_msg(eval(args[1]))
532 print_msg(wallet.config.get(key))
535 key, value = args[1:3]
536 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
537 wallet.config.set_key(key, value, True)
542 elif cmd in [ 'addresses']:
543 for addr in wallet.all_addresses():
544 if options.show_all or not wallet.is_change(addr):
546 flags = wallet.get_address_flags(addr)
547 label = wallet.labels.get(addr,'')
549 if label: label = "\"%s\""%label
551 if options.show_balance:
552 h = wallet.history.get(addr,[])
555 # if item['is_input']: ni += 1
557 b = format_satoshis(wallet.get_addr_balance(addr)[0])
560 if options.show_keys:
561 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
562 print_msg(flags, m_addr, b, label)
566 for item in wallet.get_tx_history():
567 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
569 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
573 label, is_default_label = wallet.get_label(tx_hash)
574 if not label: label = tx_hash
575 else: label = label + ' '*(64 - len(label) )
577 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
578 print_msg("# balance: ", format_satoshis(balance))
583 label = ' '.join(args[2:])
585 print_msg("Error. Syntax: label <tx_hash> <text>")
587 wallet.labels[tx] = label
590 elif cmd in ['payto', 'mktx']:
591 if from_addr and is_temporary:
592 if from_addr.find(":") == -1:
593 keypair = from_addr + ":" + prompt_password('Private key:', False)
596 from_addr = keypair.split(':')[0]
597 if not wallet.import_key(keypair,password):
598 print_msg("Error: Invalid key pair")
600 wallet.history[from_addr] = interface.retrieve_history(from_addr)
601 wallet.update_tx_history()
602 change_addr = from_addr
604 if options.change_addr:
605 change_addr = options.change_addr
607 for k, v in wallet.labels.items():
610 print_msg("alias", to_address)
612 if change_addr and v == change_addr:
615 tx = wallet.mktx( [(to_address, amount)], label, password,
616 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
619 traceback.print_exc(file=sys.stdout)
622 if tx and cmd=='payto':
623 r, h = wallet.sendtx( tx )
629 wallet.imported_keys.pop(from_addr)
630 del(wallet.history[from_addr])
633 elif cmd == 'signtx':
636 f = open(filename, 'r')
637 d = ast.literal_eval(f.read())
640 outputs = d['outputs']
641 tx = wallet.signed_tx( inputs, outputs, password )
644 elif cmd == 'sendtx':
646 r, h = wallet.sendtx( tx )
649 elif cmd == 'password':
650 new_password = prompt_password('New password:')
651 wallet.update_password(seed, password, new_password)
653 elif cmd == 'signmessage':
655 print_msg("Error: Invalid usage of signmessage.")
656 print_msg(known_commands[cmd])
659 message = ' '.join(args[2:])
661 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
662 print_msg(wallet.sign_message(address, message, password))
664 elif cmd == 'verifymessage':
668 message = ' '.join(args[3:])
670 print_msg("Error: Not all parameters were given, displaying help instead.")
671 print_msg(known_commands[cmd])
674 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
676 wallet.verify_message(address, signature, message)
678 except BaseException as e:
679 print_error("Verification error: {0}".format(e))
682 elif cmd == 'freeze':
684 print_msg(wallet.freeze(addr))
686 elif cmd == 'unfreeze':
688 print_msg(wallet.unfreeze(addr))
690 elif cmd == 'prioritize':
692 print_msg(wallet.prioritize(addr))
694 elif cmd == 'unprioritize':
696 print_msg(wallet.unprioritize(addr))
699 elif cmd == 'createmultisig':
701 from lib.bitcoin import *
703 pubkeys = ast.literal_eval(args[2])
704 assert isinstance(pubkeys,list)
705 s = multisig_script(pubkeys, num)
706 out = { "address": hash_160_to_bc_address(hash_160(s.decode('hex')), 5), "redeemScript":s }
710 elif cmd == 'createrawtransaction':
712 inputs = ast.literal_eval(args[1])
713 outputs = ast.literal_eval(args[2])
714 # convert to own format
716 i['tx_hash'] = i['txid']
717 i['index'] = i['vout']
718 outputs = map(lambda x: (x[0],int(1e8*x[1])), outputs.items())
719 tx = Transaction.from_io(inputs, outputs)
723 elif cmd == 'decoderawtransaction':
724 tx = Transaction(args[1])
725 print_json( tx.deserialize() )
728 elif cmd == 'signrawtransaction':
729 tx = Transaction(args[1])
730 txouts = args[2] if len(args)>2 else []
731 private_keys = args[3] if len(args)>3 else {}
734 for txin in tx.inputs:
735 txid = txin["prevout_hash"]
736 index = txin["prevout_n"]
737 utx = wallet.transactions.get(txid)
738 txout = utx['outputs'][index]
739 txin['address'] = txout['address']
740 txin['raw_output_script'] = txout['raw_output_script']
741 # convert to own format
742 txin['tx_hash'] = txin['prevout_hash']
743 txin['index'] = txin['prevout_n']
744 secexp, compressed = wallet.get_private_key(txin['address'], password)
745 private_keys[addr] = (secexp,compressed)
747 tx.sign( private_keys )
751 if cmd not in offline_commands and not options.offline: