diff --git a/electrum b/electrum index 0056ccd81..e322331f5 100755 --- a/electrum +++ b/electrum @@ -107,9 +107,74 @@ def init_gui(config, network, plugins): return gui +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)) + 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(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.") + + 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): +def init_cmdline(config_options): + config = SimpleConfig(config_options) cmdname = config.get('cmd') cmd = known_commands[cmdname] @@ -130,58 +195,11 @@ def init_cmdline(config): # 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!") - - def password_dialog(): - return prompt_password("Password (hit return if you do not wish to encrypt your wallet):") - - if cmd.name == '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)) - 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) - - else: - password = password_dialog() - 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.") - - wallet.storage.write() - print_msg("Wallet saved in '%s'" % wallet.storage.path) - sys.exit(0) - 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) - # create wallet instance - wallet = Wallet(storage) if cmd.requires_wallet else None - - # notify plugins - always_hook('cmdline_load_wallet', wallet) - # important warning if cmd.name in ['getprivatekeys']: print_stderr("WARNING: ALL your private keys are secret.") @@ -189,7 +207,7 @@ def init_cmdline(config): print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.") # commands needing password - if cmd.requires_password and wallet.use_encryption: + if cmd.requires_password and storage.get('use_encryption'): if config.get('password'): password = config.get('password') else: @@ -197,57 +215,48 @@ def init_cmdline(config): 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 - # 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', '') - 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) + config_options['password'] = password - elif cmd.name == 'password': + if cmd.name == 'password': new_password = prompt_password('New password:') - wallet.update_password(password, new_password) - wallet.storage.write() - sys.exit(0) + config_options['new_password'] = new_password - return cmd, password, wallet + return cmd, password -def run_offline_command(config, cmd, wallet, 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") + # notify plugins + always_hook('cmdline_load_wallet', wallet) # 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) - cmd_runner.password = password + 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 + wallet.storage.write() return result @@ -317,18 +326,21 @@ if __name__ == '__main__': gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline' plugins = Plugins(config, is_bundle or is_local or is_android, gui_name) - # run command offline + # run non-RPC commands separately + if cmd_name in ['create', 'restore', 'deseed']: + run_non_RPC(config) + sys.exit(0) + + # check if a daemon is running + server = get_daemon(config) + + # if no daemon is running, run the command offline if cmd_name not in ['gui', 'daemon']: - cmd, password, wallet = init_cmdline(config) - if not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'): - result = run_offline_command(config, cmd, wallet, password) + init_cmdline(config_options) + if server is None: #not (cmd.requires_network or cmd.requires_wallet) or config.get('offline'): + result = run_offline_command(config, config_options) print_msg(json_encode(result)) - wallet.storage.write() sys.exit(0) - else: - config_options['password'] = password - - server = get_daemon(config) # daemon is running if server is not None: diff --git a/lib/commands.py b/lib/commands.py index ed351baef..759519736 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -74,21 +74,22 @@ def command(s): class Commands: - def __init__(self, config, wallet, network, callback = None): + def __init__(self, config, wallet, network, callback = None, password=None, new_password=None): self.config = config self.wallet = wallet self.network = network self._callback = callback - self.password = None + self._password = password + self.new_password = new_password self.contacts = contacts.Contacts(self.config) def _run(self, method, args, password_getter): cmd = known_commands[method] if cmd.requires_password and self.wallet.use_encryption: - self.password = apply(password_getter,()) + self._password = apply(password_getter,()) f = getattr(self, method) result = f(*args) - self.password = None + self._password = None if self._callback: apply(self._callback, ()) return result @@ -120,7 +121,9 @@ class Commands: @command('wp') def password(self): """Change wallet password. """ - raise BaseException('Not a JSON-RPC command') + self.wallet.update_password(self._password, self.new_password) + self.wallet.storage.write() + return {'password':self.wallet.use_encryption} @command('') def getconfig(self, key): @@ -200,7 +203,7 @@ class Commands: outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items()) tx = Transaction.from_io(tx_inputs, outputs) if not unsigned: - self.wallet.sign_transaction(tx, self.password) + self.wallet.sign_transaction(tx, self._password) return tx.as_dict() @command('wp') @@ -212,7 +215,7 @@ class Commands: pubkey = bitcoin.public_key_from_private_key(privkey) t.sign({pubkey:privkey}) else: - self.wallet.sign_transaction(t, self.password) + self.wallet.sign_transaction(t, self._password) return t.as_dict() @command('') @@ -250,7 +253,7 @@ class Commands: """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses.""" is_list = type(address) is list domain = address if is_list else [address] - out = [self.wallet.get_private_key(address, self.password) for address in domain] + out = [self.wallet.get_private_key(address, self._password) for address in domain] return out if is_list else out[0] @command('w') @@ -334,19 +337,19 @@ class Commands: @command('wp') def getmasterprivate(self): """Get master private key. Return your wallet\'s master private key""" - return str(self.wallet.get_master_private_key(self.wallet.root_name, self.password)) + return str(self.wallet.get_master_private_key(self.wallet.root_name, self._password)) @command('wp') def getseed(self): """Get seed phrase. Print the generation seed of your wallet.""" - s = self.wallet.get_mnemonic(self.password) + s = self.wallet.get_mnemonic(self._password) return s.encode('utf8') @command('wp') def importprivkey(self, privkey): """Import a private key. """ try: - addr = self.wallet.import_key(privkey, self.password) + addr = self.wallet.import_key(privkey, self._password) out = "Keypair imported: " + addr except Exception as e: out = "Error: " + str(e) @@ -377,7 +380,7 @@ class Commands: def signmessage(self, address, message): """Sign a message with a key. Use quotes if your message contains whitespaces""" - sig = self.wallet.sign_message(address, message, self.password) + sig = self.wallet.sign_message(address, message, self._password) return base64.b64encode(sig) @command('') @@ -415,7 +418,7 @@ class Commands: tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr) str(tx) #this serializes if not unsigned: - self.wallet.sign_transaction(tx, self.password) + self.wallet.sign_transaction(tx, self._password) return tx @command('wpn') @@ -522,7 +525,7 @@ class Commands: @command('wp') def decrypt(self, pubkey, encrypted): """Decrypt a message encrypted with a public key.""" - return self.wallet.decrypt_message(pubkey, encrypted, self.password) + return self.wallet.decrypt_message(pubkey, encrypted, self._password) def _format_request(self, out): pr_str = { @@ -587,7 +590,7 @@ class Commands: if not alias: raise BaseException('No alias in your configuration') alias_addr = self.contacts.resolve(alias)['address'] - self.wallet.sign_payment_request(address, alias, alias_addr, self.password) + self.wallet.sign_payment_request(address, alias, alias_addr, self._password) @command('w') def rmrequest(self, address): diff --git a/lib/daemon.py b/lib/daemon.py index 94a97add4..de9354dc7 100644 --- a/lib/daemon.py +++ b/lib/daemon.py @@ -135,7 +135,6 @@ class Daemon(DaemonThread): return wallet def run_cmdline(self, config_options): - password = config_options.get('password') config = SimpleConfig(config_options) cmdname = config.get('cmd') cmd = known_commands[cmdname] @@ -146,8 +145,9 @@ class Daemon(DaemonThread): args = map(json_decode, args) # options args += map(lambda x: config.get(x), cmd.options) - cmd_runner = Commands(config, wallet, self.network) - cmd_runner.password = password + cmd_runner = Commands(config, wallet, self.network, + password=config_options.get('password'), + new_password=config_options.get('new_password')) func = getattr(cmd_runner, cmd.name) result = func(*args) return result