Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
947f901
allow no seed kdf initialization
borngraced Apr 9, 2025
d8677f4
initializing crypto ctx with empty state
borngraced Apr 9, 2025
d4ecc11
fix deadlock
borngraced Apr 9, 2025
42e854f
organizations and critical fix
borngraced Apr 9, 2025
8718bc4
refactor CryptoCtx
borngraced Apr 9, 2025
c500ab5
add todo
borngraced Apr 9, 2025
58bcd5f
complete init_crypto_ctx rpc
borngraced Apr 10, 2025
cd8d95e
fix fmt
borngraced Apr 10, 2025
2eca55e
use CryptoCtx::new_uninitialized in trezor test helper
borngraced Apr 10, 2025
342e831
more informative panic message for uninitialized CryptoCtx::Keypair
borngraced Apr 10, 2025
9a52103
implement get_crypto_ctxs_init_state rpc to get initialized crypto co…
borngraced Apr 10, 2025
9447461
inline one liners functions, rename internal_keypair -> keypair_ctx
borngraced Apr 10, 2025
e1b96e5
init_crypto_ctx -> init_crypto_keypair_ctx
borngraced Apr 10, 2025
7fd762a
add no_login_mode flag and refactor crypto keypair init
borngraced Apr 10, 2025
11e2bda
implement unit tests and minor changes
borngraced Apr 10, 2025
8431afe
fix wasm clippy
borngraced Apr 10, 2025
69e0e14
nits
borngraced Apr 14, 2025
ccfa64c
Merge branch 'dev' into no-passphrase-kdf-init
borngraced Apr 16, 2025
d0953e0
Merge branch 'dev' into no-passphrase-kdf-init
borngraced May 14, 2025
1b83c43
Merge branch 'dev' into no-passphrase-kdf-init
borngraced May 20, 2025
86c1988
Merge branch 'dev' into no-passphrase-kdf-init
borngraced May 26, 2025
3a90ad6
merge with dev and fix conflicts
borngraced May 30, 2025
b572f31
merge with origin/dev
shamardy Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EthPrivKeyBuildPolicy, CryptoCtxError> {
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())),
Expand Down
6 changes: 4 additions & 2 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PrivKeyBuildPolicy, CryptoCtxError> {
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())),
}
Expand Down
165 changes: 95 additions & 70 deletions mm2src/crypto/src/crypto_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,52 +88,33 @@ impl From<MetamaskError> 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`;
/// * BIP39 passphrase at `mm2_internal_der_path`,
/// cf. [`GlobalHDAccountCtx::new`].
secp256k1_key_pair: KeyPair,
key_pair_policy: KeyPairPolicy,
/// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`].
hw_ctx: RwLock<InitializationState<HardwareWalletArc>>,
#[cfg(target_arch = "wasm32")]
metamask_ctx: RwLock<InitializationState<MetamaskArc>>,
}

impl CryptoCtx {
pub fn is_init(ctx: &MmArc) -> MmResult<bool, InternalError> {
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<Arc<CryptoCtx>, 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),
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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<InitializationState<Arc<KeyPairCtx>>>,
/// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`].
hw_ctx: RwLock<InitializationState<HardwareWalletArc>>,
#[cfg(target_arch = "wasm32")]
metamask_ctx: RwLock<InitializationState<MetamaskArc>>,
// TODO: WalletConnectCtx goes here!
}

impl CryptoCtx {
pub fn new_uninitialized(ctx: &MmArc) -> Arc<Self> {
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<bool, InternalError> {
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<Arc<CryptoCtx>, 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<Arc<KeyPairCtx>> { self.keypair_ctx.read().to_option().cloned() }

#[inline(always)]
pub fn hw_ctx(&self) -> Option<HardwareWalletArc> { self.hw_ctx.read().to_option().cloned() }

#[cfg(target_arch = "wasm32")]
#[inline(always)]
pub fn metamask_ctx(&self) -> Option<MetamaskArc> { 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<H160> { self.hw_ctx.read().to_option().map(|hw_ctx| hw_ctx.rmd160()) }

pub fn init_with_iguana_passphrase(ctx: MmArc, passphrase: &str) -> CryptoInitResult<Arc<CryptoCtx>> {
Expand Down Expand Up @@ -285,33 +322,26 @@ impl CryptoCtx {
passphrase: &str,
policy_builder: KeyPairPolicyBuilder,
) -> CryptoInitResult<Arc<CryptoCtx>> {
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)
Expand All @@ -322,7 +352,8 @@ impl CryptoCtx {

info!("Public key hash: {rmd160}");
info!("Shared Database ID: {shared_db_id}");
Ok(result)

Ok(ctx_field)
}
}

Expand All @@ -348,12 +379,6 @@ impl KeyPairPolicyBuilder {
}
}

#[derive(Clone)]
pub enum KeyPairPolicy {
Iguana,
GlobalHDAccount(GlobalHDAccountArc),
}

async fn init_check_hw_ctx_with_trezor(
processor: Arc<dyn TrezorConnectProcessor<Error = RpcTaskError>>,
expected_pubkey: Option<HwPubkey>,
Expand Down
2 changes: 2 additions & 0 deletions mm2src/mm2_core/src/mm_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SocketAddr, String> {
let port = match self.conf.get("rpcport") {
Expand Down
6 changes: 4 additions & 2 deletions mm2src/mm2_main/src/lp_healthcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()
};
Expand Down
16 changes: 11 additions & 5 deletions mm2src/mm2_main/src/lp_native_dex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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.");
}

Expand Down
Loading
Loading