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}