#!/usr/bin/env python2 # -*- mode: python -*- # # Electrum - lightweight Bitcoin client # Copyright (C) 2011 thomasv@gitorious # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from decimal import Decimal import json import os import re import ast import sys import time import traceback 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") # pure-python dependencies need to be imported here for pyinstaller try: import aes import ecdsa import requests import six import qrcode import tlslite 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") # 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 util from electrum import SimpleConfig, Network, Wallet, WalletStorage, NetworkProxy from electrum.util import print_msg, print_error, print_stderr, print_json, set_verbosity, InvalidPassword from electrum.daemon import get_daemon from electrum.plugins import init_plugins from electrum.commands import get_parser, known_commands, Commands # get password routine def prompt_password(prompt, confirm=True): import getpass if sys.stdin.isatty(): password = getpass.getpass(prompt) if password and confirm: password2 = getpass.getpass("Confirm: ") if password != password2: sys.exit("Error: Passwords do not match.") else: password = raw_input(prompt) if not password: password = None return password def run_gui(config): url = config.get('url') if url: if os.path.exists(url): # assume this is a payment request url = "bitcoin:?r=file://"+ os.path.join(os.getcwd(), cmd) if not re.match('^bitcoin:', url): print_stderr('unknown command:', url) sys.exit(1) gui_name = config.get('gui', 'qt') if gui_name in ['lite', 'classic']: gui_name = 'qt' try: gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui']) except ImportError: traceback.print_exc(file=sys.stdout) sys.exit() # network interface if not config.get('offline'): s = get_daemon(config, False) if s: print_msg("Connected to daemon") network = NetworkProxy(s, config) network.start() else: network = None gui = gui.ElectrumGui(config, network) gui.main(url) if network: network.stop() sys.exit(0) def run_daemon(config): cmd = config.get('subcommand') if cmd not in ['start', 'stop', 'status']: print_msg("syntax: electrum daemon ") sys.exit(1) s = get_daemon(config, False) if cmd == 'start': if s: print_msg("Daemon already running") sys.exit(1) get_daemon(config, True) sys.exit(0) elif cmd in ['status','stop']: if not s: print_msg("Daemon not running") sys.exit(1) network = NetworkProxy(s, config) network.start() if cmd == 'status': print_json({ 'path': network.config.path, 'server': network.get_parameters()[0], 'blockchain_height': network.get_local_height(), 'server_height': network.get_server_height(), 'nodes': network.get_interfaces(), 'connected': network.is_connected() }) elif cmd == 'stop': network.stop_daemon() print_msg("Daemon stopped") network.stop() else: print "unknown command \"%s\""% arg sys.exit(0) def run_cmdline(config): cmdname = config.get('cmd') cmd = known_commands[cmdname] # arguments passed to function args = map(lambda x: config.get(x), map(lambda x: x[0], cmd.params)) # options args += map(lambda x: config.get(x), cmd.options) # instanciate wallet for command-line storage = WalletStorage(config.get_wallet_path()) if cmd.name in ['create', 'restore']: if storage.file_exists: sys.exit("Error: Remove the existing wallet first!") if config.get('password') is not None: password = config.get('password') elif cmd.name == 'restore' and config.get('mpk'): password = None else: password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):") if cmd.name == 'restore': mpk = config.get('mpk') if mpk: if Wallet.is_old_mpk(mpk): wallet = Wallet.from_old_mpk(mpk, storage) if Wallet.is_xpub(mpk): wallet = Wallet.from_xpub(mpk, storage) else: import getpass seed = getpass.getpass(prompt="seed:", stream=None) if config.get('concealed') else raw_input("seed:") if not Wallet.is_seed(seed): sys.exit("Error: Invalid seed") wallet = Wallet.from_seed(seed, password, storage) if not config.get('offline'): s = get_daemon(config, False) network = NetworkProxy(s, config) network.start() wallet.start_threads(network) print_msg("Recovering wallet...") wallet.restore(lambda x: x) if wallet.is_found(): print_msg("Recovery successful") else: print_msg("Warning: Found no history for this wallet") else: wallet.synchronize() print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.") else: wallet = Wallet(storage) seed = wallet.make_seed() wallet.add_seed(seed, password) wallet.create_master_keys(password) wallet.create_main_account(password) 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.") print_msg("Wallet saved in '%s'" % wallet.storage.path) # terminate sys.exit(0) if cmd.name not in ['create', 'restore'] and 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) # create wallet instance wallet = Wallet(storage) if cmd.requires_wallet else None # important warning if cmd.name in ['dumpprivkey', 'dumpprivkeys']: 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: if wallet.use_encryption: password = prompt_password('Password:', False) if not password: print_msg("Error: Password required") sys.exit(1) # check password try: seed = wallet.check_password(password) except InvalidPassword: print_msg("Error: This password does not decode this wallet.") sys.exit(1) else: password = None else: password = None # start network threads if cmd.requires_network and not config.get('offline'): s = get_daemon(config, False) if not s: print_msg("Network daemon is not running. Try 'electrum daemon start'") sys.exit(1) network = NetworkProxy(s, config) network.start() while network.is_connecting(): time.sleep(0.1) if not network.is_connected(): print_msg("daemon is not connected") sys.exit(1) if wallet: wallet.start_threads(network) wallet.update() else: network = None # See if they specificed a key on the cmd line, if not prompt if (cmd.name == 'importprivkey' and len(args)==1)\ or (cmd.name == 'signtxwithkey' and len(args)==2): args.append(prompt_password('Enter PrivateKey (will not echo):', False)) # run the command if cmd.name == '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', '', True) wallet.use_encryption = False wallet.storage.put('use_encryption', wallet.use_encryption, True) for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = '' wallet.storage.put('imported_keys', wallet.imported_keys, True) print_msg("Done.") else: print_msg("Action canceled.") elif cmd.name == 'getconfig': key = args[0] out = config.get(key) print_msg(out) elif cmd.name == 'setconfig': key, value = args[0:2] try: value = ast.literal_eval(value) except: pass config.set_key(key, value, True) print_msg(True) elif cmd.name == 'password': new_password = prompt_password('New password:') wallet.update_password(password, new_password) else: cmd_runner = Commands(config, wallet, network) func = getattr(cmd_runner, cmd.name) cmd_runner.password = password try: result = func(*args) except Exception: traceback.print_exc(file=sys.stdout) sys.exit(1) if type(result) == str: print_msg(result) elif result is not None: print_json(result) # shutdown wallet and network if cmd.requires_network and not config.get('offline'): if wallet: wallet.stop_threads() network.stop() 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 sys.argv[1] == 'help': sys.argv.remove('help') sys.argv.append('-h') parser = get_parser(run_gui, run_daemon, run_cmdline) args = parser.parse_args() # config is an object passed to the various constructors (wallet, interface, gui) if is_android: config_options = { 'portable': True, 'verbose': True, 'gui': 'android', 'auto_connect': True, } else: config_options = args.__dict__ for k, v in config_options.items(): if v is None: config_options.pop(k) if config_options.get('server'): config_options['auto_connect'] = False if config_options.get('portable') and config_options.get('wallet_path') is None: config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data') set_verbosity(config_options.get('verbose')) config = SimpleConfig(config_options) assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH) gui_name = config.get('gui', 'qt') if args.cmd == 'gui' else 'cmdline' # initialize plugins. init_plugins(config, is_bundle or is_local or is_android, gui_name) # call function attached to parser args.func(config) sys.exit(0)