diff --git a/MinWallet/Features/Wallet/Create/Biometric/BiometricSetupView.swift b/MinWallet/Features/Wallet/Create/Biometric/BiometricSetupView.swift index 792d506..a2cd92d 100644 --- a/MinWallet/Features/Wallet/Create/Biometric/BiometricSetupView.swift +++ b/MinWallet/Features/Wallet/Create/Biometric/BiometricSetupView.swift @@ -49,7 +49,7 @@ struct BiometricSetupView: View { switch screenType { case .createWallet(let seedPhrase, let nickName): let seedPhrase = seedPhrase.joined(separator: " ") - let wallet = createWallet(phrase: seedPhrase, password: MinWalletConstant.passDefaultForFaceID, networkEnv: AppSetting.NetworkEnv.mainnet.rawValue) + let wallet = createWallet(phrase: seedPhrase, password: MinWalletConstant.passDefaultForFaceID, networkEnv: AppSetting.NetworkEnv.mainnet.rawValue, walletName: "FIX ME") userInfo.saveWalletInfo(seedPhrase: seedPhrase, nickName: nickName, walletAddress: wallet.address) appSetting.isLogin = true diff --git a/MinWallet/Features/Wallet/Create/Password/CreateNewPasswordView.swift b/MinWallet/Features/Wallet/Create/Password/CreateNewPasswordView.swift index 56d13eb..841120b 100644 --- a/MinWallet/Features/Wallet/Create/Password/CreateNewPasswordView.swift +++ b/MinWallet/Features/Wallet/Create/Password/CreateNewPasswordView.swift @@ -130,7 +130,7 @@ struct CreateNewPasswordView: View { appSetting.authenticationType = .password appSetting.isLogin = true let seedPhrase = seedPhrase.joined(separator: " ") - let wallet = createWallet(phrase: seedPhrase, password: password, networkEnv: AppSetting.NetworkEnv.mainnet.rawValue) + let wallet = createWallet(phrase: seedPhrase, password: password, networkEnv: AppSetting.NetworkEnv.mainnet.rawValue, walletName: "FIX ME") userInfo.saveWalletInfo(seedPhrase: seedPhrase, nickName: nickName, walletAddress: wallet.address) navigator.push(.createWallet(.createNewWalletSuccess)) diff --git a/rust/.gitignore b/rust/.gitignore index dcd2c2e..f745451 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1,3 +1,4 @@ bindings target ios +.idea \ No newline at end of file diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d1ee171..7b245d4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -571,6 +571,8 @@ dependencies = [ "cryptoxide", "hex", "rand", + "serde", + "serde_json", "tiny-bip39", "uniffi", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 25896d2..f897468 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,6 +10,8 @@ hex = "0.4.3" rand = "0.8.5" uniffi = { version = "0.28.2", features = ["cli"] } cryptoxide = "0.4.4" +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" [build-dependencies] uniffi = { version = "0.28.2", features = ["build"] } diff --git a/rust/README.md b/rust/README.md index 2c21108..18b6eec 100644 --- a/rust/README.md +++ b/rust/README.md @@ -29,13 +29,14 @@ let phrase = genPhrase(wordCount: 12) --- -#### `createWallet(phrase: String, password: String, networkEnv: String) -> MinWallet` +#### `createWallet(phrase: String, password: String, networkEnv: String, walletName: String) -> MinWallet` Creates a wallet based on the provided mnemonic phrase, password, and network environment. - **Parameters**: - `phrase`: The mnemonic phrase used to derive the wallet. - `password`: A user-defined password for encrypting the wallet's private key. - `networkEnv`: The network environment for the wallet (e.g., `"mainnet"` or `"preprod"`). + - `walletName`: The Name of Wallet. - **Returns**: - A `MinWallet` object containing wallet details. @@ -44,7 +45,8 @@ Creates a wallet based on the provided mnemonic phrase, password, and network en let wallet = createWallet( phrase: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", password: "secure_password", - networkEnv: "preprod" + networkEnv: "preprod", + walletName: "My MinWallet" ) ``` @@ -60,6 +62,62 @@ Signs a raw transaction using the provided wallet. - `txRaw`: The raw transaction string to be signed. - **Returns**: - A signed transaction `String`. +--- + +#### `change_password(wallet: MinWallet, current_password: String, new_password: String) -> MinWallet` +Changes the password of a given wallet. + +- **Parameters**: + - `wallet`: A `MinWallet` object containing wallet details. + - `current_password`: The current password used to decrypt the wallet. + - `new_password`: The new password to set for the wallet. +- **Returns**: + - An updated `MinWallet` object with the new password applied. + +--- + +#### `verify_password(wallet: MinWallet, password: String) -> boolean` +Verifies whether the provided password matches the wallet's password. + +- **Parameters**: + - `wallet`: A `MinWallet` object containing wallet details. + - `password`: The password to be verified. +- **Returns**: + - A `boolean` indicating whether the password is correct (`true`) or incorrect (`false`). + +--- + +#### `export_wallet(wallet: MinWallet, password: String, network_env: String) -> String` +Exports the wallet's data as a string for backup or transfer purposes. + +- **Parameters**: + - `wallet`: A `MinWallet` object containing wallet details. + - `password`: The password used to encrypt the exported wallet data. + - `network_env`: The network environment for which the wallet data is exported (e.g., `mainnet` or `testnet`). +- **Returns**: + - A `String` containing the encrypted wallet data. + +--- + +#### `import_wallet(data: String, password: String, wallet_name: String) -> MinWallet` +Imports a wallet from encrypted data. + +- **Parameters**: + - `data`: The encrypted wallet data as a string. + - `password`: The password to decrypt the wallet data. + - `wallet_name`: The name to assign to the imported wallet. +- **Returns**: + - A `MinWallet` object representing the imported wallet. + +--- + +#### `get_wallet_name_from_export_wallet(data: String) -> String` +Retrieves the wallet name from exported wallet data. + +- **Parameters**: + - `data`: The exported wallet data as a string. +- **Returns**: + - A `String` containing the wallet name. #### Example: ```swift @@ -75,7 +133,7 @@ let signedTx = signTx( ### Types -#### `MinWallet` +### `MinWallet` A dictionary-like object containing details of a wallet. - **Fields**: @@ -83,6 +141,7 @@ A dictionary-like object containing details of a wallet. - `networkId` (`UInt32`): The ID of the network the wallet belongs to (mainnet: 764824073, preprod: 1). - `accountIndex` (`UInt32`): The account index within the wallet. - `encryptedKey` (`UInt32`): The wallet's encrypted private key. + - `walletName` (`String`): the wallet's name. #### Example: ```swift @@ -90,7 +149,8 @@ let wallet = MinWallet( address: "addr_test1...", networkId: 1, accountIndex: 0, - encryptedKey: "" + encryptedKey: "", + walletName: "My MinWallet" ) ``` diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9695494..a2f51ea 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -7,18 +7,62 @@ use crate::network::NetworkEnvironment; use crate::wallet::embedded::WalletStaticMethods; use bip39::{Language, Mnemonic, MnemonicType}; use cardano_serialization_lib::{ - make_vkey_witness, PrivateKey, Transaction, TransactionHash, TransactionWitnessSet, - Vkeywitnesses, + make_vkey_witness, Bip32PrivateKey, PrivateKey, Transaction, TransactionHash, + TransactionWitnessSet, Vkeywitnesses, }; +use serde::{Deserialize, Serialize}; +use std::panic; // *************************** EXPORT *************************** +#[derive(Debug, Serialize, Deserialize)] pub struct MinWallet { + wallet_name: String, address: String, network_id: u32, encrypted_key: String, account_index: u32, } +#[derive(Debug, Serialize, Deserialize)] +pub struct EWType { + wallet: EWWallet, + settings: EWSettings, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EWWallet { + export: String, + version: String, + id: String, + network_id: String, + sign_type: String, + root_key: EWRootKey, + account_list: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EWRootKey { + #[serde(rename = "pub")] + pub_key: String, + prv: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EWAccount { + id: String, + #[serde(rename = "pub")] + pub_key: String, + path: (u32, u32, u32), + sign_type: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EWSettings { + id: String, + network_id: String, + name: String, +} + pub fn gen_phrase(word_count: u32) -> String { // Match the word_count to the corresponding MnemonicType variant let m_type = match word_count { @@ -37,20 +81,136 @@ pub fn gen_phrase(word_count: u32) -> String { mnemonic.phrase().to_string() } -pub fn create_wallet(phrase: String, password: String, network_env: String) -> MinWallet { +pub fn create_wallet( + phrase: String, + password: String, + network_env: String, + wallet_name: String, +) -> MinWallet { let network_environment = NetworkEnvironment::from_string(network_env).unwrap(); - MinWallet::create(phrase.as_str(), password.as_str(), network_environment) + MinWallet::create( + phrase.as_str(), + password.as_str(), + network_environment, + wallet_name, + ) } pub fn sign_tx(wallet: MinWallet, password: String, account_index: u32, tx_raw: String) -> String { wallet.sign_tx(password.as_str(), account_index, tx_raw) } + +pub fn change_password( + wallet: MinWallet, + current_password: String, + new_password: String, +) -> MinWallet { + // Retrieve the root key using the current password and the wallet's encrypted key + let root_key = + MinWallet::get_root_key_from_password(current_password.as_str(), &wallet.encrypted_key); + + // Generate a new encrypted key using the new password and the retrieved root key + let new_encrypted_key = MinWallet::gen_encrypted_key(new_password.as_str(), &root_key); + + // Return a new MinWallet with the updated encrypted_key + MinWallet { + encrypted_key: new_encrypted_key, + ..wallet // Use the struct update syntax to copy the remaining fields + } +} + +pub fn verify_password(wallet: MinWallet, password: String) -> bool { + let result = panic::catch_unwind(|| { + MinWallet::get_root_key_from_password(password.as_str(), &wallet.encrypted_key) + }); + result.is_ok() +} + +pub fn export_wallet(wallet: MinWallet, password: String, network_env: String) -> String { + let network_environment = NetworkEnvironment::from_string(network_env.clone()).unwrap(); + let network_suffix = match network_environment { + NetworkEnvironment::Mainnet => "mm", + NetworkEnvironment::Preprod => "pm", + NetworkEnvironment::Preview => "pm", + }; + let root_private_key = + MinWallet::get_root_key_from_password(password.as_str(), &wallet.encrypted_key); + let root_pub_key = root_private_key.to_public(); + let root_account_id = root_pub_key.to_bech32().as_str()[..16].to_string(); + + let account_private_key = MinWallet::get_account(&root_private_key, 0); + let account_public_key = account_private_key.to_public(); + let account_id = account_public_key.to_bech32().as_str()[..16].to_string(); + + let wallet_export = EWType { + wallet: EWWallet { + export: "minswap".to_string(), + version: "1.0.0".to_string(), + id: root_account_id.clone() + "-" + network_suffix, + network_id: network_env.clone(), + sign_type: "mnemonic".to_string(), + root_key: EWRootKey { + pub_key: root_pub_key.to_bech32(), + prv: root_private_key.to_hex(), + }, + account_list: vec![EWAccount { + id: account_id, + pub_key: account_public_key.to_bech32(), + path: (1852, 1815, 0), + sign_type: "mnemonic".to_string(), + }], + }, + settings: EWSettings { + id: root_account_id + "-" + network_suffix, + network_id: network_env.clone(), + name: wallet.wallet_name.clone(), + }, + }; + serde_json::to_string(&wallet_export).unwrap() +} + +pub fn get_wallet_name_from_export_wallet(data: String) -> String { + let wallet_export: EWType = serde_json::from_str(data.as_str()).unwrap(); + wallet_export.settings.name +} + +pub fn import_wallet(data: String, password: String, wallet_name: String) -> MinWallet { + let wallet_export: EWType = serde_json::from_str(data.as_str()).unwrap(); + let root_key_hex = wallet_export.wallet.root_key.prv; + let root_key = Bip32PrivateKey::from_hex(root_key_hex.as_str()).unwrap(); + let account_index = 0; + let account_key = MinWallet::get_account(&root_key, account_index); + + // Encrypt root key with password + let encrypted_key = MinWallet::gen_encrypted_key(password.as_str(), &root_key); + + // Derive network ID + let network_env = wallet_export.wallet.network_id; + let network_environment = NetworkEnvironment::from_string(network_env).unwrap(); + let network_id = network_environment.to_network_id() as u32; + + // Generate addresses + let address = MinWallet::get_address(&account_key, network_id); + + MinWallet { + wallet_name, + address: address.to_bech32(None).unwrap(), + network_id, + account_index, + encrypted_key, + } +} // *************************** END EXPORT *************************** impl WalletStaticMethods for MinWallet {} impl MinWallet { - pub fn create(mnemonic: &str, password: &str, network_environment: NetworkEnvironment) -> Self { + pub fn create( + mnemonic: &str, + password: &str, + network_environment: NetworkEnvironment, + wallet_name: String, + ) -> Self { let account_index = 0; // Convert mnemonic to entropy @@ -70,6 +230,7 @@ impl MinWallet { let address = MinWallet::get_address(&account_key, network_id); MinWallet { + wallet_name, address: address.to_bech32(None).unwrap(), network_id, account_index, @@ -105,6 +266,64 @@ mod tests { use crate::crypto::hash_transaction; use cardano_serialization_lib::Transaction; + #[test] + fn test_export_wallet() { + let phrase = + "belt change crouch decorate advice emerge tongue loop cute olympic tuna donkey"; + let password = "Minswap@123456"; + let wallet_name = "My MinWallet".to_string(); + let wallet = create_wallet( + phrase.to_string(), + password.to_string(), + "preprod".to_string(), + wallet_name.clone(), + ); + let old_prk = wallet.get_private_key(password, 0); + let old_wallet_name = wallet.wallet_name.clone(); + let old_address = wallet.address.clone(); + let export_wallet_data = export_wallet(wallet, password.to_string(), "preprod".to_string()); + + let round_trip_wallet = + import_wallet(export_wallet_data, password.to_string(), wallet_name); + + assert_eq!(old_wallet_name, round_trip_wallet.wallet_name); + assert_eq!(old_address, round_trip_wallet.address); + + let new_prk = round_trip_wallet.get_private_key(password, 0); + assert_eq!(old_prk.to_hex(), new_prk.to_hex()); + } + #[test] + fn test_verify_password() { + let phrase = + "belt change crouch decorate advice emerge tongue loop cute olympic tuna donkey"; + let password = "123456"; + let wallet = create_wallet( + phrase.to_string(), + password.to_string(), + "preprod".to_string(), + "My MinWallet".to_string(), + ); + let is_correct = verify_password(wallet, "Wrong Password".to_string()); + assert_eq!(is_correct, false); + } + #[test] + fn test_change_password() { + let phrase = + "belt change crouch decorate advice emerge tongue loop cute olympic tuna donkey"; + let password = "123456"; + let wallet = create_wallet( + phrase.to_string(), + password.to_string(), + "preprod".to_string(), + "My MinWallet".to_string(), + ); + let private_key_1 = wallet.get_private_key(password, 0); + + let new_password = String::from("Minswap@123456"); + let new_wallet = change_password(wallet, password.to_string(), new_password.clone()); + let private_key_2 = new_wallet.get_private_key(new_password.as_str(), 0); + assert_eq!(private_key_1.to_hex(), private_key_2.to_hex()); + } #[test] fn test_wallet_happy_case() { let phrase = @@ -114,6 +333,7 @@ mod tests { phrase.to_string(), password.to_string(), "preprod".to_string(), + "My MinWallet".to_string(), ); assert_eq!(wallet.address, "addr_test1qp5cpdqd3n6nuaa8rr8h20lnsxzx2uxdapknyh6fryl7e32e34tccc70arj2f4m9x9zdz4vu29rzzrtszalvqzpx625s2z2338".to_string()); diff --git a/rust/src/mwrust.udl b/rust/src/mwrust.udl index 7c09f3d..85ca692 100644 --- a/rust/src/mwrust.udl +++ b/rust/src/mwrust.udl @@ -1,10 +1,16 @@ namespace mwrust { string gen_phrase(u32 word_count); - MinWallet create_wallet(string phrase, string password, string network_env); + MinWallet create_wallet(string phrase, string password, string network_env, string wallet_name); string sign_tx(MinWallet wallet, string password, u32 account_index, string tx_raw); + MinWallet change_password(MinWallet wallet, string current_password, string new_password); + boolean verify_password(MinWallet wallet, string password); + string export_wallet(MinWallet wallet, string password, string network_env); + MinWallet import_wallet(string data, string password, string wallet_name); + string get_wallet_name_from_export_wallet(string data); }; dictionary MinWallet { + string wallet_name; string address; u32 network_id; u32 account_index;