Skip to content

Commit

Permalink
Add --coin-type option to keys restore command (#856)
Browse files Browse the repository at this point in the history
* fix hardcoded coin-type

* Introduce CoinType type

* Make `coin_type` field in key file optional, default to Atom

* Properly handle invalid HD path format

Co-authored-by: allthatjazzleo <pangleo1994@gmail.com>
  • Loading branch information
romac and allthatjazzleo committed Apr 28, 2021
1 parent 426ced4 commit c32393a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 21 deletions.
21 changes: 11 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion relayer-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ibc-relayer = { version = "0.2.0", path = "../relayer" }
ibc-proto = { version = "0.8.0", path = "../proto" }

anomaly = "0.2.0"
gumdrop = "0.7"
gumdrop = { version = "0.7", features = ["default_expr"] }
serde = { version = "1", features = ["serde_derive"] }
thiserror = "1"
tokio = { version = "1.0", features = ["full"] }
Expand Down
23 changes: 18 additions & 5 deletions relayer-cli/src/commands/keys/restore.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use abscissa_core::{Command, Options, Runnable};

use anomaly::BoxError;

use ibc::ics24_host::identifier::ChainId;
use ibc_relayer::{
config::{ChainConfig, Config},
keyring::{KeyEntry, KeyRing, Store},
keyring::{CoinType, KeyEntry, KeyRing, Store},
};

use crate::application::app_config;
Expand All @@ -17,12 +17,20 @@ pub struct KeyRestoreCmd {

#[options(short = "m", required, help = "mnemonic to restore the key from")]
mnemonic: String,

#[options(
short = "t",
help = "coin type of the key to restore, default: 118 (Atom)",
default_expr = "CoinType::ATOM"
)]
coin_type: CoinType,
}

#[derive(Clone, Debug)]
pub struct KeysRestoreOptions {
pub mnemonic: String,
pub config: ChainConfig,
pub coin_type: CoinType,
}

impl KeyRestoreCmd {
Expand All @@ -34,6 +42,7 @@ impl KeyRestoreCmd {
Ok(KeysRestoreOptions {
mnemonic: self.mnemonic.clone(),
config: chain_config.clone(),
coin_type: self.coin_type,
})
}
}
Expand All @@ -49,7 +58,7 @@ impl Runnable for KeyRestoreCmd {

let key_name = opts.config.key_name.clone();
let chain_id = opts.config.id.clone();
let key = restore_key(&opts.mnemonic, opts.config);
let key = restore_key(&opts.mnemonic, opts.coin_type, opts.config);

match key {
Ok(key) => Output::success_msg(format!(
Expand All @@ -62,9 +71,13 @@ impl Runnable for KeyRestoreCmd {
}
}

pub fn restore_key(mnemonic: &str, config: ChainConfig) -> Result<KeyEntry, BoxError> {
pub fn restore_key(
mnemonic: &str,
coin_type: CoinType,
config: ChainConfig,
) -> Result<KeyEntry, BoxError> {
let mut keyring = KeyRing::new(Store::Test, config)?;
let key_entry = keyring.key_from_mnemonic(mnemonic)?;
let key_entry = keyring.key_from_mnemonic(mnemonic, coin_type)?;
keyring.add_key(key_entry.clone())?;

Ok(key_entry)
Expand Down
66 changes: 61 additions & 5 deletions relayer/src/keyring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod errors;
use std::convert::{TryFrom, TryInto};
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::str::FromStr;

use bech32::{ToBase32, Variant};
use bip39::{Language, Mnemonic, Seed};
Expand All @@ -25,6 +26,42 @@ pub const KEYSTORE_DEFAULT_FOLDER: &str = ".hermes/keys/";
pub const KEYSTORE_DISK_BACKEND: &str = "keyring-test"; // TODO: Change to "keyring"
pub const KEYSTORE_FILE_EXTENSION: &str = "json";

/// [Coin type][coin-type] associated with a key.
///
/// See [the list of all registered coin types][coin-types].
///
/// [coin-type]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#Coin_type
/// [coin-types]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct CoinType(u32);

impl CoinType {
/// Atom (Cosmos) coin type with number 118.
pub const ATOM: CoinType = CoinType(118);

pub fn new(coin_type: u32) -> Self {
Self(coin_type)
}

pub fn num(&self) -> u32 {
self.0
}
}

impl Default for CoinType {
fn default() -> Self {
Self::ATOM
}
}

impl FromStr for CoinType {
type Err = std::num::ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<u32>().map(Self::new)
}
}

/// Key entry stores the Private Key and Public Key as well the address
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyEntry {
Expand All @@ -39,6 +76,9 @@ pub struct KeyEntry {

/// Address
pub address: Vec<u8>,

/// Coin type
pub coin_type: CoinType,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand All @@ -48,6 +88,7 @@ pub struct KeyFile {
pub address: String,
pub pubkey: String,
pub mnemonic: String,
pub coin_type: Option<CoinType>,
}

impl TryFrom<KeyFile> for KeyEntry {
Expand All @@ -60,8 +101,11 @@ impl TryFrom<KeyFile> for KeyEntry {
// Decode the Bech32-encoded public key from the key file
let mut keyfile_pubkey_bytes = decode_bech32(&key_file.pubkey)?;

// Use coin type if present or default coin type (ie. Atom).
let coin_type = key_file.coin_type.unwrap_or_default();

// Decode the private key from the mnemonic
let private_key = private_key_from_mnemonic(&key_file.mnemonic)?;
let private_key = private_key_from_mnemonic(&key_file.mnemonic, coin_type)?;
let public_key = ExtendedPubKey::from_private(&Secp256k1::new(), &private_key);
let public_key_bytes = public_key.public_key.to_bytes();

Expand All @@ -88,6 +132,7 @@ impl TryFrom<KeyFile> for KeyEntry {
private_key,
account: key_file.address,
address: keyfile_address_bytes,
coin_type,
})
}
}
Expand Down Expand Up @@ -238,9 +283,13 @@ impl KeyRing {
}

/// Add a key entry in the store using a mnemonic.
pub fn key_from_mnemonic(&self, mnemonic_words: &str) -> Result<KeyEntry, Error> {
pub fn key_from_mnemonic(
&self,
mnemonic_words: &str,
coin_type: CoinType,
) -> Result<KeyEntry, Error> {
// Get the private key from the mnemonic
let private_key = private_key_from_mnemonic(mnemonic_words)?;
let private_key = private_key_from_mnemonic(mnemonic_words, coin_type)?;

// Get the public Key from the private key
let public_key = ExtendedPubKey::from_private(&Secp256k1::new(), &private_key);
Expand All @@ -257,6 +306,7 @@ impl KeyRing {
private_key,
address,
account,
coin_type,
})
}

Expand All @@ -282,14 +332,20 @@ impl KeyRing {
}

/// Decode an extended private key from a mnemonic
fn private_key_from_mnemonic(mnemonic_words: &str) -> Result<ExtendedPrivKey, Error> {
fn private_key_from_mnemonic(
mnemonic_words: &str,
coin_type: CoinType,
) -> Result<ExtendedPrivKey, Error> {
let mnemonic = Mnemonic::from_phrase(mnemonic_words, Language::English)
.map_err(|e| Kind::InvalidMnemonic.context(e))?;

let seed = Seed::new(&mnemonic, "");

// Get Private Key from seed and standard derivation path
let hd_path = StandardHDPath::try_from("m/44'/118'/0'/0/0").unwrap();
let hd_path_format = format!("m/44'/{}'/0'/0/0", coin_type.num());
let hd_path = StandardHDPath::try_from(hd_path_format.as_str())
.map_err(|_| Kind::InvalidHdPath(hd_path_format))?;

let private_key = ExtendedPrivKey::new_master(Network::Bitcoin, seed.as_bytes())
.and_then(|k| k.derive_priv(&Secp256k1::new(), &DerivationPath::from(hd_path)))
.map_err(|e| Kind::PrivateKey.context(e))?;
Expand Down
3 changes: 3 additions & 0 deletions relayer/src/keyring/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub enum Kind {

#[error("key store error")]
KeyStore,

#[error("invalid HD path: {0}")]
InvalidHdPath(String),
}

impl Kind {
Expand Down

0 comments on commit c32393a

Please sign in to comment.