hdpath/
traits.rs

1use crate::{PathValue, CustomHDPath};
2use byteorder::{BigEndian, WriteBytesExt};
3#[cfg(feature = "with-bitcoin")]
4use bitcoin::bip32::{ChildNumber, DerivationPath};
5
6/// General trait for an HDPath.
7/// Common implementations are [`StandardHDPath`], [`AccountHDPath`] and [`CustomHDPath`]
8///
9/// [`StandardHDPath`]: struct.StandardHDPath.html
10/// [`AccountHDPath`]: struct.AccountHDPath.html
11/// [`CustomHDPath`]: struct.CustomHDPath.html
12pub trait HDPath {
13
14    /// Size of the HD Path
15    fn len(&self) -> u8;
16
17    /// Get element as the specified position.
18    /// The implementation must return `Some<PathValue>` for all values up to `len()`.
19    /// And return `None` if the position if out of bounds.
20    ///
21    /// See [`PathValue`](enum.PathValue.html)
22    fn get(&self, pos: u8) -> Option<PathValue>;
23
24    /// Encode as bytes, where first byte is number of elements in path (always 5 for StandardHDPath)
25    /// following by 4-byte BE values
26    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    ///
39    /// Get parent HD Path.
40    /// Return `None` if the current path is empty (i.e. already at the top)
41    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    ///
56    /// Convert current to `CustomHDPath` structure
57    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    ///
67    /// Convert current to bitcoin lib type
68    #[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}