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
72 "Print the generation seed of your wallet.",
74 'Import a private key\nSyntax: importprivkey <privatekey>',
76 '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 "',
78 '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 "',
80 "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"",
82 "Get config parameter.",
84 "Set config parameter.",
86 "Remove seed from the wallet. The seed is stored in a file that has the name of the wallet plus '.seed'",
88 "Restore seed of the wallet. The wallet must have no seed, and the seed must match the wallet's master public key.",
93 'dumpprivkey':'similar to bitcoind\'s command',
94 'listunspent':'similar to bitcoind\'s command',
95 'createmultisig':'similar to bitcoind\'s command',
96 'createrawtransaction':'similar to bitcoind\'s command',
97 'decoderawtransaction':'similar to bitcoind\'s command',
98 'signrawtransaction':'similar to bitcoind\'s command',
104 offline_commands = [ 'password', 'mktx',
106 'help', 'validateaddress',
107 'signmessage', 'verifymessage',
108 'eval', 'set', 'get', 'create', 'addresses',
109 'importprivkey', 'seed',
112 'prioritize','unprioritize',
113 'dumpprivkey','listunspent',
114 'createmultisig', 'createrawtransaction', 'decoderawtransaction', 'signrawtransaction'
118 protected_commands = ['payto', 'password', 'mktx', 'seed', 'importprivkey','signmessage', 'signrawtransaction','dumpprivkey' ]
120 # get password routine
121 def prompt_password(prompt, confirm=True):
123 if sys.stdin.isatty():
124 password = getpass.getpass(prompt)
125 if password and confirm:
126 password2 = getpass.getpass("Confirm: ")
127 if password != password2:
128 sys.exit("Error: Passwords do not match.")
130 password = raw_input(prompt)
136 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
137 parser = optparse.OptionParser(prog=usage)
138 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
139 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
140 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
141 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
142 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
143 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
144 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
145 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.")
146 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")
147 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
148 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
149 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
150 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
151 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
152 parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
156 if __name__ == '__main__':
158 parser = arg_parser()
159 options, args = parser.parse_args()
160 set_verbosity(options.verbose)
162 # config is an object passed to the various constructors (wallet, interface, gui)
163 if 'ANDROID_DATA' in os.environ:
164 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
166 config_options = eval(str(options))
167 for k, v in config_options.items():
168 if v is None: config_options.pop(k)
170 # Wallet migration on Electrum 1.7
171 # Todo: In time we could remove this again
172 if platform.system() == "Windows":
173 util.check_windows_wallet_migration()
175 config = SimpleConfig(config_options)
176 wallet = Wallet(config)
181 elif len(args)==1 and re.match('^bitcoin:', args[0]):
186 firstarg = args[1] if len(args) > 1 else ''
188 #this entire if/else block is just concerned with importing the
189 #right GUI toolkit based the GUI command line option given
191 pref_gui = config.get('gui','classic')
193 if pref_gui == 'gtk':
195 import lib.gui as gui
197 import electrum.gui as gui
198 elif pref_gui in ['classic', 'qt']:
200 import lib.gui_qt as gui
202 import electrum.gui_qt as gui
203 elif pref_gui == 'lite':
205 import lib.gui_lite as gui
207 import electrum.gui_lite as gui
208 elif pref_gui == 'text':
210 import lib.gui_text as gui
212 import electrum.gui_text as gui
213 elif pref_gui == 'android':
215 import lib.gui_android as gui
217 import electrum.gui_android as gui
219 sys.exit("Error: Unknown GUI: " + pref_gui )
222 interface = Interface(config, True)
223 wallet.interface = interface
225 if interface.is_connected:
226 interface.send([('server.peers.subscribe',[])])
228 set_language(config.get('language'))
229 gui = gui.ElectrumGui(wallet, config)
231 found = config.wallet_file_exists
233 a = gui.restore_or_create()
236 s = gui.network_dialog()
239 wallet.init_seed(None)
241 # ask for seed and gap.
242 sg = gui.seed_dialog()
246 wallet.gap_limit = gap
249 wallet.sequence.master_public_key = seed
251 wallet.init_seed(str(seed))
254 # generate the first addresses, in case we are offline
255 if s is None or a == 'create':
261 verifier = WalletVerifier(interface, config)
262 wallet.set_verifier(verifier)
263 synchronizer = WalletSynchronizer(wallet, config)
266 if not found and a == 'restore' and s is not None:
268 keep_it = gui.restore_wallet()
269 wallet.fill_addressbook()
272 traceback.print_exc(file=sys.stdout)
275 if not keep_it: exit()
278 gui.password_dialog()
289 # we use daemon threads, their termination is enforced.
290 # this sleep command gives them time to terminate cleanly.
294 if cmd not in known_commands:
297 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
298 print_msg("Error: Wallet file not found.")
299 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
302 if cmd in ['create', 'restore']:
303 if config.wallet_file_exists:
304 sys.exit("Error: Remove the existing wallet first!")
305 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
307 server = config.get('server')
308 if not server: server = pick_random_server()
309 w_host, w_port, w_protocol = server.split(':')
310 host = raw_input("server (default:%s):"%w_host)
311 port = raw_input("port (default:%s):"%w_port)
312 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
313 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
314 gap = raw_input("gap limit (default 5):")
315 if host: w_host = host
316 if port: w_port = port
317 if protocol: w_protocol = protocol
318 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
319 if fee: wallet.fee = float(fee)
320 if gap: wallet.gap_limit = int(gap)
323 seed = raw_input("seed:")
327 print_error("Warning: Not hex, trying decode.")
328 seed = mnemonic_decode( seed.split(' ') )
330 sys.exit("Error: No seed")
334 wallet.sequence.master_public_key = seed
336 wallet.seed = str(seed)
337 wallet.init_mpk( wallet.seed )
339 if not options.offline:
340 interface = Interface(config)
342 wallet.interface = interface
344 verifier = WalletVerifier(interface, config)
345 wallet.set_verifier(verifier)
347 print_msg("Recovering wallet...")
348 WalletSynchronizer(wallet, config).start()
350 if wallet.is_found():
351 print_msg("Recovery successful")
353 print_msg("Warning: Found no history for this wallet")
356 wallet.fill_addressbook()
358 print_msg("Wallet saved in '%s'"%wallet.config.path)
360 wallet.init_seed(None)
361 wallet.synchronize() # there is no wallet thread
363 print_msg("Your wallet generation seed is: " + wallet.seed)
364 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
365 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
366 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
367 print_msg("Wallet saved in '%s'"%wallet.config.path)
370 wallet.update_password(wallet.seed, None, password)
373 if cmd in ['payto', 'mktx']:
376 amount = int( 100000000 * Decimal(args[2]) )
378 label = ' '.join(args[3:])
380 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
386 if cmd not in offline_commands and not options.offline:
387 interface = Interface(config)
388 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
390 wallet.interface = interface
391 synchronizer = WalletSynchronizer(wallet, config)
396 # check if --from_addr not in wallet (for mktx/payto)
399 if options.from_addr:
400 from_addr = options.from_addr
401 if from_addr not in wallet.all_addresses():
405 if cmd=='addresses' and options.show_keys:
406 print_msg("WARNING: ALL your private keys are secret.")
407 print_msg("Exposing a single private key can compromise your entire wallet!")
408 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
410 # commands needing password
411 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
412 if wallet.use_encryption and not is_temporary:
413 password = prompt_password('Password:', False)
415 print_msg("Error: Password required")
419 seed = wallet.decode_seed(password)
421 print_msg("Error: This password does not decode this wallet.")
427 if cmd == 'importprivkey':
428 # See if they specificed a key on the cmd line, if not prompt
432 sec = prompt_password('Enter PrivateKey (will not echo):', False)
434 addr = wallet.import_key(sec,password)
436 print_msg("Keypair imported: ", addr)
437 except BaseException as e:
438 print_msg("Error: Keypair import failed: " + str(e))
442 if cmd2 not in known_commands:
444 print_msg("Type 'electrum help <command>' to see the help for a specific command")
445 print_msg("Type 'electrum --help' to see the list of options")
446 print_msg("List of commands:", ', '.join(known_commands))
448 print_msg(known_commands[cmd2])
451 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
453 elif cmd == 'deseed':
455 print_msg("Error: This wallet has no seed")
456 elif wallet.use_encryption:
457 print_msg("Error: This wallet is encrypted")
459 ns = wallet.config.path + '.seed'
460 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
461 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
463 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
466 wallet.config.set_key('seed','', True)
467 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
471 print_msg("Action canceled.")
473 elif cmd == 'reseed':
475 print_msg("Warning: This wallet already has a seed", wallet.seed)
477 ns = wallet.config.path + '.seed'
483 sys.exit("Error: Seed file not found")
485 d = ast.literal_eval( data )
487 imported_keys = d.get('imported_keys',{})
489 sys.exit("Error: Error with seed file")
491 mpk = wallet.get_master_public_key()
493 wallet.imported_keys = imported_keys
494 wallet.use_encryption = False
495 wallet.init_mpk(seed)
496 if mpk == wallet.get_master_public_key():
498 print_msg("Done: " + wallet.config.path)
500 print_msg("Error: Master public key does not match")
502 elif cmd == 'validateaddress':
504 is_valid = wallet.is_valid(addr)
505 out = { 'isvalid':is_valid }
507 is_mine = wallet.is_mine(addr)
508 out['address'] = addr
509 out['ismine'] = is_mine
511 out['pubkey'] = wallet.get_public_key(addr)
515 elif cmd == 'balance':
521 c, u = wallet.get_balance()
523 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
525 print_msg(Decimal( c ) / 100000000)
528 c, u = wallet.get_addr_balance(addr)
530 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
532 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
534 elif cmd in [ 'contacts']:
535 for addr in wallet.addressbook:
536 print_msg(addr, " ", wallet.labels.get(addr))
539 print_msg(eval(args[1]))
544 print_msg(wallet.config.get(key))
547 key, value = args[1:3]
548 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
549 wallet.config.set_key(key, value, True)
554 elif cmd in [ 'addresses']:
555 for addr in wallet.all_addresses():
556 if options.show_all or not wallet.is_change(addr):
558 flags = wallet.get_address_flags(addr)
559 label = wallet.labels.get(addr,'')
561 if label: label = "\"%s\""%label
563 if options.show_balance:
564 h = wallet.history.get(addr,[])
567 # if item['is_input']: ni += 1
569 b = format_satoshis(wallet.get_addr_balance(addr)[0])
572 if options.show_keys:
573 m_addr += ':' + str(wallet.get_private_key(addr, password))
574 print_msg(flags, m_addr, b, label)
579 for item in wallet.get_tx_history():
580 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
582 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
586 label, is_default_label = wallet.get_label(tx_hash)
587 if not label: label = tx_hash
588 else: label = label + ' '*(64 - len(label) )
590 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
591 print_msg("# balance: ", format_satoshis(balance))
596 label = ' '.join(args[2:])
598 print_msg("Error. Syntax: label <tx_hash> <text>")
600 wallet.labels[tx] = label
603 elif cmd in ['payto', 'mktx']:
604 if from_addr and is_temporary:
605 if from_addr.find(":") == -1:
606 keypair = from_addr + ":" + prompt_password('Private key:', False)
609 from_addr = keypair.split(':')[0]
610 if not wallet.import_key(keypair,password):
611 print_msg("Error: Invalid key pair")
613 wallet.history[from_addr] = interface.retrieve_history(from_addr)
614 wallet.update_tx_history()
615 change_addr = from_addr
617 if options.change_addr:
618 change_addr = options.change_addr
620 for k, v in wallet.labels.items():
623 print_msg("alias", to_address)
625 if change_addr and v == change_addr:
628 tx = wallet.mktx( [(to_address, amount)], label, password,
629 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
632 traceback.print_exc(file=sys.stdout)
635 if tx and cmd=='payto':
636 r, h = wallet.sendtx( tx )
639 out = {"hex":str(tx), "complete":tx.is_complete}
640 if not tx.is_complete:
642 out['input_info'] = repr(tx.inputs_info).replace(' ','')
646 wallet.imported_keys.pop(from_addr)
647 del(wallet.history[from_addr])
650 elif cmd == 'sendtx':
652 r, h = wallet.sendtx( tx )
655 elif cmd == 'password':
656 new_password = prompt_password('New password:')
657 wallet.update_password(seed, password, new_password)
659 elif cmd == 'signmessage':
661 print_msg("Error: Invalid usage of signmessage.")
662 print_msg(known_commands[cmd])
665 message = ' '.join(args[2:])
667 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
669 print_msg(wallet.sign_message(address, message, password))
671 elif cmd == 'verifymessage':
675 message = ' '.join(args[3:])
677 print_msg("Error: Not all parameters were given, displaying help instead.")
678 print_msg(known_commands[cmd])
681 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
682 EC_KEY.verify_message(address, signature, message)
684 EC_KEY.verify_message(address, signature, message)
686 except BaseException as e:
687 print_error("Verification error: {0}".format(e))
690 elif cmd == 'freeze':
692 print_msg(wallet.freeze(addr))
694 elif cmd == 'unfreeze':
696 print_msg(wallet.unfreeze(addr))
698 elif cmd == 'prioritize':
700 print_msg(wallet.prioritize(addr))
702 elif cmd == 'unprioritize':
704 print_msg(wallet.unprioritize(addr))
707 elif cmd == 'dumpprivkey':
709 sec = wallet.get_private_key(addr, password)
713 elif cmd == 'createmultisig':
715 pubkeys = ast.literal_eval(args[2])
716 assert isinstance(pubkeys,list)
717 print_json( Transaction.multisig_script(pubkeys, num) )
720 elif cmd == 'createrawtransaction':
721 inputs = ast.literal_eval(args[1])
722 outputs = ast.literal_eval(args[2])
723 # convert to own format
725 i['tx_hash'] = i['txid']
726 i['index'] = i['vout']
727 outputs = map(lambda x: (x[0],int(1e8*x[1])), outputs.items())
728 tx = Transaction.from_io(inputs, outputs)
732 elif cmd == 'decoderawtransaction':
733 tx = Transaction(args[1])
734 print_json( tx.deserialize() )
737 elif cmd == 'signrawtransaction':
738 tx = Transaction(args[1])
739 inputs_info = ast.literal_eval(args[2]) if len(args)>2 else []
740 private_keys = ast.literal_eval(args[3]) if len(args)>3 else {}
741 unspent_coins = wallet.get_unspent_coins()
743 # convert private_keys to dict
745 for sec in private_keys:
746 address = bitcoin.address_from_private_key(sec)
750 for txin in tx.inputs:
751 # convert to own format
752 txin['tx_hash'] = txin['prevout_hash']
753 txin['index'] = txin['prevout_n']
755 for item in inputs_info:
756 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
757 txin['raw_output_script'] = item['scriptPubKey']
758 txin['redeemScript'] = item.get('redeemScript')
759 txin['electrumKeyID'] = item.get('electrumKeyID')
762 for item in unspent_coins:
763 if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
764 txin['raw_output_script'] = item['raw_output_script']
767 # if neither, we might want to get it from the server..
771 from lib import deserialize
772 if txin.get('electrumKeyID'):
773 n, for_change = txin.get('electrumKeyID')
774 sec = wallet.sequence.get_private_key(n, for_change, seed)
775 address = bitcoin.address_from_private_key(sec)
776 txin['address'] = address
777 private_keys[address] = sec
779 elif txin.get("redeemScript"):
780 txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
782 elif txin.get("raw_output_script"):
783 addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
784 sec = wallet.get_private_key(addr, password)
786 private_keys[addr] = sec
787 txin['address'] = addr
789 tx.sign( private_keys )
790 print_json({ "hex":str(tx),"complete":tx.is_complete})
793 elif cmd == 'listunspent':
794 print_json(wallet.get_unspent_coins())
797 if cmd not in offline_commands and not options.offline: