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/>.
20 import sys, os, time, ast
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 'dumpprivkey':'similar to bitcoind\'s command',
95 'listunspent':'similar to bitcoind\'s command',
96 'createmultisig':'similar to bitcoind\'s command',
97 'createrawtransaction':'similar to bitcoind\'s command',
98 'decoderawtransaction':'similar to bitcoind\'s command',
99 'signrawtransaction':'similar to bitcoind\'s command',
105 offline_commands = [ 'password', 'mktx', 'signtx',
107 'help', 'validateaddress',
108 'signmessage', 'verifymessage',
109 'eval', 'set', 'get', 'create', 'addresses',
110 'importprivkey', 'seed',
113 'prioritize','unprioritize',
114 'dumpprivkey','listunspent',
115 'createmultisig', 'createrawtransaction', 'decoderawtransaction', 'signrawtransaction'
119 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage', 'signrawtransaction','dumpprivkey' ]
121 # get password routine
122 def prompt_password(prompt, confirm=True):
124 if sys.stdin.isatty():
125 password = getpass.getpass(prompt)
126 if password and confirm:
127 password2 = getpass.getpass("Confirm: ")
128 if password != password2:
129 sys.exit("Error: Passwords do not match.")
131 password = raw_input(prompt)
137 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
138 parser = optparse.OptionParser(prog=usage)
139 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
140 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
141 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
142 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
143 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
144 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
145 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
146 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.")
147 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")
148 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
149 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
150 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
151 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
152 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
153 parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
157 if __name__ == '__main__':
159 parser = arg_parser()
160 options, args = parser.parse_args()
161 set_verbosity(options.verbose)
163 # config is an object passed to the various constructors (wallet, interface, gui)
164 if 'ANDROID_DATA' in os.environ:
165 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
167 config_options = eval(str(options))
168 for k, v in config_options.items():
169 if v is None: config_options.pop(k)
171 # Wallet migration on Electrum 1.7
172 # Todo: In time we could remove this again
173 if platform.system() == "Windows":
174 util.check_windows_wallet_migration()
176 config = SimpleConfig(config_options)
177 wallet = Wallet(config)
182 elif len(args)==1 and re.match('^bitcoin:', args[0]):
187 firstarg = args[1] if len(args) > 1 else ''
189 #this entire if/else block is just concerned with importing the
190 #right GUI toolkit based the GUI command line option given
192 pref_gui = config.get('gui','classic')
194 if pref_gui == 'gtk':
196 import lib.gui as gui
198 import electrum.gui as gui
199 elif pref_gui in ['classic', 'qt']:
201 import lib.gui_qt as gui
203 import electrum.gui_qt as gui
204 elif pref_gui == 'lite':
206 import lib.gui_lite as gui
208 import electrum.gui_lite as gui
209 elif pref_gui == 'text':
211 import lib.gui_text as gui
213 import electrum.gui_text as gui
214 elif pref_gui == 'android':
216 import lib.gui_android as gui
218 import electrum.gui_android as gui
220 sys.exit("Error: Unknown GUI: " + pref_gui )
223 interface = Interface(config, True)
224 wallet.interface = interface
226 if interface.is_connected:
227 interface.send([('server.peers.subscribe',[])])
229 set_language(config.get('language'))
230 gui = gui.ElectrumGui(wallet, config)
232 found = config.wallet_file_exists
234 a = gui.restore_or_create()
237 s = gui.network_dialog()
240 wallet.init_seed(None)
242 # ask for seed and gap.
243 sg = gui.seed_dialog()
247 wallet.gap_limit = gap
250 wallet.sequence.master_public_key = seed
252 wallet.init_seed(str(seed))
255 # generate the first addresses, in case we are offline
256 if s is None or a == 'create':
262 verifier = WalletVerifier(interface, config)
263 wallet.set_verifier(verifier)
264 synchronizer = WalletSynchronizer(wallet, config)
267 if not found and a == 'restore' and s is not None:
269 keep_it = gui.restore_wallet()
270 wallet.fill_addressbook()
273 traceback.print_exc(file=sys.stdout)
276 if not keep_it: exit()
279 gui.password_dialog()
290 # we use daemon threads, their termination is enforced.
291 # this sleep command gives them time to terminate cleanly.
295 if cmd not in known_commands:
298 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
299 print_msg("Error: Wallet file not found.")
300 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
303 if cmd in ['create', 'restore']:
304 if config.wallet_file_exists:
305 sys.exit("Error: Remove the existing wallet first!")
306 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
308 server = config.get('server')
309 if not server: server = pick_random_server()
310 w_host, w_port, w_protocol = server.split(':')
311 host = raw_input("server (default:%s):"%w_host)
312 port = raw_input("port (default:%s):"%w_port)
313 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
314 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
315 gap = raw_input("gap limit (default 5):")
316 if host: w_host = host
317 if port: w_port = port
318 if protocol: w_protocol = protocol
319 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
320 if fee: wallet.fee = float(fee)
321 if gap: wallet.gap_limit = int(gap)
324 seed = raw_input("seed:")
328 print_error("Warning: Not hex, trying decode.")
329 seed = mnemonic_decode( seed.split(' ') )
331 sys.exit("Error: No seed")
335 wallet.sequence.master_public_key = seed
337 wallet.seed = str(seed)
338 wallet.init_mpk( wallet.seed )
340 if not options.offline:
341 interface = Interface(config)
343 wallet.interface = interface
345 verifier = WalletVerifier(interface, config)
346 wallet.set_verifier(verifier)
348 print_msg("Recovering wallet...")
349 WalletSynchronizer(wallet, config).start()
351 if wallet.is_found():
352 print_msg("Recovery successful")
354 print_msg("Warning: Found no history for this wallet")
357 wallet.fill_addressbook()
359 print_msg("Wallet saved in '%s'"%wallet.config.path)
361 wallet.init_seed(None)
362 wallet.synchronize() # there is no wallet thread
364 print_msg("Your wallet generation seed is: " + wallet.seed)
365 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
366 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
367 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
368 print_msg("Wallet saved in '%s'"%wallet.config.path)
371 wallet.update_password(wallet.seed, None, password)
374 if cmd in ['payto', 'mktx']:
377 amount = int( 100000000 * Decimal(args[2]) )
379 label = ' '.join(args[3:])
381 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
387 if cmd not in offline_commands and not options.offline:
388 interface = Interface(config)
389 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
391 wallet.interface = interface
392 synchronizer = WalletSynchronizer(wallet, config)
397 # check if --from_addr not in wallet (for mktx/payto)
400 if options.from_addr:
401 from_addr = options.from_addr
402 if from_addr not in wallet.all_addresses():
406 if cmd=='addresses' and options.show_keys:
407 print_msg("WARNING: ALL your private keys are secret.")
408 print_msg("Exposing a single private key can compromise your entire wallet!")
409 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
411 # commands needing password
412 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
413 if wallet.use_encryption and not is_temporary:
414 password = prompt_password('Password:', False)
416 print_msg("Error: Password required")
420 seed = wallet.decode_seed(password)
422 print_msg("Error: This password does not decode this wallet.")
428 if cmd == 'importprivkey':
429 # See if they specificed a key on the cmd line, if not prompt
433 sec = prompt_password('Enter PrivateKey (will not echo):', False)
435 addr = wallet.import_key(sec,password)
437 print_msg("Keypair imported: ", addr)
438 except BaseException as e:
439 print_msg("Error: Keypair import failed: " + str(e))
443 if cmd2 not in known_commands:
445 print_msg("Type 'electrum help <command>' to see the help for a specific command")
446 print_msg("Type 'electrum --help' to see the list of options")
447 print_msg("List of commands:", ', '.join(known_commands))
449 print_msg(known_commands[cmd2])
452 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
454 elif cmd == 'deseed':
456 print_msg("Error: This wallet has no seed")
457 elif wallet.use_encryption:
458 print_msg("Error: This wallet is encrypted")
460 ns = wallet.config.path + '.seed'
461 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
462 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
464 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
467 wallet.config.set_key('seed','', True)
468 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
472 print_msg("Action canceled.")
474 elif cmd == 'reseed':
476 print_msg("Warning: This wallet already has a seed", wallet.seed)
478 ns = wallet.config.path + '.seed'
484 sys.exit("Error: Seed file not found")
486 d = ast.literal_eval( data )
488 imported_keys = d.get('imported_keys',{})
490 sys.exit("Error: Error with seed file")
492 mpk = wallet.get_master_public_key()
494 wallet.imported_keys = imported_keys
495 wallet.use_encryption = False
496 wallet.init_mpk(seed)
497 if mpk == wallet.get_master_public_key():
499 print_msg("Done: " + wallet.config.path)
501 print_msg("Error: Master public key does not match")
503 elif cmd == 'validateaddress':
505 is_valid = wallet.is_valid(addr)
506 out = { 'isvalid':is_valid }
508 is_mine = wallet.is_mine(addr)
509 out['address'] = addr
510 out['ismine'] = is_mine
512 out['pubkey'] = wallet.get_public_key(addr)
516 elif cmd == 'balance':
522 c, u = wallet.get_balance()
524 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
526 print_msg(Decimal( c ) / 100000000)
529 c, u = wallet.get_addr_balance(addr)
531 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
533 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
535 elif cmd in [ 'contacts']:
536 for addr in wallet.addressbook:
537 print_msg(addr, " ", wallet.labels.get(addr))
540 print_msg(eval(args[1]))
545 print_msg(wallet.config.get(key))
548 key, value = args[1:3]
549 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
550 wallet.config.set_key(key, value, True)
555 elif cmd in [ 'addresses']:
556 for addr in wallet.all_addresses():
557 if options.show_all or not wallet.is_change(addr):
559 flags = wallet.get_address_flags(addr)
560 label = wallet.labels.get(addr,'')
562 if label: label = "\"%s\""%label
564 if options.show_balance:
565 h = wallet.history.get(addr,[])
568 # if item['is_input']: ni += 1
570 b = format_satoshis(wallet.get_addr_balance(addr)[0])
573 if options.show_keys:
574 m_addr += ':' + str(wallet.get_private_key(addr, password))
575 print_msg(flags, m_addr, b, label)
580 for item in wallet.get_tx_history():
581 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
583 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
587 label, is_default_label = wallet.get_label(tx_hash)
588 if not label: label = tx_hash
589 else: label = label + ' '*(64 - len(label) )
591 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
592 print_msg("# balance: ", format_satoshis(balance))
597 label = ' '.join(args[2:])
599 print_msg("Error. Syntax: label <tx_hash> <text>")
601 wallet.labels[tx] = label
604 elif cmd in ['payto', 'mktx']:
605 if from_addr and is_temporary:
606 if from_addr.find(":") == -1:
607 keypair = from_addr + ":" + prompt_password('Private key:', False)
610 from_addr = keypair.split(':')[0]
611 if not wallet.import_key(keypair,password):
612 print_msg("Error: Invalid key pair")
614 wallet.history[from_addr] = interface.retrieve_history(from_addr)
615 wallet.update_tx_history()
616 change_addr = from_addr
618 if options.change_addr:
619 change_addr = options.change_addr
621 for k, v in wallet.labels.items():
624 print_msg("alias", to_address)
626 if change_addr and v == change_addr:
629 tx = wallet.mktx( [(to_address, amount)], label, password,
630 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
633 traceback.print_exc(file=sys.stdout)
636 if tx and cmd=='payto':
637 r, h = wallet.sendtx( tx )
640 print_json({"hex":str(tx), "complete":tx.is_complete})
643 wallet.imported_keys.pop(from_addr)
644 del(wallet.history[from_addr])
647 elif cmd == 'signtx':
648 tx = Transaction(args[1])
649 tx = wallet.sign_tx( tx, password )
652 elif cmd == 'sendtx':
654 r, h = wallet.sendtx( tx )
657 elif cmd == 'password':
658 new_password = prompt_password('New password:')
659 wallet.update_password(seed, password, new_password)
661 elif cmd == 'signmessage':
663 print_msg("Error: Invalid usage of signmessage.")
664 print_msg(known_commands[cmd])
667 message = ' '.join(args[2:])
669 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
671 print_msg(wallet.sign_message(address, message, password))
673 elif cmd == 'verifymessage':
677 message = ' '.join(args[3:])
679 print_msg("Error: Not all parameters were given, displaying help instead.")
680 print_msg(known_commands[cmd])
683 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
684 EC_KEY.verify_message(address, signature, message)
686 EC_KEY.verify_message(address, signature, message)
688 except BaseException as e:
689 print_error("Verification error: {0}".format(e))
692 elif cmd == 'freeze':
694 print_msg(wallet.freeze(addr))
696 elif cmd == 'unfreeze':
698 print_msg(wallet.unfreeze(addr))
700 elif cmd == 'prioritize':
702 print_msg(wallet.prioritize(addr))
704 elif cmd == 'unprioritize':
706 print_msg(wallet.unprioritize(addr))
709 elif cmd == 'dumpprivkey':
711 sec = wallet.get_private_key(addr, password)
715 elif cmd == 'createmultisig':
717 pubkeys = ast.literal_eval(args[2])
718 assert isinstance(pubkeys,list)
719 print_json( Transaction.multisig_script(pubkeys, num) )
722 elif cmd == 'createrawtransaction':
723 inputs = ast.literal_eval(args[1])
724 outputs = ast.literal_eval(args[2])
725 # convert to own format
727 i['tx_hash'] = i['txid']
728 i['index'] = i['vout']
729 outputs = map(lambda x: (x[0],int(1e8*x[1])), outputs.items())
730 tx = Transaction.from_io(inputs, outputs)
734 elif cmd == 'decoderawtransaction':
735 tx = Transaction(args[1])
736 print_json( tx.deserialize() )
739 elif cmd == 'signrawtransaction':
740 tx = Transaction(args[1])
741 txouts = ast.literal_eval(args[2]) if len(args)>2 else []
742 private_keys = ast.literal_eval(args[3]) if len(args)>3 else {}
744 for txin in tx.inputs:
745 txid = txin["prevout_hash"]
746 index = txin["prevout_n"]
747 # convert to own format
748 txin['tx_hash'] = txin['prevout_hash']
749 txin['index'] = txin['prevout_n']
752 if txout.get('txid') == txid and txout.get('vout') == index:
753 txin['raw_output_script'] = txout['scriptPubKey']
754 txin['redeemScript'] = txout['redeemScript']
758 if wallet.transactions.get(txid):
759 # lookup in my own list of transactions
760 txout = wallet.transactions[txid].outputs[index]
761 txin['address'] = txout[0]
762 #txin['raw_output_script'] = txout['raw_output_script']
765 # if neither, we might want to get it from the server..
769 for txin in tx.inputs:
770 addr = txin['address']
771 private_keys[addr] = wallet.get_private_key(addr, password)
774 for sec in private_keys:
775 address = bitcoin.address_from_private_key(sec)
779 tx.sign( private_keys )
780 print_json({ "hex":str(tx),"complete":tx.is_complete})
783 elif cmd == 'listunspent':
784 print_json(wallet.get_unspent_coins())
787 if cmd not in offline_commands and not options.offline: