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 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 )
642 wallet.imported_keys.pop(from_addr)
643 del(wallet.history[from_addr])
646 elif cmd == 'signtx':
648 f = open(filename, 'r')
649 d = ast.literal_eval(f.read())
652 outputs = d['outputs']
653 tx = wallet.signed_tx( inputs, outputs, password )
656 elif cmd == 'sendtx':
658 r, h = wallet.sendtx( tx )
661 elif cmd == 'password':
662 new_password = prompt_password('New password:')
663 wallet.update_password(seed, password, new_password)
665 elif cmd == 'signmessage':
667 print_msg("Error: Invalid usage of signmessage.")
668 print_msg(known_commands[cmd])
671 message = ' '.join(args[2:])
673 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
675 print_msg(wallet.sign_message(address, message, password))
677 elif cmd == 'verifymessage':
681 message = ' '.join(args[3:])
683 print_msg("Error: Not all parameters were given, displaying help instead.")
684 print_msg(known_commands[cmd])
687 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
688 EC_KEY.verify_message(address, signature, message)
690 EC_KEY.verify_message(address, signature, message)
692 except BaseException as e:
693 print_error("Verification error: {0}".format(e))
696 elif cmd == 'freeze':
698 print_msg(wallet.freeze(addr))
700 elif cmd == 'unfreeze':
702 print_msg(wallet.unfreeze(addr))
704 elif cmd == 'prioritize':
706 print_msg(wallet.prioritize(addr))
708 elif cmd == 'unprioritize':
710 print_msg(wallet.unprioritize(addr))
713 elif cmd == 'dumpprivkey':
715 sec = wallet.get_private_key(addr, password)
719 elif cmd == 'createmultisig':
720 from lib.bitcoin import *
722 pubkeys = ast.literal_eval(args[2])
723 assert isinstance(pubkeys,list)
724 s = multisig_script(pubkeys, num)
725 out = { "address": hash_160_to_bc_address(hash_160(s.decode('hex')), 5), "redeemScript":s }
729 elif cmd == 'createrawtransaction':
730 inputs = ast.literal_eval(args[1])
731 outputs = ast.literal_eval(args[2])
732 # convert to own format
734 i['tx_hash'] = i['txid']
735 i['index'] = i['vout']
736 outputs = map(lambda x: (x[0],int(1e8*x[1])), outputs.items())
737 tx = Transaction.from_io(inputs, outputs)
741 elif cmd == 'decoderawtransaction':
742 tx = Transaction(args[1])
743 print_json( tx.deserialize() )
746 elif cmd == 'signrawtransaction':
747 from lib.bitcoin import *
748 tx = Transaction(args[1])
749 txouts = ast.literal_eval(args[2]) if len(args)>2 else []
750 private_keys = ast.literal_eval(args[3]) if len(args)>3 else {}
752 for txin in tx.inputs:
753 txid = txin["prevout_hash"]
754 index = txin["prevout_n"]
755 # convert to own format
756 txin['tx_hash'] = txin['prevout_hash']
757 txin['index'] = txin['prevout_n']
760 if txout.get('txid') == txid and txout.get('vout') == index:
761 # compute addr from redeemScript
762 addr = hash_160_to_bc_address(hash_160(txout['redeemScript'].decode('hex')),5)
763 txin['address'] = addr
764 txin['raw_output_script'] = txout['scriptPubKey']
765 txin['redeemScript'] = txout['redeemScript']
769 if wallet.transactions.get(txid):
770 # lookup in my own list of transactions
771 txout = wallet.transactions[txid]['outputs'][index]
772 txin['address'] = txout['address']
773 txin['raw_output_script'] = txout['raw_output_script']
776 # if neither, we might want to get it from the server..
780 for txin in tx.inputs:
781 addr = txin['address']
782 private_keys[addr] = wallet.get_private_key(addr, password)
785 for sec in private_keys:
786 address = bitcoin.address_from_private_key(sec)
790 tx.sign( private_keys )
794 elif cmd == 'listunspent':
795 print_json(wallet.get_unspent_coins())
798 if cmd not in offline_commands and not options.offline: