Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge commands keys add and keys restore into a single command. #2251

Merged
merged 7 commits into from
Jun 1, 2022
Original file line number Diff line number Diff line change
@@ -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))
4 changes: 2 additions & 2 deletions ci/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
106 changes: 80 additions & 26 deletions guide/src/commands/keys/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
---

Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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 <chain_id> -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 <KEY_FILE> --mnemonic-file <MNEMONIC_FILE> <CHAIN_ID>

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 <KEY_FILE>
path to the key file

-m, --mnemonic-file <MNEMONIC_FILE>
path to file containing mnemonic to restore the key from

OPTIONS:
-k, --key-name <KEY_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]
```
#### Add a private key to a chain from a key file
```shell
hermes keys add <OPTIONS>
hermes keys add [OPTIONS] --key-file <KEY_FILE> <CHAIN_ID>
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 <KEY_FILE>
path to the key file
OPTIONS:
-k, --key-name <KEY_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]
```

To add a private key file to a chain:
Expand All @@ -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
Expand All @@ -96,44 +141,53 @@ 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 <OPTIONS>
hermes keys add [OPTIONS] --mnemonic-file <MNEMONIC_FILE> <CHAIN_ID>

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 <MNEMONIC_FILE>
path to file containing mnemonic to restore the key from

OPTIONS:
-k, --key-name <KEY_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]
```
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
an Ethereum coin type (used for Evmos, Injective, Umee, Cronos, and
possibly other networks):
```shell
hermes -c config.toml keys restore --mnemonic <MNEMONIC> --hd-path "m/44'/60'/0'/0/0" <CHAIN_ID>
hermes -c config.toml keys add --mnemonic-file <MNEMONIC_FILE> --hd-path "m/44'/60'/0'/0/0" <CHAIN_ID>
```
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:
Expand All @@ -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
Expand Down
6 changes: 1 addition & 5 deletions relayer-cli/src/commands/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
}
103 changes: 87 additions & 16 deletions relayer-cli/src/commands/keys/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <KEY_FILE> <CHAIN_ID>`
///
/// The command to restore a key from a file containing mnemonic:
///
/// `keys add [OPTIONS] --mnemonic-file <MNEMONIC_FILE> <CHAIN_ID>`
///
/// 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<PathBuf>,

#[clap(
short,
long,
required = true,
help = "path to file containing mnemonic to restore the key from",
group = "add-restore"
)]
mnemonic_file: Option<PathBuf>,

#[clap(
short = 'n',
short,
long,
help = "name of the key (defaults to the `key_name` defined in the config)"
)]
name: Option<String>,
key_name: Option<String>,

#[clap(
short = 'p',
Expand All @@ -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());

Expand All @@ -56,7 +85,6 @@ impl KeysAddCmd {

Ok(KeysAddOptions {
config: chain_config.clone(),
file: self.file.clone(),
name,
hd_path,
})
Expand All @@ -67,7 +95,6 @@ impl KeysAddCmd {
pub struct KeysAddOptions {
pub name: String,
pub config: ChainConfig,
pub file: PathBuf,
pub hd_path: HDPath,
}

Expand All @@ -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(),
}
}
}
Expand All @@ -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<KeyEntry, Box<dyn std::error::Error>> {
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)
}
Loading