From bc148065073ba01ff1a08302e5068c587dcd4c98 Mon Sep 17 00:00:00 2001 From: quantum Date: Wed, 9 Jul 2025 16:05:45 -0500 Subject: [PATCH] refactor(key-wallet): improve API with standard FromStr trait and better error handling - Implement standard FromStr trait for Address parsing - Keep explicit to_string() method for backward compatibility - Add from_string() convenience method - Improve error handling by using explicit Error::Bip32 wrapping instead of generic Into - Fix AccountDerivation to derive private keys first, then convert to public - Update tests to import FromStr trait where needed These changes make the key-wallet API more idiomatic while maintaining compatibility. --- key-wallet/examples/basic_usage.rs | 3 +- key-wallet/src/address.rs | 59 +++++++++++++++++------------- key-wallet/src/derivation.rs | 12 ++++-- key-wallet/tests/address_tests.rs | 2 +- key-wallet/tests/mnemonic_tests.rs | 2 +- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/key-wallet/examples/basic_usage.rs b/key-wallet/examples/basic_usage.rs index 39a5ec0b7..5e6c6ee91 100644 --- a/key-wallet/examples/basic_usage.rs +++ b/key-wallet/examples/basic_usage.rs @@ -67,7 +67,7 @@ fn main() -> core::result::Result<(), Box> { // 8. Address parsing example println!("\n8. Address parsing..."); let test_address = "XyPvhVmhWKDgvMJLwfFfMwhxpxGgd3TBxq"; - match key_wallet::address::Address::from_str(test_address) { + match test_address.parse::() { Ok(parsed) => { println!(" Parsed address: {}", parsed); println!(" Type: {:?}", parsed.address_type); @@ -84,7 +84,6 @@ fn demonstrate_address_generation() -> core::result::Result<(), Box Result { + pub fn from_string(s: &str) -> Result { + s.parse() + } + + /// Get the script pubkey for this address + pub fn script_pubkey(&self) -> Vec { + match self.address_type { + AddressType::P2PKH => { + let mut script = Vec::with_capacity(25); + script.push(0x76); // OP_DUP + script.push(0xa9); // OP_HASH160 + script.push(0x14); // Push 20 bytes + script.extend_from_slice(&self.hash[..]); + script.push(0x88); // OP_EQUALVERIFY + script.push(0xac); // OP_CHECKSIG + script + } + AddressType::P2SH => { + let mut script = Vec::with_capacity(23); + script.push(0xa9); // OP_HASH160 + script.push(0x14); // Push 20 bytes + script.extend_from_slice(&self.hash[..]); + script.push(0x87); // OP_EQUAL + script + } + } + } +} + +impl FromStr for Address { + type Err = Error; + + fn from_str(s: &str) -> Result { let data = base58ck::decode_check(s) .map_err(|_| Error::InvalidAddress("Invalid base58 encoding".into()))?; @@ -132,30 +165,6 @@ impl Address { hash, }) } - - /// Get the script pubkey for this address - pub fn script_pubkey(&self) -> Vec { - match self.address_type { - AddressType::P2PKH => { - let mut script = Vec::with_capacity(25); - script.push(0x76); // OP_DUP - script.push(0xa9); // OP_HASH160 - script.push(0x14); // Push 20 bytes - script.extend_from_slice(&self.hash[..]); - script.push(0x88); // OP_EQUALVERIFY - script.push(0xac); // OP_CHECKSIG - script - } - AddressType::P2SH => { - let mut script = Vec::with_capacity(23); - script.push(0xa9); // OP_HASH160 - script.push(0x14); // Push 20 bytes - script.extend_from_slice(&self.hash[..]); - script.push(0x87); // OP_EQUAL - script - } - } - } } impl fmt::Display for Address { diff --git a/key-wallet/src/derivation.rs b/key-wallet/src/derivation.rs index 1bcde5e6d..2b5eefc3c 100644 --- a/key-wallet/src/derivation.rs +++ b/key-wallet/src/derivation.rs @@ -28,7 +28,7 @@ impl KeyDerivation for ExtendedPrivKey { secp: &Secp256k1, path: &DerivationPath, ) -> Result { - self.derive_priv(secp, path).map_err(Into::into) + self.derive_priv(secp, path).map_err(Error::Bip32) } fn derive_pub( @@ -74,7 +74,7 @@ impl HDWallet { /// Derive a key at the given path pub fn derive(&self, path: &DerivationPath) -> Result { - self.master_key.derive_priv(&self.secp, path).map_err(Into::into) + self.master_key.derive_priv(&self.secp, path).map_err(Error::Bip32) } /// Derive a public key at the given path @@ -158,7 +158,9 @@ impl AccountDerivation { let path = format!("m/0/{}", index) .parse::() .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - self.account_key.derive_pub(&self.secp, &path).map_err(Into::into) + 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 @@ -166,7 +168,9 @@ impl AccountDerivation { let path = format!("m/1/{}", index) .parse::() .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?; - self.account_key.derive_pub(&self.secp, &path).map_err(Into::into) + let priv_key = self.account_key.derive_priv(&self.secp, &path) + .map_err(Error::Bip32)?; + Ok(ExtendedPubKey::from_priv(&self.secp, &priv_key)) } } diff --git a/key-wallet/tests/address_tests.rs b/key-wallet/tests/address_tests.rs index e9f52aaac..b8bfce729 100644 --- a/key-wallet/tests/address_tests.rs +++ b/key-wallet/tests/address_tests.rs @@ -1,5 +1,6 @@ //! Address tests +use std::str::FromStr; use bitcoin_hashes::{hash160, Hash}; use key_wallet::address::{Address, AddressGenerator, AddressType}; use key_wallet::derivation::HDWallet; @@ -98,7 +99,6 @@ fn test_address_generator() { let wallet = HDWallet::from_seed(&seed, Network::Dash).unwrap(); // Get account public key - let account = wallet.bip44_account(0).unwrap(); let path = key_wallet::DerivationPath::from(vec![ key_wallet::ChildNumber::from_hardened_idx(44).unwrap(), key_wallet::ChildNumber::from_hardened_idx(5).unwrap(), diff --git a/key-wallet/tests/mnemonic_tests.rs b/key-wallet/tests/mnemonic_tests.rs index 7554d75d9..0eaab8028 100644 --- a/key-wallet/tests/mnemonic_tests.rs +++ b/key-wallet/tests/mnemonic_tests.rs @@ -1,7 +1,7 @@ //! Mnemonic tests use key_wallet::mnemonic::{Language, Mnemonic}; -use key_wallet::{ExtendedPrivKey, Network}; +use key_wallet::Network; #[test] fn test_mnemonic_validation() {