#!/usr/bin/env python2 # -*- mode: python -*- # # Electrum - lightweight Bitcoin client # Copyright (C) 2011 thomasv@gitorious # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import sys script_dir = os.path.dirname(os.path.realpath(__file__)) is_bundle = getattr(sys, 'frozen', False) is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "setup-release.py")) is_android = 'ANDROID_DATA' in os.environ if is_local or is_android: sys.path.insert(0, os.path.join(script_dir, 'packages')) elif is_bundle and sys.platform=='darwin': sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages") def check_imports(): # pure-python dependencies need to be imported here for pyinstaller try: import dns import aes import ecdsa import requests import six import qrcode import pbkdf2 import google.protobuf except ImportError as e: sys.exit("Error: %s. Try 'sudo pip install '"%e.message) # the following imports are for pyinstaller from google.protobuf import descriptor from google.protobuf import message from google.protobuf import reflection from google.protobuf import descriptor_pb2 # check that we have the correct version of ecdsa try: from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1 except Exception: sys.exit("cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa") # make sure that certificates are here assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH) if not is_android: check_imports() # load local module as electrum if is_bundle or is_local or is_android: import imp imp.load_module('electrum', *imp.find_module('lib')) imp.load_module('electrum_gui', *imp.find_module('gui')) from electrum import SimpleConfig, Network, Wallet, WalletStorage from electrum.util import print_msg, print_stderr, json_encode, json_decode from electrum.util import set_verbosity, InvalidPassword, check_www_dir from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum import daemon # get password routine def prompt_password(prompt, confirm=True): import getpass password = getpass.getpass(prompt, stream=None) if password and confirm: password2 = getpass.getpass("Confirm: ") if password != password2: sys.exit("Error: Passwords do not match.") if not password: password = None return password def run_non_RPC(config): cmdname = config.get('cmd') storage = WalletStorage(config.get_wallet_path()) if storage.file_exists: sys.exit("Error: Remove the existing wallet first!") def password_dialog(): return prompt_password("Password (hit return if you do not wish to encrypt your wallet):") if cmdname == 'restore': text = config.get('text') password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None try: wallet = Wallet.from_text(text, password, storage) except BaseException as e: sys.exit(str(e)) wallet.create_main_account() if not config.get('offline'): network = Network(config) network.start() wallet.start_threads(network) print_msg("Recovering wallet...") wallet.synchronize() wallet.wait_until_synchronized() msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet" else: msg = "This wallet was restored offline. It may contain more addresses than displayed." print_msg(msg) elif cmdname == 'create': password = password_dialog() wallet = Wallet(storage) seed = wallet.make_seed() wallet.add_seed(seed, password) wallet.create_master_keys(password) wallet.create_main_account() wallet.synchronize() print_msg("Your wallet generation seed is:\n\"%s\"" % seed) print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.") elif cmdname == 'deseed': if not wallet.seed: print_msg("Error: This wallet has no seed") else: ns = wallet.storage.path + '.seedless' print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns) if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']: wallet.storage.path = ns wallet.seed = '' wallet.storage.put('seed', '') wallet.use_encryption = False wallet.storage.put('use_encryption', wallet.use_encryption) for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = '' wallet.storage.put('imported_keys', wallet.imported_keys) print_msg("Done.") else: print_msg("Action canceled.") wallet.storage.write() sys.exit(0) wallet.storage.write() print_msg("Wallet saved in '%s'" % wallet.storage.path) sys.exit(0) def init_cmdline(config_options): config = SimpleConfig(config_options) cmdname = config.get('cmd') cmd = known_commands[cmdname] if cmdname == 'signtransaction' and config.get('privkey'): cmd.requires_wallet = False cmd.requires_password = False if cmdname in ['payto', 'paytomany'] and config.get('unsigned'): cmd.requires_password = False if cmdname in ['payto', 'paytomany'] and config.get('broadcast'): cmd.requires_network = True if cmdname in ['createrawtx'] and config.get('unsigned'): cmd.requires_password = False cmd.requires_wallet = False # instanciate wallet for command-line storage = WalletStorage(config.get_wallet_path()) if cmd.requires_wallet and not storage.file_exists: print_msg("Error: Wallet file not found.") print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option") sys.exit(0) # important warning if cmd.name in ['getprivatekeys']: print_stderr("WARNING: ALL your private keys are secret.") print_stderr("Exposing a single private key can compromise your entire wallet!") print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.") # commands needing password if cmd.requires_password and storage.get('use_encryption'): if config.get('password'): password = config.get('password') else: password = prompt_password('Password:', False) if not password: print_msg("Error: Password required") sys.exit(1) else: password = None config_options['password'] = password if cmd.name == 'password': new_password = prompt_password('New password:') config_options['new_password'] = new_password return cmd, password def run_offline_command(config, config_options): cmdname = config.get('cmd') cmd = known_commands[cmdname] storage = WalletStorage(config.get_wallet_path()) wallet = Wallet(storage) if cmd.requires_wallet else None # check password if cmd.requires_password and storage.get('use_encryption'): password = config_options.get('password') try: seed = wallet.check_password(password) except InvalidPassword: print_msg("Error: This password does not decode this wallet.") sys.exit(1) if cmd.requires_network: print_stderr("Warning: running command offline") # arguments passed to function args = map(lambda x: config.get(x), cmd.params) # decode json arguments args = map(json_decode, args) # options args += map(lambda x: config.get(x), cmd.options) cmd_runner = Commands(config, wallet, None, password=config_options.get('password'), new_password=config_options.get('new_password')) func = getattr(cmd_runner, cmd.name) result = func(*args) # save wallet if wallet: wallet.storage.write() return result def init_plugins(config, gui_name): from electrum.plugins import Plugins return Plugins(config, is_bundle or is_local or is_android, gui_name) if __name__ == '__main__': # on osx, delete Process Serial Number arg generated for apps launched in Finder sys.argv = filter(lambda x: not x.startswith('-psn'), sys.argv) # old 'help' syntax if len(sys.argv)>1 and sys.argv[1] == 'help': sys.argv.remove('help') sys.argv.append('-h') # read arguments from stdin pipe and prompt for i, arg in enumerate(sys.argv): if arg == '-': if not sys.stdin.isatty(): sys.argv[i] = sys.stdin.read() break else: raise BaseException('Cannot get argument from stdin') elif arg == '?': sys.argv[i] = raw_input("Enter argument:") elif arg == ':': sys.argv[i] = prompt_password('Enter argument (will not echo):', False) # parse command line parser = get_parser() args = parser.parse_args() # config is an object passed to the various constructors (wallet, interface, gui) if is_android: config_options = { 'verbose': True, 'cmd': 'gui', 'gui': 'kivy', #'auto_connect': True, } else: config_options = args.__dict__ for k, v in config_options.items(): if v is None or (k in config_variables.get(args.cmd, {}).keys()): config_options.pop(k) if config_options.get('server'): config_options['auto_connect'] = False if config_options.get('portable'): config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data') set_verbosity(config_options.get('verbose')) # check uri uri = config_options.get('url') if uri: if not uri.startswith('bitcoin:'): print_stderr('unknown command:', uri) sys.exit(1) config_options['url'] = uri config = SimpleConfig(config_options) cmdname = config.get('cmd') # run non-RPC commands separately if cmdname in ['create', 'restore', 'deseed']: run_non_RPC(config) sys.exit(0) if cmdname == 'gui': fd, server = daemon.get_fd_or_server(config) if fd is not None: plugins = init_plugins(config, config.get('gui', 'qt')) d = daemon.Daemon(config, fd) d.start() d.init_gui(config, plugins) sys.exit(0) else: result = server.gui(config_options) elif cmdname == 'daemon': subcommand = config.get('subcommand') assert subcommand in ['start', 'stop', 'status'] if subcommand == 'start': fd, server = daemon.get_fd_or_server(config) if fd is not None: pid = os.fork() if pid: print_stderr("starting daemon (PID %d)" % pid) sys.exit(0) init_plugins(config, 'cmdline') d = daemon.Daemon(config, fd) d.start() if config.get('websocket_server'): from electrum import websockets websockets.WebSocketServer(config, d.network).start() if config.get('requests_dir'): check_www_dir(config.get('requests_dir')) d.join() sys.exit(0) else: result = server.daemon(config_options) else: server = daemon.get_server(config) if server is not None: result = server.daemon(config_options) else: print_msg("Daemon not running") sys.exit(1) else: # command line init_cmdline(config_options) server = daemon.get_server(config) if server is not None: result = server.run_cmdline(config_options) else: cmd = known_commands[cmdname] if cmd.requires_network: print_msg("Daemon not running; try 'electrum daemon start'") sys.exit(1) else: init_plugins(config, 'cmdline') result = run_offline_command(config, config_options) # print result if type(result) in [str, unicode]: print_msg(result) elif type(result) is dict and result.get('error'): print_stderr(result.get('error')) elif result is not None: print_msg(json_encode(result)) sys.exit(0)