mirror of https://github.com/nucypher/nucypher.git
Support for complete node reinitialization/relocation using an existing mnemonic
parent
e4694c9ea1
commit
1e08388565
|
@ -11,7 +11,6 @@ from nucypher.cli.literature import (
|
|||
GENERIC_PASSWORD_PROMPT,
|
||||
PASSWORD_COLLECTION_NOTICE,
|
||||
)
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.constants import NUCYPHER_ENVVAR_KEYSTORE_PASSWORD
|
||||
from nucypher.crypto.keystore import _WORD_COUNT, Keystore
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
|
@ -36,7 +35,7 @@ def get_client_password(checksum_address: str, envvar: str = None, confirm: bool
|
|||
return client_password
|
||||
|
||||
|
||||
def unlock_signer_account(config: CharacterConfiguration, json_ipc: bool) -> None:
|
||||
def unlock_signer_account(config, json_ipc: bool) -> None:
|
||||
|
||||
# TODO: Remove this block after deprecating 'operator_address'
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
|
@ -67,7 +66,11 @@ def get_nucypher_password(emitter, confirm: bool = False, envvar=NUCYPHER_ENVVAR
|
|||
return keystore_password
|
||||
|
||||
|
||||
def unlock_nucypher_keystore(emitter: StdoutEmitter, password: str, character_configuration: CharacterConfiguration) -> bool:
|
||||
def unlock_nucypher_keystore(
|
||||
emitter: StdoutEmitter,
|
||||
password: str,
|
||||
character_configuration,
|
||||
) -> bool:
|
||||
"""Unlocks a nucypher keystore and attaches it to the supplied configuration if successful."""
|
||||
emitter.message(DECRYPTING_CHARACTER_KEYSTORE.format(name=character_configuration.NAME.capitalize()), color='yellow')
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ class UrsulaConfigOptions:
|
|||
# TODO: Exit codes (not only for this, but for other exceptions)
|
||||
return click.get_current_context().exit(1)
|
||||
|
||||
def generate_config(self, emitter, config_root, force, key_material):
|
||||
def generate_config(self, emitter, config_root, force, key_material, with_mnemonic):
|
||||
|
||||
if self.dev:
|
||||
raise RuntimeError(
|
||||
|
@ -193,6 +193,7 @@ class UrsulaConfigOptions:
|
|||
return UrsulaConfiguration.generate(
|
||||
password=get_nucypher_password(emitter=emitter, confirm=True),
|
||||
key_material=bytes.fromhex(key_material) if key_material else None,
|
||||
with_mnemonic=with_mnemonic,
|
||||
config_root=config_root,
|
||||
rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
|
@ -323,7 +324,14 @@ def ursula():
|
|||
@option_config_root
|
||||
@group_general_config
|
||||
@option_key_material
|
||||
def init(general_config, config_options, force, config_root, key_material):
|
||||
@click.option(
|
||||
"--with-mnemonic",
|
||||
help="Initialize with a mnemonic phrase instead of generating a new keypair from scratch",
|
||||
is_flag=True,
|
||||
)
|
||||
def init(
|
||||
general_config, config_options, force, config_root, key_material, with_mnemonic
|
||||
):
|
||||
"""Create a new Ursula node configuration."""
|
||||
emitter = setup_emitter(general_config, config_options.operator_address)
|
||||
_pre_launch_warnings(emitter, dev=None, force=force)
|
||||
|
@ -350,15 +358,24 @@ def init(general_config, config_options, force, config_root, key_material):
|
|||
return click.get_current_context().exit(1)
|
||||
|
||||
click.clear()
|
||||
emitter.echo(
|
||||
"Hello Operator, welcome on board :-) \n\n"
|
||||
"NOTE: Initializing a new Ursula node configuration is a one-time operation\n"
|
||||
"for the lifetime of your node. This is a two-step process:\n\n"
|
||||
"1. Creating a password to encrypt your operator keys\n"
|
||||
"2. Securing a taco node seed phase\n\n"
|
||||
"Please follow the prompts.",
|
||||
color="cyan",
|
||||
)
|
||||
if with_mnemonic:
|
||||
emitter.echo(
|
||||
"Hello Operator, welcome back :-) \n\n"
|
||||
"You are about to initialize a new Ursula node configuration using an existing mnemonic phrase.\n"
|
||||
"Have your mnemonic phrase ready and ensure you are in a secure environment.\n"
|
||||
"Please follow the prompts.",
|
||||
color="cyan",
|
||||
)
|
||||
else:
|
||||
emitter.echo(
|
||||
"Hello Operator, welcome on board :-) \n\n"
|
||||
"NOTE: Initializing a new Ursula node configuration is a one-time operation\n"
|
||||
"for the lifetime of your node. This is a two-step process:\n\n"
|
||||
"1. Creating a password to encrypt your operator keys\n"
|
||||
"2. Securing a taco node seed phase\n\n"
|
||||
"Please follow the prompts.",
|
||||
color="cyan",
|
||||
)
|
||||
|
||||
if not config_options.eth_endpoint:
|
||||
raise click.BadOptionUsage(
|
||||
|
@ -381,7 +398,11 @@ def init(general_config, config_options, force, config_root, key_material):
|
|||
message="Select TACo Domain",
|
||||
)
|
||||
ursula_config = config_options.generate_config(
|
||||
emitter=emitter, config_root=config_root, force=force, key_material=key_material
|
||||
emitter=emitter,
|
||||
config_root=config_root,
|
||||
force=force,
|
||||
key_material=key_material,
|
||||
with_mnemonic=with_mnemonic,
|
||||
)
|
||||
filepath = ursula_config.to_configuration_file()
|
||||
paint_new_installation_help(
|
||||
|
@ -460,7 +481,6 @@ def audit(config_file, keystore_filepath, view_mnemonic):
|
|||
required=False,
|
||||
)
|
||||
def recover(config_file, keystore_filepath):
|
||||
# TODO: Combine with work in PR #2682
|
||||
emitter = StdoutEmitter()
|
||||
if keystore_filepath and config_file:
|
||||
raise click.BadOptionUsage(
|
||||
|
@ -510,7 +530,7 @@ def destroy(general_config, config_options, config_file, force):
|
|||
is_flag=True,
|
||||
)
|
||||
def public_keys(config_file, keystore_filepath, from_mnemonic):
|
||||
"""Display the public key of a keystore."""
|
||||
"""Display the public keys of a keystore."""
|
||||
emitter = StdoutEmitter()
|
||||
|
||||
if sum(1 for i in (keystore_filepath, config_file, from_mnemonic) if i) > 1:
|
||||
|
|
|
@ -48,9 +48,7 @@ Path to Logs: {USER_LOG_DIR}
|
|||
|
||||
- Never share secret keys with anyone!
|
||||
- Backup your keystore! Character keys are required to interact with the protocol!
|
||||
- Remember your password! Without the password, it's impossible to decrypt the key!
|
||||
|
||||
"""
|
||||
- Remember your password! Without the password, it's impossible to decrypt the key!"""
|
||||
)
|
||||
|
||||
if character_name == 'ursula':
|
||||
|
|
|
@ -24,6 +24,7 @@ from nucypher.blockchain.eth.registry import (
|
|||
)
|
||||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli.actions.auth import collect_mnemonic
|
||||
from nucypher.config import constants
|
||||
from nucypher.config.util import cast_paths_from
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
|
@ -583,18 +584,23 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
|
||||
Warning: This method allows mutation and may result in an inconsistent configuration.
|
||||
"""
|
||||
# config file should exist and we we override -> no need for modifier
|
||||
# config file should exist and we override -> no need for modifier
|
||||
return super().update(filepath=self.config_file_location, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def generate(
|
||||
cls, password: str, key_material: Optional[bytes] = None, *args, **kwargs
|
||||
cls,
|
||||
password: str,
|
||||
key_material: Optional[bytes] = None,
|
||||
with_mnemonic: bool = False,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Generates local directories, private keys, and initial configuration for a new node.
|
||||
"""
|
||||
"""Generates local directories, private keys, and initial configuration for a new node."""
|
||||
node_config = cls(dev_mode=False, *args, **kwargs)
|
||||
node_config.initialize(key_material=key_material, password=password)
|
||||
node_config.initialize(
|
||||
key_material=key_material, with_mnemonic=with_mnemonic, password=password
|
||||
)
|
||||
node_config.keystore.unlock(password)
|
||||
return node_config
|
||||
|
||||
|
@ -789,7 +795,12 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
power_ups.append(power_up)
|
||||
return power_ups
|
||||
|
||||
def initialize(self, password: str, key_material: Optional[bytes] = None) -> Path:
|
||||
def initialize(
|
||||
self,
|
||||
password: str,
|
||||
key_material: Optional[bytes] = None,
|
||||
with_mnemonic: bool = False,
|
||||
) -> Path:
|
||||
"""Initialize a new configuration and write installation files to disk."""
|
||||
|
||||
# Development
|
||||
|
@ -806,6 +817,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
key_material=key_material,
|
||||
password=password,
|
||||
interactive=self.MNEMONIC_KEYSTORE,
|
||||
with_mnemonic=with_mnemonic,
|
||||
)
|
||||
|
||||
self._cache_runtime_filepaths()
|
||||
|
@ -824,6 +836,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
password: str,
|
||||
key_material: Optional[bytes] = None,
|
||||
interactive: bool = True,
|
||||
with_mnemonic: bool = False,
|
||||
) -> Keystore:
|
||||
if key_material:
|
||||
self.__keystore = Keystore.import_secure(
|
||||
|
@ -831,6 +844,12 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
password=password,
|
||||
keystore_dir=self.keystore_dir,
|
||||
)
|
||||
elif with_mnemonic:
|
||||
self.__keystore = Keystore.restore(
|
||||
password=password,
|
||||
keystore_dir=self.keystore_dir,
|
||||
words=collect_mnemonic(self.emitter),
|
||||
)
|
||||
else:
|
||||
if interactive:
|
||||
self.__keystore = Keystore.generate(
|
||||
|
|
Loading…
Reference in New Issue