Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ use coin_balance::{AddressBalanceStatus, HDAddressBalance, HDWalletBalanceOps};

pub mod lp_price;
pub mod watcher_common;
pub mod priv_key;

pub mod coin_errors;
use coin_errors::{AddressFromPubkeyError, MyAddressError, ValidatePaymentError, ValidatePaymentFut,
Expand Down
233 changes: 233 additions & 0 deletions mm2src/coins/priv_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
use crate::hd_wallet::{HDAccountOps, HDWalletOps};
use crate::{CoinWithDerivationMethod, CoinWithPrivKeyPolicy, DerivationMethod, MmCoin, MmCoinEnum, PrivKeyPolicy};
use bip32::ChildNumber;
use common::HttpStatusCode;
use crypto::Bip44Chain;
use derive_more::Display;
use http::StatusCode;
use keys::{KeyPair, Private};
use mm2_err_handle::prelude::*;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DerivedPrivKey {
pub coin: String,
pub address: String,
pub derivation_path: String,
pub priv_key: String,
pub pub_key: String,
}

#[derive(Debug, Deserialize)]
pub struct DerivePrivKeyReq {
pub account_id: u32,
pub chain: Option<Bip44Chain>,
pub address_id: u32,
}

#[derive(Debug, Display, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum DerivePrivKeyError {
#[display(fmt = "No such coin: {}", ticker)]
NoSuchCoin {
ticker: String,
},
#[display(fmt = "Coin {} doesn't support HD wallet derivation", ticker)]
CoinDoesntSupportDerivation {
ticker: String,
},
#[display(fmt = "Hardware/remote wallet doesn't allow exporting private keys")]
HwWalletNotAllowed,
#[display(fmt = "Internal error: {}", reason)]
Internal {
reason: String,
},
}

impl HttpStatusCode for DerivePrivKeyError {
fn status_code(&self) -> StatusCode {
match self {
DerivePrivKeyError::NoSuchCoin { .. } => StatusCode::NOT_FOUND,
DerivePrivKeyError::CoinDoesntSupportDerivation { .. } => StatusCode::BAD_REQUEST,
DerivePrivKeyError::HwWalletNotAllowed => StatusCode::FORBIDDEN,
DerivePrivKeyError::Internal { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

pub async fn derive_priv_key(coin: MmCoinEnum, req: &DerivePrivKeyReq) -> Result<DerivedPrivKey, MmError<DerivePrivKeyError>> {
match coin {
MmCoinEnum::UtxoCoin(c) => derive_priv_key_for_utxo_coin(c, req).await,
MmCoinEnum::Bch(c) => derive_priv_key_for_utxo_coin(c, req).await,
MmCoinEnum::QtumCoin(c) => derive_priv_key_for_utxo_coin(c, req).await,
MmCoinEnum::EthCoin(c) => derive_priv_key_for_eth_coin(c, req).await,
_ => MmError::err(DerivePrivKeyError::CoinDoesntSupportDerivation {
ticker: coin.ticker().to_string(),
}),
}
}

async fn derive_priv_key_for_utxo_coin(
coin: impl MmCoin + CoinWithPrivKeyPolicy + CoinWithDerivationMethod + AsRef<crate::utxo::UtxoCoinFields>,
req: &DerivePrivKeyReq,
) -> Result<DerivedPrivKey, MmError<DerivePrivKeyError>> {
let coin_fields = coin.as_ref();

match coin.priv_key_policy() {
PrivKeyPolicy::Iguana(_) => MmError::err(DerivePrivKeyError::CoinDoesntSupportDerivation {
ticker: coin.ticker().to_string(),
}),
PrivKeyPolicy::Trezor | PrivKeyPolicy::WalletConnect { .. } => {
MmError::err(DerivePrivKeyError::HwWalletNotAllowed)
},
PrivKeyPolicy::HDWallet { .. } => {
let hd_wallet = match coin.derivation_method() {
DerivationMethod::HDWallet(hd_wallet) => hd_wallet,
_ => {
return MmError::err(DerivePrivKeyError::CoinDoesntSupportDerivation {
ticker: coin.ticker().to_string(),
})
},
};

let account = hd_wallet
.get_account(req.account_id)
.await
.ok_or_else(|| DerivePrivKeyError::Internal {
reason: format!("Account {} not found", req.account_id),
})?;

let mut path_to_address = account.account_derivation_path();
path_to_address.push(req.chain.unwrap_or(Bip44Chain::External).to_child_number());
path_to_address.push(ChildNumber::new(req.address_id, false).expect("non-hardened"));

let secret_key = coin
.priv_key_policy()
.hd_wallet_derived_priv_key_or_err(&path_to_address)
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error deriving secret key: {}", e),
})?;

let private = Private {
prefix: coin_fields.conf.wif_prefix,
secret: secret_key.into(),
compressed: true,
checksum_type: coin_fields.conf.checksum_type,
};

let key_pair = KeyPair::from_private(private)
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error creating key pair from secret: {}", e),
})?;

let pubkey_slice = key_pair.public_slice();
let pubkey: [u8; 33] = pubkey_slice
.try_into()
.map_err(|_| DerivePrivKeyError::Internal {
reason: "Error converting pubkey slice to array".to_string(),
})?;

let address = coin
.address_from_pubkey(&pubkey.into())
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error getting address from pubkey: {}", e),
})?;

let priv_key_wif = key_pair.private().to_string();
let pub_key_hex = hex::encode(pubkey);

let response = DerivedPrivKey {
coin: coin.ticker().to_string(),
address: address.to_string(),
derivation_path: path_to_address.to_string(),
priv_key: priv_key_wif,
pub_key: pub_key_hex,
};
Ok(response)
},
#[cfg(target_arch = "wasm32")]
PrivKeyPolicy::Metamask(_) => MmError::err(DerivePrivKeyError::HwWalletNotAllowed),
}
}

async fn derive_priv_key_for_eth_coin(
coin: impl MmCoin + CoinWithPrivKeyPolicy + CoinWithDerivationMethod,
req: &DerivePrivKeyReq,
) -> Result<DerivedPrivKey, MmError<DerivePrivKeyError>> {
match coin.priv_key_policy() {
PrivKeyPolicy::Iguana(_) => MmError::err(DerivePrivKeyError::CoinDoesntSupportDerivation {
ticker: coin.ticker().to_string(),
}),
PrivKeyPolicy::Trezor | PrivKeyPolicy::WalletConnect { .. } => {
MmError::err(DerivePrivKeyError::HwWalletNotAllowed)
},
PrivKeyPolicy::HDWallet { .. } => {
let hd_wallet = match coin.derivation_method() {
DerivationMethod::HDWallet(hd_wallet) => hd_wallet,
_ => {
return MmError::err(DerivePrivKeyError::CoinDoesntSupportDerivation {
ticker: coin.ticker().to_string(),
})
},
};

let account = hd_wallet
.get_account(req.account_id)
.await
.ok_or_else(|| DerivePrivKeyError::Internal {
reason: format!("Account {} not found", req.account_id),
})?;

let mut path_to_address = account.account_derivation_path();
path_to_address.push(req.chain.unwrap_or(Bip44Chain::External).to_child_number());
path_to_address.push(ChildNumber::new(req.address_id, false).expect("non-hardened"));

let secret_key = coin
.priv_key_policy()
.hd_wallet_derived_priv_key_or_err(&path_to_address)
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error deriving secret key: {}", e),
})?;

let private = Private {
prefix: 0, // ETH doesn't use WIF format
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only difference compare to UTXO implementation?

secret: secret_key.into(),
compressed: true,
checksum_type: Default::default(),
};

let key_pair = KeyPair::from_private(private)
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error creating key pair from secret: {}", e),
})?;

let pubkey_slice = key_pair.public_slice();
let pubkey: [u8; 33] = pubkey_slice
.try_into()
.map_err(|_| DerivePrivKeyError::Internal {
reason: "Error converting pubkey slice to array".to_string(),
})?;

let address = coin
.address_from_pubkey(&pubkey.into())
.map_err(|e| DerivePrivKeyError::Internal {
reason: format!("Error getting address from pubkey: {}", e),
})?;

let priv_key_hex = format!("0x{}", hex::encode(key_pair.private_bytes()));
let pub_key_hex = format!("0x{}", hex::encode(pubkey));

let response = DerivedPrivKey {
coin: coin.ticker().to_string(),
address: address.to_string(),
derivation_path: path_to_address.to_string(),
priv_key: priv_key_hex,
pub_key: pub_key_hex,
};
Ok(response)
},
#[cfg(target_arch = "wasm32")]
PrivKeyPolicy::Metamask(_) => MmError::err(DerivePrivKeyError::HwWalletNotAllowed),
}
}
2 changes: 2 additions & 0 deletions mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::rpc::lp_commands::one_inch::rpcs::{one_inch_v6_0_classic_swap_contrac
one_inch_v6_0_classic_swap_liquidity_sources_rpc,
one_inch_v6_0_classic_swap_quote_rpc,
one_inch_v6_0_classic_swap_tokens_rpc};
use crate::rpc::lp_commands::priv_key::derive_priv_key_rpc;
use crate::rpc::lp_commands::pubkey::*;
use crate::rpc::lp_commands::tokens::get_token_info;
use crate::rpc::lp_commands::tokens::{approve_token_rpc, get_token_allowance_rpc};
Expand Down Expand Up @@ -220,6 +221,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult<Re
"enable_tendermint_token" => handle_mmrpc(ctx, request, enable_token::<TendermintToken>).await,
"get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await,
"get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins_rpc).await,
"derive_priv_key" => handle_mmrpc(ctx, request, derive_priv_key_rpc).await,
"get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await,
"get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await,
"get_my_address" => handle_mmrpc(ctx, request, get_my_address).await,
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/src/rpc/lp_commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub(crate) mod db_id;
pub mod legacy;
pub(crate) mod lr_swap;
pub(crate) mod one_inch;
pub mod priv_key;
pub(crate) mod pubkey;
pub(crate) mod tokens;
pub(crate) mod trezor;
35 changes: 35 additions & 0 deletions mm2src/mm2_main/src/rpc/lp_commands/priv_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use coins::lp_coinfind_any;
use coins::priv_key::{derive_priv_key, DerivePrivKeyError, DerivePrivKeyReq, DerivedPrivKey};
use crypto::Bip44Chain;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
use serde::Deserialize;

#[derive(Deserialize)]
pub struct DerivePrivKeyRequest {
pub coin: String,
pub account_id: u32,
pub address_id: u32,
#[serde(default)]
pub chain: Option<Bip44Chain>,
}

pub async fn derive_priv_key_rpc(ctx: MmArc, req: DerivePrivKeyRequest) -> MmResult<DerivedPrivKey, DerivePrivKeyError> {
let coin = lp_coinfind_any(&ctx, &req.coin)
.await
.map_err(|e| DerivePrivKeyError::Internal {
reason: e.to_string(),
})?
.ok_or_else(|| DerivePrivKeyError::NoSuchCoin {
ticker: req.coin.clone(),
})?
.inner;

let req = DerivePrivKeyReq {
account_id: req.account_id,
chain: req.chain,
address_id: req.address_id,
};

derive_priv_key(coin, &req).await
}
Loading
Loading