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")
144 if __name__ == '__main__':
146 parser = arg_parser()
147 options, args = parser.parse_args()
148 set_verbosity(options.verbose)
150 # config is an object passed to the various constructors (wallet, interface, gui)
151 if 'ANDROID_DATA' in os.environ:
152 config_options = {'wallet_path':"/sdcard/electrum.dat", 'blockchain_headers_path':'/sdcard/sl4a/scripts/e4a-%s'%ELECTRUM_VERSION, 'gui':'android'}
154 config_options = eval(str(options))
155 for k, v in config_options.items():
156 if v is None: config_options.pop(k)
158 config = SimpleConfig(config_options)
159 wallet = Wallet(config)
164 elif len(args)==1 and re.match('^bitcoin:', args[0]):
169 firstarg = args[1] if len(args) > 1 else ''
171 #this entire if/else block is just concerned with importing the
172 #right GUI toolkit based the GUI command line option given
174 pref_gui = config.get('gui','classic')
175 if pref_gui == 'gtk':
177 import lib.gui as gui
179 import electrum.gui as gui
180 elif pref_gui in ['classic', 'qt']:
182 import lib.gui_qt as gui
184 import electrum.gui_qt as gui
185 elif pref_gui == 'lite':
187 import lib.gui_lite as gui
189 import electrum.gui_lite as gui
190 elif pref_gui == 'text':
192 import lib.gui_text as gui
194 import electrum.gui_text as gui
195 elif pref_gui == 'android':
197 import lib.gui_android as gui
199 import electrum.gui_android as gui
201 sys.exit("Error: Unknown GUI: " + pref_gui )
204 interface = Interface(config, True)
205 wallet.interface = interface
207 interface.send([('server.peers.subscribe',[])])
209 gui = gui.ElectrumGui(wallet, config)
211 found = config.wallet_file_exists
213 a = gui.restore_or_create()
216 s = gui.network_dialog()
219 wallet.new_seed(None)
220 wallet.init_mpk( wallet.seed )
222 # ask for seed and gap.
223 if not gui.seed_dialog(): exit()
224 wallet.init_mpk( wallet.seed )
226 # generate the first addresses, in case we are offline
227 if s is None or a == 'create':
233 verifier = WalletVerifier(interface, config)
234 wallet.set_verifier(verifier)
235 synchronizer = WalletSynchronizer(wallet, config)
238 if not found and a == 'restore' and s is not None:
240 keep_it = gui.restore_wallet()
241 wallet.fill_addressbook()
244 traceback.print_exc(file=sys.stdout)
247 if not keep_it: exit()
250 gui.password_dialog()
261 # we use daemon threads, their termination is enforced.
262 # this sleep command gives them time to terminate cleanly.
266 if cmd not in known_commands:
269 if not config.wallet_file_exists and cmd not in ['help','create','restore']:
270 print_msg("Error: Wallet file not found.")
271 print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
274 if cmd in ['create', 'restore']:
275 if config.wallet_file_exists:
276 sys.exit("Error: Remove the existing wallet first!")
277 password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
279 server = config.get('server')
280 if not server: server = pick_random_server()
281 w_host, w_port, w_protocol = server.split(':')
282 host = raw_input("server (default:%s):"%w_host)
283 port = raw_input("port (default:%s):"%w_port)
284 protocol = raw_input("protocol [t=tcp;h=http;n=native] (default:%s):"%w_protocol)
285 fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
286 gap = raw_input("gap limit (default 5):")
287 if host: w_host = host
288 if port: w_port = port
289 if protocol: w_protocol = protocol
290 wallet.config.set_key('server', w_host + ':' + w_port + ':' +w_protocol)
291 if fee: wallet.fee = float(fee)
292 if gap: wallet.gap_limit = int(gap)
295 seed = raw_input("seed:")
299 print_error("Warning: Not hex, trying decode.")
300 seed = mnemonic_decode( seed.split(' ') )
302 sys.exit("Error: No seed")
304 wallet.seed = str(seed)
305 wallet.init_mpk( wallet.seed )
306 if not options.offline:
308 interface = Interface(config)
310 wallet.interface = interface
312 verifier = WalletVerifier(interface, config)
313 wallet.set_verifier(verifier)
315 print_msg("Recovering wallet...")
316 WalletSynchronizer(wallet, config).start()
318 if wallet.is_found():
319 print_msg("Recovery successful")
321 print_msg("Warning: Found no history for this wallet")
324 wallet.fill_addressbook()
326 print_msg("Wallet saved in '%s'"%wallet.config.path)
328 wallet.new_seed(None)
329 wallet.init_mpk( wallet.seed )
330 wallet.synchronize() # there is no wallet thread
332 print_msg("Your wallet generation seed is: " + wallet.seed)
333 print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
334 print_msg("Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:")
335 print_msg("\""+' '.join(mnemonic_encode(wallet.seed))+"\"")
336 print_msg("Wallet saved in '%s'"%wallet.config.path)
339 wallet.update_password(wallet.seed, None, password)
342 if cmd in ['payto', 'mktx']:
345 amount = int( 100000000 * Decimal(args[2]) )
347 label = ' '.join(args[3:])
349 options.tx_fee = int( 100000000 * Decimal(options.tx_fee) )
355 if cmd not in offline_commands and not options.offline:
356 interface = Interface(config)
357 interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
359 wallet.interface = interface
360 synchronizer = WalletSynchronizer(wallet, config)
365 # check if --from_addr not in wallet (for mktx/payto)
368 if options.from_addr:
369 from_addr = options.from_addr
370 if from_addr not in wallet.all_addresses():
374 if cmd=='addresses' and options.show_keys:
375 print_msg("WARNING: ALL your private keys are secret.")
376 print_msg("Exposing a single private key can compromise your entire wallet!")
377 print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
379 # commands needing password
380 if cmd in protected_commands or ( cmd=='addresses' and options.show_keys):
381 password = prompt_password('Password:', False) if wallet.use_encryption and not is_temporary else None
384 wallet.pw_decode( wallet.seed, password)
386 print_msg("Error: This password does not decode this wallet.")
390 # See if they specificed a key on the cmd line, if not prompt
394 keypair = prompt_password('Enter Address:PrivateKey (will not echo):', False)
396 wallet.import_key(keypair,password)
398 print_msg("Keypair imported")
399 except BaseException as e:
400 print_msg("Error: Keypair import failed: " + str(e))
404 if cmd2 not in known_commands:
406 print_msg("Type 'electrum help <command>' to see the help for a specific command")
407 print_msg("Type 'electrum --help' to see the list of options")
408 print_msg("List of commands:", ', '.join(known_commands))
410 print_msg(known_commands[cmd2])
413 seed = wallet.pw_decode( wallet.seed, password)
414 print_msg(seed + ' "' + ' '.join(mnemonic_encode(seed)) + '"')
416 elif cmd == 'deseed':
418 print_msg("Error: This wallet has no seed")
419 elif wallet.use_encryption:
420 print_msg("Error: This wallet is encrypted")
422 ns = wallet.config.path + '.seed'
423 print_msg("Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(wallet.config.path,ns))
424 if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
426 f.write(repr({'seed':wallet.seed, 'imported_keys':wallet.imported_keys})+"\n")
429 for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
433 print_msg("Action canceled.")
435 elif cmd == 'reseed':
437 print_msg("Warning: This wallet already has a seed", wallet.seed)
439 ns = wallet.config.path + '.seed'
445 sys.exit("Error: Seed file not found")
448 d = ast.literal_eval( data )
450 imported_keys = d.get('imported_keys',{})
452 sys.exit("Error: Error with seed file")
454 mpk = wallet.master_public_key
456 wallet.imported_keys = imported_keys
457 wallet.use_encryption = False
458 wallet.init_mpk(seed)
459 if mpk == wallet.master_public_key:
461 print_msg("Done: " + wallet.config.path)
463 print_msg("Error: Master public key does not match")
465 elif cmd == 'validateaddress':
467 print_msg(wallet.is_valid(addr))
469 elif cmd == 'balance':
475 c, u = wallet.get_balance()
477 print_msg(Decimal( c ) / 100000000 , Decimal( u ) / 100000000)
479 print_msg(Decimal( c ) / 100000000)
482 c, u = wallet.get_addr_balance(addr)
484 print_msg("%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000)))
486 print_msg("%s %s" % (addr, str(Decimal(c)/100000000)))
488 elif cmd in [ 'contacts']:
489 for addr in wallet.addressbook:
490 print_msg(addr, " ", wallet.labels.get(addr))
493 print_msg(eval(args[1]))
498 print_msg(wallet.config.get(key))
501 key, value = args[1:3]
502 if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
503 wallet.config.set_key(key, value, True)
508 elif cmd in [ 'addresses']:
509 for addr in wallet.all_addresses():
510 if options.show_all or not wallet.is_change(addr):
512 flags = wallet.get_address_flags(addr)
513 label = wallet.labels.get(addr,'')
515 if label: label = "\"%s\""%label
517 if options.show_balance:
518 h = wallet.history.get(addr,[])
521 # if item['is_input']: ni += 1
523 b = format_satoshis(wallet.get_addr_balance(addr)[0])
526 if options.show_keys:
527 m_addr += ':' + str(wallet.get_private_key_base58(addr, password))
528 print_msg(flags, m_addr, b, label)
532 for item in wallet.get_tx_history():
533 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
535 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
539 label, is_default_label = wallet.get_label(tx_hash)
540 if not label: label = tx_hash
541 else: label = label + ' '*(64 - len(label) )
543 print_msg("%17s"%time_str, " " + label + " " + format_satoshis(value)+ " "+ format_satoshis(balance))
544 print_msg("# balance: ", format_satoshis(balance))
549 label = ' '.join(args[2:])
551 print_msg("Error. Syntax: label <tx_hash> <text>")
553 wallet.labels[tx] = label
556 elif cmd in ['payto', 'mktx']:
557 if from_addr and is_temporary:
558 if from_addr.find(":") == -1:
559 keypair = from_addr + ":" + prompt_password('Private key:', False)
562 from_addr = keypair.split(':')[0]
563 if not wallet.import_key(keypair,password):
564 print_msg("Error: Invalid key pair")
566 wallet.history[from_addr] = interface.retrieve_history(from_addr)
567 wallet.update_tx_history()
568 change_addr = from_addr
570 if options.change_addr:
571 change_addr = options.change_addr
573 for k, v in wallet.labels.items():
576 print_msg("alias", to_address)
578 if change_addr and v == change_addr:
581 tx = wallet.mktx( [(to_address, amount)], label, password,
582 fee = options.tx_fee, change_addr = change_addr, from_addr = from_addr )
585 traceback.print_exc(file=sys.stdout)
588 if tx and cmd=='payto':
589 r, h = wallet.sendtx( tx )
595 wallet.imported_keys.pop(from_addr)
596 del(wallet.history[from_addr])
599 elif cmd == 'signtx':
602 f = open(filename, 'r')
603 d = ast.literal_eval(f.read())
606 outputs = d['outputs']
607 tx = wallet.signed_tx( inputs, outputs, password )
610 elif cmd == 'sendtx':
612 r, h = wallet.sendtx( tx )
615 elif cmd == 'password':
617 seed = wallet.pw_decode( wallet.seed, password)
619 sys.exit("Error: Password does not decrypt this wallet.")
621 new_password = prompt_password('New password:')
622 wallet.update_password(seed, password, new_password)
624 elif cmd == 'signmessage':
626 print_msg("Error: Invalid usage of signmessage.")
627 print_msg(known_commands[cmd])
630 message = ' '.join(args[2:])
632 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
633 print_msg(wallet.sign_message(address, message, password))
635 elif cmd == 'verifymessage':
639 message = ' '.join(args[3:])
641 print_msg("Error: Not all parameters were given, displaying help instead.")
642 print_msg(known_commands[cmd])
645 print_msg("Warning: Message was reconstructed from several arguments:", repr(message))
647 wallet.verify_message(address, signature, message)
649 except BaseException as e:
650 print_error("Verification error: {0}".format(e))
653 elif cmd == 'freeze':
655 print_msg(wallet.freeze(addr))
657 elif cmd == 'unfreeze':
659 print_msg(wallet.unfreeze(addr))
661 elif cmd == 'prioritize':
663 print_msg(wallet.prioritize(addr))
665 elif cmd == 'unprioritize':
667 print_msg(wallet.unprioritize(addr))
670 if cmd not in offline_commands and not options.offline: