diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 47722ccab2..b0d0b0f000 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -832,12 +832,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)? + .keypair_ctx() + .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; 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 1626e9d8af..dc0426c125 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4395,12 +4395,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)? + .keypair_ctx() + .ok_or(MmError::new(CryptoCtxError::NotInitialized))?; 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 e591ac37b4..d56b1a2576 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -88,7 +88,13 @@ impl From for MetamaskCtxInitError { fn from(value: MetamaskError) -> Self { MetamaskCtxInitError::MetamaskError(value) } } -pub struct CryptoCtx { +#[derive(Clone)] +pub enum KeyPairPolicy { + Iguana, + GlobalHDAccount(GlobalHDAccountArc), +} + +pub struct KeyPairCtx { /// secp256k1 key pair derived from either: /// * Iguana passphrase, /// cf. `key_pair_from_seed`; @@ -96,44 +102,19 @@ pub struct CryptoCtx { /// cf. [`GlobalHDAccountCtx::new`]. secp256k1_key_pair: KeyPair, key_pair_policy: KeyPairPolicy, - /// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`]. - hw_ctx: RwLock>, - #[cfg(target_arch = "wasm32")] - metamask_ctx: RwLock>, } -impl CryptoCtx { - 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 from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { - let ctx_field = 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()))) - } - - #[inline] +impl KeyPairCtx { + #[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(); + // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. bits256 { bytes: *array_ref!(public, 1, 32), @@ -147,9 +128,9 @@ impl CryptoCtx { /// /// # 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. @@ -159,11 +140,11 @@ impl CryptoCtx { /// /// # 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] - pub fn mm2_internal_pubkey(&self) -> PublicKey { *self.secp256k1_key_pair.public() } + #[inline(always)] + 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. @@ -172,11 +153,11 @@ impl CryptoCtx { /// /// # 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] - pub fn mm2_internal_pubkey_hex(&self) -> String { hex::encode(&*self.mm2_internal_pubkey()) } + #[inline(always)] + 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. @@ -185,33 +166,89 @@ impl CryptoCtx { /// /// # 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] - pub fn mm2_internal_privkey_secret(&self) -> Secp256k1Secret { self.secp256k1_key_pair.private().secret } + #[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] - pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.secp256k1_key_pair.private().secret.as_slice() } + #[inline(always)] + pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.mm2_internal_privkey_secret().as_slice() } +} - #[inline] +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>, + // TODO: WalletConnectCtx goes here! +} + +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 result = Arc::new(selfi); + { + let mut ctx_field = ctx.crypto_ctx.lock().unwrap(); + *ctx_field = Some(result.clone()); + } + + 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().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(always)] + pub fn keypair_ctx(&self) -> Option> { self.keypair_ctx.read().to_option().cloned() } + + #[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> { @@ -285,33 +322,26 @@ 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.keypair_ctx().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).map_mm_err()?; - let crypto_ctx = CryptoCtx { - secp256k1_key_pair, + let keypair = KeyPairCtx { key_pair_policy, - hw_ctx: RwLock::new(InitializationState::NotInitialized), - #[cfg(target_arch = "wasm32")] - metamask_ctx: RwLock::new(InitializationState::NotInitialized), + secp256k1_key_pair, }; - let result = Arc::new(crypto_ctx); - *ctx_field = Some(result.clone()); - drop(ctx_field); + let keypair = InitializationState::Ready(keypair.into()); + *ctx_field.keypair_ctx.write() = keypair; ctx.rmd160 .set(rmd160) @@ -322,7 +352,8 @@ impl CryptoCtx { info!("Public key hash: {rmd160}"); info!("Shared Database ID: {shared_db_id}"); - Ok(result) + + Ok(ctx_field) } } @@ -348,12 +379,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_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index e159298c49..07d1bf2d08 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -243,6 +243,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_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs index fb09652f09..cab5d13b4b 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}; @@ -348,7 +347,10 @@ 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 crypto_ctx = CryptoCtx::from_ctx(&ctx) + .unwrap() + .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 3600370ffc..b29b8cc5fe 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -365,10 +365,14 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { init_ordermatch_context(&ctx).map_mm_err()?; init_p2p(ctx.clone()).await.map_mm_err()?; - if !CryptoCtx::is_init(&ctx).map_mm_err()? { + if !CryptoCtx::is_crypto_keypair_ctx_init(&ctx).map_mm_err()? { return Ok(()); } + init_crypto_keypair_ctx_services(ctx).await +} + +pub async fn init_crypto_keypair_ctx_services(ctx: MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] { fix_directories(&ctx)?; @@ -474,10 +478,12 @@ async fn kick_start(ctx: MmArc) -> MmInitResult<()> { fn get_p2p_key(ctx: &MmArc, is_seed_node: bool) -> P2PResult<[u8; 32]> { // TODO: Use persistent peer ID regardless the node type. + let crypto_ctx = + CryptoCtx::from_ctx(ctx).map(|c| c.keypair_ctx().map(|v| sha256(v.mm2_internal_privkey_slice()).take())); + if is_seed_node { - if let Ok(crypto_ctx) = CryptoCtx::from_ctx(ctx) { - let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); - return Ok(key.take()); + if let Ok(Some(key)) = crypto_ctx { + return Ok(key); } } @@ -533,7 +539,7 @@ fn p2p_precheck(ctx: &MmArc) -> P2PResult<()> { } } - if is_seed_node && !CryptoCtx::is_init(ctx).unwrap_or(false) { + if is_seed_node && !CryptoCtx::is_crypto_keypair_ctx_init(ctx).unwrap_or(false) { return precheck_err("Seed node requires a persistent identity to generate its P2P key."); } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index f874eac21b..3244fdf287 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -527,7 +527,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.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), } @@ -2394,6 +2394,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") + .keypair_ctx() + .expect("`CryptoCtx::keypair_ctx` not initialized with a seedphrase") .mm2_internal_pubkey_hex(); while !ctx.is_stopping() { @@ -3121,7 +3123,10 @@ 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"); + let crypto_ctx = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .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(); @@ -3351,7 +3356,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") + .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(); @@ -3574,6 +3582,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") + .keypair_ctx() + .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); @@ -3858,6 +3868,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") + .keypair_ctx() + .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"); @@ -3962,9 +3974,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) => ctx.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(); @@ -4016,9 +4028,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) => ctx.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 { @@ -4131,9 +4143,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) => ctx.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(); @@ -4320,7 +4332,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)) + .keypair_ctx() + .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 29b8b6fdf2..3cd7dbb9b5 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -245,7 +245,10 @@ 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"); + let crypto_ctx = CryptoCtx::from_ctx(ctx) + .expect("CryptoCtx must be initialized already") + .keypair_ctx() + .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); (*crypto_ctx.mm2_internal_key_pair(), None) }, } @@ -261,7 +264,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") + .keypair_ctx() + .expect("'CryptoCtx::keypair_ctx' must be initialized with passphrase"); + (crypto_ctx.mm2_internal_privkey_secret().take(), None) }, } @@ -2157,9 +2164,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(); + .keypair_ctx() + .expect("`CryptoCtx::keypair_ctx` must be initialized"); + let maker_key_pair = crypto_ctx.mm2_internal_key_pair(); fix_directories(&maker_ctx).unwrap(); block_on(init_p2p(maker_ctx.clone())).unwrap(); @@ -2195,9 +2204,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(); + .keypair_ctx() + .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 69cba10ac4..0b214c291f 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; @@ -1403,7 +1403,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)) + .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 ecc8490b28..b6c65e5cf4 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; @@ -2129,7 +2130,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)) + .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()); my_persistent_pub.into() @@ -2661,7 +2664,11 @@ 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).map_mm_err()?.mm2_internal_public_id(); + let our_public_id = CryptoCtx::from_ctx(ctx) + .map_mm_err()? + .keypair_ctx() + .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) @@ -2755,23 +2762,23 @@ pub async fn max_taker_vol(ctx: MmArc, req: Json) -> Result>, S /// Let `real_max_vol` be the actual desired volume. Performing the following steps yields /// an approximate maximum volume: /// -/// - `max_possible = balance - locked_amount` +/// - `max_possible = balance - locked_amount` /// The largest possible max volume, replacing unknown fees with zero. -/// - `max_trade_fee = trade_fee(max_possible)` +/// - `max_trade_fee = trade_fee(max_possible)` /// The largest possible `trade_fee`. -/// - `max_possible_2 = balance - locked_amount - max_trade_fee` +/// - `max_possible_2 = balance - locked_amount - max_trade_fee` /// A more accurate upper bound (`real_max_vol <= max_possible_2 <= max_possible`). -/// - `max_dex_fee = dex_fee(max_possible_2)` +/// - `max_dex_fee = dex_fee(max_possible_2)` /// Passed into `fee_to_send_taker_fee`. /// - `max_fee_to_send_taker_fee = fee_to_send_taker_fee(max_dex_fee)` /// -/// After that, +/// After that, /// ```rust /// min_max_vol = balance - locked_amount /// - max_trade_fee /// - max_fee_to_send_taker_fee /// - dex_fee(max_vol) -/// ``` +/// ``` /// can be solved as in the first case. /// pub async fn calc_max_taker_vol( diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index c2392c3911..2e7f04a3b1 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -1,7 +1,7 @@ use common::password_policy::{password_policy, PasswordPolicyError}; 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 derive_more::Display; use enum_derives::EnumFromStringify; use http::StatusCode; @@ -11,6 +11,8 @@ use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; +use crate::lp_native_dex::{init_crypto_keypair_ctx_services, MmInitError}; + cfg_wasm32! { use crate::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; @@ -169,12 +171,12 @@ async fn try_load_wallet_passphrase( #[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. @@ -304,10 +306,10 @@ 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, + wallet_name: Option<&str>, ) -> WalletInitResult> { match (wallet_name, passphrase) { (None, None) => Ok(None), @@ -326,12 +328,13 @@ 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.enable_hd() { true => CryptoCtx::init_with_global_hd_account(ctx.clone(), passphrase).map_mm_err()?, false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), passphrase).map_mm_err()?, }; + Ok(()) } @@ -356,19 +359,28 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// 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)?; + ctx.wallet_name .set(wallet_name.clone()) - .map_to_mm(|_| WalletInitError::InternalError("Already Initialized".to_string())) + .map_to_mm(|_| WalletInitError::InternalError("Wallet already initialized".to_string())) .map_mm_err()?; - let passphrase = process_passphrase_logic(ctx, wallet_name.as_deref(), passphrase) + if ctx.no_login_mode() { + CryptoCtx::new_uninitialized(ctx); + return Ok(()); + } + + let processed_passphrase = process_passphrase_logic(ctx, maybe_passphrase, wallet_name.as_deref()) .await .map_mm_err()?; - if let Some(passphrase) = passphrase { - initialize_crypto_context(ctx, &passphrase).map_mm_err()?; + if let Some(passphrase) = processed_passphrase { + return initialize_crypto_context(ctx, &passphrase); } + // If we reach this point, the wallet remains uninitialized. + CryptoCtx::new_uninitialized(ctx); + Ok(()) } @@ -700,3 +712,90 @@ pub async fn delete_wallet_rpc(ctx: MmArc, req: DeleteWalletRequest) -> MmResult }, } } + +#[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 {} + +/// 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() { + return MmError::err(CryptoCtxRpcError::AlreadyInitialized); + }; + + let (wallet_name, maybe_passphrase) = deserialize_wallet_config(&ctx).map_mm_err()?; + let passphrase = process_passphrase_logic(&ctx, maybe_passphrase, wallet_name.as_deref()) + .await + .map_mm_err()? + .ok_or(MmError::new(CryptoCtxRpcError::InternalError( + "No passphrase was processed".to_owned(), + )))?; + + initialize_crypto_context(&ctx, &passphrase).map_mm_err()?; + init_crypto_keypair_ctx_services(ctx).await.map_mm_err()?; + + 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.keypair_ctx().is_some(), + }) +} diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 6eb5457aaf..8dc4fd0100 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1078,7 +1078,10 @@ 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 crypto_ctx = CryptoCtx::from_ctx(ctx) + .unwrap() + .keypair_ctx() + .expect("`CryptoCtx::keypair_ctx` must be initialized with a passphrase"); let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); key.take() }; @@ -1802,7 +1805,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 + .keypair_ctx() + .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(); @@ -1852,7 +1859,10 @@ 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 crypto_ctx = CryptoCtx::from_ctx(ctx) + .unwrap() + .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/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 6e55fc0e65..f4c7ccf490 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -11,7 +11,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, delete_wallet_rpc, get_mnemonic_rpc, get_wallet_names_rpc}; +use crate::lp_wallet::{change_mnemonic_password, delete_wallet_rpc, 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::lr_swap::{lr_execute_routed_trade_rpc, lr_find_best_quote_rpc, lr_get_quotes_for_tokens_rpc}; @@ -219,6 +220,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_state" => handle_mmrpc(ctx, request, get_crypto_ctxs_init_state).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins_rpc).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, @@ -233,6 +235,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_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, diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs index 6a84641f45..3f6c914452 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -35,6 +35,8 @@ impl HttpStatusCode for GetPublicKeyError { pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { let public_key = CryptoCtx::from_ctx(&ctx) .map_mm_err()? + .keypair_ctx() + .ok_or(CryptoCtxError::NotInitialized)? .mm2_internal_pubkey() .to_string(); Ok(GetPublicKeyResponse { public_key }) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs b/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs index 45345ef4d5..d3dea82e3c 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs @@ -1200,6 +1200,7 @@ fn test_zombie_order_after_balance_reduce_and_mm_restart() { "coins": coins, "rpc_password": "pass", "i_am_seed": false, + "is_bootstrap_node": true, "seednodes": [mm_seed.ip], }); 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 aca3f247f6..ad17402210 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -4076,11 +4076,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(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() - .clone() - else { + .keypair_ctx() + .unwrap(); + let KeyPairPolicy::GlobalHDAccount(hd_acc) = crypto_ctx.key_pair_policy() else { panic!("Expected 'KeyPairPolicy::GlobalHDAccount'"); }; 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 439467c334..3df7511f58 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -5731,6 +5731,56 @@ 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, + "is_bootstrap_node": 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, + "is_bootstrap_node": 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() { @@ -6691,7 +6741,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, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 37a8b6bfb4..e2d84ed4be 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -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; @@ -439,6 +441,7 @@ impl Mm2TestConf { "coins": coins, "rpc_password": DEFAULT_RPC_PASSWORD, "seednodes": seednodes, + }), rpc_password: DEFAULT_RPC_PASSWORD.into(), } @@ -4132,3 +4135,77 @@ 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() } + +#[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(); + 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); +}