hdpath/
purpose.rs

1use std::cmp::Ordering;
2use crate::{PathValue, Error};
3use std::convert::TryFrom;
4#[cfg(feature = "with-bitcoin")]
5use bitcoin::bip32::{ChildNumber};
6
7/// The purpose number, a first number in HD Path, which is supposed to be reference actual format. Supposed to be a hardened value
8/// See [BIP-43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
9#[derive(Debug, Clone, Eq, Hash)]
10pub enum Purpose {
11    None, //0'
12    Pubkey, //44'
13    ScriptHash, //49'
14    Witness, //84'
15    Custom(u32)
16}
17
18impl PartialOrd for Purpose {
19    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
20        if self.as_value().to_raw() > other.as_value().to_raw() {
21            Some(Ordering::Greater)
22        } else if self.as_value().to_raw() == other.as_value().to_raw() {
23            Some(Ordering::Equal)
24        } else {
25            Some(Ordering::Less)
26        }
27    }
28}
29
30impl Ord for Purpose {
31    fn cmp(&self, other: &Self) -> Ordering {
32        if self.as_value().to_raw() > other.as_value().to_raw() {
33            Ordering::Greater
34        } else if self.as_value().to_raw() == other.as_value().to_raw() {
35            Ordering::Equal
36        } else {
37            Ordering::Less
38        }
39    }
40}
41
42impl PartialEq for Purpose {
43    fn eq(&self, other: &Self) -> bool {
44        self.as_value().to_raw() == other.as_value().to_raw()
45    }
46}
47
48impl Purpose {
49    pub fn as_value(&self) -> PathValue {
50        let n = match self {
51            Purpose::None => 0,
52            Purpose::Pubkey => 44,
53            Purpose::ScriptHash => 49,
54            Purpose::Witness => 84,
55            Purpose::Custom(n) => *n
56        };
57        PathValue::Hardened(n)
58    }
59}
60
61impl TryFrom<u32> for Purpose {
62    type Error = Error;
63
64    fn try_from(value: u32) -> Result<Self, Self::Error> {
65        match value {
66            44 => Ok(Purpose::Pubkey),
67            49 => Ok(Purpose::ScriptHash),
68            84 => Ok(Purpose::Witness),
69            n => if PathValue::is_ok(n) {
70                Ok(Purpose::Custom(n))
71            } else {
72                Err(Error::HighBitIsSet)
73            }
74        }
75    }
76}
77
78impl From<Purpose> for u32 {
79    fn from(value: Purpose) -> Self {
80        match value {
81            Purpose::None => 0,
82            Purpose::Pubkey => 44,
83            Purpose::ScriptHash => 49,
84            Purpose::Witness => 84,
85            Purpose::Custom(n) => n.clone()
86        }
87    }
88}
89
90impl From<&Purpose> for u32 {
91    fn from(value: &Purpose) -> Self {
92        match value {
93            Purpose::None => 0,
94            Purpose::Pubkey => 44,
95            Purpose::ScriptHash => 49,
96            Purpose::Witness => 84,
97            Purpose::Custom(n) => n.clone()
98        }
99    }
100}
101
102impl TryFrom<usize> for Purpose {
103    type Error = Error;
104
105    fn try_from(value: usize) -> Result<Self, Self::Error> {
106        Purpose::try_from(value as u32)
107    }
108}
109
110impl TryFrom<i32> for Purpose {
111    type Error = Error;
112
113    fn try_from(value: i32) -> Result<Self, Self::Error> {
114        if value < 0 {
115            return Err(Error::InvalidPurpose(0))
116        }
117        Purpose::try_from(value as u32)
118    }
119}
120
121impl TryFrom<PathValue> for Purpose {
122    type Error = Error;
123
124    fn try_from(value: PathValue) -> Result<Self, Self::Error> {
125        Purpose::try_from(value.as_number())
126    }
127}
128
129#[cfg(feature = "with-bitcoin")]
130impl From<Purpose> for ChildNumber {
131    fn from(value: Purpose) -> Self {
132        ChildNumber::from_hardened_idx(value.into()).unwrap()
133    }
134}
135
136#[cfg(feature = "with-bitcoin")]
137impl From<&Purpose> for ChildNumber {
138    fn from(value: &Purpose) -> Self {
139        ChildNumber::from_hardened_idx(value.into()).unwrap()
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::convert::TryFrom;
147
148    #[test]
149    pub fn create_standard_purpose() {
150        assert_eq!(Purpose::Pubkey, Purpose::try_from(44 as u32).unwrap());
151        assert_eq!(Purpose::Pubkey, Purpose::try_from(44 as usize).unwrap());
152        assert_eq!(Purpose::Pubkey, Purpose::try_from(44).unwrap());
153
154        assert_eq!(Purpose::ScriptHash, Purpose::try_from(49).unwrap());
155        assert_eq!(Purpose::Witness, Purpose::try_from(84).unwrap());
156    }
157
158    #[test]
159    pub fn create_custom_purpose() {
160        assert_eq!(Purpose::Custom(101), Purpose::try_from(101).unwrap());
161    }
162
163    #[test]
164    pub fn compare() {
165        assert!(Purpose::None < Purpose::Witness);
166        assert!(Purpose::None < Purpose::Pubkey);
167        assert!(Purpose::Pubkey < Purpose::Witness);
168        assert!(Purpose::ScriptHash < Purpose::Witness);
169        assert!(Purpose::Custom(0) < Purpose::Witness);
170        assert!(Purpose::Custom(100) > Purpose::Witness);
171        assert!(Purpose::Custom(50) > Purpose::Pubkey);
172    }
173
174    #[test]
175    pub fn order() {
176        let mut values = [
177            Purpose::Witness, Purpose::None, Purpose::Pubkey, Purpose::ScriptHash, Purpose::Pubkey,
178            Purpose::Custom(44), Purpose::Custom(84), Purpose::Custom(50), Purpose::Custom(1000)
179        ];
180        values.sort();
181        assert_eq!(
182            [
183                Purpose::None, Purpose::Pubkey, Purpose::Pubkey, Purpose::Custom(44), Purpose::ScriptHash,
184                Purpose::Custom(50), Purpose::Witness,
185                Purpose::Custom(84),  Purpose::Custom(1000)
186            ],
187            values
188        )
189    }
190
191}