hdpath/
path_short.rs

1use crate::{Purpose, CustomHDPath, Error, PathValue};
2use std::convert::TryFrom;
3#[cfg(feature = "with-bitcoin")]
4use bitcoin::util::bip32::{ChildNumber, DerivationPath};
5use std::str::FromStr;
6use crate::traits::HDPath;
7use std::fmt;
8
9#[derive(Debug, Clone, Eq, PartialEq, Hash)]
10pub struct ShortHDPath {
11    pub purpose: Purpose,
12    pub coin_type: u32,
13    pub account: u32,
14    pub index: u32
15}
16
17impl HDPath for ShortHDPath {
18    fn len(&self) -> u8 {
19        4
20    }
21
22    fn get(&self, pos: u8) -> Option<PathValue> {
23        match pos {
24            0 => Some(self.purpose.as_value()),
25            1 => Some(PathValue::Hardened(self.coin_type)),
26            2 => Some(PathValue::Hardened(self.account)),
27            3 => Some(PathValue::Normal(self.index)),
28            _ => None
29        }
30    }
31}
32
33impl TryFrom<CustomHDPath> for ShortHDPath {
34    type Error = Error;
35
36    fn try_from(value: CustomHDPath) -> Result<Self, Self::Error> {
37        if value.0.len() != 4 {
38            return Err(Error::InvalidLength(value.0.len()))
39        }
40        if let Some(PathValue::Hardened(p)) = value.0.get(0) {
41            let purpose = Purpose::try_from(*p)?;
42            if let Some(PathValue::Hardened(coin_type)) = value.0.get(1) {
43                if let Some(PathValue::Hardened(account)) = value.0.get(2) {
44                    if let Some(PathValue::Normal(index)) = value.0.get(3) {
45                        return Ok(ShortHDPath {
46                            purpose,
47                            coin_type: *coin_type,
48                            account: *account,
49                            index: *index
50                        })
51                    }
52                }
53            }
54            Err(Error::InvalidStructure)
55        } else {
56            Err(Error::InvalidStructure)
57        }
58    }
59}
60
61impl TryFrom<&str> for ShortHDPath
62{
63    type Error = Error;
64
65    fn try_from(value: &str) -> Result<Self, Self::Error> {
66        ShortHDPath::from_str(value)
67    }
68}
69
70impl FromStr for ShortHDPath {
71    type Err = Error;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        let value = CustomHDPath::from_str(s)?;
75        ShortHDPath::try_from(value)
76    }
77}
78
79impl fmt::Display for ShortHDPath {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        write!(f, "m/{}'/{}'/{}'/{}",
82               self.purpose.as_value().as_number(),
83               self.coin_type,
84               self.account,
85               self.index
86        )
87    }
88}
89
90#[cfg(feature = "with-bitcoin")]
91impl std::convert::From<&ShortHDPath> for Vec<ChildNumber> {
92    fn from(value: &ShortHDPath) -> Self {
93        let result = [
94            ChildNumber::from_hardened_idx(value.purpose.as_value().as_number())
95                .expect("Purpose is not Hardened"),
96            ChildNumber::from_hardened_idx(value.coin_type)
97                .expect("Coin Type is not Hardened"),
98            ChildNumber::from_hardened_idx(value.account)
99                .expect("Account is not Hardened"),
100            ChildNumber::from_normal_idx(value.index)
101                .expect("Index is Hardened"),
102        ];
103        return result.to_vec();
104    }
105}
106
107#[cfg(feature = "with-bitcoin")]
108impl std::convert::From<ShortHDPath> for Vec<ChildNumber> {
109    fn from(value: ShortHDPath) -> Self {
110        Vec::<ChildNumber>::from(&value)
111    }
112}
113
114#[cfg(feature = "with-bitcoin")]
115impl std::convert::From<ShortHDPath> for DerivationPath {
116    fn from(value: ShortHDPath) -> Self {
117        DerivationPath::from(Vec::<ChildNumber>::from(&value))
118    }
119}
120
121#[cfg(feature = "with-bitcoin")]
122impl std::convert::From<&ShortHDPath> for DerivationPath {
123    fn from(value: &ShortHDPath) -> Self {
124        DerivationPath::from(Vec::<ChildNumber>::from(value))
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    pub fn to_string_short() {
134        assert_eq!("m/44'/60'/0'/0", ShortHDPath {
135            purpose: Purpose::Pubkey,
136            coin_type: 60,
137            account: 0,
138            index: 0
139        }.to_string());
140        assert_eq!("m/44'/61'/0'/0", ShortHDPath {
141            purpose: Purpose::Pubkey,
142            coin_type: 61,
143            account: 0,
144            index: 0
145        }.to_string());
146        assert_eq!("m/101'/61'/0'/0", ShortHDPath {
147            purpose: Purpose::Custom(101),
148            coin_type: 61,
149            account: 0,
150            index: 0
151        }.to_string());
152    }
153
154    #[test]
155    pub fn to_string_short_all() {
156        let paths = vec![
157            "m/44'/0'/0'/0",
158            "m/44'/60'/0'/1",
159            "m/44'/60'/160720'/0",
160            "m/44'/60'/160720'/101",
161        ];
162        for p in paths {
163            assert_eq!(p, ShortHDPath::try_from(p).unwrap().to_string())
164        }
165    }
166}
167
168#[cfg(all(test, feature = "with-bitcoin"))]
169mod tests_with_bitcoin {
170    use super::*;
171    use std::convert::TryFrom;
172    use bitcoin::util::bip32::ChildNumber;
173
174    #[test]
175    pub fn convert_to_childnumbers() {
176        let hdpath = ShortHDPath::try_from("m/44'/60'/2'/100").unwrap();
177        let childs: Vec<ChildNumber> = hdpath.into();
178        assert_eq!(childs.len(), 4);
179        assert_eq!(childs[0], ChildNumber::from_hardened_idx(44).unwrap());
180        assert_eq!(childs[1], ChildNumber::from_hardened_idx(60).unwrap());
181        assert_eq!(childs[2], ChildNumber::from_hardened_idx(2).unwrap());
182        assert_eq!(childs[3], ChildNumber::from_normal_idx(100).unwrap());
183    }
184
185}