diff --git a/.changelog/unreleased/breaking-changes/ibc-relayer-cli/1075-change-key-name-flag.md b/.changelog/unreleased/breaking-changes/ibc-relayer-cli/1075-change-key-name-flag.md new file mode 100644 index 0000000000..28e9d3a4d8 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/ibc-relayer-cli/1075-change-key-name-flag.md @@ -0,0 +1 @@ +- Merged commands `keys add` and `keys restore` into single command `keys add`. The flag to specify the key name for the CLI command `keys add` has been changed from `-n` to `-k`. Restoring a key now takes a file containing the mnemonic as input instead of directly taking the mnemonic. ([#1075](https://github.com/informalsystems/ibc-rs/issues/1075)) \ No newline at end of file diff --git a/ci/e2e.sh b/ci/e2e.sh index f1bdc06dce..cd8b26a387 100755 --- a/ci/e2e.sh +++ b/ci/e2e.sh @@ -36,8 +36,8 @@ echo "Add keys for chains" echo "-----------------------------------------------------------------------------------------------------------------" hermes -c "$CONFIG_PATH" keys add "$CHAIN_A" -f user_seed_"$CHAIN_A".json hermes -c "$CONFIG_PATH" keys add "$CHAIN_B" -f user_seed_"$CHAIN_B".json -hermes -c "$CONFIG_PATH" keys add "$CHAIN_A" -f user2_seed_"$CHAIN_A".json -n user2 -hermes -c "$CONFIG_PATH" keys add "$CHAIN_B" -f user2_seed_"$CHAIN_B".json -n user2 +hermes -c "$CONFIG_PATH" keys add "$CHAIN_A" -f user2_seed_"$CHAIN_A".json -k user2 +hermes -c "$CONFIG_PATH" keys add "$CHAIN_B" -f user2_seed_"$CHAIN_B".json -k user2 echo "=================================================================================================================" echo " END-TO-END TESTS " diff --git a/guide/src/commands/keys/index.md b/guide/src/commands/keys/index.md index f5ed5ddaa3..e246d0ef4a 100644 --- a/guide/src/commands/keys/index.md +++ b/guide/src/commands/keys/index.md @@ -4,9 +4,8 @@ > store the private key file. The key file will be stored on the local file system > in the user __$HOME__ folder under `$HOME/.hermes/keys/` -> __BREAKING__: As of Hermes v0.2.0, the format of the keys stored on disk has changed, and -> keys which had been previously configured must now be re-imported using either the `keys add` -> or the `keys restore` commands. +> __BREAKING__: As of Hermes v1.0.0, the sub-command `keys restore` has been removed. +> Please use the sub-command `keys add` in order to restore a key. --- @@ -20,7 +19,7 @@ To see the available sub-commands for the `keys` command run: hermes help keys ``` -Currently there are two sub-commands supported `add` and `list`: +The available sub-commands are the following: ```shell USAGE: @@ -31,10 +30,12 @@ DESCRIPTION: SUBCOMMANDS: help Get usage information - add Adds a key to a configured chain + add Adds key to a configured chain or restores a key to a configured chain + using a mnemonic + balance Query balance for a key from a configured chain. If no key is given, the + key is retrieved from the configuration file delete Delete key(s) from a configured chain list List keys configured on a chain - restore restore a key to a configured chain using a mnemonic ``` ### Key Seed file (Private Key) @@ -63,23 +64,55 @@ The command outputs a JSON similar to the one below. You can save this to a file (e.g. `key_seed.json`) and use it to add to the relayer with `hermes keys add -f key_seed.json`. See the `Adding Keys` section for more details. -### Adding Keys +### Adding and restoring Keys + +The command `keys add` has two exclusive flags, `--key-file` and `--mnemonic-file` which are respectively used to add and restore a key. + +```shell + hermes keys add [OPTIONS] --key-file --mnemonic-file + +DESCRIPTION: + Adds key to a configured chain or restores a key to a configured chain using a mnemonic + +ARGS: + chain_id identifier of the chain + +FLAGS: + -f, --key-file + path to the key file + + -m, --mnemonic-file + path to file containing mnemonic to restore the key from + +OPTIONS: + -k, --key-name + name of the key (defaults to the `key_name` defined in the config) + + -p, --hd-path + derivation path for this key [default: m/44'/118'/0'/0/0] +``` #### Add a private key to a chain from a key file ```shell - hermes keys add + hermes keys add [OPTIONS] --key-file DESCRIPTION: - Adds a key to a configured chain + Adds key to a configured chain or restores a key to a configured chain using a mnemonic -POSITIONAL ARGUMENTS: +ARGS: chain_id identifier of the chain FLAGS: - -f, --file FILE path to the key file - -n, --name NAME name of the key (defaults to the `key_name` defined in the config) - -p, --hd-path HD-PATH derivation path for this key (default: m/44'/118'/0'/0/0) + -f, --key-file + path to the key file + +OPTIONS: + -k, --key-name + name of the key (defaults to the `key_name` defined in the config) + + -p, --hd-path + derivation path for this key [default: m/44'/118'/0'/0/0] ``` To add a private key file to a chain: @@ -88,6 +121,18 @@ To add a private key file to a chain: hermes -c config.toml keys add [CHAIN_ID] -f [PRIVATE_KEY_FILE] ``` +The content of the file key should have the same format as the output of the `gaiad keys add` command: + +```json +{ + "name": "testkey", + "type": "local", + "address": "cosmos1tc3vcuxyyac0dmayf887t95tdg7qpyql48w7gj", + "pubkey": "cosmospub1addwnpepqgg7ng4ycm60pdxfzdfh4hjvkwcr3da59mr8k883vsstx60ruv7kur4525u", + "mnemonic": "[24 words mnemonic]" +} +``` + If the command is successful a message similar to the one below will be displayed: ```json @@ -96,34 +141,39 @@ Success: Added key testkey ([ADDRESS]) on [CHAIN ID] chain > **Key name:** > By default, the key will be named after the `key_name` property specified in the configuration file. -> To use a different key name, specify the `--name` option when invoking `keys add`. +> To use a different key name, specify the `--key-name` option when invoking `keys add`. > > ``` -> hermes -c config.toml keys add [CHAINID] -f [PRIVATE_KEY_FILE] -n [KEY_NAME] +> hermes -c config.toml keys add [CHAINID] -f [PRIVATE_KEY_FILE] -k [KEY_NAME] > ``` #### Restore a private key to a chain from a mnemonic ```shell -USAGE: - hermes keys restore + hermes keys add [OPTIONS] --mnemonic-file DESCRIPTION: - restore a key to a configured chain using a mnemonic + Adds key to a configured chain or restores a key to a configured chain using a mnemonic -POSITIONAL ARGUMENTS: +ARGS: chain_id identifier of the chain FLAGS: - -m, --mnemonic MNEMONIC mnemonic to restore the key from - -n, --name NAME name of the key (defaults to the `key_name` defined in the config) - -p, --hd-path HD-PATH derivation path for this key (default: m/44'/118'/0'/0/0) + -m, --mnemonic-file + path to file containing mnemonic to restore the key from + +OPTIONS: + -k, --key-name + name of the key (defaults to the `key_name` defined in the config) + + -p, --hd-path + derivation path for this key [default: m/44'/118'/0'/0/0] ``` To restore a key from its mnemonic: ```shell -hermes -c config.toml keys restore [CHAIN_ID] -m "[MNEMONIC]" +hermes -c config.toml keys add [CHAIN_ID] -m "[MNEMONIC_FILE]" ``` or using an explicit [derivation path](https://github.com/satoshilabs/slips/blob/master/slip-0044.md), for example @@ -131,9 +181,13 @@ an Ethereum coin type (used for Evmos, Injective, Umee, Cronos, and possibly other networks): ```shell -hermes -c config.toml keys restore --mnemonic --hd-path "m/44'/60'/0'/0/0" +hermes -c config.toml keys add --mnemonic-file --hd-path "m/44'/60'/0'/0/0" ``` +The mnemonic file needs to have the 24 mnemonic words on the same line, separated by a white space. So the content should have the following format: +``` +word1 word2 word3 ... word24 +``` If the command is successful a message similar to the one below will be displayed: @@ -143,10 +197,10 @@ Success: Restore key testkey ([ADDRESS]) on [CHAIN ID] chain > **Key name:** > By default, the key will be named after the `key_name` property specified in the configuration file. -> To use a different key name, specify the `--name` option when invoking `keys restore`. +> To use a different key name, specify the `--key-name` option when invoking `keys add`. > > ``` -> hermes -c config.toml keys restore [CHAINID] -m "[MNEMONIC]" -n [KEY_NAME] +> hermes -c config.toml keys add [CHAINID] -m "[MNEMONIC_FILE]" -k [KEY_NAME] > ``` ### Delete keys diff --git a/relayer-cli/src/commands/keys.rs b/relayer-cli/src/commands/keys.rs index 32b294b1fa..20a67429b1 100644 --- a/relayer-cli/src/commands/keys.rs +++ b/relayer-cli/src/commands/keys.rs @@ -6,12 +6,11 @@ mod add; mod balance; mod delete; mod list; -mod restore; /// `keys` subcommand #[derive(Command, Debug, Parser, Runnable)] pub enum KeysCmd { - /// Adds a key to a configured chain + /// Adds key to a configured chain or restores a key to a configured chain using a mnemonic Add(add::KeysAddCmd), /// Delete key(s) from a configured chain @@ -20,9 +19,6 @@ pub enum KeysCmd { /// List keys configured on a chain List(list::KeysListCmd), - /// Restore a key to a configured chain using a mnemonic - Restore(restore::KeyRestoreCmd), - /// Query balance for a key from a configured chain. If no key is given, the key is retrieved from the configuration file. Balance(balance::KeyBalanceCmd), } diff --git a/relayer-cli/src/commands/keys/add.rs b/relayer-cli/src/commands/keys/add.rs index 1e128b97e5..30e4844e89 100644 --- a/relayer-cli/src/commands/keys/add.rs +++ b/relayer-cli/src/commands/keys/add.rs @@ -16,20 +16,49 @@ use ibc_relayer::{ use crate::application::app_config; use crate::conclude::Output; +/// The data structure that represents the arguments when invoking the `keys add` CLI command. +/// +/// The command has one argument and two exclusive flags: +/// +/// The command to add a key from a file: +/// +/// `keys add [OPTIONS] --key-file ` +/// +/// The command to restore a key from a file containing mnemonic: +/// +/// `keys add [OPTIONS] --mnemonic-file ` +/// +/// The key-file and mnemonic-file flags can't be given at the same time, this will cause a terminating error. +/// If successful the key will be created or restored, depending on which flag was given. #[derive(Clone, Command, Debug, Parser)] pub struct KeysAddCmd { #[clap(required = true, help = "identifier of the chain")] chain_id: ChainId, - #[clap(short = 'f', long, required = true, help = "path to the key file")] - file: PathBuf, + #[clap( + short = 'f', + long, + required = true, + help = "path to the key file", + group = "add-restore" + )] + key_file: Option, + + #[clap( + short, + long, + required = true, + help = "path to file containing mnemonic to restore the key from", + group = "add-restore" + )] + mnemonic_file: Option, #[clap( - short = 'n', + short, long, help = "name of the key (defaults to the `key_name` defined in the config)" )] - name: Option, + key_name: Option, #[clap( short = 'p', @@ -47,7 +76,7 @@ impl KeysAddCmd { .ok_or_else(|| format!("chain '{}' not found in configuration file", self.chain_id))?; let name = self - .name + .key_name .clone() .unwrap_or_else(|| chain_config.key_name.clone()); @@ -56,7 +85,6 @@ impl KeysAddCmd { Ok(KeysAddOptions { config: chain_config.clone(), - file: self.file.clone(), name, hd_path, }) @@ -67,7 +95,6 @@ impl KeysAddCmd { pub struct KeysAddOptions { pub name: String, pub config: ChainConfig, - pub file: PathBuf, pub hd_path: HDPath, } @@ -80,15 +107,43 @@ impl Runnable for KeysAddCmd { Ok(result) => result, }; - let key = add_key(&opts.config, &opts.name, &opts.file, &opts.hd_path); - - match key { - Ok(key) => Output::success_msg(format!( - "Added key '{}' ({}) on chain {}", - opts.name, key.account, opts.config.id - )) - .exit(), - Err(e) => Output::error(format!("{}", e)).exit(), + // Check if --file or --mnemonic was given as input. + match (self.key_file.clone(), self.mnemonic_file.clone()) { + (Some(key_file), _) => { + let key = add_key(&opts.config, &opts.name, &key_file, &opts.hd_path); + match key { + Ok(key) => Output::success_msg(format!( + "Added key '{}' ({}) on chain {}", + opts.name, key.account, opts.config.id + )) + .exit(), + Err(e) => Output::error(format!( + "An error occurred adding the key on chain {} from file {:?}: {}", + self.chain_id, key_file, e + )) + .exit(), + } + } + (_, Some(mnemonic_file)) => { + let key = restore_key(&mnemonic_file, &opts.name, &opts.hd_path, &opts.config); + + match key { + Ok(key) => Output::success_msg(format!( + "Restored key '{}' ({}) on chain {}", + opts.name, key.account, opts.config.id + )) + .exit(), + Err(e) => Output::error(format!( + "An error occurred restoring the key on chain {} from file {:?}: {}", + self.chain_id, mnemonic_file, e + )) + .exit(), + } + } + // This case should never trigger. + // The 'required' parameter for the flags will trigger an error if both flags have not been given. + // And the 'group' parameter for the flags will trigger an error if both flags are given. + _ => Output::error(format!("--mnemonic-file and --key-file can't both be None")).exit(), } } } @@ -107,3 +162,19 @@ pub fn add_key( keyring.add_key(key_name, key.clone())?; Ok(key) } + +pub fn restore_key( + mnemonic: &Path, + key_name: &str, + hdpath: &HDPath, + config: &ChainConfig, +) -> Result> { + let mnemonic_content = + fs::read_to_string(mnemonic).map_err(|_| "error reading the mnemonic file")?; + + let mut keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; + let key_entry = keyring.key_from_mnemonic(&mnemonic_content, hdpath, &config.address_type)?; + + keyring.add_key(key_name, key_entry.clone())?; + Ok(key_entry) +} diff --git a/relayer-cli/src/commands/keys/restore.rs b/relayer-cli/src/commands/keys/restore.rs deleted file mode 100644 index 2b33031368..0000000000 --- a/relayer-cli/src/commands/keys/restore.rs +++ /dev/null @@ -1,108 +0,0 @@ -use core::str::FromStr; - -use abscissa_core::clap::Parser; -use abscissa_core::{Command, Runnable}; - -use ibc::core::ics24_host::identifier::ChainId; -use ibc_relayer::{ - config::{ChainConfig, Config}, - keyring::{HDPath, KeyEntry, KeyRing, Store}, -}; - -use crate::application::app_config; -use crate::conclude::Output; - -#[derive(Clone, Command, Debug, Parser)] -pub struct KeyRestoreCmd { - #[clap(required = true, help = "identifier of the chain")] - chain_id: ChainId, - - #[clap( - short = 'm', - long, - required = true, - help = "mnemonic to restore the key from" - )] - mnemonic: String, - - #[clap( - short = 'n', - long, - help = "name of the key (defaults to the `key_name` defined in the config)" - )] - name: Option, - - #[clap( - short = 'p', - long, - help = "derivation path for this key", - default_value = "m/44'/118'/0'/0/0" - )] - hd_path: String, -} - -#[derive(Clone, Debug)] -pub struct KeysRestoreOptions { - pub mnemonic: String, - pub config: ChainConfig, - pub hd_path: HDPath, - pub key_name: String, -} - -impl KeyRestoreCmd { - fn validate_options(&self, config: &Config) -> Result { - let chain_config = config - .find_chain(&self.chain_id) - .ok_or_else(|| format!("chain '{}' not found in configuration file", self.chain_id))?; - - let hd_path = HDPath::from_str(&self.hd_path) - .map_err(|_| format!("invalid derivation path: {}", self.hd_path))?; - - let key_name = self - .name - .clone() - .unwrap_or_else(|| chain_config.key_name.clone()); - - Ok(KeysRestoreOptions { - mnemonic: self.mnemonic.clone(), - config: chain_config.clone(), - hd_path, - key_name, - }) - } -} - -impl Runnable for KeyRestoreCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => Output::error(err).exit(), - Ok(result) => result, - }; - - let key = restore_key(&opts.mnemonic, &opts.key_name, &opts.hd_path, &opts.config); - - match key { - Ok(key) => Output::success_msg(format!( - "Restored key '{}' ({}) on chain {}", - opts.key_name, key.account, opts.config.id - )) - .exit(), - Err(e) => Output::error(format!("{}", e)).exit(), - } - } -} - -pub fn restore_key( - mnemonic: &str, - key_name: &str, - hdpath: &HDPath, - config: &ChainConfig, -) -> Result> { - let mut keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; - let key_entry = keyring.key_from_mnemonic(mnemonic, hdpath, &config.address_type)?; - - keyring.add_key(key_name, key_entry.clone())?; - Ok(key_entry) -}