diff --git a/key-wallet-ffi/src/lib_tests.rs b/key-wallet-ffi/src/lib_tests.rs deleted file mode 100644 index 4cddc9ef3..000000000 --- a/key-wallet-ffi/src/lib_tests.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Internal tests for key-wallet-ffi -//! -//! These tests verify the FFI implementation works correctly. - -#[cfg(test)] -mod tests { - use crate::{ - validate_mnemonic, Address, AddressGenerator, ExtPrivKey, ExtPubKey, HDWallet, Language, - Mnemonic, Network, - }; - - #[test] - fn test_mnemonic_functionality() { - // Test mnemonic validation - let valid_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".to_string(); - let is_valid = validate_mnemonic(valid_phrase.clone(), Language::English).unwrap(); - assert!(is_valid); - - // Test creating from phrase - let mnemonic = Mnemonic::new(valid_phrase, Language::English).unwrap(); - assert_eq!(mnemonic.phrase().split_whitespace().count(), 12); - - // Test seed generation - let seed = mnemonic.to_seed("".to_string()); - assert_eq!(seed.len(), 64); - } - - #[test] - fn test_hd_wallet_functionality() { - // Create wallet from seed - let seed = vec![0u8; 64]; - let wallet = HDWallet::from_seed(seed, Network::Testnet).unwrap(); - - // Test getting account keys - let account_xpriv = wallet.get_account_xpriv(0).unwrap(); - let account_xpub = wallet.get_account_xpub(0).unwrap(); - - // Test deriving keys - let path = "m/44'/1'/0'/0/0".to_string(); - let derived_xpriv = wallet.derive_xpriv(path.clone()).unwrap(); - let derived_xpub = wallet.derive_xpub(path.clone()).unwrap(); - // Verify we got keys - assert!(!account_xpriv.xpriv.is_empty()); - assert!(!account_xpriv.derivation_path.is_empty()); - assert!(!account_xpub.xpub.is_empty()); - assert!(!derived_xpriv.is_empty()); - assert!(!derived_xpub.xpub.is_empty()); - } - - #[test] - fn test_address_functionality() { - // Test creating P2PKH address from public key - let pubkey = vec![ - 0x02, 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, 0x93, 0x82, 0x6d, 0xc6, 0x1c, - 0x19, 0xf4, 0x7c, 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, 0x29, 0xce, 0x32, - 0x5a, 0x0f, 0x46, 0x79, 0xef, - ]; - let address = Address::from_public_key(pubkey, Network::Testnet).unwrap(); - let address_str = address.to_string(); - assert!(address_str.starts_with('y')); // Testnet P2PKH addresses start with 'y' - - // Test parsing from string - let parsed = Address::from_string(address_str.clone(), Network::Testnet).unwrap(); - assert_eq!(parsed.to_string(), address_str); - assert_eq!(parsed.get_network(), Network::Testnet); - - // Test script pubkey - let script = address.get_script_pubkey(); - assert!(script.len() > 0); - } - - #[test] - fn test_address_generator_functionality() { - let seed = vec![0u8; 64]; - let wallet = HDWallet::from_seed(seed, Network::Testnet).unwrap(); - - // Get account extended public key - let account_xpub = wallet.get_account_xpub(0).unwrap(); - - let generator = AddressGenerator::new(Network::Testnet); - - // Test single address generation - let single_addr = generator.generate(account_xpub.clone(), true, 0).unwrap(); - assert!(single_addr.to_string().starts_with('y')); - - // Test address range generation - let addresses = generator.generate_range(account_xpub, true, 0, 5).unwrap(); - assert_eq!(addresses.len(), 5); - for addr in &addresses { - assert!(addr.to_string().starts_with('y')); - } - } - - #[test] - fn test_extended_key_methods() { - // Generate a valid extended key from a known seed - let seed = vec![0u8; 64]; - let wallet = HDWallet::from_seed(seed, Network::Testnet).unwrap(); - let account_xpriv = wallet.get_account_xpriv(0).unwrap(); - - // Test ExtPrivKey - let xpriv = ExtPrivKey::from_string(account_xpriv.xpriv).unwrap(); - - // Test getting xpub - let xpub = xpriv.get_xpub(); - assert!(xpub.xpub.starts_with("tpub")); // Testnet public key - - // Test deriving child - let child = xpriv.derive_child(0, false).unwrap(); - assert!(!child.to_string().is_empty()); - - // Test ExtPubKey - let xpub_obj = ExtPubKey::from_string(xpub.xpub).unwrap(); - let pubkey_bytes = xpub_obj.get_public_key(); - assert_eq!(pubkey_bytes.len(), 33); // Compressed public key - } - - #[test] - fn test_error_handling() { - // Test invalid mnemonic - let invalid_phrase = "invalid mnemonic phrase".to_string(); - let result = Mnemonic::new(invalid_phrase, Language::English); - assert!(result.is_err()); - - // Test invalid address - let result = Address::from_string("invalid_address".to_string(), Network::Testnet); - assert!(result.is_err()); - - // Test invalid derivation path - let seed = vec![0u8; 64]; - let wallet = HDWallet::from_seed(seed, Network::Testnet).unwrap(); - let result = wallet.derive_xpriv("invalid/path".to_string()); - assert!(result.is_err()); - } - - #[test] - fn test_network_compatibility_in_address_parsing() { - // Create a testnet address - let pubkey = vec![ - 0x02, 0x9b, 0x63, 0x47, 0x39, 0x85, 0x05, 0xf5, 0xec, 0x93, 0x82, 0x6d, 0xc6, 0x1c, - 0x19, 0xf4, 0x7c, 0x66, 0xc0, 0x28, 0x3e, 0xe9, 0xbe, 0x98, 0x0e, 0x29, 0xce, 0x32, - 0x5a, 0x0f, 0x46, 0x79, 0xef, - ]; - let testnet_addr = Address::from_public_key(pubkey, Network::Testnet).unwrap(); - let addr_str = testnet_addr.to_string(); - - // Should work with testnet - let parsed = Address::from_string(addr_str.clone(), Network::Testnet); - assert!(parsed.is_ok()); - - // Should also work with devnet and regtest (same prefixes) - let parsed = Address::from_string(addr_str.clone(), Network::Devnet); - assert!(parsed.is_ok()); - - let parsed = Address::from_string(addr_str.clone(), Network::Regtest); - assert!(parsed.is_ok()); - - // Should fail with mainnet (different prefix) - let parsed = Address::from_string(addr_str.clone(), Network::Mainnet); - assert!(parsed.is_err()); - } -} diff --git a/key-wallet/src/derivation.rs b/key-wallet/src/derivation.rs index 8ba603027..1a5f97c1b 100644 --- a/key-wallet/src/derivation.rs +++ b/key-wallet/src/derivation.rs @@ -45,150 +45,6 @@ impl KeyDerivation for ExtendedPrivKey { } } -/// HD Wallet implementation -#[derive(Clone)] -pub struct HDWallet { - master_key: ExtendedPrivKey, - secp: Secp256k1, -} - -impl core::fmt::Debug for HDWallet { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("HDWallet").field("master_key", &"").finish() - } -} - -impl HDWallet { - /// Create a new HD wallet from a master key - pub fn new(master_key: ExtendedPrivKey) -> Self { - Self { - master_key, - secp: Secp256k1::new(), - } - } - - /// Create from a seed - pub fn from_seed(seed: &[u8], network: crate::Network) -> Result { - let master_key = ExtendedPrivKey::new_master(network, seed)?; - Ok(Self::new(master_key)) - } - - /// Get the master extended private key - pub fn master_key(&self) -> &ExtendedPrivKey { - &self.master_key - } - - /// Get the master extended public key - pub fn master_pub_key(&self) -> ExtendedPubKey { - ExtendedPubKey::from_priv(&self.secp, &self.master_key) - } - - /// Derive a key at the given path - pub fn derive(&self, path: &DerivationPath) -> Result { - self.master_key.derive_priv(&self.secp, path).map_err(Error::Bip32) - } - - /// Derive a public key at the given path - pub fn derive_pub(&self, path: &DerivationPath) -> Result { - let priv_key = self.derive(path)?; - Ok(ExtendedPubKey::from_priv(&self.secp, &priv_key)) - } - - /// Get a standard BIP44 account key - pub fn bip44_account(&self, account: u32) -> Result { - let path = match self.master_key.network { - crate::Network::Mainnet => crate::dip9::DASH_BIP44_PATH_MAINNET, - Network::Testnet | Network::Devnet | Network::Regtest => { - crate::dip9::DASH_BIP44_PATH_TESTNET - } - _ => return Err(Error::InvalidNetwork), - }; - - // Convert to DerivationPath and append account index - let mut full_path = crate::bip32::DerivationPath::from(path); - let child_number = crate::bip32::ChildNumber::from_hardened_idx(account) - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - full_path.push(child_number); - - self.derive(&full_path) - } - - /// Get a CoinJoin account key - pub fn coinjoin_account(&self, account: u32) -> Result { - let path = match self.master_key.network { - crate::Network::Mainnet => crate::dip9::COINJOIN_PATH_MAINNET, - Network::Testnet | Network::Devnet | Network::Regtest => { - crate::dip9::COINJOIN_PATH_TESTNET - } - _ => return Err(Error::InvalidNetwork), - }; - - // Convert to DerivationPath and append account index - let mut full_path = crate::bip32::DerivationPath::from(path); - let child_number = crate::bip32::ChildNumber::from_hardened_idx(account) - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - full_path.push(child_number); - - self.derive(&full_path) - } - - /// Get an identity authentication key - pub fn identity_authentication_key( - &self, - identity_index: u32, - key_index: u32, - ) -> Result { - let path = match self.master_key.network { - crate::Network::Mainnet => crate::dip9::IDENTITY_AUTHENTICATION_PATH_MAINNET, - Network::Testnet | Network::Devnet | Network::Regtest => { - crate::dip9::IDENTITY_AUTHENTICATION_PATH_TESTNET - } - _ => return Err(Error::InvalidNetwork), - }; - - // Convert to DerivationPath and append indices - let mut full_path = crate::bip32::DerivationPath::from(path); - full_path.push(crate::bip32::ChildNumber::from_hardened_idx(identity_index).unwrap()); - full_path.push(crate::bip32::ChildNumber::from_hardened_idx(key_index).unwrap()); - - self.derive(&full_path) - } -} - -/// Address derivation for a specific account -pub struct AccountDerivation { - account_key: ExtendedPrivKey, - secp: Secp256k1, -} - -impl AccountDerivation { - /// Create a new account derivation - pub fn new(account_key: ExtendedPrivKey) -> Self { - Self { - account_key, - secp: Secp256k1::new(), - } - } - - /// Derive an external (receive) address at index - pub fn receive_address(&self, index: u32) -> Result { - let path = format!("m/0/{}", index) - .parse::() - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - let priv_key = self.account_key.derive_priv(&self.secp, &path).map_err(Error::Bip32)?; - Ok(ExtendedPubKey::from_priv(&self.secp, &priv_key)) - } - - /// Derive an internal (change) address at index - pub fn change_address(&self, index: u32) -> Result { - let path = format!("m/1/{}", index) - .parse::() - .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - let priv_key = self.account_key.derive_priv(&self.secp, &path).map_err(Error::Bip32)?; - Ok(ExtendedPubKey::from_priv(&self.secp, &priv_key)) - } -} - /// Builder for constructing derivation paths #[derive(Debug, Clone)] pub struct DerivationPathBuilder { @@ -443,24 +299,9 @@ impl DerivationStrategy { #[cfg(test)] mod tests { use super::*; - use crate::mnemonic::{Language, Mnemonic}; + use crate::mnemonic::Mnemonic; use dashcore_hashes::Hash; - #[test] - fn test_hd_wallet_derivation() { - let mnemonic = Mnemonic::from_phrase( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - Language::English - ).unwrap(); - - let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Mainnet).unwrap(); - - // Test BIP44 account derivation - let account0 = wallet.bip44_account(0).unwrap(); - assert_ne!(&account0.private_key[..], &wallet.master_key().private_key[..]); - } - // ✓ Test BIP32 derivation with exact DashSync test vectors #[test] fn test_bip32_derivation_vectors() { @@ -595,7 +436,7 @@ mod tests { ).unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Mainnet).unwrap(); + let master_key = ExtendedPrivKey::new_master(crate::Network::Mainnet, &seed).unwrap(); let secp = secp256k1::Secp256k1::new(); // Test identity authentication derivation (purpose 9' for Dash Platform) @@ -615,8 +456,8 @@ mod tests { }, // Key index ]); - let identity_key = wallet.master_key().derive_priv(&secp, &identity_auth_path).unwrap(); - assert_ne!(&identity_key.private_key[..], &wallet.master_key().private_key[..]); + let identity_key = master_key.derive_priv(&secp, &identity_auth_path).unwrap(); + assert_ne!(&identity_key.private_key[..], &master_key.private_key[..]); // Test identity registration derivation // m/9'/5'/1'/1 (DIP-9: Identity Registration) @@ -635,7 +476,7 @@ mod tests { }, ]); - let reg_key = wallet.master_key().derive_priv(&secp, &identity_reg_path).unwrap(); + let reg_key = master_key.derive_priv(&secp, &identity_reg_path).unwrap(); assert_ne!(®_key.private_key[..], &identity_key.private_key[..]); // Test identity top-up derivation @@ -655,7 +496,7 @@ mod tests { }, ]); - let topup_key = wallet.master_key().derive_priv(&secp, &identity_topup_path).unwrap(); + let topup_key = master_key.derive_priv(&secp, &identity_topup_path).unwrap(); assert_ne!(&topup_key.private_key[..], ®_key.private_key[..]); assert_ne!(&topup_key.private_key[..], &identity_key.private_key[..]); @@ -673,7 +514,7 @@ mod tests { }, // Provider index ]); - let voting_key = wallet.master_key().derive_priv(&secp, &provider_voting_path).unwrap(); + let voting_key = master_key.derive_priv(&secp, &provider_voting_path).unwrap(); assert_ne!(&voting_key.private_key[..], &topup_key.private_key[..]); // Test provider operator derivation @@ -690,7 +531,7 @@ mod tests { }, // Provider index ]); - let operator_key = wallet.master_key().derive_priv(&secp, &provider_op_path).unwrap(); + let operator_key = master_key.derive_priv(&secp, &provider_op_path).unwrap(); assert_ne!(&operator_key.private_key[..], &voting_key.private_key[..]); } @@ -703,7 +544,7 @@ mod tests { ).unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Testnet).unwrap(); + let master_key = ExtendedPrivKey::new_master(crate::Network::Testnet, &seed).unwrap(); // Test builder for BIP44 path let bip44_path = DerivationPathBuilder::new() @@ -737,8 +578,8 @@ mod tests { // Test derivation with the built path let secp = secp256k1::Secp256k1::new(); - let derived = wallet.master_key().derive_priv(&secp, &bip44_path).unwrap(); - assert_ne!(&derived.private_key[..], &wallet.master_key().private_key[..]); + let derived = master_key.derive_priv(&secp, &bip44_path).unwrap(); + assert_ne!(&derived.private_key[..], &master_key.private_key[..]); } // ✓ Test key signing and verification @@ -750,14 +591,14 @@ mod tests { ).unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Testnet).unwrap(); + let master_key = ExtendedPrivKey::new_master(crate::Network::Testnet, &seed).unwrap(); let secp = secp256k1::Secp256k1::new(); // Derive a key for signing let path = DerivationPath::from(vec![ChildNumber::Hardened { index: 0, }]); - let signing_key = wallet.master_key().derive_priv(&secp, &path).unwrap(); + let signing_key = master_key.derive_priv(&secp, &path).unwrap(); // Test message let message = b"Hello Dash!"; @@ -795,14 +636,14 @@ mod tests { ).unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Testnet).unwrap(); + let master_key = ExtendedPrivKey::new_master(crate::Network::Testnet, &seed).unwrap(); let secp = secp256k1::Secp256k1::new(); // Derive a key for signing let path = DerivationPath::from(vec![ChildNumber::Normal { index: 0, }]); - let signing_key = wallet.master_key().derive_priv(&secp, &path).unwrap(); + let signing_key = master_key.derive_priv(&secp, &path).unwrap(); let public_key = ExtendedPubKey::from_priv(&secp, &signing_key); // Test message @@ -841,7 +682,7 @@ mod tests { .unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, crate::Network::Testnet).unwrap(); + let master_key = ExtendedPrivKey::new_master(crate::Network::Testnet, &seed).unwrap(); let secp = secp256k1::Secp256k1::new(); // Test DashPay contact derivation path: m/9'/5'/15'/0' @@ -861,7 +702,7 @@ mod tests { }, // account 0 ]); - let dashpay_key = wallet.master_key().derive_priv(&secp, &dashpay_path).unwrap(); + let dashpay_key = master_key.derive_priv(&secp, &dashpay_path).unwrap(); let dashpay_pubkey = ExtendedPubKey::from_priv(&secp, &dashpay_key); // Verify this produces a different key than other special paths @@ -880,7 +721,7 @@ mod tests { index: 0, }, ]); - let auth_key = wallet.master_key().derive_priv(&secp, &auth_path).unwrap(); + let auth_key = master_key.derive_priv(&secp, &auth_path).unwrap(); // Keys should be different assert_ne!(dashpay_key.private_key, auth_key.private_key); @@ -905,7 +746,7 @@ mod tests { }, // account 1 ]); - let dashpay_key_1 = wallet.master_key().derive_priv(&secp, &dashpay_account_1).unwrap(); + let dashpay_key_1 = master_key.derive_priv(&secp, &dashpay_account_1).unwrap(); // Different accounts should have different keys assert_ne!(dashpay_key.private_key, dashpay_key_1.private_key); diff --git a/key-wallet/src/tests/account_tests.rs b/key-wallet/src/tests/account_tests.rs index c6bb3da16..de0a3f51b 100644 --- a/key-wallet/src/tests/account_tests.rs +++ b/key-wallet/src/tests/account_tests.rs @@ -4,7 +4,6 @@ use crate::account::{Account, AccountType, StandardAccountType}; use crate::bip32::{ExtendedPrivKey, ExtendedPubKey}; -use crate::derivation::HDWallet; use crate::mnemonic::{Language, Mnemonic}; use crate::Network; use secp256k1::Secp256k1; @@ -28,7 +27,7 @@ fn create_test_extended_priv_key(network: Network) -> ExtendedPrivKey { fn test_bip44_account_creation() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); // Create multiple BIP44 accounts with different indices for index in 0..10 { @@ -38,7 +37,7 @@ fn test_bip44_account_creation() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv( Some([0u8; 32]), // wallet_id @@ -69,7 +68,7 @@ fn test_bip44_account_creation() { fn test_bip32_account_creation() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); // Create multiple BIP32 accounts with different indices for index in 0..5 { @@ -79,7 +78,7 @@ fn test_bip32_account_creation() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -105,7 +104,7 @@ fn test_bip32_account_creation() { fn test_coinjoin_account_creation() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); // Create CoinJoin accounts for index in 0..3 { @@ -114,7 +113,7 @@ fn test_coinjoin_account_creation() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -138,12 +137,12 @@ fn test_coinjoin_account_creation() { fn test_identity_registration_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::IdentityRegistration; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -158,7 +157,7 @@ fn test_identity_registration_account() { fn test_identity_topup_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); // Test multiple identity topup accounts with different registration indices for registration_index in 0..3 { @@ -167,7 +166,7 @@ fn test_identity_topup_account() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -191,12 +190,12 @@ fn test_identity_topup_account() { fn test_identity_topup_not_bound_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::IdentityTopUpNotBoundToIdentity; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -211,12 +210,12 @@ fn test_identity_topup_not_bound_account() { fn test_identity_invitation_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::IdentityInvitation; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -231,12 +230,12 @@ fn test_identity_invitation_account() { fn test_provider_voting_keys_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::ProviderVotingKeys; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -251,12 +250,12 @@ fn test_provider_voting_keys_account() { fn test_provider_owner_keys_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::ProviderOwnerKeys; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -271,12 +270,12 @@ fn test_provider_owner_keys_account() { fn test_provider_operator_keys_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::ProviderOperatorKeys; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -291,12 +290,12 @@ fn test_provider_operator_keys_account() { fn test_provider_platform_keys_account() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::ProviderPlatformKeys; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -311,7 +310,7 @@ fn test_provider_platform_keys_account() { fn test_account_extended_key_generation() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::Standard { index: 0, @@ -319,13 +318,12 @@ fn test_account_extended_key_generation() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); // Verify extended public key can be derived let xpub = account.extended_public_key(); - let secp = secp256k1::Secp256k1::new(); let expected_xpub = ExtendedPubKey::from_priv(&secp, &account_key); assert_eq!(xpub, expected_xpub); @@ -370,7 +368,7 @@ fn test_watch_only_account_creation() { fn test_account_network_consistency() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let account_type = AccountType::Standard { index: 0, @@ -378,7 +376,7 @@ fn test_account_network_consistency() { }; let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some([0u8; 32]), account_type, account_key, network).unwrap(); @@ -387,7 +385,6 @@ fn test_account_network_consistency() { // Test that wrong network would be rejected when deriving addresses // The account should generate addresses for the network it was created with - let secp = Secp256k1::new(); // Derive a child key for address generation (m/44'/1'/0'/0/0 for first receive address) let receive_path = [ @@ -419,7 +416,7 @@ fn test_account_network_consistency() { fn test_multiple_account_types_same_wallet() { let network = Network::Testnet; let master = create_test_extended_priv_key(network); - let hd_wallet = HDWallet::new(master); + let secp = Secp256k1::new(); let wallet_id = [1u8; 32]; // Create one of each account type @@ -451,7 +448,7 @@ fn test_multiple_account_types_same_wallet() { for account_type in account_types { let derivation_path = account_type.derivation_path(network).unwrap(); - let account_key = hd_wallet.derive(&derivation_path).unwrap(); + let account_key = master.derive_priv(&secp, &derivation_path).unwrap(); let account = Account::from_xpriv(Some(wallet_id), account_type, account_key, network).unwrap(); diff --git a/key-wallet/src/wallet/accounts.rs b/key-wallet/src/wallet/accounts.rs index 700a57ff3..f6c4041f1 100644 --- a/key-wallet/src/wallet/accounts.rs +++ b/key-wallet/src/wallet/accounts.rs @@ -9,8 +9,8 @@ use crate::account::BLSAccount; use crate::account::EdDSAAccount; use crate::account::{Account, AccountType}; use crate::bip32::ExtendedPubKey; -use crate::derivation::HDWallet; use crate::error::{Error, Result}; +use secp256k1::Secp256k1; impl Wallet { /// Add a new account to the wallet @@ -44,8 +44,9 @@ impl Wallet { // This will fail if the wallet doesn't have a private key (watch-only or externally managed) let root_key = self.root_extended_priv_key()?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; Account::from_xpriv(Some(wallet_id), account_type, account_xpriv, self.network)? }; @@ -97,8 +98,9 @@ impl Wallet { let seed = mnemonic.to_seed(passphrase); let root_key = super::root_extended_keys::RootExtendedPrivKey::new_master(&seed)?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; let account = Account::from_xpriv(Some(wallet_id), account_type, account_xpriv, self.network)?; @@ -158,8 +160,9 @@ impl Wallet { // This will fail if the wallet doesn't have a private key let root_key = self.root_extended_priv_key()?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; // Create BLS seed from derived private key let seed = account_xpriv.private_key.secret_bytes(); @@ -216,8 +219,9 @@ impl Wallet { let seed = mnemonic.to_seed(passphrase); let root_key = super::root_extended_keys::RootExtendedPrivKey::new_master(&seed)?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; // Create BLS seed from derived private key let bls_seed = account_xpriv.private_key.secret_bytes(); @@ -279,8 +283,9 @@ impl Wallet { // This will fail if the wallet doesn't have a private key let root_key = self.root_extended_priv_key()?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; // Create Ed25519 seed from derived private key let seed = account_xpriv.private_key.secret_bytes(); @@ -337,8 +342,9 @@ impl Wallet { let seed = mnemonic.to_seed(passphrase); let root_key = super::root_extended_keys::RootExtendedPrivKey::new_master(&seed)?; let master_key = root_key.to_extended_priv_key(self.network); - let hd_wallet = HDWallet::new(master_key); - let account_xpriv = hd_wallet.derive(&derivation_path)?; + let secp = Secp256k1::new(); + let account_xpriv = + master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; // Create Ed25519 seed from derived private key let ed25519_seed = account_xpriv.private_key.secret_bytes(); diff --git a/key-wallet/src/wallet/bip38.rs b/key-wallet/src/wallet/bip38.rs index 8c06c1d1e..df557f34e 100644 --- a/key-wallet/src/wallet/bip38.rs +++ b/key-wallet/src/wallet/bip38.rs @@ -52,22 +52,24 @@ impl Wallet { let master_key = root_key.to_extended_priv_key(self.network); use crate::account::AccountType; - use crate::derivation::HDWallet; - let hd_wallet = HDWallet::new(master_key); - let account_key = match &account.account_type { + match &account.account_type { AccountType::CoinJoin { .. - } => hd_wallet.coinjoin_account(account_index)?, - AccountType::Standard { + } + | AccountType::Standard { .. - } => hd_wallet.bip44_account(account_index)?, + } => {} _ => { return Err(Error::InvalidParameter( "Unsupported account type for BIP38 export".into(), )) } - }; + } + + let derivation_path = account.account_type.derivation_path(self.network)?; + let secp = secp256k1::Secp256k1::new(); + let account_key = master_key.derive_priv(&secp, &derivation_path).map_err(Error::Bip32)?; let secret_key = account_key.private_key; encrypt_private_key(&secret_key, password, true, self.network) diff --git a/key-wallet/tests/derivation_tests.rs b/key-wallet/tests/derivation_tests.rs index 3fcbb596f..39eee9d7f 100644 --- a/key-wallet/tests/derivation_tests.rs +++ b/key-wallet/tests/derivation_tests.rs @@ -1,116 +1,16 @@ //! Derivation tests +//! +//! DIP-17 Platform Payment Key Derivation test vectors. These are the only +//! derivation tests that live at this level; trivial BIP32/BIP44 derivation +//! is covered by the per-module tests in `key-wallet/src/derivation.rs`, +//! `key-wallet/src/dip9.rs`, and `key-wallet/src/tests/account_tests.rs`. use dashcore::hashes::Hash; -use key_wallet::derivation::{AccountDerivation, HDWallet}; use key_wallet::mnemonic::{Language, Mnemonic}; -use key_wallet::{DerivationPath, ExtendedPubKey, Network}; +use key_wallet::{DerivationPath, ExtendedPrivKey, ExtendedPubKey, Network}; use secp256k1::Secp256k1; use std::str::FromStr; -#[test] -fn test_hd_wallet_creation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Master key should be at depth 0 - assert_eq!(wallet.master_key().depth, 0); -} - -#[test] -fn test_bip44_account_derivation() { - let mnemonic = Mnemonic::from_phrase( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", - Language::English - ).unwrap(); - - let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Derive first account - let account0 = wallet.bip44_account(0).unwrap(); - assert_eq!(account0.depth, 3); // m/44'/5'/0' - - // Derive second account - let account1 = wallet.bip44_account(1).unwrap(); - assert_eq!(account1.depth, 3); // m/44'/5'/1' - - // Keys should be different - assert_ne!(account0.private_key.secret_bytes(), account1.private_key.secret_bytes()); -} - -#[test] -fn test_coinjoin_account_derivation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Derive CoinJoin account - let coinjoin_account = wallet.coinjoin_account(0).unwrap(); - assert_eq!(coinjoin_account.depth, 4); // m/9'/5'/4'/0' -} - -#[test] -fn test_identity_key_derivation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Derive identity authentication key - let identity_key = wallet.identity_authentication_key(0, 0).unwrap(); - assert_eq!(identity_key.depth, 6); // m/5'/5'/3'/0'/0'/0' -} - -#[test] -fn test_custom_path_derivation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Derive custom path - let path = DerivationPath::from_str("m/0/1/2").unwrap(); - let derived = wallet.derive(&path).unwrap(); - assert_eq!(derived.depth, 3); -} - -#[test] -fn test_account_address_derivation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Get account - let account = wallet.bip44_account(0).unwrap(); - let account_derivation = AccountDerivation::new(account); - - // Derive receive addresses - let addr0 = account_derivation.receive_address(0).unwrap(); - let addr1 = account_derivation.receive_address(1).unwrap(); - - // Addresses should be different - assert_ne!(addr0.public_key, addr1.public_key); - - // Derive change addresses - let change0 = account_derivation.change_address(0).unwrap(); - let change1 = account_derivation.change_address(1).unwrap(); - - // Change addresses should be different from receive addresses - assert_ne!(addr0.public_key, change0.public_key); - assert_ne!(change0.public_key, change1.public_key); -} - -#[test] -fn test_public_key_derivation() { - let seed = [0u8; 64]; - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); - - // Derive public key directly - let path = DerivationPath::from_str("m/44'/5'/0'/0/0").unwrap(); - let xpub = wallet.derive_pub(&path).unwrap(); - - // Should match derivation from private key - let xprv = wallet.derive(&path).unwrap(); - let secp = Secp256k1::new(); - let xpub_from_prv = ExtendedPubKey::from_priv(&secp, &xprv); - - assert_eq!(xpub.public_key, xpub_from_prv.public_key); -} - // ============================================================================= // DIP-17 Platform Payment Key Derivation Test Vectors // ============================================================================= @@ -138,11 +38,12 @@ fn test_dip17_platform_payment_vector1_mainnet() { .unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); + let secp = Secp256k1::new(); + let master_key = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); // Derive Platform Payment key: m/9'/5'/17'/0'/0'/0 let path = DerivationPath::from_str("m/9'/5'/17'/0'/0'/0").unwrap(); - let xprv = wallet.derive(&path).unwrap(); + let xprv = master_key.derive_priv(&secp, &path).unwrap(); // Verify private key matches DIP-17 test vector let privkey_hex = hex::encode(xprv.private_key.secret_bytes()); @@ -152,7 +53,6 @@ fn test_dip17_platform_payment_vector1_mainnet() { ); // Get compressed public key - let secp = Secp256k1::new(); let xpub = ExtendedPubKey::from_priv(&secp, &xprv); let pubkey = PublicKey::new(xpub.public_key); let pubkey_hex = hex::encode(pubkey.to_bytes()); @@ -185,14 +85,14 @@ fn test_dip17_platform_payment_vector1_testnet() { .unwrap(); let seed = mnemonic.to_seed(""); - let wallet = HDWallet::from_seed(&seed, Network::Testnet).unwrap(); + let secp = Secp256k1::new(); + let master_key = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); // Derive Platform Payment key: m/9'/1'/17'/0'/0'/0 (testnet uses coin_type 1') let path = DerivationPath::from_str("m/9'/1'/17'/0'/0'/0").unwrap(); - let xprv = wallet.derive(&path).unwrap(); + let xprv = master_key.derive_priv(&secp, &path).unwrap(); // Get compressed public key and HASH160 - let secp = Secp256k1::new(); let xpub = ExtendedPubKey::from_priv(&secp, &xprv); let pubkey = PublicKey::new(xpub.public_key); let pubkey_hash = pubkey.pubkey_hash(); @@ -220,9 +120,10 @@ fn test_dip17_platform_payment_vector2() { // Test mainnet let seed = mnemonic.to_seed(""); - let wallet_mainnet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); + let secp = Secp256k1::new(); + let master_mainnet = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); let path_mainnet = DerivationPath::from_str("m/9'/5'/17'/0'/0'/1").unwrap(); - let xprv_mainnet = wallet_mainnet.derive(&path_mainnet).unwrap(); + let xprv_mainnet = master_mainnet.derive_priv(&secp, &path_mainnet).unwrap(); // Verify private key let privkey_hex = hex::encode(xprv_mainnet.private_key.secret_bytes()); @@ -231,7 +132,6 @@ fn test_dip17_platform_payment_vector2() { "Private key mismatch for DIP-17 vector 2" ); - let secp = Secp256k1::new(); let xpub_mainnet = ExtendedPubKey::from_priv(&secp, &xprv_mainnet); let pubkey_mainnet = PublicKey::new(xpub_mainnet.public_key); @@ -251,9 +151,9 @@ fn test_dip17_platform_payment_vector2() { ); // Test testnet derivation - let wallet_testnet = HDWallet::from_seed(&seed, Network::Testnet).unwrap(); + let master_testnet = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); let path_testnet = DerivationPath::from_str("m/9'/1'/17'/0'/0'/1").unwrap(); - let xprv_testnet = wallet_testnet.derive(&path_testnet).unwrap(); + let xprv_testnet = master_testnet.derive_priv(&secp, &path_testnet).unwrap(); let xpub_testnet = ExtendedPubKey::from_priv(&secp, &xprv_testnet); let pubkey_testnet = PublicKey::new(xpub_testnet.public_key); let pubkey_hash_testnet = pubkey_testnet.pubkey_hash(); @@ -282,9 +182,10 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { // Test mainnet with key_class' = 1' let seed = mnemonic.to_seed(""); - let wallet_mainnet = HDWallet::from_seed(&seed, Network::Mainnet).unwrap(); + let secp = Secp256k1::new(); + let master_mainnet = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); let path_mainnet = DerivationPath::from_str("m/9'/5'/17'/0'/1'/0").unwrap(); - let xprv_mainnet = wallet_mainnet.derive(&path_mainnet).unwrap(); + let xprv_mainnet = master_mainnet.derive_priv(&secp, &path_mainnet).unwrap(); // Verify private key let privkey_hex = hex::encode(xprv_mainnet.private_key.secret_bytes()); @@ -293,7 +194,6 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { "Private key mismatch for DIP-17 vector 3" ); - let secp = Secp256k1::new(); let xpub_mainnet = ExtendedPubKey::from_priv(&secp, &xprv_mainnet); let pubkey_mainnet = PublicKey::new(xpub_mainnet.public_key); @@ -313,9 +213,9 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { ); // Test testnet with key_class' = 1' - let wallet_testnet = HDWallet::from_seed(&seed, Network::Testnet).unwrap(); + let master_testnet = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); let path_testnet = DerivationPath::from_str("m/9'/1'/17'/0'/1'/0").unwrap(); - let xprv_testnet = wallet_testnet.derive(&path_testnet).unwrap(); + let xprv_testnet = master_testnet.derive_priv(&secp, &path_testnet).unwrap(); let xpub_testnet = ExtendedPubKey::from_priv(&secp, &xprv_testnet); let pubkey_testnet = PublicKey::new(xpub_testnet.public_key); let pubkey_hash_testnet = pubkey_testnet.pubkey_hash();