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 'Imports a key pair\nSyntax: import <address>:<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',
105 'prioritize','unprioritize']
108 protected_commands = ['payto', 'password', 'mktx', 'signtx', 'seed', 'import','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")
145 if __name__ == '__main__':
147 parser = arg_parser()
148 options, args = parser.parse_args()
149 set_verbosity(options.verbose)
151 # config is an object passed to the various constructors (wallet, interface, gui)
152 if 'ANDROID_DATA' in os.environ:
153 config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'}
155 config_options = eval(str(options))
156 for k, v in config_options.items():
157 if v is None: config_options.pop(k)
159 config = SimpleConfig(config_options)
160 wallet = Wallet(config)
165 elif len(args)==1 and re.match('^bitcoin:', args[0]):
170 firstarg = args[1] if len(args) > 1 else ''
172 #this entire if/else block is just concerned with importing the
173 #right GUI toolkit based the GUI command line option given
175 pref_gui = config.get('gui','classic')
176 if pref_gui == 'gtk':
178 import lib.gui as gui
180 import electrum.gui as gui
181 elif pref_gui in ['classic', 'qt']:
183 import lib.gui_qt as gui
185 import electrum.gui_qt as gui
186 elif pref_gui == 'lite':
188 import lib.gui_lite as gui
190 import electrum.gui_lite as gui
191 elif pref_gui == 'text':
193 import lib.gui_text as gui
195 import electrum.gui_text as gui
196 elif pref_gui == 'android':
198 import lib.gui_android as gui
200 import electrum.gui_android as gui
202 sys.exit("Error: Unknown GUI: " + pref_gui )
205 interface = Interface(config, True)
206 wallet.interface = interface
208 interface.send([('server.peers.subscribe',[])])
210 set_language(config.get('language'))
211 gui = gui.ElectrumGui(wallet, config)
213 found = config.wallet_file_exists
215 a = gui.restore_or_create()
218 s = gui.network_dialog()
221 wallet.new_seed(None)
222 wallet.init_mpk( wallet.seed )
224 # ask for seed and gap.
225 if not gui.seed_dialog(): exit()
226 wallet.init_mpk( wallet.seed )
228 # generate the first addresses, in case we are offline
229 if s is None or a == 'create':
235 verifier = WalletVerifier(interface, config)
236 wallet.set_verifier(verifier)
237 synchronizer = WalletSynchronizer(wallet, config)
240 if not found and a == 'restore' and s is not None:
242 keep_it = gui.restore_wallet()
243 wallet.fill_addressbook()
246 traceback.print_exc(file=sys.stdout)
249 if not keep_it: exit()
252 gui.password_dialog()
263 # we use daemon threads, their termination is enforced.
264 # this sleep command gives them time to terminate cleanly.
268 if cmd not in known_commands:
271 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
272 print_msg("Error: Wallet file not found.")
273 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
276 if cmd in ['create', 'restore']:
277 if config.wallet_file_exists:
278 sys.exit("Error: Remove the existing wallet first!")
279 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
281 server = config.get('server')
282 if not server: server = pick_random_server()
283 w_host, w_port, w_protocol = server.split(':')
284 host = raw_input("server (default:%s):"%w_host)
285 port = raw_input("port (default:%s):"%w_port)
286 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
287 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
288 gap = raw_input("gap limit (default 5):")
289 if host: w_host = host
290 if port: w_port = port
291 if protocol: w_protocol = protocol
292 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
293 if fee: wallet.fee = float(fee)
294 if gap: wallet.gap_limit = int(gap)
297 seed = raw_input("seed:")
301 print_error("Warning: Not hex, trying decode.")
302 seed = mnemonic_decode( seed.split(' ') )
304 sys.exit("Error: No seed")
306 wallet.seed = str(seed)
307 wallet.init_mpk( wallet.seed )
308 if not options.offline:
310 interface = Interface(config)
312 wallet.interface = interface
314 verifier = WalletVerifier(interface, config)
315 wallet.set_verifier(verifier)
317 print_msg("Recovering wallet...")
318 WalletSynchronizer(wallet, config).start()
320 if wallet.is_found():
321 print_msg("Recovery successful")
323 print_msg("Warning: Found no history for this wallet")
326 wallet.fill_addressbook()
328 print_msg("Wallet saved in '%s'"%wallet.config.path)
330 wallet.new_seed(None)
331 wallet.init_mpk( wallet.seed )
332 wallet.synchronize() # there is no wallet thread
334 print_msg("Your wallet generation seed is: " + wallet.seed)
335 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
336 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
337 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
338 print_msg("Wallet saved in '%s'"%wallet.config.path)
341 wallet.update_password(wallet.seed, None, password)
344 if cmd in ['payto', 'mktx']:
347 amount = int( 100000000 * Decimal(args[2]) )
349 label = ' '.join(args[3:])
351 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
357 if cmd not in offline_commands and not options.offline:
358 interface = Interface(config)
359 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
361 wallet.interface = interface
362 synchronizer = WalletSynchronizer(wallet, config)
367 # check if --from_addr not in wallet (for mktx/payto)
370 if options.from_addr:
371 from_addr = options.from_addr
372 if from_addr not in wallet.all_addresses():
376 if cmd=='addresses' and options.show_keys:
377 print_msg("WARNING: ALL your private keys are secret.")
378 print_msg("Exposing a single private key can compromise your entire wallet!")
379 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
381 # commands needing password
382 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
383 if wallet.use_encryption and not is_temporary:
384 password = prompt_password('Password:', False)
386 print_msg("Error: Password required")
390 wallet.pw_decode( wallet.seed, password)
392 print_msg("Error: This password does not decode this wallet.")
398 # See if they specificed a key on the cmd line, if not prompt
402 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
404 wallet.import_key(keypair,password)
406 print_msg("Keypair imported")
407 except BaseException as e:
408 print_msg("Error: Keypair import failed: " + str(e))
412 if cmd2 not in known_commands:
414 print_msg("Type 'electrum help <command>' to see the help for a specific command")
415 print_msg("Type 'electrum --help' to see the list of options")
416 print_msg("List of commands:", ', '.join(known_commands))
418 print_msg(known_commands[cmd2])
421 seed = wallet.pw_decode( wallet.seed, password)
422 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
424 elif cmd == 'deseed':
426 print_msg("Error: This wallet has no seed")
427 elif wallet.use_encryption:
428 print_msg("Error: This wallet is encrypted")
430 ns = wallet.config.path + '.seed'
431 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
432 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
434 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
437 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
441 print_msg("Action canceled.")
443 elif cmd == 'reseed':
445 print_msg("Warning: This wallet already has a seed", wallet.seed)
447 ns = wallet.config.path + '.seed'
453 sys.exit("Error: Seed file not found")
456 d = ast.literal_eval( data )
458 imported_keys = d.get('imported_keys',{})
460 sys.exit("Error: Error with seed file")
462 mpk = wallet.master_public_key
464 wallet.imported_keys = imported_keys
465 wallet.use_encryption = False
466 wallet.init_mpk(seed)
467 if mpk == wallet.master_public_key:
469 print_msg("Done: " + wallet.config.path)
471 print_msg("Error: Master public key does not match")
473 elif cmd == 'validateaddress':
475 print_msg(wallet.is_valid(addr))
477 elif cmd == 'balance':
483 c, u = wallet.get_balance()
485 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
487 print_msg(Decimal( c ) / 100000000)
490 c, u = wallet.get_addr_balance(addr)
492 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
494 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
496 elif cmd in [ 'contacts']:
497 for addr in wallet.addressbook:
498 print_msg(addr, " ", wallet.labels.get(addr))
501 print_msg(eval(args[1]))
506 print_msg(wallet.config.get(key))
509 key, value = args[1:3]
510 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
511 wallet.config.set_key(key, value, True)
516 elif cmd in [ 'addresses']:
517 for addr in wallet.all_addresses():
518 if options.show_all or not wallet.is_change(addr):
520 flags = wallet.get_address_flags(addr)
521 label = wallet.labels.get(addr,'')
523 if label: label = "\"%s\""%label
525 if options.show_balance:
526 h = wallet.history.get(addr,[])
529 # if item['is_input']: ni += 1
531 b = format_satoshis(wallet.get_addr_balance(addr)[0])
534 if options.show_keys:
535 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
536 print_msg(flags, m_addr, b, label)
540 for item in wallet.get_tx_history():
541 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
543 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
547 label, is_default_label = wallet.get_label(tx_hash)
548 if not label: label = tx_hash
549 else: label = label + ' '*(64 - len(label) )
551 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
552 print_msg("# balance: ", format_satoshis(balance))
557 label = ' '.join(args[2:])
559 print_msg("Error. Syntax: label <tx_hash> <text>")
561 wallet.labels[tx] = label
564 elif cmd in ['payto', 'mktx']:
565 if from_addr and is_temporary:
566 if from_addr.find(":") == -1:
567 keypair = from_addr + ":" + prompt_password('Private key:', False)
570 from_addr = keypair.split(':')[0]
571 if not wallet.import_key(keypair,password):
572 print_msg("Error: Invalid key pair")
574 wallet.history[from_addr] = interface.retrieve_history(from_addr)
575 wallet.update_tx_history()
576 change_addr = from_addr
578 if options.change_addr:
579 change_addr = options.change_addr
581 for k, v in wallet.labels.items():
584 print_msg("alias", to_address)
586 if change_addr and v == change_addr:
589 tx = wallet.mktx( [(to_address, amount)], label, password,
590 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
593 traceback.print_exc(file=sys.stdout)
596 if tx and cmd=='payto':
597 r, h = wallet.sendtx( tx )
603 wallet.imported_keys.pop(from_addr)
604 del(wallet.history[from_addr])
607 elif cmd == 'signtx':
610 f = open(filename, 'r')
611 d = ast.literal_eval(f.read())
614 outputs = d['outputs']
615 tx = wallet.signed_tx( inputs, outputs, password )
618 elif cmd == 'sendtx':
620 r, h = wallet.sendtx( tx )
623 elif cmd == 'password':
625 seed = wallet.pw_decode( wallet.seed, password)
627 sys.exit("Error: Password does not decrypt this wallet.")
629 new_password = prompt_password('New password:')
630 wallet.update_password(seed, password, new_password)
632 elif cmd == 'signmessage':
634 print_msg("Error: Invalid usage of signmessage.")
635 print_msg(known_commands[cmd])
638 message = ' '.join(args[2:])
640 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
641 print_msg(wallet.sign_message(address, message, password))
643 elif cmd == 'verifymessage':
647 message = ' '.join(args[3:])
649 print_msg("Error: Not all parameters were given, displaying help instead.")
650 print_msg(known_commands[cmd])
653 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
655 wallet.verify_message(address, signature, message)
657 except BaseException as e:
658 print_error("Verification error: {0}".format(e))
661 elif cmd == 'freeze':
663 print_msg(wallet.freeze(addr))
665 elif cmd == 'unfreeze':
667 print_msg(wallet.unfreeze(addr))
669 elif cmd == 'prioritize':
671 print_msg(wallet.prioritize(addr))
673 elif cmd == 'unprioritize':
675 print_msg(wallet.unprioritize(addr))
678 if cmd not in offline_commands and not options.offline: