rustywallet_address/bitcoin/
mod.rs

1//! Bitcoin address types and generation.
2
3mod p2pkh;
4mod p2wpkh;
5mod p2tr;
6
7pub use p2pkh::*;
8pub use p2wpkh::*;
9pub use p2tr::*;
10
11use crate::error::AddressError;
12use crate::network::Network;
13
14/// Bitcoin address types.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum BitcoinAddressType {
17    /// Pay-to-Public-Key-Hash (Legacy)
18    P2PKH,
19    /// Pay-to-Witness-Public-Key-Hash (SegWit)
20    P2WPKH,
21    /// Pay-to-Taproot
22    P2TR,
23}
24
25impl std::fmt::Display for BitcoinAddressType {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            BitcoinAddressType::P2PKH => write!(f, "P2PKH"),
29            BitcoinAddressType::P2WPKH => write!(f, "P2WPKH"),
30            BitcoinAddressType::P2TR => write!(f, "P2TR"),
31        }
32    }
33}
34
35/// Bitcoin address with type and network info.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct BitcoinAddress {
38    address_type: BitcoinAddressType,
39    network: Network,
40    payload: Vec<u8>,
41    encoded: String,
42}
43
44impl BitcoinAddress {
45    /// Get the address type.
46    #[inline]
47    pub fn address_type(&self) -> BitcoinAddressType {
48        self.address_type
49    }
50
51    /// Get the network.
52    #[inline]
53    pub fn network(&self) -> Network {
54        self.network
55    }
56
57    /// Get the payload (hash or x-only pubkey).
58    #[inline]
59    pub fn payload(&self) -> &[u8] {
60        &self.payload
61    }
62
63    /// Parse address from string with auto-detection.
64    pub fn parse(s: &str) -> Result<Self, AddressError> {
65        s.parse()
66    }
67
68    /// Validate address string.
69    pub fn validate(s: &str) -> Result<(), AddressError> {
70        s.parse::<Self>().map(|_| ())
71    }
72}
73
74impl std::fmt::Display for BitcoinAddress {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(f, "{}", self.encoded)
77    }
78}
79
80impl std::str::FromStr for BitcoinAddress {
81    type Err = AddressError;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        // Try P2PKH (Base58Check)
85        if s.starts_with('1') || s.starts_with('m') || s.starts_with('n') {
86            return s.parse::<P2PKHAddress>().map(|a| a.into());
87        }
88
89        // Try SegWit/Taproot (Bech32/Bech32m)
90        let lower = s.to_lowercase();
91        if lower.starts_with("bc1q") || lower.starts_with("tb1q") {
92            return s.parse::<P2WPKHAddress>().map(|a| a.into());
93        }
94        if lower.starts_with("bc1p") || lower.starts_with("tb1p") {
95            return s.parse::<P2TRAddress>().map(|a| a.into());
96        }
97
98        Err(AddressError::UnsupportedAddressType(format!(
99            "Unknown Bitcoin address format: {}",
100            s
101        )))
102    }
103}
104
105impl From<P2PKHAddress> for BitcoinAddress {
106    fn from(addr: P2PKHAddress) -> Self {
107        Self {
108            address_type: BitcoinAddressType::P2PKH,
109            network: addr.network(),
110            payload: addr.hash().to_vec(),
111            encoded: addr.to_string(),
112        }
113    }
114}
115
116impl From<P2WPKHAddress> for BitcoinAddress {
117    fn from(addr: P2WPKHAddress) -> Self {
118        Self {
119            address_type: BitcoinAddressType::P2WPKH,
120            network: addr.network(),
121            payload: addr.hash().to_vec(),
122            encoded: addr.to_string(),
123        }
124    }
125}
126
127impl From<P2TRAddress> for BitcoinAddress {
128    fn from(addr: P2TRAddress) -> Self {
129        Self {
130            address_type: BitcoinAddressType::P2TR,
131            network: addr.network(),
132            payload: addr.x_only_pubkey().to_vec(),
133            encoded: addr.to_string(),
134        }
135    }
136}