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}