diff --git a/bindings/core/src/lib.rs b/bindings/core/src/lib.rs index dabde56ed6..1332b3f7d4 100644 --- a/bindings/core/src/lib.rs +++ b/bindings/core/src/lib.rs @@ -80,6 +80,7 @@ pub(crate) trait OmittedDebug { f.write_str("") } } +impl OmittedDebug for crypto::keys::bip39::Mnemonic {} impl OmittedDebug for String {} impl OmittedDebug for SecretManagerDto {} impl OmittedDebug for Option { diff --git a/bindings/core/tests/secrets_debug.rs b/bindings/core/tests/secrets_debug.rs index d133a94bbd..657c66f2f3 100644 --- a/bindings/core/tests/secrets_debug.rs +++ b/bindings/core/tests/secrets_debug.rs @@ -27,8 +27,10 @@ fn method_interface_secrets_debug() { ); } + let mnemonic = "giant dynamic museum toddler six deny defense ostrich bomb access mercy blood explain muscle shoot shallow glad autumn author calm heavy hawk abuse rally"; + let client_method = UtilsMethod::MnemonicToHexSeed { - mnemonic: "mnemonic".to_string(), + mnemonic: mnemonic.to_string(), }; assert_eq!( format!("{:?}", client_method), @@ -36,11 +38,11 @@ fn method_interface_secrets_debug() { ); let wallet_method = UtilsMethod::VerifyMnemonic { - mnemonic: "mnemonic".to_string(), + mnemonic: mnemonic.to_string(), }; assert_eq!(format!("{:?}", wallet_method), "VerifyMnemonic { mnemonic: }"); - let response = Response::GeneratedMnemonic("mnemonic".to_string()); + let response = Response::GeneratedMnemonic(mnemonic.to_owned()); assert_eq!(format!("{:?}", response), "GeneratedMnemonic()"); let wallet_options = WalletOptions { diff --git a/cli/src/helper.rs b/cli/src/helper.rs index f0915e31d9..9c2c2aa1e1 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -201,7 +201,7 @@ pub async fn import_mnemonic(path: &str) -> Result { } } -async fn write_mnemonic_to_file(path: &str, mnemonic: &str) -> Result<(), Error> { +async fn write_mnemonic_to_file(path: &str, mnemonic: &Mnemonic) -> Result<(), Error> { let mut open_options = OpenOptions::new(); open_options.create(true).append(true); @@ -209,7 +209,7 @@ async fn write_mnemonic_to_file(path: &str, mnemonic: &str) -> Result<(), Error> open_options.mode(0o600); let mut file = open_options.open(path).await?; - file.write_all(format!("{mnemonic}\n").as_bytes()).await?; + file.write_all(format!("{}\n", mnemonic.as_ref()).as_bytes()).await?; Ok(()) } diff --git a/sdk/src/client/secret/mnemonic.rs b/sdk/src/client/secret/mnemonic.rs index 09aab95466..41b2f46e44 100644 --- a/sdk/src/client/secret/mnemonic.rs +++ b/sdk/src/client/secret/mnemonic.rs @@ -154,6 +154,76 @@ impl MnemonicSecretManager { } } +// impl From for MnemonicSecretManager { +// fn from(m: Mnemonic) -> Self { +// Self(Client::mnemonic_to_seed(&m)) +// } +// } + +// /// A mnemonic (space separated list of words) that allows to create a seed from. +// #[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] +// pub struct Mnemonic(String); + +// impl Mnemonic { +// pub fn as_str(&self) -> &str { +// self.0.as_str() +// } +// } + +// impl TryFrom for Mnemonic { +// type Error = Error; + +// fn try_from(value: String) -> Result { +// let value: Zeroizing = Zeroizing::new(value); +// // trim because empty spaces could create a different seed https://github.com/iotaledger/crypto.rs/issues/125 +// let trimmed = value.as_str().trim(); +// // first we check if the mnemonic is valid to give meaningful errors +// if let Err(err) = crypto::keys::bip39::wordlist::verify(trimmed, &crypto::keys::bip39::wordlist::ENGLISH) { +// Err(crate::client::Error::InvalidMnemonic(format!("{err:?}"))) +// } else { +// Ok(Self(trimmed.to_string())) +// } +// } +// } + +// pub trait MnemonicLike: Send { +// fn to_mnemonic(self) -> Result; +// } + +// impl MnemonicLike for Mnemonic { +// fn to_mnemonic(self) -> Result { +// Ok(self) +// } +// } + +// impl MnemonicLike for String { +// fn to_mnemonic(self) -> Result { +// Mnemonic::try_from(self) +// } +// } + +// impl MnemonicLike for Vec { +// fn to_mnemonic(mut self) -> Result { +// let m = self.join(" "); +// self.zeroize(); +// Mnemonic::try_from(m) +// } +// } + +// impl MnemonicLike for [&'static str; 24] { +// fn to_mnemonic(self) -> Result { +// let m = self.join(" "); +// Mnemonic::try_from(m) +// } +// } + +// // that's only necessary to use it in `assert!` macros +// impl core::fmt::Debug for Mnemonic { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// write!(f, "") +// } +// } + #[cfg(test)] mod tests { use super::*; @@ -194,4 +264,65 @@ mod tests { "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r" ); } + + // #[test] + // fn mnemonic_like() { + // // Mnemonic from a space-separated word list stored in a `String` + // let mnemonic1 = "giant dynamic museum toddler six deny defense ostrich bomb access mercy blood explain muscle + // shoot shallow glad autumn author calm heavy hawk abuse rally".to_owned().to_mnemonic().unwrap(); // Mnemonic + // from a word list stored in a `Vec` let mnemonic2 = [ + // "giant", "dynamic", "museum", "toddler", "six", "deny", "defense", "ostrich", "bomb", "access", "mercy", + // "blood", "explain", "muscle", "shoot", "shallow", "glad", "autumn", "author", "calm", "heavy", "hawk", + // "abuse", "rally", + // ] + // .into_iter() + // .map(|s| s.to_owned()) + // .collect::>() + // .to_mnemonic() + // .unwrap(); + // // Mnemonic from a word list stored in a `[&'static str; 24]` + // let mnemonic3 = [ + // "giant", "dynamic", "museum", "toddler", "six", "deny", "defense", "ostrich", "bomb", "access", "mercy", + // "blood", "explain", "muscle", "shoot", "shallow", "glad", "autumn", "author", "calm", "heavy", "hawk", + // "abuse", "rally", + // ] + // .to_mnemonic() + // .unwrap(); + + // assert_eq!(mnemonic1, mnemonic2); + // assert_eq!(mnemonic1, mnemonic3); + // assert_eq!(mnemonic2, mnemonic3); + + // // Different mnemonic + // let mnemonic4 = [ + // "endorse", "answer", "radar", "about", "source", "reunion", "marriage", "tag", "sausage", "weekend", + // "frost", "daring", "base", "attack", "because", "joke", "dream", "slender", "leisure", "group", "reason", + // "prepare", "broken", "river", + // ] + // .to_mnemonic() + // .unwrap(); + + // assert_ne!(mnemonic1, mnemonic4); + // assert_ne!(mnemonic2, mnemonic4); + // assert_ne!(mnemonic3, mnemonic4); + + // // Incorrect mnemonic + // assert!( + // [ + // "dynamic", "giant", "museum", "toddler", "six", "deny", "defense", "ostrich", "bomb", "access", + // "mercy", "blood", "explain", "muscle", "shoot", "shallow", "glad", "autumn", "author", "calm", + // "heavy", "hawk", "abuse", "rally" + // ] + // .to_mnemonic() + // .is_err() + // ); + // } + + // #[test] + // fn zeroize_mnemonic() { + // let mut mnemonic1 = "giant dynamic museum toddler six deny defense ostrich bomb access mercy blood explain + // muscle shoot shallow glad autumn author calm heavy hawk abuse rally".to_owned().to_mnemonic().unwrap(); + // mnemonic1.zeroize(); + // assert!(mnemonic1.as_str().is_empty()); + // } } diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index 81330f3596..42b3ecd3cf 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -194,17 +194,22 @@ impl TryFrom for SecretManager { Ok(match value { #[cfg(feature = "stronghold")] SecretManagerDto::Stronghold(stronghold_dto) => { + let StrongholdDto { + password, + snapshot_path, + timeout, + } = stronghold_dto; let mut builder = StrongholdSecretManager::builder(); - if let Some(password) = stronghold_dto.password { + if let Some(password) = password { builder = builder.password(password); } - if let Some(timeout) = stronghold_dto.timeout { + if let Some(timeout) = timeout { builder = builder.timeout(Duration::from_secs(timeout)); } - Self::Stronghold(builder.build(&stronghold_dto.snapshot_path)?) + Self::Stronghold(builder.build(snapshot_path)?) } #[cfg(feature = "ledger_nano")] diff --git a/sdk/src/client/secret/types.rs b/sdk/src/client/secret/types.rs index 912619ecef..428dadf7af 100644 --- a/sdk/src/client/secret/types.rs +++ b/sdk/src/client/secret/types.rs @@ -18,7 +18,7 @@ use crate::{ utils::serde::bip44::option_bip44, }; -/// Stronghold DTO to allow the creation of a Stronghold secret manager from bindings +/// Stronghold DTO to allow the creation of a Stronghold secret manager from bindings. #[cfg(feature = "stronghold")] #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] diff --git a/sdk/tests/client/common/mod.rs b/sdk/tests/client/common/mod.rs index 5a0ff66a34..a079ebc99b 100644 --- a/sdk/tests/client/common/mod.rs +++ b/sdk/tests/client/common/mod.rs @@ -29,7 +29,12 @@ pub async fn create_client_and_secret_manager_with_funds( ) -> Result<(Client, SecretManager)> { let client = Client::builder().with_node(NODE_LOCAL)?.finish().await?; - let secret_manager = SecretManager::try_from_mnemonic(mnemonic.unwrap_or(Client::generate_mnemonic().unwrap()))?; + let mnemonic = if let Some(mnemonic) = mnemonic { + mnemonic + } else { + Client::generate_mnemonic()? + }; + let secret_manager = SecretManager::try_from_mnemonic(mnemonic)?; let address = secret_manager .generate_ed25519_addresses( diff --git a/sdk/tests/wallet/account_recovery.rs b/sdk/tests/wallet/account_recovery.rs index 2168ef797b..cffeebf35b 100644 --- a/sdk/tests/wallet/account_recovery.rs +++ b/sdk/tests/wallet/account_recovery.rs @@ -6,12 +6,7 @@ use std::time::Duration; use iota_sdk::{ - client::{ - api::GetAddressesOptions, - constants::SHIMMER_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - Client, - }, + client::{api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, secret::SecretManager, Client}, wallet::Result, }; @@ -66,7 +61,7 @@ async fn account_recovery_with_balance_and_empty_addresses() -> Result<()> { .finish() .await?; - let secret_manager = SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic.clone())?); + let secret_manager = SecretManager::try_from_mnemonic(mnemonic.clone())?; let addresses = secret_manager .generate_ed25519_addresses( diff --git a/sdk/tests/wallet/common/mod.rs b/sdk/tests/wallet/common/mod.rs index f93d5850ab..55ac5fcaf0 100644 --- a/sdk/tests/wallet/common/mod.rs +++ b/sdk/tests/wallet/common/mod.rs @@ -31,8 +31,7 @@ pub use self::constants::*; #[allow(dead_code, unused_variables)] pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, node: Option<&str>) -> Result { let client_options = ClientOptions::new().with_node(node.unwrap_or(NODE_LOCAL))?; - let secret_manager = - MnemonicSecretManager::try_from_mnemonic(mnemonic.unwrap_or(Client::generate_mnemonic().unwrap()))?; + let secret_manager = MnemonicSecretManager::try_from_mnemonic(mnemonic.unwrap_or(Client::generate_mnemonic()?))?; #[allow(unused_mut)] let mut wallet_builder = Wallet::builder() diff --git a/sdk/tests/wallet/core.rs b/sdk/tests/wallet/core.rs index 2e50bf4765..4c06e6eb16 100644 --- a/sdk/tests/wallet/core.rs +++ b/sdk/tests/wallet/core.rs @@ -81,6 +81,8 @@ async fn different_seed() -> Result<()> { #[cfg(feature = "storage")] #[tokio::test] async fn changed_coin_type() -> Result<()> { + use crypto::keys::bip39::Mnemonic; + let storage_path = "test-storage/changed_coin_type"; setup(storage_path)?; @@ -92,10 +94,9 @@ async fn changed_coin_type() -> Result<()> { drop(_account); drop(wallet); + // Recreate Wallet with same mnemonic let err = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( - mnemonic.clone(), - )?)) + .with_secret_manager(SecretManager::try_from_mnemonic(mnemonic.clone())?) .with_coin_type(IOTA_COIN_TYPE) .with_storage_path(storage_path) .finish() @@ -113,9 +114,7 @@ async fn changed_coin_type() -> Result<()> { // Building the wallet with the same coin type still works let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( - mnemonic, - )?)) + .with_secret_manager(SecretManager::try_from_mnemonic(mnemonic.clone())?) .with_storage_path(storage_path) .finish() .await?;