1use crate::{PathValue, CustomHDPath};
2use byteorder::{BigEndian, WriteBytesExt};
3#[cfg(feature = "with-bitcoin")]
4use bitcoin::bip32::{ChildNumber, DerivationPath};
5
6pub trait HDPath {
13
14 fn len(&self) -> u8;
16
17 fn get(&self, pos: u8) -> Option<PathValue>;
23
24 fn to_bytes(&self) -> Vec<u8> {
27 let len = self.len();
28 let mut buf = Vec::with_capacity(1 + 4 * (len as usize));
29 buf.push(len);
30 for i in 0..len {
31 buf.write_u32::<BigEndian>(self.get(i)
32 .expect(format!("No valut at {}", i).as_str())
33 .to_raw()).unwrap();
34 }
35 buf
36 }
37
38 fn parent(&self) -> Option<CustomHDPath> {
42 if self.len() == 0 {
43 return None
44 }
45 let len = self.len();
46 let mut parent_hd_path = Vec::with_capacity(len as usize - 1);
47 for i in 0..len - 1 {
48 parent_hd_path.push(self.get(i).unwrap());
49 }
50 let parent_hd_path = CustomHDPath::try_new(parent_hd_path)
51 .expect("No parent HD Path");
52 Some(parent_hd_path)
53 }
54
55 fn as_custom(&self) -> CustomHDPath {
58 let len = self.len();
59 let mut path = Vec::with_capacity(len as usize);
60 for i in 0..len {
61 path.push(self.get(i).unwrap());
62 }
63 CustomHDPath::try_new(path).expect("Invalid HD Path")
64 }
65
66 #[cfg(feature = "with-bitcoin")]
69 fn as_bitcoin(&self) -> DerivationPath {
70 let len = self.len();
71 let mut path = Vec::with_capacity(len as usize);
72 for i in 0..len {
73 path.push(ChildNumber::from(self.get(i).unwrap()));
74 }
75 DerivationPath::from(path)
76 }
77}
78
79#[cfg(feature = "with-bitcoin")]
80impl std::convert::From<&dyn HDPath> for DerivationPath {
81 fn from(value: &dyn HDPath) -> Self {
82 let mut path = Vec::with_capacity(value.len() as usize);
83 for i in 0..value.len() {
84 path.push(ChildNumber::from(value.get(i).expect("no-path-element")));
85 }
86 DerivationPath::from(path)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::{StandardHDPath, AccountHDPath};
94 use std::str::FromStr;
95
96 impl StandardHDPath {
97 pub fn to_trait(&self) -> &dyn HDPath {
98 self
99 }
100 }
101
102 #[test]
103 fn get_parent_from_std() {
104 let act = StandardHDPath::from_str("m/44'/0'/1'/1/2").unwrap();
105 let parent = act.parent();
106 assert!(parent.is_some());
107 let parent = parent.unwrap();
108 assert_eq!(
109 "m/44'/0'/1'/1", parent.to_string()
110 );
111 }
112
113 #[test]
114 fn get_parent_twice() {
115 let act = StandardHDPath::from_str("m/44'/0'/1'/1/2").unwrap();
116 let parent = act.parent().unwrap().parent();
117 assert!(parent.is_some());
118 let parent = parent.unwrap();
119 assert_eq!(
120 "m/44'/0'/1'", parent.to_string()
121 );
122 }
123
124 #[test]
125 fn get_parent_from_account() {
126 let act = AccountHDPath::from_str("m/84'/0'/1'").unwrap();
127 let parent = act.parent();
128 assert!(parent.is_some());
129 let parent = parent.unwrap();
130 assert_eq!(
131 "m/84'/0'", parent.to_string()
132 );
133 }
134
135 #[test]
136 fn get_parent_from_custom() {
137 let act = CustomHDPath::from_str("m/84'/0'/1'/0/16").unwrap();
138 let parent = act.parent();
139 assert!(parent.is_some());
140 let parent = parent.unwrap();
141 assert_eq!(
142 "m/84'/0'/1'/0", parent.to_string()
143 );
144 }
145
146 #[test]
147 fn convert_account_to_custom() {
148 let src = AccountHDPath::from_str("m/84'/0'/1'").unwrap();
149 let act = src.as_custom();
150 assert_eq!(CustomHDPath::from_str("m/84'/0'/1'").unwrap(), act);
151 }
152
153 #[test]
154 fn convert_standard_to_custom() {
155 let src = StandardHDPath::from_str("m/84'/0'/1'/0/2").unwrap();
156 let act = src.as_custom();
157 assert_eq!(CustomHDPath::from_str("m/84'/0'/1'/0/2").unwrap(), act);
158 }
159}
160
161#[cfg(all(test, feature = "with-bitcoin"))]
162mod tests_with_bitcoin {
163 use crate::{StandardHDPath, HDPath};
164 use std::str::FromStr;
165 use bitcoin::bip32::{DerivationPath};
166
167 #[test]
168 fn convert_to_bitcoin() {
169 let source = StandardHDPath::from_str("m/44'/0'/1'/1/2").unwrap();
170 let act = DerivationPath::from(source.to_trait());
171 assert_eq!(
172 DerivationPath::from_str("m/44'/0'/1'/1/2").unwrap(),
173 act
174 )
175 }
176
177 #[test]
178 fn convert_to_bitcoin_directly() {
179 let source = StandardHDPath::from_str("m/44'/0'/1'/1/2").unwrap();
180 let act = source.as_bitcoin();
181 assert_eq!(
182 DerivationPath::from_str("m/44'/0'/1'/1/2").unwrap(),
183 act
184 )
185 }
186}