Skip to main content

kobe_btc/
types.rs

1//! Common types for Bitcoin wallet operations.
2
3#[cfg(feature = "alloc")]
4use alloc::{format, string::ToString};
5
6#[cfg(feature = "alloc")]
7use crate::{Error, Network};
8use core::fmt;
9#[cfg(feature = "alloc")]
10use core::str::FromStr;
11
12/// Bitcoin address types.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum AddressType {
15    /// Pay to Public Key Hash (Legacy) - starts with 1 or m/n
16    P2pkh,
17    /// Pay to Script Hash wrapping P2WPKH (SegWit compatible) - starts with 3 or 2
18    P2shP2wpkh,
19    /// Pay to Witness Public Key Hash (Native SegWit) - starts with bc1q or tb1q
20    #[default]
21    P2wpkh,
22    /// Pay to Taproot (Taproot/SegWit v1) - starts with bc1p or tb1p
23    P2tr,
24}
25
26impl AddressType {
27    /// Get the BIP purpose for this address type.
28    #[inline]
29    #[must_use]
30    pub const fn purpose(self) -> u32 {
31        match self {
32            Self::P2pkh => 44,
33            Self::P2shP2wpkh => 49,
34            Self::P2wpkh => 84,
35            Self::P2tr => 86,
36        }
37    }
38
39    /// Get address type name.
40    #[inline]
41    #[must_use]
42    pub const fn name(self) -> &'static str {
43        match self {
44            Self::P2pkh => "P2PKH (Legacy)",
45            Self::P2shP2wpkh => "P2SH-P2WPKH (SegWit)",
46            Self::P2wpkh => "P2WPKH (Native SegWit)",
47            Self::P2tr => "P2TR (Taproot)",
48        }
49    }
50}
51
52impl fmt::Display for AddressType {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "{}", self.name())
55    }
56}
57
58/// BIP32 derivation path.
59#[cfg(feature = "alloc")]
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct DerivationPath {
62    inner: bitcoin::bip32::DerivationPath,
63}
64
65#[cfg(feature = "alloc")]
66impl DerivationPath {
67    /// Create a BIP44/49/84 standard path.
68    ///
69    /// Format: `m/purpose'/coin_type'/account'/change/address_index`
70    #[must_use]
71    pub fn bip_standard(
72        address_type: AddressType,
73        network: Network,
74        account: u32,
75        change: bool,
76        address_index: u32,
77    ) -> Self {
78        let purpose = address_type.purpose();
79        let coin_type = network.coin_type();
80        let change_val = if change { 1 } else { 0 };
81
82        let path_str = format!("m/{purpose}'/{coin_type}'/{account}'/{change_val}/{address_index}");
83
84        Self {
85            inner: path_str.parse().expect("valid BIP standard path"),
86        }
87    }
88
89    /// Create from a custom path string.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error if the path string is invalid.
94    pub fn from_path_str(path: &str) -> Result<Self, Error> {
95        let inner = bitcoin::bip32::DerivationPath::from_str(path)
96            .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
97        Ok(Self { inner })
98    }
99
100    /// Get the inner bitcoin derivation path.
101    #[inline]
102    #[must_use]
103    pub fn inner(&self) -> &bitcoin::bip32::DerivationPath {
104        &self.inner
105    }
106}
107
108#[cfg(feature = "alloc")]
109impl fmt::Display for DerivationPath {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(f, "m/{}", self.inner)
112    }
113}
114
115#[cfg(feature = "alloc")]
116impl AsRef<bitcoin::bip32::DerivationPath> for DerivationPath {
117    fn as_ref(&self) -> &bitcoin::bip32::DerivationPath {
118        &self.inner
119    }
120}