hdpath/
path_value.rs

1#[cfg(feature = "with-bitcoin")]
2use bitcoin::bip32::ChildNumber;
3
4pub const FIRST_BIT: u32 = 0x80000000;
5
6#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
7pub enum PathValue {
8    Normal(u32),
9    Hardened(u32),
10}
11
12impl PathValue {
13    pub fn is_ok(value: u32) -> bool {
14        value < FIRST_BIT
15    }
16
17    pub fn try_normal(value: u32) -> Result<PathValue, ()> {
18        if !PathValue::is_ok(value) {
19            Err(())
20        } else {
21            Ok(PathValue::Normal(value))
22        }
23    }
24
25    pub fn normal(value: u32) -> PathValue {
26        if let Ok(result) = PathValue::try_normal(value) {
27            result
28        } else {
29            panic!("Raw hardened value passed")
30        }
31    }
32
33    pub fn try_hardened(value: u32) -> Result<PathValue, ()> {
34        if !PathValue::is_ok(value) {
35            Err(())
36        } else {
37            Ok(PathValue::Hardened(value))
38        }
39    }
40
41    pub fn hardened(value: u32) -> PathValue {
42        if let Ok(result) = PathValue::try_hardened(value) {
43            result
44        } else {
45            panic!("Raw hardened value passed")
46        }
47    }
48
49    pub fn from_raw(value: u32) -> PathValue {
50        if value >= FIRST_BIT {
51            PathValue::Hardened(value - FIRST_BIT)
52        } else {
53            PathValue::Normal(value)
54        }
55    }
56
57    pub fn as_number(&self) -> u32 {
58        match &self {
59            PathValue::Normal(n) => *n,
60            PathValue::Hardened(n) => *n
61        }
62    }
63
64    pub fn to_raw(&self) -> u32 {
65        match &self {
66            PathValue::Normal(n) => *n,
67            PathValue::Hardened(n) => *n + FIRST_BIT
68        }
69    }
70}
71
72#[cfg(feature = "with-bitcoin")]
73impl From<PathValue> for ChildNumber {
74    fn from(value: PathValue) -> Self {
75        match value {
76            PathValue::Hardened(i) => ChildNumber::from_hardened_idx(i).unwrap(),
77            PathValue::Normal(i) => ChildNumber::from_normal_idx(i).unwrap(),
78        }
79    }
80}
81
82impl std::fmt::Display for PathValue {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            PathValue::Normal(n) => write!(f, "{}", n),
86            PathValue::Hardened(n) => write!(f, "{}'", n)
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    #[cfg(feature = "with-bitcoin")]
95    use bitcoin::bip32::ChildNumber;
96
97    #[test]
98    #[cfg(feature = "with-bitcoin")]
99    fn convert_to_bitcoin() {
100        let act: ChildNumber = PathValue::Normal(0).into();
101        assert_eq!(ChildNumber::from_normal_idx(0).unwrap(), act);
102
103        let act: ChildNumber = PathValue::Normal(1).into();
104        assert_eq!(ChildNumber::from_normal_idx(1).unwrap(), act);
105
106        let act: ChildNumber = PathValue::Normal(100).into();
107        assert_eq!(ChildNumber::from_normal_idx(100).unwrap(), act);
108
109        let act: ChildNumber = PathValue::Hardened(0).into();
110        assert_eq!(ChildNumber::from_hardened_idx(0).unwrap(), act);
111
112        let act: ChildNumber = PathValue::Hardened(1).into();
113        assert_eq!(ChildNumber::from_hardened_idx(1).unwrap(), act);
114
115        let act: ChildNumber = PathValue::Hardened(11).into();
116        assert_eq!(ChildNumber::from_hardened_idx(11).unwrap(), act);
117    }
118
119    #[test]
120    fn to_string_normal() {
121        assert_eq!(PathValue::Normal(0).to_string(), "0");
122        assert_eq!(PathValue::Normal(11).to_string(), "11");
123    }
124
125    #[test]
126    fn to_string_hardened() {
127        assert_eq!(PathValue::Hardened(0).to_string(), "0'");
128        assert_eq!(PathValue::Hardened(1).to_string(), "1'");
129    }
130
131    #[test]
132    fn display_normal() {
133        assert_eq!(format!("{}", PathValue::Normal(0)), "0");
134        assert_eq!(format!("{}", PathValue::Normal(11)), "11");
135    }
136
137    #[test]
138    fn display_hardened() {
139        assert_eq!(format!("{}", PathValue::Hardened(0)), "0'");
140        assert_eq!(format!("{}", PathValue::Hardened(11)), "11'");
141    }
142
143    #[test]
144    fn ok_for_small_values() {
145        let values = vec![
146            0u32, 1, 2, 3,
147            100, 1000, 10000,
148            0x80000000 - 1
149        ];
150        for value in values {
151            assert!(PathValue::is_ok(value), "value: {}", value);
152        }
153    }
154
155    #[test]
156    fn not_ok_for_large_values() {
157        let values = vec![
158            0x80000000, 0x80000001,
159            0xffffffff
160        ];
161        for value in values {
162            assert!(!PathValue::is_ok(value), "value: {}", value);
163        }
164    }
165
166    #[test]
167    fn create_normal() {
168        assert_eq!(PathValue::Normal(0), PathValue::normal(0));
169        assert_eq!(PathValue::Normal(1), PathValue::normal(1));
170        assert_eq!(PathValue::Normal(101), PathValue::normal(101));
171    }
172
173    #[test]
174    fn create_hardened() {
175        assert_eq!(PathValue::Hardened(0), PathValue::hardened(0));
176        assert_eq!(PathValue::Hardened(1), PathValue::hardened(1));
177        assert_eq!(PathValue::Hardened(101), PathValue::hardened(101));
178    }
179
180    #[test]
181    #[should_panic]
182    fn panic_on_invalid_normal() {
183        PathValue::normal(0x80000001);
184    }
185
186    #[test]
187    #[should_panic]
188    fn panic_on_invalid_hardened() {
189        PathValue::hardened(0x80000001);
190    }
191
192    #[test]
193    fn create_from_raw_normal() {
194        assert_eq!(PathValue::Normal(0), PathValue::from_raw(0));
195        assert_eq!(PathValue::Normal(100), PathValue::from_raw(100));
196        assert_eq!(PathValue::Normal(0xffffff), PathValue::from_raw(0xffffff));
197    }
198
199    #[test]
200    fn create_from_raw_hardened() {
201        assert_eq!(PathValue::hardened(0), PathValue::from_raw(0x80000000));
202        assert_eq!(PathValue::hardened(1), PathValue::from_raw(0x80000001));
203        assert_eq!(PathValue::hardened(44), PathValue::from_raw(0x8000002c));
204    }
205
206    #[test]
207    fn to_raw_normal() {
208        assert_eq!(0, PathValue::Normal(0).to_raw());
209        assert_eq!(123, PathValue::Normal(123).to_raw());
210    }
211
212    #[test]
213    fn to_raw_hardened() {
214        assert_eq!(0x80000000, PathValue::Hardened(0).to_raw());
215        assert_eq!(0x8000002c, PathValue::Hardened(44).to_raw());
216    }
217
218    #[test]
219    fn as_number_normal() {
220        assert_eq!(0, PathValue::Normal(0).as_number());
221        assert_eq!(123, PathValue::Normal(123).as_number());
222    }
223
224    #[test]
225    fn as_number_hardened() {
226        assert_eq!(0, PathValue::Hardened(0).as_number());
227        assert_eq!(123, PathValue::Hardened(123).as_number());
228    }
229}