From 947f9016b55b975be1baa75779c5609ab1c8f66b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 15:03:47 +0100 Subject: [PATCH 01/17] allow no seed kdf initialization --- mm2src/coins/eth.rs | 2 +- mm2src/coins/lp_coins.rs | 2 +- mm2src/crypto/src/crypto_ctx.rs | 35 +++++++-- mm2src/mm2_main/src/lp_healthcheck.rs | 3 +- mm2src/mm2_main/src/lp_native_dex.rs | 6 +- mm2src/mm2_main/src/lp_swap.rs | 6 +- mm2src/mm2_main/src/lp_wallet.rs | 15 ++-- mm2src/mm2_main/src/ordermatch_tests.rs | 4 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 + .../src/rpc/lp_commands/crypto_ctx.rs | 76 +++++++++++++++++++ mm2src/mm2_main/src/rpc/lp_commands/mod.rs | 1 + .../tests/docker_tests/docker_tests_inner.rs | 3 +- 12 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 12195caf19..a4e029a452 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -791,7 +791,7 @@ impl EthPrivKeyBuildPolicy { pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { let crypto_ctx = CryptoCtx::from_ctx(ctx)?; - match crypto_ctx.key_pair_policy() { + match &crypto_ctx.key_pair_policy() { KeyPairPolicy::Iguana => { // Use an internal private key as the coin secret. let priv_key = crypto_ctx.mm2_internal_privkey_secret(); diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 0bf7f34315..f7f00b8735 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4337,7 +4337,7 @@ impl PrivKeyBuildPolicy { pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { let crypto_ctx = CryptoCtx::from_ctx(ctx)?; - match crypto_ctx.key_pair_policy() { + match &crypto_ctx.key_pair_policy() { // Use an internal private key as the coin secret. KeyPairPolicy::Iguana => Ok(PrivKeyBuildPolicy::IguanaPrivKey( crypto_ctx.mm2_internal_privkey_secret(), diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index ffc83603a6..2a8d733260 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -91,7 +91,7 @@ impl From for MetamaskCtxInitError { fn from(value: MetamaskError) -> Self { MetamaskCtxInitError::MetamaskError(value) } } -pub struct CryptoCtx { +pub struct InternalKeyPair { /// secp256k1 key pair derived from either: /// * Iguana passphrase, /// cf. `key_pair_from_seed`; @@ -99,6 +99,10 @@ pub struct CryptoCtx { /// cf. [`GlobalHDAccountCtx::new`]. secp256k1_key_pair: KeyPair, key_pair_policy: KeyPairPolicy, +} + +pub struct CryptoCtx { + key_pair: RwLock>>, /// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`]. hw_ctx: RwLock>, #[cfg(target_arch = "wasm32")] @@ -106,6 +110,15 @@ pub struct CryptoCtx { } impl CryptoCtx { + pub fn new_uninitialized() -> Self { + Self { + key_pair: RwLock::new(InitializationState::NotInitialized), + hw_ctx: RwLock::new(InitializationState::NotInitialized), + #[cfg(target_arch = "wasm32")] + metamask_ctx: RwLock::new(InitializationState::NotInitialized), + } + } + pub fn is_init(ctx: &MmArc) -> MmResult { match CryptoCtx::from_ctx(ctx).split_mm() { Ok(_) => Ok(true), @@ -129,7 +142,10 @@ impl CryptoCtx { } #[inline] - pub fn key_pair_policy(&self) -> &KeyPairPolicy { &self.key_pair_policy } + fn key_pair(&self) -> Arc { self.key_pair.read().to_option().cloned().unwrap() } + + #[inline] + pub fn key_pair_policy(&self) -> KeyPairPolicy { self.key_pair().key_pair_policy.clone() } /// This is our public ID, allowing us to be different from other peers. /// This should also be our public key which we'd use for P2P message verification. @@ -153,7 +169,7 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_key_pair(&self) -> &KeyPair { &self.secp256k1_key_pair } + pub fn mm2_internal_key_pair(&self) -> KeyPair { self.key_pair().secp256k1_key_pair } /// Returns `secp256k1` public key. /// It can be used for mm2 internal purposes such as P2P peer ID. @@ -166,7 +182,7 @@ impl CryptoCtx { /// at the activated coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_pubkey(&self) -> PublicKey { *self.secp256k1_key_pair.public() } + pub fn mm2_internal_pubkey(&self) -> PublicKey { *self.mm2_internal_key_pair().public() } /// Returns `secp256k1` public key hex. /// It can be used for mm2 internal purposes such as P2P peer ID. @@ -191,7 +207,7 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_privkey_secret(&self) -> Secp256k1Secret { self.secp256k1_key_pair.private().secret } + pub fn mm2_internal_privkey_secret(&self) -> Secp256k1Secret { self.mm2_internal_key_pair().private().secret } /// Returns `secp256k1` private key as `[u8]` slice. /// It can be used for mm2 internal purposes such as signing P2P messages. @@ -205,7 +221,7 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.secp256k1_key_pair.private().secret.as_slice() } + pub fn mm2_internal_privkey_slice(&self) -> Vec { self.mm2_internal_privkey_secret().to_vec() } #[inline] pub fn hw_ctx(&self) -> Option { self.hw_ctx.read().to_option().cloned() } @@ -304,9 +320,12 @@ impl CryptoCtx { let rmd160 = secp256k1_key_pair.public().address_hash(); let shared_db_id = shared_db_id_from_seed(passphrase)?; - let crypto_ctx = CryptoCtx { - secp256k1_key_pair, + let keypair = InternalKeyPair { key_pair_policy, + secp256k1_key_pair, + }; + let crypto_ctx = CryptoCtx { + key_pair: RwLock::new(InitializationState::Ready(keypair.into())), hw_ctx: RwLock::new(InitializationState::NotInitialized), #[cfg(target_arch = "wasm32")] metamask_ctx: RwLock::new(InitializationState::NotInitialized), diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs index fb09652f09..68a5c12a0b 100644 --- a/mm2src/mm2_main/src/lp_healthcheck.rs +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -7,7 +7,6 @@ use derive_more::Display; use futures::channel::oneshot::{self, Receiver, Sender}; use lazy_static::lazy_static; use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::MmError; use mm2_err_handle::prelude::*; use mm2_libp2p::p2p_ctx::P2PContext; use mm2_libp2p::{decode_message, encode_message, pub_sub_topic, Libp2pPublic, PeerAddress, TopicPrefix}; @@ -349,7 +348,7 @@ mod tests { let ctx = mm_ctx_with_iguana(Some("dummy-value")); let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(&ctx).unwrap(); - let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); key.take() }; diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index ba364f73ae..65ab749962 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -438,6 +438,10 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { return Ok(()); } + lp_init_continue_impl(ctx).await +} + +pub async fn lp_init_continue_impl(ctx: MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] { fix_directories(&ctx)?; @@ -555,7 +559,7 @@ fn get_p2p_key(ctx: &MmArc, i_am_seed: bool) -> P2PResult<[u8; 32]> { // TODO: Use persistent peer ID regardless the node type. if i_am_seed { if let Ok(crypto_ctx) = CryptoCtx::from_ctx(ctx) { - let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); + let key = sha256(&crypto_ctx.mm2_internal_privkey_slice()); return Ok(key.take()); } } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index b15806c059..9dac413db5 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -247,7 +247,7 @@ pub fn p2p_keypair_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke Some(keypair) => (*keypair, Some(keypair.libp2p_peer_id())), None => { let crypto_ctx = CryptoCtx::from_ctx(ctx).expect("CryptoCtx must be initialized already"); - (*crypto_ctx.mm2_internal_key_pair(), None) + (crypto_ctx.mm2_internal_key_pair(), None) }, } } @@ -2155,7 +2155,7 @@ mod lp_swap_tests { }); let maker_ctx = MmCtxBuilder::default().with_conf(maker_ctx_conf).into_mm_arc(); - let maker_key_pair = *CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) + let maker_key_pair = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) .unwrap() .mm2_internal_key_pair(); @@ -2193,7 +2193,7 @@ mod lp_swap_tests { }); let taker_ctx = MmCtxBuilder::default().with_conf(taker_ctx_conf).into_mm_arc(); - let taker_key_pair = *CryptoCtx::init_with_iguana_passphrase(taker_ctx.clone(), &taker_passphrase) + let taker_key_pair = CryptoCtx::init_with_iguana_passphrase(taker_ctx.clone(), &taker_passphrase) .unwrap() .mm2_internal_key_pair(); diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index cc4f128278..5f797e0a74 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -147,12 +147,12 @@ async fn read_and_decrypt_passphrase_if_available( #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] -enum Passphrase { +pub(crate) enum Passphrase { Encrypted(EncryptedData), Decrypted(String), } -fn deserialize_wallet_config(ctx: &MmArc) -> WalletInitResult<(Option, Option)> { +pub(crate) fn deserialize_wallet_config(ctx: &MmArc) -> WalletInitResult<(Option, Option)> { let passphrase = deserialize_config_field::>(ctx, "passphrase")?; // New approach for passphrase, `wallet_name` is needed in the config to enable multi-wallet support. // In this case the passphrase will be generated if not provided. @@ -251,7 +251,7 @@ async fn process_wallet_with_name( } } -async fn process_passphrase_logic( +pub(crate) async fn process_passphrase_logic( ctx: &MmArc, wallet_name: Option<&str>, passphrase: Option, @@ -273,7 +273,7 @@ async fn process_passphrase_logic( } } -fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult<()> { +pub(crate) fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult<()> { // This defaults to false to maintain backward compatibility. match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { true => CryptoCtx::init_with_global_hd_account(ctx.clone(), passphrase)?, @@ -304,12 +304,13 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { let (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; - ctx.wallet_name - .set(wallet_name.clone()) - .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; let passphrase = process_passphrase_logic(ctx, wallet_name.as_deref(), passphrase).await?; + if let Some(passphrase) = passphrase { + ctx.wallet_name + .set(wallet_name.clone()) + .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; initialize_crypto_context(ctx, &passphrase)?; } diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 7f88f129b3..d9711f1481 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -986,7 +986,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); - let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); key.take() }; @@ -1742,7 +1742,7 @@ fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Recei let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); - let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); key.take() }; diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index f3286e5ab5..b310bbd139 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -1,3 +1,4 @@ +use super::lp_commands::crypto_ctx::init_crypto_ctx; use super::streaming_activations; use super::{DispatcherError, DispatcherResult, PUBLIC_METHODS}; use crate::lp_healthcheck::peer_connection_healthcheck_rpc; @@ -223,6 +224,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_shared_db_id).await, "get_token_info" => handle_mmrpc(ctx, request, get_token_info).await, "get_wallet_names" => handle_mmrpc(ctx, request, get_wallet_names_rpc).await, + "init_crypto_ctx" => handle_mmrpc(ctx, request, init_crypto_ctx).await, "max_maker_vol" => handle_mmrpc(ctx, request, max_maker_vol).await, "my_recent_swaps" => handle_mmrpc(ctx, request, my_recent_swaps_rpc).await, "my_swap_status" => handle_mmrpc(ctx, request, my_swap_status_rpc).await, diff --git a/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs b/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs new file mode 100644 index 0000000000..309b670c4f --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs @@ -0,0 +1,76 @@ +use common::HttpStatusCode; +use crypto::CryptoCtxError; +use enum_derives::EnumFromStringify; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; + +use crate::lp_native_dex::{lp_init_continue_impl, MmInitError}; +use crate::lp_wallet::{initialize_crypto_context, process_passphrase_logic, Passphrase, WalletInitError}; + +#[derive(Serialize, Display, SerializeErrorType, EnumFromStringify)] +#[serde(tag = "error_type", content = "error_data")] +pub enum CryptoCtxRpcError { + #[display(fmt = "Unable to initialized crypto ctx")] + InitializationFailed, + #[display(fmt = "crypto ctx is already initialized")] + AlreadyInitialized, + InternalError(String), + #[from_stringify("MmInitError")] + MmInitError(String), + #[from_stringify("WalletInitError")] + WalletInitError(String), +} + +impl From for CryptoCtxRpcError { + fn from(e: CryptoCtxError) -> Self { + match e { + CryptoCtxError::NotInitialized => Self::InitializationFailed, + CryptoCtxError::Internal(_) => Self::InternalError(e.to_string()), + } + } +} + +impl HttpStatusCode for CryptoCtxRpcError { + fn status_code(&self) -> StatusCode { + match self { + Self::InitializationFailed | Self::AlreadyInitialized | Self::WalletInitError(_) | Self::MmInitError(_) => { + StatusCode::BAD_REQUEST + }, + Self::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[derive(Deserialize)] +pub struct CryptoCtxInitRequest { + passphrase: String, + wallet_name: Option, +} + +pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { + common::log::info!("Initializing KDF with passphrase"); + if let Some(true) = ctx.initialized.get() { + return MmError::err(CryptoCtxRpcError::AlreadyInitialized); + }; + + let CryptoCtxInitRequest { + wallet_name, + passphrase, + } = req; + let passphrase: Passphrase = Passphrase::Decrypted(passphrase); + + ctx.wallet_name + .set(wallet_name.clone()) + .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; + + let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)).await?; + + if let Some(passphrase) = passphrase { + initialize_crypto_context(&ctx, &passphrase)?; + } + + lp_init_continue_impl(ctx).await?; + + Ok(()) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs index e61d5aead8..7896c99b9e 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod crypto_ctx; pub(crate) mod db_id; pub mod legacy; pub(crate) mod one_inch; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index ff7e6415fb..5db3fa3cde 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -4043,10 +4043,9 @@ fn test_withdraw_and_send_eth_erc20() { fn test_withdraw_and_send_hd_eth_erc20() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - let KeyPairPolicy::GlobalHDAccount(hd_acc) = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE) + let KeyPairPolicy::GlobalHDAccount(ref hd_acc) = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE) .unwrap() .key_pair_policy() - .clone() else { panic!("Expected 'KeyPairPolicy::GlobalHDAccount'"); }; From d8677f433c7eb116d71d023c597107a57227f664 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 15:59:22 +0100 Subject: [PATCH 02/17] initializing crypto ctx with empty state --- mm2src/crypto/src/crypto_ctx.rs | 49 ++++++++++++++++------------ mm2src/mm2_main/src/lp_native_dex.rs | 2 +- mm2src/mm2_main/src/lp_wallet.rs | 2 ++ 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 2a8d733260..8ffdac811f 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -110,13 +110,21 @@ pub struct CryptoCtx { } impl CryptoCtx { - pub fn new_uninitialized() -> Self { - Self { + pub fn new_uninitialized(ctx: &MmArc) -> Arc { + let selfi = Self { key_pair: RwLock::new(InitializationState::NotInitialized), hw_ctx: RwLock::new(InitializationState::NotInitialized), #[cfg(target_arch = "wasm32")] metamask_ctx: RwLock::new(InitializationState::NotInitialized), - } + }; + + let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); + let result = Arc::new(selfi); + *ctx_field = Some(result.clone()); + + drop(ctx_field); + + result } pub fn is_init(ctx: &MmArc) -> MmResult { @@ -127,6 +135,14 @@ impl CryptoCtx { } } + pub fn is_crypto_keypair_init(ctx: &MmArc) -> MmResult { + match CryptoCtx::from_ctx(ctx).split_mm() { + Ok(c) => Ok(c.key_pair.read().to_option().is_some()), + Err((CryptoCtxError::NotInitialized, _trace)) => Ok(false), + Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), + } + } + pub fn from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { let ctx_field = ctx .crypto_ctx @@ -304,18 +320,15 @@ impl CryptoCtx { passphrase: &str, policy_builder: KeyPairPolicyBuilder, ) -> CryptoInitResult> { - let mut ctx_field = ctx - .crypto_ctx - .lock() - .map_to_mm(|poison| CryptoInitError::Internal(poison.to_string()))?; - if ctx_field.is_some() { - return MmError::err(CryptoInitError::InitializedAlready); - } - if passphrase.is_empty() { return MmError::err(CryptoInitError::EmptyPassphrase); } + let ctx_field = CryptoCtx::from_ctx(&ctx).expect("Should already be initialized"); + if ctx_field.key_pair.read().to_option().is_some() { + return MmError::err(CryptoInitError::InitializedAlready); + } + let (secp256k1_key_pair, key_pair_policy) = policy_builder.build(passphrase)?; let rmd160 = secp256k1_key_pair.public().address_hash(); let shared_db_id = shared_db_id_from_seed(passphrase)?; @@ -324,16 +337,9 @@ impl CryptoCtx { key_pair_policy, secp256k1_key_pair, }; - let crypto_ctx = CryptoCtx { - key_pair: RwLock::new(InitializationState::Ready(keypair.into())), - hw_ctx: RwLock::new(InitializationState::NotInitialized), - #[cfg(target_arch = "wasm32")] - metamask_ctx: RwLock::new(InitializationState::NotInitialized), - }; - let result = Arc::new(crypto_ctx); - *ctx_field = Some(result.clone()); - drop(ctx_field); + let keypair = InitializationState::Ready(keypair.into()); + *ctx_field.key_pair.write() = keypair; ctx.rmd160 .set(rmd160) @@ -344,7 +350,8 @@ impl CryptoCtx { info!("Public key hash: {rmd160}"); info!("Shared Database ID: {shared_db_id}"); - Ok(result) + + Ok(ctx_field) } } diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 65ab749962..6079ce4729 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -434,7 +434,7 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { init_ordermatch_context(&ctx)?; init_p2p(ctx.clone()).await?; - if !CryptoCtx::is_init(&ctx)? { + if !CryptoCtx::is_crypto_keypair_init(&ctx)? { return Ok(()); } diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 5f797e0a74..84115a24ad 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -314,6 +314,8 @@ pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResul initialize_crypto_context(ctx, &passphrase)?; } + CryptoCtx::new_uninitialized(ctx); + Ok(()) } From d4ecc11912ba115e5c9f9a6503ab27659be2804b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 16:19:59 +0100 Subject: [PATCH 03/17] fix deadlock --- mm2src/crypto/src/crypto_ctx.rs | 29 +++++++++++------------------ mm2src/mm2_main/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 8ffdac811f..51f232ed7b 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -127,34 +127,27 @@ impl CryptoCtx { result } - pub fn is_init(ctx: &MmArc) -> MmResult { - match CryptoCtx::from_ctx(ctx).split_mm() { - Ok(_) => Ok(true), - Err((CryptoCtxError::NotInitialized, _trace)) => Ok(false), - Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), - } - } - pub fn is_crypto_keypair_init(ctx: &MmArc) -> MmResult { match CryptoCtx::from_ctx(ctx).split_mm() { Ok(c) => Ok(c.key_pair.read().to_option().is_some()), - Err((CryptoCtxError::NotInitialized, _trace)) => Ok(false), Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), } } pub fn from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { - let ctx_field = ctx + if let Some(ctx) = ctx .crypto_ctx .lock() - .map_to_mm(|poison| CryptoCtxError::Internal(poison.to_string()))?; - let ctx = match ctx_field.deref() { - Some(ctx) => ctx, - None => return MmError::err(CryptoCtxError::NotInitialized), - }; - ctx.clone() - .downcast() - .map_err(|_| MmError::new(CryptoCtxError::Internal("Error casting the context field".to_owned()))) + .map_to_mm(|poison| CryptoCtxError::Internal(poison.to_string()))? + .deref() + { + return ctx + .clone() + .downcast() + .map_err(|_| MmError::new(CryptoCtxError::Internal("Error casting the context field".to_owned()))); + } + + Ok(Self::new_uninitialized(ctx)) } #[inline] diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 28291cb210..65c34cf8c6 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -25,7 +25,7 @@ enable-sia = ["coins/enable-sia", "coins_activation/enable-sia"] sepolia-maker-swap-v2-tests = [] sepolia-taker-swap-v2-tests = [] test-ext-api = ["trading_api/test-ext-api"] -new-db-arch = [] # A temporary feature to integrate the new db architecture incrementally +new-db-arch = ["mm2_core/new-db-arch"] # A temporary feature to integrate the new db architecture incrementally [dependencies] async-std = { version = "1.5", features = ["unstable"] } From 42e854f2246c9304218ce35d86e3bb9cc49f80a7 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 18:28:15 +0100 Subject: [PATCH 04/17] organizations and critical fix --- mm2src/mm2_main/src/lp_native_dex.rs | 10 +-- mm2src/mm2_main/src/lp_wallet.rs | 76 ++++++++++++++++++- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 3 +- .../src/rpc/lp_commands/crypto_ctx.rs | 76 ------------------- mm2src/mm2_main/src/rpc/lp_commands/mod.rs | 1 - 5 files changed, 79 insertions(+), 87 deletions(-) delete mode 100644 mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 6079ce4729..25bd3e71c3 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -431,17 +431,17 @@ fn init_wasm_event_streaming(ctx: &MmArc) { } pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { - init_ordermatch_context(&ctx)?; - init_p2p(ctx.clone()).await?; - if !CryptoCtx::is_crypto_keypair_init(&ctx)? { return Ok(()); } - lp_init_continue_impl(ctx).await + init_ordermatch_context(&ctx)?; + init_p2p(ctx.clone()).await?; + + initialize_crypto_context_services(ctx).await } -pub async fn lp_init_continue_impl(ctx: MmArc) -> MmInitResult<()> { +pub async fn initialize_crypto_context_services(ctx: MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] { fix_directories(&ctx)?; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 84115a24ad..c385067a18 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -1,6 +1,6 @@ use common::HttpStatusCode; -use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, - MnemonicError}; +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoCtxError, CryptoInitError, + EncryptedData, MnemonicError}; use enum_derives::EnumFromStringify; use http::StatusCode; use itertools::Itertools; @@ -9,6 +9,8 @@ use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; +use crate::lp_native_dex::{initialize_crypto_context_services, MmInitError}; + cfg_wasm32! { use crate::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; @@ -311,7 +313,7 @@ pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResul ctx.wallet_name .set(wallet_name.clone()) .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; - initialize_crypto_context(ctx, &passphrase)?; + return initialize_crypto_context(ctx, &passphrase); } CryptoCtx::new_uninitialized(ctx); @@ -563,3 +565,71 @@ pub async fn change_mnemonic_password(ctx: MmArc, req: ChangeMnemonicPasswordReq Ok(()) } + +#[derive(Serialize, Display, SerializeErrorType, EnumFromStringify)] +#[serde(tag = "error_type", content = "error_data")] +pub enum CryptoCtxRpcError { + #[display(fmt = "Unable to initialized crypto ctx")] + InitializationFailed, + #[display(fmt = "crypto ctx is already initialized")] + AlreadyInitialized, + #[from_stringify("serde_json::error::Error")] + InternalError(String), + #[from_stringify("MmInitError")] + MmInitError(String), + #[from_stringify("WalletInitError")] + WalletInitError(String), +} + +impl From for CryptoCtxRpcError { + fn from(e: CryptoCtxError) -> Self { + match e { + CryptoCtxError::NotInitialized => Self::InitializationFailed, + CryptoCtxError::Internal(_) => Self::InternalError(e.to_string()), + } + } +} + +impl HttpStatusCode for CryptoCtxRpcError { + fn status_code(&self) -> StatusCode { + match self { + Self::InitializationFailed | Self::AlreadyInitialized | Self::WalletInitError(_) | Self::MmInitError(_) => { + StatusCode::BAD_REQUEST + }, + Self::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[derive(Deserialize)] +pub struct CryptoCtxInitRequest { + passphrase: String, + wallet_name: Option, +} + +pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { + common::log::info!("Initializing KDF with passphrase"); + if let Some(true) = ctx.initialized.get() { + return MmError::err(CryptoCtxRpcError::AlreadyInitialized); + }; + + let CryptoCtxInitRequest { + wallet_name, + passphrase, + } = req; + let passphrase: Passphrase = serde_json::from_str(&passphrase)?; + + ctx.wallet_name + .set(wallet_name.clone()) + .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; + + let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)).await?; + + if let Some(passphrase) = passphrase { + initialize_crypto_context(&ctx, &passphrase)?; + } + + initialize_crypto_context_services(ctx).await?; + + Ok(()) +} diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index b310bbd139..d6045ecfd6 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -1,4 +1,3 @@ -use super::lp_commands::crypto_ctx::init_crypto_ctx; use super::streaming_activations; use super::{DispatcherError, DispatcherResult, PUBLIC_METHODS}; use crate::lp_healthcheck::peer_connection_healthcheck_rpc; @@ -11,7 +10,7 @@ use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, s stop_version_stat_collection, update_version_stat_collection}; use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; -use crate::lp_wallet::{change_mnemonic_password, get_mnemonic_rpc, get_wallet_names_rpc}; +use crate::lp_wallet::{change_mnemonic_password, get_mnemonic_rpc, get_wallet_names_rpc, init_crypto_ctx}; use crate::rpc::lp_commands::db_id::get_shared_db_id; use crate::rpc::lp_commands::one_inch::rpcs::{one_inch_v6_0_classic_swap_contract_rpc, one_inch_v6_0_classic_swap_create_rpc, diff --git a/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs b/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs deleted file mode 100644 index 309b670c4f..0000000000 --- a/mm2src/mm2_main/src/rpc/lp_commands/crypto_ctx.rs +++ /dev/null @@ -1,76 +0,0 @@ -use common::HttpStatusCode; -use crypto::CryptoCtxError; -use enum_derives::EnumFromStringify; -use http::StatusCode; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; - -use crate::lp_native_dex::{lp_init_continue_impl, MmInitError}; -use crate::lp_wallet::{initialize_crypto_context, process_passphrase_logic, Passphrase, WalletInitError}; - -#[derive(Serialize, Display, SerializeErrorType, EnumFromStringify)] -#[serde(tag = "error_type", content = "error_data")] -pub enum CryptoCtxRpcError { - #[display(fmt = "Unable to initialized crypto ctx")] - InitializationFailed, - #[display(fmt = "crypto ctx is already initialized")] - AlreadyInitialized, - InternalError(String), - #[from_stringify("MmInitError")] - MmInitError(String), - #[from_stringify("WalletInitError")] - WalletInitError(String), -} - -impl From for CryptoCtxRpcError { - fn from(e: CryptoCtxError) -> Self { - match e { - CryptoCtxError::NotInitialized => Self::InitializationFailed, - CryptoCtxError::Internal(_) => Self::InternalError(e.to_string()), - } - } -} - -impl HttpStatusCode for CryptoCtxRpcError { - fn status_code(&self) -> StatusCode { - match self { - Self::InitializationFailed | Self::AlreadyInitialized | Self::WalletInitError(_) | Self::MmInitError(_) => { - StatusCode::BAD_REQUEST - }, - Self::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -#[derive(Deserialize)] -pub struct CryptoCtxInitRequest { - passphrase: String, - wallet_name: Option, -} - -pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { - common::log::info!("Initializing KDF with passphrase"); - if let Some(true) = ctx.initialized.get() { - return MmError::err(CryptoCtxRpcError::AlreadyInitialized); - }; - - let CryptoCtxInitRequest { - wallet_name, - passphrase, - } = req; - let passphrase: Passphrase = Passphrase::Decrypted(passphrase); - - ctx.wallet_name - .set(wallet_name.clone()) - .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; - - let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)).await?; - - if let Some(passphrase) = passphrase { - initialize_crypto_context(&ctx, &passphrase)?; - } - - lp_init_continue_impl(ctx).await?; - - Ok(()) -} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs index 7896c99b9e..e61d5aead8 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod crypto_ctx; pub(crate) mod db_id; pub mod legacy; pub(crate) mod one_inch; From 8718bc4d9aab0a734bc58cca0cddf3019697e0e0 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 22:11:37 +0100 Subject: [PATCH 05/17] refactor CryptoCtx --- mm2src/coins/eth.rs | 9 +- mm2src/coins/lp_coins.rs | 8 +- mm2src/crypto/src/crypto_ctx.rs | 127 +++++++++--------- mm2src/mm2_main/src/lp_healthcheck.rs | 7 +- mm2src/mm2_main/src/lp_native_dex.rs | 17 +-- mm2src/mm2_main/src/lp_ordermatch.rs | 47 ++++++- mm2src/mm2_main/src/lp_swap.rs | 25 +++- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 6 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 10 +- mm2src/mm2_main/src/lp_wallet.rs | 40 +++--- mm2src/mm2_main/src/ordermatch_tests.rs | 18 ++- mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs | 6 +- .../tests/docker_tests/docker_tests_inner.rs | 7 +- 13 files changed, 208 insertions(+), 119 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a4e029a452..557f6cc061 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -789,12 +789,15 @@ pub enum EthPrivKeyBuildPolicy { impl EthPrivKeyBuildPolicy { /// Detects the `EthPrivKeyBuildPolicy` with which the given `MmArc` is initialized. pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { - let crypto_ctx = CryptoCtx::from_ctx(ctx)?; + let crypto_ctx = CryptoCtx::from_ctx(ctx)? + .internal_keypair() + .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; - match &crypto_ctx.key_pair_policy() { + match crypto_ctx.key_pair_policy() { KeyPairPolicy::Iguana => { // Use an internal private key as the coin secret. - let priv_key = crypto_ctx.mm2_internal_privkey_secret(); + let priv_key = *crypto_ctx.mm2_internal_privkey_secret(); + Ok(EthPrivKeyBuildPolicy::IguanaPrivKey(priv_key)) }, KeyPairPolicy::GlobalHDAccount(global_hd) => Ok(EthPrivKeyBuildPolicy::GlobalHDAccount(global_hd.clone())), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f7f00b8735..7aaeae10f2 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4335,12 +4335,14 @@ pub enum PrivKeyBuildPolicy { impl PrivKeyBuildPolicy { /// Detects the `PrivKeyBuildPolicy` with which the given `MmArc` is initialized. pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { - let crypto_ctx = CryptoCtx::from_ctx(ctx)?; + let crypto_ctx = CryptoCtx::from_ctx(ctx)? + .internal_keypair() + .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; - match &crypto_ctx.key_pair_policy() { + match crypto_ctx.key_pair_policy() { // Use an internal private key as the coin secret. KeyPairPolicy::Iguana => Ok(PrivKeyBuildPolicy::IguanaPrivKey( - crypto_ctx.mm2_internal_privkey_secret(), + *crypto_ctx.mm2_internal_privkey_secret(), )), KeyPairPolicy::GlobalHDAccount(global_hd) => Ok(PrivKeyBuildPolicy::GlobalHDAccount(global_hd.clone())), } diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 51f232ed7b..9e8616a7e7 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -91,7 +91,7 @@ impl From for MetamaskCtxInitError { fn from(value: MetamaskError) -> Self { MetamaskCtxInitError::MetamaskError(value) } } -pub struct InternalKeyPair { +pub struct KeyPairCtx { /// secp256k1 key pair derived from either: /// * Iguana passphrase, /// cf. `key_pair_from_seed`; @@ -101,60 +101,9 @@ pub struct InternalKeyPair { key_pair_policy: KeyPairPolicy, } -pub struct CryptoCtx { - key_pair: RwLock>>, - /// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`]. - hw_ctx: RwLock>, - #[cfg(target_arch = "wasm32")] - metamask_ctx: RwLock>, -} - -impl CryptoCtx { - pub fn new_uninitialized(ctx: &MmArc) -> Arc { - let selfi = Self { - key_pair: RwLock::new(InitializationState::NotInitialized), - hw_ctx: RwLock::new(InitializationState::NotInitialized), - #[cfg(target_arch = "wasm32")] - metamask_ctx: RwLock::new(InitializationState::NotInitialized), - }; - - let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); - let result = Arc::new(selfi); - *ctx_field = Some(result.clone()); - - drop(ctx_field); - - result - } - - pub fn is_crypto_keypair_init(ctx: &MmArc) -> MmResult { - match CryptoCtx::from_ctx(ctx).split_mm() { - Ok(c) => Ok(c.key_pair.read().to_option().is_some()), - Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), - } - } - - pub fn from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { - if let Some(ctx) = ctx - .crypto_ctx - .lock() - .map_to_mm(|poison| CryptoCtxError::Internal(poison.to_string()))? - .deref() - { - return ctx - .clone() - .downcast() - .map_err(|_| MmError::new(CryptoCtxError::Internal("Error casting the context field".to_owned()))); - } - - Ok(Self::new_uninitialized(ctx)) - } - +impl KeyPairCtx { #[inline] - fn key_pair(&self) -> Arc { self.key_pair.read().to_option().cloned().unwrap() } - - #[inline] - pub fn key_pair_policy(&self) -> KeyPairPolicy { self.key_pair().key_pair_policy.clone() } + pub fn key_pair_policy(&self) -> &KeyPairPolicy { &self.key_pair_policy } /// This is our public ID, allowing us to be different from other peers. /// This should also be our public key which we'd use for P2P message verification. @@ -162,6 +111,7 @@ impl CryptoCtx { pub fn mm2_internal_public_id(&self) -> bits256 { // Compressed public key is going to be 33 bytes. let public = self.mm2_internal_pubkey(); + // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. bits256 { bytes: *array_ref!(public, 1, 32), @@ -178,7 +128,7 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_key_pair(&self) -> KeyPair { self.key_pair().secp256k1_key_pair } + pub fn mm2_internal_key_pair(&self) -> &KeyPair { &self.secp256k1_key_pair } /// Returns `secp256k1` public key. /// It can be used for mm2 internal purposes such as P2P peer ID. @@ -191,7 +141,7 @@ impl CryptoCtx { /// at the activated coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_pubkey(&self) -> PublicKey { *self.mm2_internal_key_pair().public() } + pub fn mm2_internal_pubkey(&self) -> &PublicKey { self.mm2_internal_key_pair().public() } /// Returns `secp256k1` public key hex. /// It can be used for mm2 internal purposes such as P2P peer ID. @@ -204,7 +154,7 @@ impl CryptoCtx { /// at the activated coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_pubkey_hex(&self) -> String { hex::encode(&*self.mm2_internal_pubkey()) } + pub fn mm2_internal_pubkey_hex(&self) -> String { hex::encode(self.mm2_internal_pubkey().deref()) } /// Returns `secp256k1` private key as `H256` bytes. /// It can be used for mm2 internal purposes such as signing P2P messages. @@ -216,7 +166,7 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_privkey_secret(&self) -> Secp256k1Secret { self.mm2_internal_key_pair().private().secret } + pub fn mm2_internal_privkey_secret(&self) -> &Secp256k1Secret { &self.mm2_internal_key_pair().private().secret } /// Returns `secp256k1` private key as `[u8]` slice. /// It can be used for mm2 internal purposes such as signing P2P messages. @@ -230,7 +180,60 @@ impl CryptoCtx { /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. #[inline] - pub fn mm2_internal_privkey_slice(&self) -> Vec { self.mm2_internal_privkey_secret().to_vec() } + pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.mm2_internal_privkey_secret().as_slice() } +} + +pub struct CryptoCtx { + keypair_ctx: RwLock>>, + /// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`]. + hw_ctx: RwLock>, + #[cfg(target_arch = "wasm32")] + metamask_ctx: RwLock>, +} + +impl CryptoCtx { + pub fn new_uninitialized(ctx: &MmArc) -> Arc { + let selfi = Self { + keypair_ctx: RwLock::new(InitializationState::NotInitialized), + hw_ctx: RwLock::new(InitializationState::NotInitialized), + #[cfg(target_arch = "wasm32")] + metamask_ctx: RwLock::new(InitializationState::NotInitialized), + }; + + let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); + let result = Arc::new(selfi); + *ctx_field = Some(result.clone()); + + drop(ctx_field); + + result + } + + pub fn is_crypto_keypair_ctx_init(ctx: &MmArc) -> MmResult { + match CryptoCtx::from_ctx(ctx).split_mm() { + Ok(c) => Ok(c.keypair_ctx.read().to_option().is_some()), + Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), + } + } + + pub fn from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { + if let Some(ctx) = ctx + .crypto_ctx + .lock() + .map_to_mm(|poison| CryptoCtxError::Internal(poison.to_string()))? + .deref() + { + return ctx + .clone() + .downcast() + .map_err(|_| MmError::new(CryptoCtxError::Internal("Error casting the context field".to_owned()))); + } + + Ok(Self::new_uninitialized(ctx)) + } + + #[inline] + pub fn internal_keypair(&self) -> Option> { self.keypair_ctx.read().to_option().cloned() } #[inline] pub fn hw_ctx(&self) -> Option { self.hw_ctx.read().to_option().cloned() } @@ -318,7 +321,7 @@ impl CryptoCtx { } let ctx_field = CryptoCtx::from_ctx(&ctx).expect("Should already be initialized"); - if ctx_field.key_pair.read().to_option().is_some() { + if ctx_field.keypair_ctx.read().to_option().is_some() { return MmError::err(CryptoInitError::InitializedAlready); } @@ -326,13 +329,13 @@ impl CryptoCtx { let rmd160 = secp256k1_key_pair.public().address_hash(); let shared_db_id = shared_db_id_from_seed(passphrase)?; - let keypair = InternalKeyPair { + let keypair = KeyPairCtx { key_pair_policy, secp256k1_key_pair, }; let keypair = InitializationState::Ready(keypair.into()); - *ctx_field.key_pair.write() = keypair; + *ctx_field.keypair_ctx.write() = keypair; ctx.rmd160 .set(rmd160) diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs index 68a5c12a0b..40cca5352c 100644 --- a/mm2src/mm2_main/src/lp_healthcheck.rs +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -347,8 +347,11 @@ mod tests { fn ctx() -> MmArc { let ctx = mm_ctx_with_iguana(Some("dummy-value")); let p2p_key = { - let crypto_ctx = CryptoCtx::from_ctx(&ctx).unwrap(); - let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); + let crypto_ctx = CryptoCtx::from_ctx(&ctx) + .unwrap() + .internal_keypair() + .expect("CryptoCtx must be initialized with a passphrase"); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 25bd3e71c3..cc853c9be7 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -431,17 +431,17 @@ fn init_wasm_event_streaming(ctx: &MmArc) { } pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { - if !CryptoCtx::is_crypto_keypair_init(&ctx)? { - return Ok(()); - } - init_ordermatch_context(&ctx)?; init_p2p(ctx.clone()).await?; - initialize_crypto_context_services(ctx).await + if !CryptoCtx::is_crypto_keypair_ctx_init(&ctx)? { + return Ok(()); + } + + init_crypto_keypair_ctx_services(ctx).await } -pub async fn initialize_crypto_context_services(ctx: MmArc) -> MmInitResult<()> { +pub async fn init_crypto_keypair_ctx_services(ctx: MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] { fix_directories(&ctx)?; @@ -557,9 +557,10 @@ async fn kick_start(ctx: MmArc) -> MmInitResult<()> { fn get_p2p_key(ctx: &MmArc, i_am_seed: bool) -> P2PResult<[u8; 32]> { // TODO: Use persistent peer ID regardless the node type. + let crypto_ctx = CryptoCtx::from_ctx(ctx).map(|c| c.internal_keypair()); if i_am_seed { - if let Ok(crypto_ctx) = CryptoCtx::from_ctx(ctx) { - let key = sha256(&crypto_ctx.mm2_internal_privkey_slice()); + if let Ok(Some(crypto_ctx)) = crypto_ctx { + let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); return Ok(key.take()); } } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 18475ce191..2669d7da34 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -523,7 +523,7 @@ where F: Fn(String) -> E, { match CryptoCtx::from_ctx(ctx).split_mm() { - Ok(crypto_ctx) => Ok(Some(CryptoCtx::mm2_internal_pubkey_hex(crypto_ctx.as_ref()))), + Ok(crypto_ctx) => Ok(crypto_ctx.internal_keypair().map(|k| k.mm2_internal_pubkey_hex())), Err((CryptoCtxError::NotInitialized, _)) => Ok(None), Err((CryptoCtxError::Internal(error), trace)) => MmError::err_with_trace(err_construct(error), trace), } @@ -2333,6 +2333,8 @@ pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { // broadcast_maker_orders_keep_alive_loop is spawned only if CryptoCtx is initialized. let persistent_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") + .internal_keypair() + .expect("CryptoCtx not initialized with a seedphrase") .mm2_internal_pubkey_hex(); while !ctx.is_stopping() { @@ -3043,7 +3045,10 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO let taker_amount = maker_match.reserved.get_rel_amount().clone(); // lp_connect_start_bob is called only from process_taker_connect, which returns if CryptoCtx is not initialized - let crypto_ctx = CryptoCtx::from_ctx(&ctx).expect("'CryptoCtx' must be initialized already"); + let crypto_ctx = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .internal_keypair() + .expect("'CryptoCtx' must be initialized with a passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3272,7 +3277,10 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat }; // lp_connected_alice is called only from process_maker_connected, which returns if CryptoCtx is not initialized - let crypto_ctx = CryptoCtx::from_ctx(&ctx).expect("'CryptoCtx' must be initialized already"); + let crypto_ctx = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .internal_keypair() + .expect("'CryptoCtx' must be initialized with passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3482,6 +3490,8 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { // lp_ordermatch_loop is spawned only if CryptoCtx is initialized let my_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") + .internal_keypair() + .expect("CryptoCtx not initialized with a seedphrase") .mm2_internal_pubkey_hex(); let maker_order_timeout = ctx.conf["maker_order_timeout"].as_u64().unwrap_or(MAKER_ORDER_TIMEOUT); @@ -3753,6 +3763,8 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: // Taker order existence is checked previously - it can't be created if CryptoCtx is not initialized let our_public_id = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") + .internal_keypair() + .expect("'CryptoCtx' must be initialized passphrase") .mm2_internal_public_id(); if our_public_id.bytes == from_pubkey.0 { log::warn!("Skip maker reserved from our pubkey"); @@ -3858,7 +3870,13 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); let our_public_id = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => ctx.mm2_internal_public_id(), + Ok(ctx) => { + let Some(keypair) = ctx.internal_keypair() else { + return; + }; + + keypair.mm2_internal_public_id() + }, Err(_) => return, }; @@ -3912,7 +3930,13 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: TakerRequest) { let our_public_id: H256Json = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => ctx.mm2_internal_public_id().bytes.into(), + Ok(ctx) => { + let Some(keypair) = ctx.internal_keypair() else { + return; + }; + + keypair.mm2_internal_public_id().bytes.into() + }, Err(_) => return, }; @@ -4025,7 +4049,13 @@ async fn process_taker_connect(ctx: MmArc, sender_pubkey: PublicKey, connect_msg let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); let our_public_id = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => ctx.mm2_internal_public_id(), + Ok(ctx) => { + let Some(keypair) = ctx.internal_keypair() else { + return; + }; + + keypair.mm2_internal_public_id() + }, Err(_) => return, }; @@ -4218,7 +4248,10 @@ pub async fn lp_auto_buy( }; let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(ctx)); let mut my_taker_orders = ordermatch_ctx.my_taker_orders.lock().await; - let our_public_id = try_s!(CryptoCtx::from_ctx(&ctx)).mm2_internal_public_id(); + let our_public_id = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) + .internal_keypair() + .ok_or("CryptoCtx must be initialized with a passphrase")) + .mm2_internal_public_id(); let rel_volume = &input.volume * &input.price; let conf_settings = OrderConfirmationsSettings { base_confs: input.base_confs.unwrap_or_else(|| base_coin.required_confirmations()), diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 9dac413db5..fcf4f1bfef 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -246,8 +246,11 @@ pub fn p2p_keypair_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke match p2p_privkey { Some(keypair) => (*keypair, Some(keypair.libp2p_peer_id())), None => { - let crypto_ctx = CryptoCtx::from_ctx(ctx).expect("CryptoCtx must be initialized already"); - (crypto_ctx.mm2_internal_key_pair(), None) + let crypto_ctx = CryptoCtx::from_ctx(ctx) + .expect("CryptoCtx must be initialized already") + .internal_keypair() + .expect("CryptoCtx must be initialized with a passphrase"); + (*crypto_ctx.mm2_internal_key_pair(), None) }, } } @@ -262,7 +265,11 @@ pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke match p2p_privkey { Some(keypair) => (keypair.private_bytes(), Some(keypair.libp2p_peer_id())), None => { - let crypto_ctx = CryptoCtx::from_ctx(ctx).expect("CryptoCtx must be initialized already"); + let crypto_ctx = CryptoCtx::from_ctx(ctx) + .expect("CryptoCtx must be initialized already") + .internal_keypair() + .expect("'CryptoCtx' must be initialized with passphrase"); + (crypto_ctx.mm2_internal_privkey_secret().take(), None) }, } @@ -2155,9 +2162,11 @@ mod lp_swap_tests { }); let maker_ctx = MmCtxBuilder::default().with_conf(maker_ctx_conf).into_mm_arc(); - let maker_key_pair = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) + let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) .unwrap() - .mm2_internal_key_pair(); + .internal_keypair() + .unwrap(); + let maker_key_pair = crypto_ctx.mm2_internal_key_pair(); fix_directories(&maker_ctx).unwrap(); block_on(init_p2p(maker_ctx.clone())).unwrap(); @@ -2193,9 +2202,11 @@ mod lp_swap_tests { }); let taker_ctx = MmCtxBuilder::default().with_conf(taker_ctx_conf).into_mm_arc(); - let taker_key_pair = CryptoCtx::init_with_iguana_passphrase(taker_ctx.clone(), &taker_passphrase) + let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &taker_passphrase) .unwrap() - .mm2_internal_key_pair(); + .internal_keypair() + .unwrap(); + let taker_key_pair = crypto_ctx.mm2_internal_key_pair(); fix_directories(&taker_ctx).unwrap(); block_on(init_p2p(taker_ctx.clone())).unwrap(); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index c7e5a43329..39a4f11528 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -26,7 +26,7 @@ use common::{bits256, executor::Timer, now_ms}; use common::{now_sec, wait_until_sec}; use crypto::privkey::SerializableSecp256k1Keypair; use crypto::secret_hash_algo::SecretHashAlgo; -use crypto::CryptoCtx; +use crypto::{CryptoCtx, CryptoCtxError}; use futures::{compat::Future01CompatExt, select, FutureExt}; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; @@ -1398,7 +1398,9 @@ impl MakerSwap { let mut taker = bits256::from([0; 32]); taker.bytes = data.taker_pubkey.0; - let crypto_ctx = try_s!(CryptoCtx::from_ctx(&ctx)); + let crypto_ctx = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) + .internal_keypair() + .ok_or(CryptoCtxError::NotInitialized)); let my_persistent_pub = H264::from(try_s!(TryInto::<[u8; 33]>::try_into( crypto_ctx.mm2_internal_key_pair().public_slice() ))); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 74e69384bf..2d110c7cd0 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -25,6 +25,7 @@ use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPayment use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, now_sec, wait_until_sec}; +use crypto::CryptoCtxError; use crypto::{privkey::SerializableSecp256k1Keypair, CryptoCtx}; use futures::{compat::Future01CompatExt, future::try_join, select, FutureExt}; use http::Response; @@ -2120,7 +2121,9 @@ impl TakerSwap { data.taker_coin_swap_contract_address = taker_coin.swap_contract_address(); } - let crypto_ctx = try_s!(CryptoCtx::from_ctx(&ctx)); + let crypto_ctx = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) + .internal_keypair() + .ok_or(CryptoCtxError::NotInitialized)); let my_persistent_pub = { let my_persistent_pub: [u8; 33] = try_s!(crypto_ctx.mm2_internal_key_pair().public_slice().try_into()); my_persistent_pub.into() @@ -2651,7 +2654,10 @@ pub async fn taker_swap_trade_preimage( rel_confs: rel_coin.required_confirmations(), rel_nota: rel_coin.requires_notarization(), }; - let our_public_id = CryptoCtx::from_ctx(ctx)?.mm2_internal_public_id(); + let our_public_id = CryptoCtx::from_ctx(ctx)? + .internal_keypair() + .ok_or(CryptoCtxError::NotInitialized)? + .mm2_internal_public_id(); let order_builder = TakerOrderBuilder::new(&base_coin, &rel_coin) .with_base_amount(base_amount) .with_rel_amount(rel_amount) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index c385067a18..ab35d2873a 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -9,7 +9,7 @@ use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; -use crate::lp_native_dex::{initialize_crypto_context_services, MmInitError}; +use crate::lp_native_dex::{init_crypto_keypair_ctx_services, MmInitError}; cfg_wasm32! { use crate::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; @@ -281,6 +281,7 @@ pub(crate) fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> Wallet true => CryptoCtx::init_with_global_hd_account(ctx.clone(), passphrase)?, false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), passphrase)?, }; + Ok(()) } @@ -305,19 +306,27 @@ pub(crate) fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> Wallet /// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. /// pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { - let (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; + let (wallet_name, maybe_passphrase) = deserialize_wallet_config(ctx)?; + + if maybe_passphrase.is_none() { + CryptoCtx::new_uninitialized(ctx); + return Ok(()); + } - let passphrase = process_passphrase_logic(ctx, wallet_name.as_deref(), passphrase).await?; + // SAFETY: We now have a passphrase: safely unwrap it. + let raw_passphrase = maybe_passphrase.unwrap(); + ctx.wallet_name + .set(wallet_name.clone()) + .map_to_mm(|_| WalletInitError::InternalError("Wallet already initialized".to_string()))?; - if let Some(passphrase) = passphrase { - ctx.wallet_name - .set(wallet_name.clone()) - .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; - return initialize_crypto_context(ctx, &passphrase); + if let Some(processed_passphrase) = + process_passphrase_logic(ctx, wallet_name.as_deref(), Some(raw_passphrase)).await? + { + return initialize_crypto_context(ctx, &processed_passphrase); } + // If we reach this point, the wallet remains uninitialized. CryptoCtx::new_uninitialized(ctx); - Ok(()) } @@ -623,13 +632,14 @@ pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult< .set(wallet_name.clone()) .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; - let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)).await?; - - if let Some(passphrase) = passphrase { - initialize_crypto_context(&ctx, &passphrase)?; - } + let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)) + .await? + .ok_or(WalletInitError::CryptoInitError( + "passphrase is required to init keypair_ctx".to_string(), + ))?; - initialize_crypto_context_services(ctx).await?; + initialize_crypto_context(&ctx, &passphrase)?; + init_crypto_keypair_ctx_services(ctx).await?; Ok(()) } diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index d9711f1481..5c65504194 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -985,8 +985,11 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(10); let p2p_key = { - let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); - let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); + let crypto_ctx = CryptoCtx::from_ctx(ctx) + .unwrap() + .internal_keypair() + .expect("CryptoCtx must be initialized with a passphrase"); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; @@ -1691,7 +1694,11 @@ fn test_choose_taker_confs_settings_sell_action() { fn make_ctx_for_tests() -> (MmArc, String, [u8; 32]) { let ctx = MmArc::new(MmCtx::default()); ctx.init_metrics().unwrap(); + let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "passphrase").unwrap(); + let crypto_ctx = crypto_ctx + .internal_keypair() + .expect("CryptoCtx' not initialized with passphrase"); let secret = crypto_ctx.mm2_internal_privkey_secret().take(); let pubkey = crypto_ctx.mm2_internal_pubkey_hex(); @@ -1741,8 +1748,11 @@ fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Recei let (cmd_tx, cmd_rx) = mpsc::channel(10); let p2p_key = { - let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); - let key = bitcrypto::sha256(&crypto_ctx.mm2_internal_privkey_slice()); + let crypto_ctx = CryptoCtx::from_ctx(ctx) + .unwrap() + .internal_keypair() + .expect("KDF should be initialized with a passphrase"); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs index f5a5a95063..256bb1eb76 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -33,7 +33,11 @@ impl HttpStatusCode for GetPublicKeyError { } pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); + let public_key = CryptoCtx::from_ctx(&ctx)? + .internal_keypair() + .ok_or(CryptoCtxError::NotInitialized)? + .mm2_internal_pubkey() + .to_string(); Ok(GetPublicKeyResponse { public_key }) } diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 5db3fa3cde..4a8053f52e 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -4043,10 +4043,11 @@ fn test_withdraw_and_send_eth_erc20() { fn test_withdraw_and_send_hd_eth_erc20() { const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - let KeyPairPolicy::GlobalHDAccount(ref hd_acc) = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE) + let crypto_ctx = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE) .unwrap() - .key_pair_policy() - else { + .internal_keypair() + .unwrap(); + let KeyPairPolicy::GlobalHDAccount(hd_acc) = crypto_ctx.key_pair_policy() else { panic!("Expected 'KeyPairPolicy::GlobalHDAccount'"); }; From c500ab5e0b2008f85130f74c215723e54590b634 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 9 Apr 2025 22:22:56 +0100 Subject: [PATCH 06/17] add todo --- mm2src/mm2_main/src/lp_wallet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index ab35d2873a..598b58d702 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -616,6 +616,7 @@ pub struct CryptoCtxInitRequest { wallet_name: Option, } +/// TODO: handle wallet_password, wallet_name, passphrase properly. pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { common::log::info!("Initializing KDF with passphrase"); if let Some(true) = ctx.initialized.get() { From 58bcd5f8f2eab2e54f74879d858fa130fc33c499 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 02:24:07 +0100 Subject: [PATCH 07/17] complete init_crypto_ctx rpc --- mm2src/mm2_main/src/lp_wallet.rs | 114 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 598b58d702..3446211ecb 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -255,8 +255,9 @@ async fn process_wallet_with_name( pub(crate) async fn process_passphrase_logic( ctx: &MmArc, - wallet_name: Option<&str>, passphrase: Option, + wallet_name: Option<&str>, + wallet_password: Option<&str>, ) -> WalletInitResult> { match (wallet_name, passphrase) { (None, None) => Ok(None), @@ -269,7 +270,10 @@ pub(crate) async fn process_passphrase_logic( .into()), (Some(wallet_name), passphrase_option) => { - let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + let wallet_password = wallet_password.ok_or(MmError::new(WalletInitError::ErrorDeserializingConfig { + field: "wallet.password".to_owned(), + error: "wallet_password data not provided".to_owned(), + }))?; process_wallet_with_name(ctx, wallet_name, passphrase_option, &wallet_password).await }, } @@ -308,20 +312,21 @@ pub(crate) fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> Wallet pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { let (wallet_name, maybe_passphrase) = deserialize_wallet_config(ctx)?; - if maybe_passphrase.is_none() { - CryptoCtx::new_uninitialized(ctx); - return Ok(()); - } - - // SAFETY: We now have a passphrase: safely unwrap it. - let raw_passphrase = maybe_passphrase.unwrap(); ctx.wallet_name .set(wallet_name.clone()) .map_to_mm(|_| WalletInitError::InternalError("Wallet already initialized".to_string()))?; - if let Some(processed_passphrase) = - process_passphrase_logic(ctx, wallet_name.as_deref(), Some(raw_passphrase)).await? - { + let wallet_password = deserialize_config_field::>(ctx, "wallet_password")?; + + let result = process_passphrase_logic( + ctx, + maybe_passphrase, + wallet_name.as_deref(), + wallet_password.as_deref(), + ) + .await?; + + if let Some(processed_passphrase) = result { return initialize_crypto_context(ctx, &processed_passphrase); } @@ -612,35 +617,86 @@ impl HttpStatusCode for CryptoCtxRpcError { #[derive(Deserialize)] pub struct CryptoCtxInitRequest { - passphrase: String, + passphrase: Option, + #[serde(default)] wallet_name: Option, + #[serde(default)] + wallet_password: Option, } -/// TODO: handle wallet_password, wallet_name, passphrase properly. +/// Initializes CryptoCtxc with the provided passphrase and optional wallet data. +/// ``` +/// let req = CryptoCtxInitRequest { +/// passphrase: "\"my secure passphrase\"".to_string(), +/// wallet_name: Some("my_wallet".to_string()), +/// wallet_password: Some("wallet_password".to_string()), +/// }; +/// let result = init_crypto_ctx(ctx, req).await; +/// ``` +/// OR +/// ``` +/// let req = CryptoCtxInitRequest { +/// passphrase: "\"my secure passphrase\"".to_string(), +/// }; +/// let result = init_crypto_ctx(ctx, req).await; +/// ``` +/// pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { common::log::info!("Initializing KDF with passphrase"); if let Some(true) = ctx.initialized.get() { return MmError::err(CryptoCtxRpcError::AlreadyInitialized); }; + let passphrase = match &req.passphrase { + Some(p) => json::from_str::>(&format!("\"{}\"", p))?, + None => None, + }; + let wallet_name = req.wallet_name; + let wallet_password = req.wallet_password; + + // Validate that either both values are Some or both are None + match (&wallet_name.is_some(), &wallet_password.is_some()) { + (false, false) => {}, + (true, true) => { + let name = wallet_name.as_deref().unwrap(); + let password = wallet_password.as_deref().unwrap(); + + if name.is_empty() { + return MmError::err(CryptoCtxRpcError::InternalError("Wallet name cannot be empty".into())); + } + if password.is_empty() { + return MmError::err(CryptoCtxRpcError::InternalError( + "Wallet password cannot be empty".into(), + )); + } + }, + _ => { + // One is Some while the other is None - invalid state + return MmError::err(CryptoCtxRpcError::InternalError( + "Wallet name and password must both be provided or both be omitted".into(), + )); + }, + } - let CryptoCtxInitRequest { - wallet_name, - passphrase, - } = req; - let passphrase: Passphrase = serde_json::from_str(&passphrase)?; - - ctx.wallet_name - .set(wallet_name.clone()) - .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string()))?; + // Since it will be set during KDF startup, + // we need to make ensure consistency. + if let Some(err) = ctx.wallet_name.get() { + if err != &wallet_name { + return MmError::err(CryptoCtxRpcError::InternalError( + "KDF Startup initialized wallet_name is different from wallet_name in params".to_string(), + )); + } + }; - let passphrase = process_passphrase_logic(&ctx, wallet_name.as_deref(), Some(passphrase)) - .await? - .ok_or(WalletInitError::CryptoInitError( - "passphrase is required to init keypair_ctx".to_string(), - ))?; + let processed_passphrase = + process_passphrase_logic(&ctx, passphrase, wallet_name.as_deref(), wallet_password.as_deref()) + .await? + .ok_or(MmError::new(WalletInitError::CryptoInitError( + "passphrase is required to init keypair_ctx".to_string(), + )))?; - initialize_crypto_context(&ctx, &passphrase)?; + initialize_crypto_context(&ctx, &processed_passphrase)?; init_crypto_keypair_ctx_services(ctx).await?; + common::log::info!("Initialized KDF with passphrase"); Ok(()) } From cd8d95e0243410203f8ffb54eddcdc4938b42bf6 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 02:45:14 +0100 Subject: [PATCH 08/17] fix fmt --- mm2src/mm2_main/src/lp_wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 3446211ecb..530cbfed1b 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -274,7 +274,7 @@ pub(crate) async fn process_passphrase_logic( field: "wallet.password".to_owned(), error: "wallet_password data not provided".to_owned(), }))?; - process_wallet_with_name(ctx, wallet_name, passphrase_option, &wallet_password).await + process_wallet_with_name(ctx, wallet_name, passphrase_option, wallet_password).await }, } } From 2eca55edfd994f6570ec5bbb79891d97f8229f55 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 03:02:24 +0100 Subject: [PATCH 09/17] use CryptoCtx::new_uninitialized in trezor test helper --- mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 5e702371b1..7a48ff8224 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -6121,7 +6121,7 @@ mod trezor_tests { pub async fn mm_ctx_with_trezor(conf: Json) -> MmArc { let ctx = mm_ctx_with_custom_db_with_conf(Some(conf)); - CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "123456").unwrap(); // for now we need passphrase seed for init + let _ = CryptoCtx::new_uninitialized(&ctx); let req: RpcInitReq = serde_json::from_value(json!({ "device_pubkey": null })).unwrap(); let res = match init_trezor(ctx.clone(), req).await { Ok(res) => res, From 342e831a6f8d071d5044130c9fd1037fedca2b6b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 13:02:34 +0100 Subject: [PATCH 10/17] more informative panic message for uninitialized CryptoCtx::Keypair --- mm2src/crypto/src/crypto_ctx.rs | 8 ++++---- mm2src/mm2_main/src/lp_healthcheck.rs | 2 +- mm2src/mm2_main/src/lp_ordermatch.rs | 10 +++++----- mm2src/mm2_main/src/lp_swap.rs | 6 +++--- mm2src/mm2_main/src/ordermatch_tests.rs | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 9e8616a7e7..6a974a0be8 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -200,11 +200,11 @@ impl CryptoCtx { metamask_ctx: RwLock::new(InitializationState::NotInitialized), }; - let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); let result = Arc::new(selfi); - *ctx_field = Some(result.clone()); - - drop(ctx_field); + { + let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); + *ctx_field = Some(result.clone()); + } result } diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs index 40cca5352c..fa122efdd7 100644 --- a/mm2src/mm2_main/src/lp_healthcheck.rs +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -350,7 +350,7 @@ mod tests { let crypto_ctx = CryptoCtx::from_ctx(&ctx) .unwrap() .internal_keypair() - .expect("CryptoCtx must be initialized with a passphrase"); + .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 2669d7da34..ce15f0170d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2334,7 +2334,7 @@ pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { let persistent_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") .internal_keypair() - .expect("CryptoCtx not initialized with a seedphrase") + .expect("`CryptoCtx::keypair_ctx` not initialized with a seedphrase") .mm2_internal_pubkey_hex(); while !ctx.is_stopping() { @@ -3048,7 +3048,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO let crypto_ctx = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") .internal_keypair() - .expect("'CryptoCtx' must be initialized with a passphrase"); + .expect("'CryptoCtx::keypair_ctx' must be initialized with a passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3280,7 +3280,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat let crypto_ctx = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") .internal_keypair() - .expect("'CryptoCtx' must be initialized with passphrase"); + .expect("'CryptoCtx::keypair_ctx' must be initialized with passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3491,7 +3491,7 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { let my_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") .internal_keypair() - .expect("CryptoCtx not initialized with a seedphrase") + .expect("`CryptoCtx::keypair_ctx` not initialized with a seedphrase") .mm2_internal_pubkey_hex(); let maker_order_timeout = ctx.conf["maker_order_timeout"].as_u64().unwrap_or(MAKER_ORDER_TIMEOUT); @@ -3764,7 +3764,7 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: let our_public_id = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") .internal_keypair() - .expect("'CryptoCtx' must be initialized passphrase") + .expect("'CryptoCtx::keypair_ctx' must be initialized passphrase") .mm2_internal_public_id(); if our_public_id.bytes == from_pubkey.0 { log::warn!("Skip maker reserved from our pubkey"); diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index fcf4f1bfef..315a28b048 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -249,7 +249,7 @@ pub fn p2p_keypair_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke let crypto_ctx = CryptoCtx::from_ctx(ctx) .expect("CryptoCtx must be initialized already") .internal_keypair() - .expect("CryptoCtx must be initialized with a passphrase"); + .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); (*crypto_ctx.mm2_internal_key_pair(), None) }, } @@ -268,7 +268,7 @@ pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke let crypto_ctx = CryptoCtx::from_ctx(ctx) .expect("CryptoCtx must be initialized already") .internal_keypair() - .expect("'CryptoCtx' must be initialized with passphrase"); + .expect("'CryptoCtx::keypair_ctx' must be initialized with passphrase"); (crypto_ctx.mm2_internal_privkey_secret().take(), None) }, @@ -2165,7 +2165,7 @@ mod lp_swap_tests { let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) .unwrap() .internal_keypair() - .unwrap(); + .expect("`CryptoCtx::keypair_ctx` must be initialized"); let maker_key_pair = crypto_ctx.mm2_internal_key_pair(); fix_directories(&maker_ctx).unwrap(); diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 5c65504194..7256703b38 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -988,7 +988,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let crypto_ctx = CryptoCtx::from_ctx(ctx) .unwrap() .internal_keypair() - .expect("CryptoCtx must be initialized with a passphrase"); + .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; @@ -1698,7 +1698,7 @@ fn make_ctx_for_tests() -> (MmArc, String, [u8; 32]) { let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "passphrase").unwrap(); let crypto_ctx = crypto_ctx .internal_keypair() - .expect("CryptoCtx' not initialized with passphrase"); + .expect("CryptoCtx::keypair_ctx' not initialized with passphrase"); let secret = crypto_ctx.mm2_internal_privkey_secret().take(); let pubkey = crypto_ctx.mm2_internal_pubkey_hex(); @@ -1751,7 +1751,7 @@ fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Recei let crypto_ctx = CryptoCtx::from_ctx(ctx) .unwrap() .internal_keypair() - .expect("KDF should be initialized with a passphrase"); + .expect("`CryptoCtx::keypair_ctx` should be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; From 9a5210311b15d24523e6f9f49cc6459dc0e45923 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 13:29:57 +0100 Subject: [PATCH 11/17] implement get_crypto_ctxs_init_state rpc to get initialized crypto contexts state --- mm2src/mm2_main/src/lp_wallet.rs | 26 +++++++++++++++++++ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 530cbfed1b..1e53636915 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -700,3 +700,29 @@ pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult< common::log::info!("Initialized KDF with passphrase"); Ok(()) } + +/// Get all initialiazed CryptoCtx contexts' +#[derive(Serialize)] +pub struct GetInitializedCryptoCtxRes { + pub keypair_ctx: bool, + pub hw_ctx: bool, + #[cfg(target_arch = "wasm32")] + pub metamask_ctx: bool, +} + +#[derive(Deserialize)] +pub struct GetInitializedCryptoCtxReq {} + +pub async fn get_crypto_ctxs_init_state( + ctx: MmArc, + _req: GetInitializedCryptoCtxReq, +) -> MmResult { + let crypto_ctx = CryptoCtx::from_ctx(&ctx).mm_err(|err| MnemonicRpcError::Internal(err.to_string()))?; + + Ok(GetInitializedCryptoCtxRes { + #[cfg(target_arch = "wasm32")] + metamask_ctx: crypto_ctx.metamask_ctx().is_some(), + hw_ctx: crypto_ctx.hw_ctx().is_some(), + keypair_ctx: crypto_ctx.internal_keypair().is_some(), + }) +} diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index d6045ecfd6..06db034af7 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -10,7 +10,8 @@ use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, s stop_version_stat_collection, update_version_stat_collection}; use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; -use crate::lp_wallet::{change_mnemonic_password, get_mnemonic_rpc, get_wallet_names_rpc, init_crypto_ctx}; +use crate::lp_wallet::{change_mnemonic_password, get_crypto_ctxs_init_state, get_mnemonic_rpc, get_wallet_names_rpc, + init_crypto_ctx}; use crate::rpc::lp_commands::db_id::get_shared_db_id; use crate::rpc::lp_commands::one_inch::rpcs::{one_inch_v6_0_classic_swap_contract_rpc, one_inch_v6_0_classic_swap_create_rpc, @@ -209,6 +210,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, + "get_crypto_ctxs_init_state" => handle_mmrpc(ctx, request, get_crypto_ctxs_init_state).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, From 9447461b584c2da0c55ce07682caf82b3d61bdad Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 14:16:03 +0100 Subject: [PATCH 12/17] inline one liners functions, rename internal_keypair -> keypair_ctx --- mm2src/coins/eth.rs | 2 +- mm2src/coins/lp_coins.rs | 2 +- mm2src/crypto/src/crypto_ctx.rs | 39 ++++++++++--------- mm2src/mm2_main/src/lp_healthcheck.rs | 2 +- mm2src/mm2_main/src/lp_native_dex.rs | 2 +- mm2src/mm2_main/src/lp_ordermatch.rs | 20 +++++----- mm2src/mm2_main/src/lp_swap.rs | 8 ++-- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 2 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 4 +- mm2src/mm2_main/src/lp_wallet.rs | 2 +- mm2src/mm2_main/src/ordermatch_tests.rs | 6 +-- mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs | 2 +- .../tests/docker_tests/docker_tests_inner.rs | 2 +- 13 files changed, 48 insertions(+), 45 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 557f6cc061..6f14877f77 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -790,7 +790,7 @@ impl EthPrivKeyBuildPolicy { /// Detects the `EthPrivKeyBuildPolicy` with which the given `MmArc` is initialized. pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { let crypto_ctx = CryptoCtx::from_ctx(ctx)? - .internal_keypair() + .keypair_ctx() .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; match crypto_ctx.key_pair_policy() { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 7aaeae10f2..da5fe276c4 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4336,7 +4336,7 @@ impl PrivKeyBuildPolicy { /// Detects the `PrivKeyBuildPolicy` with which the given `MmArc` is initialized. pub fn detect_priv_key_policy(ctx: &MmArc) -> MmResult { let crypto_ctx = CryptoCtx::from_ctx(ctx)? - .internal_keypair() + .keypair_ctx() .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; match crypto_ctx.key_pair_policy() { diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 6a974a0be8..ce802a9988 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -102,12 +102,12 @@ pub struct KeyPairCtx { } impl KeyPairCtx { - #[inline] + #[inline(always)] pub fn key_pair_policy(&self) -> &KeyPairPolicy { &self.key_pair_policy } /// This is our public ID, allowing us to be different from other peers. /// This should also be our public key which we'd use for P2P message verification. - #[inline] + #[inline(always)] pub fn mm2_internal_public_id(&self) -> bits256 { // Compressed public key is going to be 33 bytes. let public = self.mm2_internal_pubkey(); @@ -125,9 +125,9 @@ impl KeyPairCtx { /// /// # Security /// - /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair is used to activate coins. + /// If [`CryptoCtx::keypair_ctx`] is `Iguana`, then the returning key-pair is used to activate coins. /// Please use this method carefully. - #[inline] + #[inline(always)] pub fn mm2_internal_key_pair(&self) -> &KeyPair { &self.secp256k1_key_pair } /// Returns `secp256k1` public key. @@ -137,10 +137,10 @@ impl KeyPairCtx { /// /// # Security /// - /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair can be also used + /// If [`CryptoCtx::keypair_ctx`] is `Iguana`, then the returning key-pair can be also used /// at the activated coins. /// Please use this method carefully. - #[inline] + #[inline(always)] pub fn mm2_internal_pubkey(&self) -> &PublicKey { self.mm2_internal_key_pair().public() } /// Returns `secp256k1` public key hex. @@ -150,10 +150,10 @@ impl KeyPairCtx { /// /// # Security /// - /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning public key can be also used + /// If [`CryptoCtx::keypair_ctx`] is `Iguana`, then the returning public key can be also used /// at the activated coins. /// Please use this method carefully. - #[inline] + #[inline(always)] pub fn mm2_internal_pubkey_hex(&self) -> String { hex::encode(self.mm2_internal_pubkey().deref()) } /// Returns `secp256k1` private key as `H256` bytes. @@ -163,23 +163,23 @@ impl KeyPairCtx { /// /// # Security /// - /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. + /// If [`CryptoCtx::keypair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. - #[inline] + #[inline(always)] pub fn mm2_internal_privkey_secret(&self) -> &Secp256k1Secret { &self.mm2_internal_key_pair().private().secret } /// Returns `secp256k1` private key as `[u8]` slice. /// It can be used for mm2 internal purposes such as signing P2P messages. - /// Please consider using [`CryptoCtx::mm2_internal_privkey_bytes`] instead. + /// Please consider using [`CryptoCtx::keypair_ctx::mm2_internal_privkey_bytes`] instead. /// /// If you don't need to borrow the secret bytes, consider using [`CryptoCtx::mm2_internal_privkey_bytes`] instead. /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. /// /// # Security /// - /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. + /// If [`CryptoCtx::keypair_ctx`] is `Iguana`, then the returning private is used to activate coins. /// Please use this method carefully. - #[inline] + #[inline(always)] pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.mm2_internal_privkey_secret().as_slice() } } @@ -189,6 +189,7 @@ pub struct CryptoCtx { hw_ctx: RwLock>, #[cfg(target_arch = "wasm32")] metamask_ctx: RwLock>, + // TODO: WalletConnectCtx goes here! } impl CryptoCtx { @@ -209,9 +210,10 @@ impl CryptoCtx { result } + #[inline(always)] pub fn is_crypto_keypair_ctx_init(ctx: &MmArc) -> MmResult { match CryptoCtx::from_ctx(ctx).split_mm() { - Ok(c) => Ok(c.keypair_ctx.read().to_option().is_some()), + Ok(c) => Ok(c.keypair_ctx().is_some()), Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), } } @@ -232,17 +234,18 @@ impl CryptoCtx { Ok(Self::new_uninitialized(ctx)) } - #[inline] - pub fn internal_keypair(&self) -> Option> { self.keypair_ctx.read().to_option().cloned() } + #[inline(always)] + pub fn keypair_ctx(&self) -> Option> { self.keypair_ctx.read().to_option().cloned() } - #[inline] + #[inline(always)] pub fn hw_ctx(&self) -> Option { self.hw_ctx.read().to_option().cloned() } #[cfg(target_arch = "wasm32")] + #[inline(always)] pub fn metamask_ctx(&self) -> Option { self.metamask_ctx.read().to_option().cloned() } /// Returns an `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey that identifies a Hardware Wallet device or an HD master private key. - #[inline] + #[inline(always)] pub fn hw_wallet_rmd160(&self) -> Option { self.hw_ctx.read().to_option().map(|hw_ctx| hw_ctx.rmd160()) } pub fn init_with_iguana_passphrase(ctx: MmArc, passphrase: &str) -> CryptoInitResult> { diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs index fa122efdd7..cab5d13b4b 100644 --- a/mm2src/mm2_main/src/lp_healthcheck.rs +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -349,7 +349,7 @@ mod tests { let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(&ctx) .unwrap() - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index cc853c9be7..cb42cdf84c 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -557,7 +557,7 @@ async fn kick_start(ctx: MmArc) -> MmInitResult<()> { fn get_p2p_key(ctx: &MmArc, i_am_seed: bool) -> P2PResult<[u8; 32]> { // TODO: Use persistent peer ID regardless the node type. - let crypto_ctx = CryptoCtx::from_ctx(ctx).map(|c| c.internal_keypair()); + let crypto_ctx = CryptoCtx::from_ctx(ctx).map(|c| c.keypair_ctx()); if i_am_seed { if let Ok(Some(crypto_ctx)) = crypto_ctx { let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index ce15f0170d..661f793e2e 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -523,7 +523,7 @@ where F: Fn(String) -> E, { match CryptoCtx::from_ctx(ctx).split_mm() { - Ok(crypto_ctx) => Ok(crypto_ctx.internal_keypair().map(|k| k.mm2_internal_pubkey_hex())), + Ok(crypto_ctx) => Ok(crypto_ctx.keypair_ctx().map(|k| k.mm2_internal_pubkey_hex())), Err((CryptoCtxError::NotInitialized, _)) => Ok(None), Err((CryptoCtxError::Internal(error), trace)) => MmError::err_with_trace(err_construct(error), trace), } @@ -2333,7 +2333,7 @@ pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { // broadcast_maker_orders_keep_alive_loop is spawned only if CryptoCtx is initialized. let persistent_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` not initialized with a seedphrase") .mm2_internal_pubkey_hex(); @@ -3047,7 +3047,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO // lp_connect_start_bob is called only from process_taker_connect, which returns if CryptoCtx is not initialized let crypto_ctx = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") - .internal_keypair() + .keypair_ctx() .expect("'CryptoCtx::keypair_ctx' must be initialized with a passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3279,7 +3279,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat // lp_connected_alice is called only from process_maker_connected, which returns if CryptoCtx is not initialized let crypto_ctx = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") - .internal_keypair() + .keypair_ctx() .expect("'CryptoCtx::keypair_ctx' must be initialized with passphrase"); let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); let my_persistent_pub = compressed_pub_key_from_priv_raw(&raw_priv.take(), ChecksumType::DSHA256).unwrap(); @@ -3490,7 +3490,7 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { // lp_ordermatch_loop is spawned only if CryptoCtx is initialized let my_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` not initialized with a seedphrase") .mm2_internal_pubkey_hex(); @@ -3763,7 +3763,7 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: // Taker order existence is checked previously - it can't be created if CryptoCtx is not initialized let our_public_id = CryptoCtx::from_ctx(&ctx) .expect("'CryptoCtx' must be initialized already") - .internal_keypair() + .keypair_ctx() .expect("'CryptoCtx::keypair_ctx' must be initialized passphrase") .mm2_internal_public_id(); if our_public_id.bytes == from_pubkey.0 { @@ -3871,7 +3871,7 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: let our_public_id = match CryptoCtx::from_ctx(&ctx) { Ok(ctx) => { - let Some(keypair) = ctx.internal_keypair() else { + let Some(keypair) = ctx.keypair_ctx() else { return; }; @@ -3931,7 +3931,7 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: TakerRequest) { let our_public_id: H256Json = match CryptoCtx::from_ctx(&ctx) { Ok(ctx) => { - let Some(keypair) = ctx.internal_keypair() else { + let Some(keypair) = ctx.keypair_ctx() else { return; }; @@ -4050,7 +4050,7 @@ async fn process_taker_connect(ctx: MmArc, sender_pubkey: PublicKey, connect_msg let our_public_id = match CryptoCtx::from_ctx(&ctx) { Ok(ctx) => { - let Some(keypair) = ctx.internal_keypair() else { + let Some(keypair) = ctx.keypair_ctx() else { return; }; @@ -4249,7 +4249,7 @@ pub async fn lp_auto_buy( let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(ctx)); let mut my_taker_orders = ordermatch_ctx.my_taker_orders.lock().await; let our_public_id = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) - .internal_keypair() + .keypair_ctx() .ok_or("CryptoCtx must be initialized with a passphrase")) .mm2_internal_public_id(); let rel_volume = &input.volume * &input.price; diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 315a28b048..99117c1980 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -248,7 +248,7 @@ pub fn p2p_keypair_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke None => { let crypto_ctx = CryptoCtx::from_ctx(ctx) .expect("CryptoCtx must be initialized already") - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); (*crypto_ctx.mm2_internal_key_pair(), None) }, @@ -267,7 +267,7 @@ pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke None => { let crypto_ctx = CryptoCtx::from_ctx(ctx) .expect("CryptoCtx must be initialized already") - .internal_keypair() + .keypair_ctx() .expect("'CryptoCtx::keypair_ctx' must be initialized with passphrase"); (crypto_ctx.mm2_internal_privkey_secret().take(), None) @@ -2164,7 +2164,7 @@ mod lp_swap_tests { let maker_ctx = MmCtxBuilder::default().with_conf(maker_ctx_conf).into_mm_arc(); let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) .unwrap() - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` must be initialized"); let maker_key_pair = crypto_ctx.mm2_internal_key_pair(); @@ -2204,7 +2204,7 @@ mod lp_swap_tests { let taker_ctx = MmCtxBuilder::default().with_conf(taker_ctx_conf).into_mm_arc(); let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &taker_passphrase) .unwrap() - .internal_keypair() + .keypair_ctx() .unwrap(); let taker_key_pair = crypto_ctx.mm2_internal_key_pair(); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 39a4f11528..e90f8f3c83 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -1399,7 +1399,7 @@ impl MakerSwap { taker.bytes = data.taker_pubkey.0; let crypto_ctx = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) - .internal_keypair() + .keypair_ctx() .ok_or(CryptoCtxError::NotInitialized)); let my_persistent_pub = H264::from(try_s!(TryInto::<[u8; 33]>::try_into( crypto_ctx.mm2_internal_key_pair().public_slice() diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 2d110c7cd0..833499774a 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -2122,7 +2122,7 @@ impl TakerSwap { } let crypto_ctx = try_s!(try_s!(CryptoCtx::from_ctx(&ctx)) - .internal_keypair() + .keypair_ctx() .ok_or(CryptoCtxError::NotInitialized)); let my_persistent_pub = { let my_persistent_pub: [u8; 33] = try_s!(crypto_ctx.mm2_internal_key_pair().public_slice().try_into()); @@ -2655,7 +2655,7 @@ pub async fn taker_swap_trade_preimage( rel_nota: rel_coin.requires_notarization(), }; let our_public_id = CryptoCtx::from_ctx(ctx)? - .internal_keypair() + .keypair_ctx() .ok_or(CryptoCtxError::NotInitialized)? .mm2_internal_public_id(); let order_builder = TakerOrderBuilder::new(&base_coin, &rel_coin) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 1e53636915..8df2ec4299 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -723,6 +723,6 @@ pub async fn get_crypto_ctxs_init_state( #[cfg(target_arch = "wasm32")] metamask_ctx: crypto_ctx.metamask_ctx().is_some(), hw_ctx: crypto_ctx.hw_ctx().is_some(), - keypair_ctx: crypto_ctx.internal_keypair().is_some(), + keypair_ctx: crypto_ctx.keypair_ctx().is_some(), }) } diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 7256703b38..e8d4a3e16b 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -987,7 +987,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(ctx) .unwrap() - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() @@ -1697,7 +1697,7 @@ fn make_ctx_for_tests() -> (MmArc, String, [u8; 32]) { let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "passphrase").unwrap(); let crypto_ctx = crypto_ctx - .internal_keypair() + .keypair_ctx() .expect("CryptoCtx::keypair_ctx' not initialized with passphrase"); let secret = crypto_ctx.mm2_internal_privkey_secret().take(); @@ -1750,7 +1750,7 @@ fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Recei let p2p_key = { let crypto_ctx = CryptoCtx::from_ctx(ctx) .unwrap() - .internal_keypair() + .keypair_ctx() .expect("`CryptoCtx::keypair_ctx` should be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs index 256bb1eb76..069498451e 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -34,7 +34,7 @@ impl HttpStatusCode for GetPublicKeyError { pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { let public_key = CryptoCtx::from_ctx(&ctx)? - .internal_keypair() + .keypair_ctx() .ok_or(CryptoCtxError::NotInitialized)? .mm2_internal_pubkey() .to_string(); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 4a8053f52e..e5b3fb5264 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -4045,7 +4045,7 @@ fn test_withdraw_and_send_hd_eth_erc20() { let crypto_ctx = CryptoCtx::init_with_global_hd_account(MM_CTX.clone(), PASSPHRASE) .unwrap() - .internal_keypair() + .keypair_ctx() .unwrap(); let KeyPairPolicy::GlobalHDAccount(hd_acc) = crypto_ctx.key_pair_policy() else { panic!("Expected 'KeyPairPolicy::GlobalHDAccount'"); From e1b96e56445411a68eb44b3a8d6d64b196370f30 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 14:35:40 +0100 Subject: [PATCH 13/17] init_crypto_ctx -> init_crypto_keypair_ctx --- mm2src/crypto/src/crypto_ctx.rs | 12 ++++++------ mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index ce802a9988..ad9e1f5f72 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -91,6 +91,12 @@ impl From for MetamaskCtxInitError { fn from(value: MetamaskError) -> Self { MetamaskCtxInitError::MetamaskError(value) } } +#[derive(Clone)] +pub enum KeyPairPolicy { + Iguana, + GlobalHDAccount(GlobalHDAccountArc), +} + pub struct KeyPairCtx { /// secp256k1 key pair derived from either: /// * Iguana passphrase, @@ -376,12 +382,6 @@ impl KeyPairPolicyBuilder { } } -#[derive(Clone)] -pub enum KeyPairPolicy { - Iguana, - GlobalHDAccount(GlobalHDAccountArc), -} - async fn init_check_hw_ctx_with_trezor( processor: Arc>, expected_pubkey: Option, diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 06db034af7..4ad80705af 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -225,7 +225,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_shared_db_id).await, "get_token_info" => handle_mmrpc(ctx, request, get_token_info).await, "get_wallet_names" => handle_mmrpc(ctx, request, get_wallet_names_rpc).await, - "init_crypto_ctx" => handle_mmrpc(ctx, request, init_crypto_ctx).await, + "init_crypto_keypair_ctx" => handle_mmrpc(ctx, request, init_crypto_ctx).await, "max_maker_vol" => handle_mmrpc(ctx, request, max_maker_vol).await, "my_recent_swaps" => handle_mmrpc(ctx, request, my_recent_swaps_rpc).await, "my_swap_status" => handle_mmrpc(ctx, request, my_swap_status_rpc).await, From 7fd762aea08aa11c417d0c2078059bc5146fb151 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 15:56:48 +0100 Subject: [PATCH 14/17] add no_login_mode flag and refactor crypto keypair init --- mm2src/crypto/src/crypto_ctx.rs | 2 +- mm2src/mm2_core/src/mm_ctx.rs | 2 + mm2src/mm2_main/src/lp_wallet.rs | 104 +++--------------- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 +- 4 files changed, 21 insertions(+), 89 deletions(-) diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index ad9e1f5f72..83834ff8a8 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -330,7 +330,7 @@ impl CryptoCtx { } let ctx_field = CryptoCtx::from_ctx(&ctx).expect("Should already be initialized"); - if ctx_field.keypair_ctx.read().to_option().is_some() { + if ctx_field.keypair_ctx().is_some() { return MmError::err(CryptoInitError::InitializedAlready); } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index b3da7071d4..7e0e83bac4 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -236,6 +236,8 @@ impl MmCtx { pub fn is_seed_node(&self) -> bool { self.conf["i_am_seed"].as_bool().unwrap_or(false) } + pub fn no_login_mode(&self) -> bool { self.conf["no_login_mode"].as_bool().unwrap_or(false) } + #[cfg(not(target_arch = "wasm32"))] pub fn rpc_ip_port(&self) -> Result { let port = match self.conf.get("rpcport") { diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 8df2ec4299..7b7887d028 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -257,7 +257,6 @@ pub(crate) async fn process_passphrase_logic( ctx: &MmArc, passphrase: Option, wallet_name: Option<&str>, - wallet_password: Option<&str>, ) -> WalletInitResult> { match (wallet_name, passphrase) { (None, None) => Ok(None), @@ -270,11 +269,8 @@ pub(crate) async fn process_passphrase_logic( .into()), (Some(wallet_name), passphrase_option) => { - let wallet_password = wallet_password.ok_or(MmError::new(WalletInitError::ErrorDeserializingConfig { - field: "wallet.password".to_owned(), - error: "wallet_password data not provided".to_owned(), - }))?; - process_wallet_with_name(ctx, wallet_name, passphrase_option, wallet_password).await + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + process_wallet_with_name(ctx, wallet_name, passphrase_option, &wallet_password).await }, } } @@ -316,22 +312,16 @@ pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResul .set(wallet_name.clone()) .map_to_mm(|_| WalletInitError::InternalError("Wallet already initialized".to_string()))?; - let wallet_password = deserialize_config_field::>(ctx, "wallet_password")?; + let processed_passphrase = process_passphrase_logic(ctx, maybe_passphrase, wallet_name.as_deref()).await?; - let result = process_passphrase_logic( - ctx, - maybe_passphrase, - wallet_name.as_deref(), - wallet_password.as_deref(), - ) - .await?; - - if let Some(processed_passphrase) = result { - return initialize_crypto_context(ctx, &processed_passphrase); + if !ctx.no_login_mode() && processed_passphrase.is_some() { + // SAFTETY: processed_passphrase is checked for None so it's in fact safe to unwrap(). + return initialize_crypto_context(ctx, &processed_passphrase.unwrap()); } // If we reach this point, the wallet remains uninitialized. CryptoCtx::new_uninitialized(ctx); + Ok(()) } @@ -616,88 +606,28 @@ impl HttpStatusCode for CryptoCtxRpcError { } #[derive(Deserialize)] -pub struct CryptoCtxInitRequest { - passphrase: Option, - #[serde(default)] - wallet_name: Option, - #[serde(default)] - wallet_password: Option, -} +pub struct CryptoCtxInitRequest {} /// Initializes CryptoCtxc with the provided passphrase and optional wallet data. -/// ``` -/// let req = CryptoCtxInitRequest { -/// passphrase: "\"my secure passphrase\"".to_string(), -/// wallet_name: Some("my_wallet".to_string()), -/// wallet_password: Some("wallet_password".to_string()), -/// }; -/// let result = init_crypto_ctx(ctx, req).await; -/// ``` -/// OR -/// ``` -/// let req = CryptoCtxInitRequest { -/// passphrase: "\"my secure passphrase\"".to_string(), -/// }; -/// let result = init_crypto_ctx(ctx, req).await; -/// ``` /// -pub async fn init_crypto_ctx(ctx: MmArc, req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { +pub async fn init_crypto_ctx(ctx: MmArc, _req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { common::log::info!("Initializing KDF with passphrase"); if let Some(true) = ctx.initialized.get() { return MmError::err(CryptoCtxRpcError::AlreadyInitialized); }; - let passphrase = match &req.passphrase { - Some(p) => json::from_str::>(&format!("\"{}\"", p))?, - None => None, - }; - let wallet_name = req.wallet_name; - let wallet_password = req.wallet_password; - - // Validate that either both values are Some or both are None - match (&wallet_name.is_some(), &wallet_password.is_some()) { - (false, false) => {}, - (true, true) => { - let name = wallet_name.as_deref().unwrap(); - let password = wallet_password.as_deref().unwrap(); - - if name.is_empty() { - return MmError::err(CryptoCtxRpcError::InternalError("Wallet name cannot be empty".into())); - } - if password.is_empty() { - return MmError::err(CryptoCtxRpcError::InternalError( - "Wallet password cannot be empty".into(), - )); - } - }, - _ => { - // One is Some while the other is None - invalid state - return MmError::err(CryptoCtxRpcError::InternalError( - "Wallet name and password must both be provided or both be omitted".into(), - )); - }, - } - // Since it will be set during KDF startup, - // we need to make ensure consistency. - if let Some(err) = ctx.wallet_name.get() { - if err != &wallet_name { - return MmError::err(CryptoCtxRpcError::InternalError( - "KDF Startup initialized wallet_name is different from wallet_name in params".to_string(), - )); - } - }; - - let processed_passphrase = - process_passphrase_logic(&ctx, passphrase, wallet_name.as_deref(), wallet_password.as_deref()) - .await? - .ok_or(MmError::new(WalletInitError::CryptoInitError( - "passphrase is required to init keypair_ctx".to_string(), - )))?; + let (wallet_name, maybe_passphrase) = deserialize_wallet_config(&ctx)?; + let passphrase = process_passphrase_logic(&ctx, maybe_passphrase, wallet_name.as_deref()) + .await? + .ok_or(MmError::new(CryptoCtxRpcError::InternalError( + "No passphrase was processed".to_owned(), + )))?; - initialize_crypto_context(&ctx, &processed_passphrase)?; + initialize_crypto_context(&ctx, &passphrase)?; init_crypto_keypair_ctx_services(ctx).await?; common::log::info!("Initialized KDF with passphrase"); + Ok(()) } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 4ad80705af..3034597926 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -210,7 +210,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, - "get_crypto_ctxs_init_state" => handle_mmrpc(ctx, request, get_crypto_ctxs_init_state).await, + "get_crypto_ctxs_state" => handle_mmrpc(ctx, request, get_crypto_ctxs_init_state).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, From 11e2bda4c33b9294c83a3f8e77ca6364c0c6edac Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 20:20:38 +0100 Subject: [PATCH 15/17] implement unit tests and minor changes --- mm2src/mm2_main/src/lp_wallet.rs | 12 +-- .../tests/mm2_tests/mm2_tests_inner.rs | 48 ++++++++++++ mm2src/mm2_test_helpers/src/for_tests.rs | 76 ++++++++++++++++++- 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 7b7887d028..52b5fcd82b 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -312,11 +312,14 @@ pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResul .set(wallet_name.clone()) .map_to_mm(|_| WalletInitError::InternalError("Wallet already initialized".to_string()))?; - let processed_passphrase = process_passphrase_logic(ctx, maybe_passphrase, wallet_name.as_deref()).await?; + if ctx.no_login_mode() { + CryptoCtx::new_uninitialized(ctx); + return Ok(()); + } - if !ctx.no_login_mode() && processed_passphrase.is_some() { - // SAFTETY: processed_passphrase is checked for None so it's in fact safe to unwrap(). - return initialize_crypto_context(ctx, &processed_passphrase.unwrap()); + let processed_passphrase = process_passphrase_logic(ctx, maybe_passphrase, wallet_name.as_deref()).await?; + if let Some(passphrase) = processed_passphrase { + return initialize_crypto_context(ctx, &passphrase); } // If we reach this point, the wallet remains uninitialized. @@ -609,7 +612,6 @@ impl HttpStatusCode for CryptoCtxRpcError { pub struct CryptoCtxInitRequest {} /// Initializes CryptoCtxc with the provided passphrase and optional wallet data. -/// pub async fn init_crypto_ctx(ctx: MmArc, _req: CryptoCtxInitRequest) -> MmResult<(), CryptoCtxRpcError> { common::log::info!("Initializing KDF with passphrase"); if let Some(true) = ctx.initialized.get() { diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 7a48ff8224..3b2313fcc7 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -5279,6 +5279,54 @@ fn test_no_login() { assert!(version.0.is_success(), "!version: {}", version.1); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_no_login_mode_with_passphrase() { + use mm2_test_helpers::for_tests::{no_login_mode_test_impl, DEFAULT_RPC_PASSWORD}; + + let coins = json!([morty_conf()]); + // test kdf no_login_mode with passphrase + let no_login_conf = Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "coins": coins, + "i_am_seed": true, + "rpc_password": DEFAULT_RPC_PASSWORD, + "no_login_mode": true, + "passphrase": "password", + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + }; + + no_login_mode_test_impl(no_login_conf); +} + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_no_login_mode_with_wallet_creds() { + use mm2_test_helpers::for_tests::{no_login_mode_test_impl, DEFAULT_RPC_PASSWORD}; + + let coins = json!([morty_conf()]); + + // test kdf no_login_mode with wallet_name/wallet_password + let no_login_conf = Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "coins": coins, + "i_am_seed": true, + "rpc_password": DEFAULT_RPC_PASSWORD, + "no_login_mode": true, + "wallet_name": "sami", + "wallet_password": "password", + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + }; + + no_login_mode_test_impl(no_login_conf); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_gui_storage_accounts_functionality() { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 621e76a597..d905eafe3a 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -2,7 +2,7 @@ #![allow(missing_docs)] -use crate::electrums::tqtum_electrums; +use crate::electrums::{marty_electrums, tqtum_electrums}; use crate::structs::*; use common::custom_futures::repeatable::{Ready, Retry}; use common::executor::Timer; @@ -427,6 +427,7 @@ impl Mm2TestConf { "coins": coins, "rpc_password": DEFAULT_RPC_PASSWORD, "seednodes": seednodes, + }), rpc_password: DEFAULT_RPC_PASSWORD.into(), } @@ -4090,3 +4091,76 @@ pub async fn active_swaps(mm: &MarketMakerIt) -> ActiveSwapsResponse { assert_eq!(response.0, StatusCode::OK, "'active_swaps' failed: {}", response.1); json::from_str(&response.1).unwrap() } + +pub fn no_login_mode_test_impl(no_login_conf: Mm2TestConf) { + let no_login_node = MarketMakerIt::start(no_login_conf.conf, no_login_conf.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = no_login_node.mm_dump(); + log!("log path: {}", no_login_node.log_path.display()); + + // Check if CryptoCtx::keypair_ctx is initialized. + let resp = block_on(no_login_node.rpc(&json!({ + "userpass": no_login_node.userpass, + "mmrpc": "2.0", + "method": "get_crypto_ctxs_state", + "params": {} + }))) + .unwrap(); + assert!(resp.0.is_success(), "!get_crypto_ctxs_state {}", resp.1); + // keypair_ctx must not be initialized + assert!( + !serde_json::from_str::(&resp.1).unwrap()["result"]["keypair_ctx"] + .as_bool() + .unwrap() + ); + + // Try activating a coin which requires CryptoCtx::keypair_ctx to be initialized. + // Should fail. + let resp = block_on(no_login_node.rpc(&json!({ + "userpass": no_login_node.userpass, + "method": "electrum", + "coin": MORTY, + "servers": marty_electrums(), + "mm2": 1, + "tx_history": false, + }))) + .unwrap(); + // Coin can't be activated due to no CryptoCtx contexts initialized. + assert!(resp.0.is_server_error(), "!electrum {}", resp.1); + + // Initialized CryptoCtx keypair_ctx. + let resp = block_on(no_login_node.rpc(&json!({ + "userpass": no_login_node.userpass, + "mmrpc": "2.0", + "method": "init_crypto_keypair_ctx", + "params": {} + }))) + .unwrap(); + assert!(resp.0.is_success(), "!init_crypto_keypair_ctx {}", resp.1); + + // CryptoCtx::keypair_ctx is initialized. + let resp = block_on(no_login_node.rpc(&json!({ + "userpass": no_login_node.userpass, + "mmrpc": "2.0", + "method": "get_crypto_ctxs_state", + "params": {} + }))) + .unwrap(); + assert!(resp.0.is_success(), "!get_crypto_ctxs_state {}", resp.1); + assert!( + serde_json::from_str::(&resp.1).unwrap()["result"]["keypair_ctx"] + .as_bool() + .unwrap() + ); + + // Coin activation should work now. + let resp = block_on(no_login_node.rpc(&json!({ + "userpass": no_login_node.userpass, + "method": "electrum", + "coin": MORTY, + "servers": marty_electrums(), + "mm2": 1, + "tx_history": false, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!electrum {}", resp.1); +} From 8431afef2bf782a9c4ee4633d6515899fdbf5247 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 10 Apr 2025 20:32:20 +0100 Subject: [PATCH 16/17] fix wasm clippy --- mm2src/mm2_test_helpers/src/for_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index d905eafe3a..71a0b2a238 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -2,7 +2,7 @@ #![allow(missing_docs)] -use crate::electrums::{marty_electrums, tqtum_electrums}; +use crate::electrums::tqtum_electrums; use crate::structs::*; use common::custom_futures::repeatable::{Ready, Retry}; use common::executor::Timer; @@ -30,6 +30,8 @@ use std::sync::Mutex; use uuid::Uuid; cfg_native! { + use crate::electrums::marty_electrums; + use common::block_on; use common::log::dashboard_path; use mm2_io::fs::slurp; @@ -4092,6 +4094,7 @@ pub async fn active_swaps(mm: &MarketMakerIt) -> ActiveSwapsResponse { json::from_str(&response.1).unwrap() } +#[cfg(not(target_arch = "wasm32"))] pub fn no_login_mode_test_impl(no_login_conf: Mm2TestConf) { let no_login_node = MarketMakerIt::start(no_login_conf.conf, no_login_conf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = no_login_node.mm_dump(); From 69e0e14ba1b762a3cc920414260791fe0ad8f141 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 14 Apr 2025 14:14:14 +0100 Subject: [PATCH 17/17] nits --- mm2src/mm2_main/src/lp_ordermatch.rs | 36 +++++++--------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 661f793e2e..1cd1e96d31 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -3869,15 +3869,9 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: log::debug!("Processing MakerConnected {:?}", connected); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); - let our_public_id = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => { - let Some(keypair) = ctx.keypair_ctx() else { - return; - }; - - keypair.mm2_internal_public_id() - }, - Err(_) => return, + let our_public_id = match CryptoCtx::from_ctx(&ctx).map(|c| c.keypair_ctx()) { + Ok(Some(keypair)) => keypair.mm2_internal_public_id(), + _ => return, }; let unprefixed_from = from_pubkey.unprefixed(); @@ -3929,15 +3923,9 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: PublicKey, connected: } async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: TakerRequest) { - let our_public_id: H256Json = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => { - let Some(keypair) = ctx.keypair_ctx() else { - return; - }; - - keypair.mm2_internal_public_id().bytes.into() - }, - Err(_) => return, + let our_public_id: H256Json = match CryptoCtx::from_ctx(&ctx).map(|c| c.keypair_ctx()) { + Ok(Some(keypair)) => keypair.mm2_internal_public_id().bytes.into(), + _ => return, }; if our_public_id == from_pubkey { @@ -4048,15 +4036,9 @@ async fn process_taker_connect(ctx: MmArc, sender_pubkey: PublicKey, connect_msg log::debug!("Processing TakerConnect {:?}", connect_msg); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); - let our_public_id = match CryptoCtx::from_ctx(&ctx) { - Ok(ctx) => { - let Some(keypair) = ctx.keypair_ctx() else { - return; - }; - - keypair.mm2_internal_public_id() - }, - Err(_) => return, + let our_public_id = match CryptoCtx::from_ctx(&ctx).map(|c| c.keypair_ctx()) { + Ok(Some(keypair)) => keypair.mm2_internal_public_id(), + _ => return, }; let sender_unprefixed = sender_pubkey.unprefixed();