From e4988ed78d410802a3cbad0a996e71f7e2e0dbdd Mon Sep 17 00:00:00 2001 From: benthecarman Date: Mon, 15 Jan 2024 21:02:09 +0000 Subject: [PATCH] Allow overriding derived nsec --- mutiny-core/src/lib.rs | 9 +++++++- mutiny-core/src/nostr/mod.rs | 17 ++++++++++++--- mutiny-core/src/nostr/nwc.rs | 10 +++++---- mutiny-core/src/storage.rs | 2 ++ mutiny-wasm/src/lib.rs | 41 +++++++++++++++++++++++++++++++++++- 5 files changed, 70 insertions(+), 9 deletions(-) diff --git a/mutiny-core/src/lib.rs b/mutiny-core/src/lib.rs index a66c90f61..22982a951 100644 --- a/mutiny-core/src/lib.rs +++ b/mutiny-core/src/lib.rs @@ -67,7 +67,7 @@ use crate::{ subscription::MutinySubscriptionClient, }; use crate::{nostr::NostrManager, utils::sleep}; -use ::nostr::key::XOnlyPublicKey; +use ::nostr::key::{SecretKey, XOnlyPublicKey}; use ::nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag}; use async_lock::RwLock; use bdk_chain::ConfirmationTime; @@ -397,6 +397,7 @@ pub struct MutinyWalletConfig { pub struct MutinyWalletBuilder { xprivkey: ExtendedPrivKey, + nsec_override: Option, storage: S, glue_db: Option, config: Option, @@ -415,6 +416,7 @@ impl MutinyWalletBuilder { MutinyWalletBuilder:: { xprivkey, storage, + nsec_override: None, glue_db: None, config: None, session_id: None, @@ -440,6 +442,10 @@ impl MutinyWalletBuilder { self } + pub fn with_nsec(&mut self, nsec: SecretKey) { + self.nsec_override = Some(nsec) + } + pub fn with_glue_db(&mut self, glue_db: GlueDB) { self.glue_db = Some(glue_db); } @@ -517,6 +523,7 @@ impl MutinyWalletBuilder { // create nostr manager let nostr = Arc::new(NostrManager::from_mnemonic( self.xprivkey, + self.nsec_override, self.storage.clone(), logger.clone(), stop.clone(), diff --git a/mutiny-core/src/nostr/mod.rs b/mutiny-core/src/nostr/mod.rs index 685ae2ba5..1e31b1892 100644 --- a/mutiny-core/src/nostr/mod.rs +++ b/mutiny-core/src/nostr/mod.rs @@ -1082,6 +1082,13 @@ impl NostrManager { Ok(None) } + /// Decrypts a DM using the primary key + pub fn decrypt_dm(&self, pubkey: XOnlyPublicKey, message: &str) -> Result { + let secret = self.primary_key.secret_key().expect("must have"); + let decrypted = decrypt(&secret, &pubkey, message)?; + Ok(decrypted) + } + /// Derives the client and server keys for Nostr Wallet Connect given a profile index /// The left key is the client key and the right key is the server key pub(crate) fn derive_nwc_keys( @@ -1135,14 +1142,18 @@ impl NostrManager { /// Creates a new NostrManager pub fn from_mnemonic( xprivkey: ExtendedPrivKey, + nsec_override: Option, storage: S, logger: Arc, stop: Arc, ) -> Result { let context = Secp256k1::new(); - // generate the default primary key - let primary_key = Self::derive_nostr_key(&context, xprivkey, 0, None, None)?; + // use provided nsec, otherwise generate it from seed + let primary_key = match nsec_override { + Some(nsec) => Keys::new(nsec), + None => Self::derive_nostr_key(&context, xprivkey, 0, None, None)?, + }; // get from storage let profiles: Vec = storage.get_data(NWC_STORAGE_KEY)?.unwrap_or_default(); @@ -1216,7 +1227,7 @@ mod test { let stop = Arc::new(AtomicBool::new(false)); - NostrManager::from_mnemonic(xprivkey, storage, logger, stop).unwrap() + NostrManager::from_mnemonic(xprivkey, None, storage, logger, stop).unwrap() } #[test] diff --git a/mutiny-core/src/nostr/nwc.rs b/mutiny-core/src/nostr/nwc.rs index 5f04e890b..1b09785e1 100644 --- a/mutiny-core/src/nostr/nwc.rs +++ b/mutiny-core/src/nostr/nwc.rs @@ -1217,7 +1217,7 @@ mod wasm_test { let xprivkey = ExtendedPrivKey::new_master(Network::Regtest, &[0; 64]).unwrap(); let stop = Arc::new(AtomicBool::new(false)); let nostr_manager = - NostrManager::from_mnemonic(xprivkey, storage.clone(), mw.logger.clone(), stop) + NostrManager::from_mnemonic(xprivkey, None, storage.clone(), mw.logger.clone(), stop) .unwrap(); let profile = nostr_manager @@ -1266,7 +1266,8 @@ mod wasm_test { let xprivkey = ExtendedPrivKey::new_master(Network::Regtest, &[0; 64]).unwrap(); let stop = Arc::new(AtomicBool::new(false)); let nostr_manager = - NostrManager::from_mnemonic(xprivkey, storage.clone(), logger.clone(), stop).unwrap(); + NostrManager::from_mnemonic(xprivkey, None, storage.clone(), logger.clone(), stop) + .unwrap(); let profile = nostr_manager .create_new_profile( @@ -1444,6 +1445,7 @@ mod wasm_test { let stop = Arc::new(AtomicBool::new(false)); let nostr_manager = NostrManager::from_mnemonic( xprivkey, + None, storage.clone(), Arc::new(MutinyLogger::default()), stop, @@ -1494,7 +1496,7 @@ mod wasm_test { let xprivkey = ExtendedPrivKey::new_master(Network::Regtest, &[0; 64]).unwrap(); let stop = Arc::new(AtomicBool::new(false)); let nostr_manager = - NostrManager::from_mnemonic(xprivkey, storage.clone(), mw.logger.clone(), stop) + NostrManager::from_mnemonic(xprivkey, None, storage.clone(), mw.logger.clone(), stop) .unwrap(); let budget = 10_000; @@ -1576,7 +1578,7 @@ mod wasm_test { let xprivkey = ExtendedPrivKey::new_master(Network::Regtest, &[0; 64]).unwrap(); let stop = Arc::new(AtomicBool::new(false)); let nostr_manager = - NostrManager::from_mnemonic(xprivkey, storage.clone(), logger, stop).unwrap(); + NostrManager::from_mnemonic(xprivkey, None, storage.clone(), logger, stop).unwrap(); let budget = 10_000; let profile = nostr_manager diff --git a/mutiny-core/src/storage.rs b/mutiny-core/src/storage.rs index c9fb09b10..6f5bcf36d 100644 --- a/mutiny-core/src/storage.rs +++ b/mutiny-core/src/storage.rs @@ -20,6 +20,7 @@ use uuid::Uuid; pub const KEYCHAIN_STORE_KEY: &str = "bdk_keychain"; pub const MNEMONIC_KEY: &str = "mnemonic"; +pub const NSEC_KEY: &str = "nsec"; pub(crate) const NEED_FULL_SYNC_KEY: &str = "needs_full_sync"; pub const NODES_KEY: &str = "nodes"; pub const FEDERATIONS_KEY: &str = "federations"; @@ -34,6 +35,7 @@ pub const LAST_DM_SYNC_TIME_KEY: &str = "last_dm_sync_time"; fn needs_encryption(key: &str) -> bool { match key { MNEMONIC_KEY => true, + NSEC_KEY => true, str if str.starts_with(CHANNEL_MANAGER_KEY) => true, _ => false, } diff --git a/mutiny-wasm/src/lib.rs b/mutiny-wasm/src/lib.rs index 305dcdfd9..d9d1bad45 100644 --- a/mutiny-wasm/src/lib.rs +++ b/mutiny-wasm/src/lib.rs @@ -43,7 +43,8 @@ use mutiny_core::{ nodemanager::{create_lsp_config, NodeManager}, }; use mutiny_core::{logging::MutinyLogger, nostr::ProfileType}; -use nostr::ToBech32; +use nostr::key::{Secp256k1, SecretKey}; +use nostr::{FromBech32, ToBech32}; use std::str::FromStr; use std::sync::Arc; use std::{ @@ -99,6 +100,7 @@ impl MutinyWallet { skip_device_lock: Option, safe_mode: Option, skip_hodl_invoices: Option, + nsec_override: Option, ) -> Result { utils::set_panic_hook(); let mut init = INITIALIZED.lock().await; @@ -126,6 +128,7 @@ impl MutinyWallet { skip_device_lock, safe_mode, skip_hodl_invoices, + nsec_override, ) .await { @@ -157,6 +160,7 @@ impl MutinyWallet { skip_device_lock: Option, safe_mode: Option, skip_hodl_invoices: Option, + nsec_override: Option, ) -> Result { let safe_mode = safe_mode.unwrap_or(false); let logger = Arc::new(MutinyLogger::default()); @@ -266,7 +270,13 @@ impl MutinyWallet { let mut mw_builder = MutinyWalletBuilder::new(xprivkey, storage).with_config(config); mw_builder.with_session_id(logger.session_id.clone()); + if let Some(nsec) = nsec_override { + let nsec = + SecretKey::from_bech32(&nsec).map_err(|_| MutinyJsError::InvalidArgumentsError)?; + mw_builder.with_nsec(nsec); + } let inner = mw_builder.build().await?; + Ok(MutinyWallet { mnemonic, inner }) } @@ -1579,6 +1589,12 @@ impl MutinyWallet { Ok(()) } + /// Decrypts a DM using the primary key + pub fn decrypt_dm(&self, pubkey: String, message: String) -> Result { + let pubkey = parse_npub(&pubkey)?; + Ok(self.inner.nostr.decrypt_dm(pubkey, &message)?) + } + /// Resets the scorer and network graph. This can be useful if you get stuck in a bad state. #[wasm_bindgen] pub async fn reset_router(&self) -> Result<(), MutinyJsError> { @@ -1690,6 +1706,18 @@ impl MutinyWallet { bitcoin::Amount::from_sat(sats).to_btc() } + /// Convert an npub string to a hex string + #[wasm_bindgen] + pub async fn nsec_to_npub(nsec: String) -> Result { + let nsec = + SecretKey::from_bech32(&nsec).map_err(|_| MutinyJsError::InvalidArgumentsError)?; + Ok(nsec + .x_only_public_key(&Secp256k1::new()) + .0 + .to_bech32() + .expect("bech32")) + } + /// Convert an npub string to a hex string #[wasm_bindgen] pub async fn npub_to_hexpub(npub: String) -> Result { @@ -1745,6 +1773,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1777,6 +1806,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1803,6 +1833,7 @@ mod tests { None, None, None, + None, ) .await; @@ -1842,6 +1873,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -1867,6 +1899,7 @@ mod tests { None, None, None, + None, ) .await; @@ -1913,6 +1946,7 @@ mod tests { None, None, None, + None, ) .await .unwrap(); @@ -1957,6 +1991,7 @@ mod tests { None, None, None, + None, ) .await .unwrap(); @@ -1988,6 +2023,7 @@ mod tests { None, None, None, + None, ) .await; @@ -2023,6 +2059,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -2088,6 +2125,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize"); @@ -2142,6 +2180,7 @@ mod tests { None, None, None, + None, ) .await .expect("mutiny wallet should initialize");