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/>.
26 sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
31 sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
36 from electrum import *
38 from decimal import Decimal
41 'help':'Prints this help',
42 'validateaddress':'Check that the address is valid',
43 'balance': "Display the balance of your wallet or of an address.\nSyntax: balance [<address>]",
44 'contacts': "Show your list of contacts",
45 'create':'Create a wallet',
46 'restore':'Restore a wallet',
47 'payto':"""Create and broadcast a transaction.
48 Syntax: payto <recipient> <amount> [label]
49 <recipient> can be a bitcoin address or a label
50 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
53 'Broadcasts a transaction to the network. \nSyntax: sendtx <tx>\n<tx> must be in hexadecimal.',
55 "Changes your password",
57 """Shows your list of addresses.
59 -a: show all addresses, including change addresses
61 -b: show the balance of addresses""",
63 'history':"Shows the transaction history",
64 'label':'Assign a label to an item\nSyntax: label <tx_hash> <label>',
66 """Create a signed transaction, password protected.
67 Syntax: mktx <recipient> <amount> [label]
68 options:\n --fee, -f: set transaction fee\n --fromaddr, -s: send from address -\n --changeaddr, -c: send change to address
70 'signtx':"Sign an unsigned transaction created by a deseeded wallet\nSyntax: signtx <filename>",
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.",
97 offline_commands = [ 'password', 'mktx', 'signtx',
99 'help', 'validateaddress',
100 'signmessage', 'verifymessage',
101 'eval', 'set', 'get', 'create', 'addresses',
102 'importprivkey', 'seed',
105 'prioritize','unprioritize']
108 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'importprivkey','signmessage' ]
110 # get password routine
111 def prompt_password(prompt, confirm=True):
113 if sys.stdin.isatty():
114 password = getpass.getpass(prompt)
115 if password and confirm:
116 password2 = getpass.getpass("Confirm: ")
117 if password != password2:
118 sys.exit("Error: Passwords do not match.")
120 password = raw_input(prompt)
126 usage = "usage: %prog [options] command\nCommands: "+ (', '.join(known_commands))
127 parser = optparse.OptionParser(prog=usage)
128 parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk or text")
129 parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
130 parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
131 parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
132 parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses")
133 parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses")
134 parser.add_option("-f", "--fee", dest="tx_fee", default="0.005", help="set tx fee")
135 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.")
136 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")
137 parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is t or h")
138 parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
139 parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
140 parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
141 parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
142 parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
146 if __name__ == '__main__':
148 parser = arg_parser()
149 options, args = parser.parse_args()
150 set_verbosity(options.verbose)
152 # config is an object passed to the various constructors (wallet, interface, gui)
153 if 'ANDROID_DATA' in os.environ:
154 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
156 config_options = eval(str(options))
157 for k, v in config_options.items():
158 if v is None: config_options.pop(k)
160 config = SimpleConfig(config_options)
161 wallet = Wallet(config)
166 elif len(args)==1 and re.match('^bitcoin:', args[0]):
171 firstarg = args[1] if len(args) > 1 else ''
173 #this entire if/else block is just concerned with importing the
174 #right GUI toolkit based the GUI command line option given
176 pref_gui = config.get('gui','classic')
177 if pref_gui == 'gtk':
179 import lib.gui as gui
181 import electrum.gui as gui
182 elif pref_gui in ['classic', 'qt']:
184 import lib.gui_qt as gui
186 import electrum.gui_qt as gui
187 elif pref_gui == 'lite':
189 import lib.gui_lite as gui
191 import electrum.gui_lite as gui
192 elif pref_gui == 'text':
194 import lib.gui_text as gui
196 import electrum.gui_text as gui
197 elif pref_gui == 'android':
199 import lib.gui_android as gui
201 import electrum.gui_android as gui
203 sys.exit("Error: Unknown GUI: " + pref_gui )
206 interface = Interface(config, True)
207 wallet.interface = interface
209 if interface.is_connected:
210 interface.send([('server.peers.subscribe',[])])
212 set_language(config.get('language'))
213 gui = gui.ElectrumGui(wallet, config)
215 found = config.wallet_file_exists
217 a = gui.restore_or_create()
220 s = gui.network_dialog()
223 wallet.init_seed(None)
225 # ask for seed and gap.
226 sg = gui.seed_dialog()
230 wallet.gap_limit = gap
233 wallet.master_public_key = seed
235 wallet.init_seed(str(seed))
238 # generate the first addresses, in case we are offline
239 if s is None or a == 'create':
245 verifier = WalletVerifier(interface, config)
246 wallet.set_verifier(verifier)
247 synchronizer = WalletSynchronizer(wallet, config)
250 if not found and a == 'restore' and s is not None:
252 keep_it = gui.restore_wallet()
253 wallet.fill_addressbook()
256 traceback.print_exc(file=sys.stdout)
259 if not keep_it: exit()
262 gui.password_dialog()
273 # we use daemon threads, their termination is enforced.
274 # this sleep command gives them time to terminate cleanly.
278 if cmd not in known_commands:
281 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
282 print_msg("Error: Wallet file not found.")
283 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
286 if cmd in ['create', 'restore']:
287 if config.wallet_file_exists:
288 sys.exit("Error: Remove the existing wallet first!")
289 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
291 server = config.get('server')
292 if not server: server = pick_random_server()
293 w_host, w_port, w_protocol = server.split(':')
294 host = raw_input("server (default:%s):"%w_host)
295 port = raw_input("port (default:%s):"%w_port)
296 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
297 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
298 gap = raw_input("gap limit (default 5):")
299 if host: w_host = host
300 if port: w_port = port
301 if protocol: w_protocol = protocol
302 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
303 if fee: wallet.fee = float(fee)
304 if gap: wallet.gap_limit = int(gap)
307 seed = raw_input("seed:")
311 print_error("Warning: Not hex, trying decode.")
312 seed = mnemonic_decode( seed.split(' ') )
314 sys.exit("Error: No seed")
318 wallet.master_public_key = seed
320 wallet.seed = str(seed)
321 wallet.init_mpk( wallet.seed )
323 if not options.offline:
324 interface = Interface(config)
326 wallet.interface = interface
328 verifier = WalletVerifier(interface, config)
329 wallet.set_verifier(verifier)
331 print_msg("Recovering wallet...")
332 WalletSynchronizer(wallet, config).start()
334 if wallet.is_found():
335 print_msg("Recovery successful")
337 print_msg("Warning: Found no history for this wallet")
340 wallet.fill_addressbook()
342 print_msg("Wallet saved in '%s'"%wallet.config.path)
344 wallet.init_seed(None)
345 wallet.synchronize() # there is no wallet thread
347 print_msg("Your wallet generation seed is: " + wallet.seed)
348 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
349 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
350 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
351 print_msg("Wallet saved in '%s'"%wallet.config.path)
354 wallet.update_password(wallet.seed, None, password)
357 if cmd in ['payto', 'mktx']:
360 amount = int( 100000000 * Decimal(args[2]) )
362 label = ' '.join(args[3:])
364 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
370 if cmd not in offline_commands and not options.offline:
371 interface = Interface(config)
372 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
374 wallet.interface = interface
375 synchronizer = WalletSynchronizer(wallet, config)
380 # check if --from_addr not in wallet (for mktx/payto)
383 if options.from_addr:
384 from_addr = options.from_addr
385 if from_addr not in wallet.all_addresses():
389 if cmd=='addresses' and options.show_keys:
390 print_msg("WARNING: ALL your private keys are secret.")
391 print_msg("Exposing a single private key can compromise your entire wallet!")
392 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
394 # commands needing password
395 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
396 if wallet.use_encryption and not is_temporary:
397 password = prompt_password('Password:', False)
399 print_msg("Error: Password required")
403 seed = wallet.decode_seed(password)
405 print_msg("Error: This password does not decode this wallet.")
411 if cmd == 'importprivkey':
412 # See if they specificed a key on the cmd line, if not prompt
416 sec = prompt_password('Enter PrivateKey (will not echo):', False)
418 addr = wallet.import_key(sec,password)
420 print_msg("Keypair imported: ", addr)
421 except BaseException as e:
422 print_msg("Error: Keypair import failed: " + str(e))
426 if cmd2 not in known_commands:
428 print_msg("Type 'electrum help <command>' to see the help for a specific command")
429 print_msg("Type 'electrum --help' to see the list of options")
430 print_msg("List of commands:", ', '.join(known_commands))
432 print_msg(known_commands[cmd2])
435 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
437 elif cmd == 'deseed':
439 print_msg("Error: This wallet has no seed")
440 elif wallet.use_encryption:
441 print_msg("Error: This wallet is encrypted")
443 ns = wallet.config.path + '.seed'
444 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
445 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
447 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
450 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
454 print_msg("Action canceled.")
456 elif cmd == 'reseed':
458 print_msg("Warning: This wallet already has a seed", wallet.seed)
460 ns = wallet.config.path + '.seed'
466 sys.exit("Error: Seed file not found")
469 d = ast.literal_eval( data )
471 imported_keys = d.get('imported_keys',{})
473 sys.exit("Error: Error with seed file")
475 mpk = wallet.master_public_key
477 wallet.imported_keys = imported_keys
478 wallet.use_encryption = False
479 wallet.init_mpk(seed)
480 if mpk == wallet.master_public_key:
482 print_msg("Done: " + wallet.config.path)
484 print_msg("Error: Master public key does not match")
486 elif cmd == 'validateaddress':
488 print_msg(wallet.is_valid(addr))
490 elif cmd == 'balance':
496 c, u = wallet.get_balance()
498 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
500 print_msg(Decimal( c ) / 100000000)
503 c, u = wallet.get_addr_balance(addr)
505 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
507 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
509 elif cmd in [ 'contacts']:
510 for addr in wallet.addressbook:
511 print_msg(addr, " ", wallet.labels.get(addr))
514 print_msg(eval(args[1]))
519 print_msg(wallet.config.get(key))
522 key, value = args[1:3]
523 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
524 wallet.config.set_key(key, value, True)
529 elif cmd in [ 'addresses']:
530 for addr in wallet.all_addresses():
531 if options.show_all or not wallet.is_change(addr):
533 flags = wallet.get_address_flags(addr)
534 label = wallet.labels.get(addr,'')
536 if label: label = "\"%s\""%label
538 if options.show_balance:
539 h = wallet.history.get(addr,[])
542 # if item['is_input']: ni += 1
544 b = format_satoshis(wallet.get_addr_balance(addr)[0])
547 if options.show_keys:
548 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
549 print_msg(flags, m_addr, b, label)
553 for item in wallet.get_tx_history():
554 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
556 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
560 label, is_default_label = wallet.get_label(tx_hash)
561 if not label: label = tx_hash
562 else: label = label + ' '*(64 - len(label) )
564 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
565 print_msg("# balance: ", format_satoshis(balance))
570 label = ' '.join(args[2:])
572 print_msg("Error. Syntax: label <tx_hash> <text>")
574 wallet.labels[tx] = label
577 elif cmd in ['payto', 'mktx']:
578 if from_addr and is_temporary:
579 if from_addr.find(":") == -1:
580 keypair = from_addr + ":" + prompt_password('Private key:', False)
583 from_addr = keypair.split(':')[0]
584 if not wallet.import_key(keypair,password):
585 print_msg("Error: Invalid key pair")
587 wallet.history[from_addr] = interface.retrieve_history(from_addr)
588 wallet.update_tx_history()
589 change_addr = from_addr
591 if options.change_addr:
592 change_addr = options.change_addr
594 for k, v in wallet.labels.items():
597 print_msg("alias", to_address)
599 if change_addr and v == change_addr:
602 tx = wallet.mktx( [(to_address, amount)], label, password,
603 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
606 traceback.print_exc(file=sys.stdout)
609 if tx and cmd=='payto':
610 r, h = wallet.sendtx( tx )
616 wallet.imported_keys.pop(from_addr)
617 del(wallet.history[from_addr])
620 elif cmd == 'signtx':
623 f = open(filename, 'r')
624 d = ast.literal_eval(f.read())
627 outputs = d['outputs']
628 tx = wallet.signed_tx( inputs, outputs, password )
631 elif cmd == 'sendtx':
633 r, h = wallet.sendtx( tx )
636 elif cmd == 'password':
637 new_password = prompt_password('New password:')
638 wallet.update_password(seed, password, new_password)
640 elif cmd == 'signmessage':
642 print_msg("Error: Invalid usage of signmessage.")
643 print_msg(known_commands[cmd])
646 message = ' '.join(args[2:])
648 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
649 print_msg(wallet.sign_message(address, message, password))
651 elif cmd == 'verifymessage':
655 message = ' '.join(args[3:])
657 print_msg("Error: Not all parameters were given, displaying help instead.")
658 print_msg(known_commands[cmd])
661 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
663 wallet.verify_message(address, signature, message)
665 except BaseException as e:
666 print_error("Verification error: {0}".format(e))
669 elif cmd == 'freeze':
671 print_msg(wallet.freeze(addr))
673 elif cmd == 'unfreeze':
675 print_msg(wallet.unfreeze(addr))
677 elif cmd == 'prioritize':
679 print_msg(wallet.prioritize(addr))
681 elif cmd == 'unprioritize':
683 print_msg(wallet.unprioritize(addr))
686 if cmd not in offline_commands and not options.offline: