1use crate::network::EthereumNetwork;
2use wagyu_model::derivation_path::{ChildIndex, DerivationPath, DerivationPathError};
3
4use std::convert::TryFrom;
5use std::{fmt, marker::PhantomData, str::FromStr};
6
7#[derive(Clone, PartialEq, Eq)]
9pub enum EthereumDerivationPath<N: EthereumNetwork> {
10 Ethereum(ChildIndex),
12 Exodus(ChildIndex),
14 Jaxx(ChildIndex),
16 MetaMask(ChildIndex),
18 MyEtherWallet(ChildIndex),
20 Trezor(ChildIndex),
22
23 KeepKey(ChildIndex),
25 LedgerLive(ChildIndex),
27
28 Electrum(ChildIndex),
30 ImToken(ChildIndex),
32 LedgerLegacy(ChildIndex),
34
35 Custom(Vec<ChildIndex>, PhantomData<N>),
37}
38
39impl<N: EthereumNetwork> DerivationPath for EthereumDerivationPath<N> {
40 fn to_vec(&self) -> Result<Vec<ChildIndex>, DerivationPathError> {
42 match self {
43 EthereumDerivationPath::Ethereum(index)
44 | EthereumDerivationPath::Exodus(index)
45 | EthereumDerivationPath::Jaxx(index)
46 | EthereumDerivationPath::MetaMask(index)
47 | EthereumDerivationPath::MyEtherWallet(index)
48 | EthereumDerivationPath::Trezor(index) => match index.is_normal() {
49 true => Ok(vec![
50 N::HD_PURPOSE,
51 N::HD_COIN_TYPE,
52 ChildIndex::Hardened(0),
53 ChildIndex::Normal(0),
54 *index,
55 ]),
56 false => Err(DerivationPathError::ExpectedBIP44Path),
57 },
58
59 EthereumDerivationPath::KeepKey(index) | EthereumDerivationPath::LedgerLive(index) => {
60 match index.is_hardened() {
61 true => Ok(vec![
62 N::HD_PURPOSE,
63 N::HD_COIN_TYPE,
64 *index,
65 ChildIndex::Normal(0),
66 ChildIndex::Normal(0),
67 ]),
68 false => Err(DerivationPathError::ExpectedBIP44Path),
69 }
70 }
71
72 EthereumDerivationPath::Electrum(index)
73 | EthereumDerivationPath::ImToken(index)
74 | EthereumDerivationPath::LedgerLegacy(index) => match index.is_normal() {
75 true => Ok(vec![N::HD_PURPOSE, N::HD_COIN_TYPE, ChildIndex::Hardened(0), *index]),
76 false => Err(DerivationPathError::ExpectedValidEthereumDerivationPath),
77 },
78
79 EthereumDerivationPath::Custom(path, _) => match path.len() < 256 {
80 true => Ok(path.clone()),
81 false => Err(DerivationPathError::ExpectedValidEthereumDerivationPath),
82 },
83 }
84 }
85
86 fn from_vec(path: &Vec<ChildIndex>) -> Result<Self, DerivationPathError> {
88 if path.len() == 4 {
89 if path[0] == N::HD_PURPOSE
91 && path[1] == N::HD_COIN_TYPE
92 && path[2] == ChildIndex::Hardened(0)
93 && path[3].is_normal()
94 {
95 return Ok(EthereumDerivationPath::Electrum(path[3]));
96 }
97 }
98
99 if path.len() == 5 {
100 if path[0] == N::HD_PURPOSE
102 && path[1] == N::HD_COIN_TYPE
103 && path[2] == ChildIndex::Hardened(0)
104 && path[3] == ChildIndex::Normal(0)
105 && path[4].is_normal()
106 {
107 return Ok(EthereumDerivationPath::Ethereum(path[4]));
108 }
109 if path[0] == ChildIndex::Hardened(49)
111 && path[1] == N::HD_COIN_TYPE
112 && path[2].is_hardened()
113 && path[3] == ChildIndex::Normal(0)
114 && path[4] == ChildIndex::Normal(0)
115 {
116 return Ok(EthereumDerivationPath::LedgerLive(path[2]));
117 }
118 }
119
120 Ok(EthereumDerivationPath::Custom(path.to_vec(), PhantomData))
122 }
123}
124
125impl<N: EthereumNetwork> FromStr for EthereumDerivationPath<N> {
126 type Err = DerivationPathError;
127
128 fn from_str(path: &str) -> Result<Self, Self::Err> {
129 let mut parts = path.split("/");
130
131 if parts.next().unwrap() != "m" {
132 return Err(DerivationPathError::InvalidDerivationPath(path.to_string()));
133 }
134
135 let path: Result<Vec<ChildIndex>, Self::Err> = parts.map(str::parse).collect();
136 Self::from_vec(&path?)
137 }
138}
139
140impl<N: EthereumNetwork> TryFrom<Vec<ChildIndex>> for EthereumDerivationPath<N> {
141 type Error = DerivationPathError;
142
143 fn try_from(path: Vec<ChildIndex>) -> Result<Self, Self::Error> {
144 Self::from_vec(&path)
145 }
146}
147
148impl<'a, N: EthereumNetwork> TryFrom<&'a [ChildIndex]> for EthereumDerivationPath<N> {
149 type Error = DerivationPathError;
150
151 fn try_from(path: &'a [ChildIndex]) -> Result<Self, Self::Error> {
152 Self::try_from(path.to_vec())
153 }
154}
155
156impl<N: EthereumNetwork> fmt::Debug for EthereumDerivationPath<N> {
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 fmt::Display::fmt(&self, f)
159 }
160}
161
162impl<N: EthereumNetwork> fmt::Display for EthereumDerivationPath<N> {
163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 match self.to_vec() {
165 Ok(path) => {
166 f.write_str("m")?;
167 for index in path.iter() {
168 f.write_str("/")?;
169 fmt::Display::fmt(index, f)?;
170 }
171 Ok(())
172 }
173 Err(_) => Err(fmt::Error),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::network::*;
182 use wagyu_model::derivation_path::{ChildIndex, DerivationPathError};
183
184 use std::convert::TryInto;
185 use std::str::FromStr;
186
187 #[test]
188 fn valid_path() {
189 type N = Mainnet;
190
191 assert_eq!(
192 EthereumDerivationPath::<N>::from_str("m"),
193 Ok(vec![].try_into().unwrap())
194 );
195 assert_eq!(
196 EthereumDerivationPath::<N>::from_str("m/0"),
197 Ok(vec![ChildIndex::normal(0).unwrap()].try_into().unwrap())
198 );
199 assert_eq!(
200 EthereumDerivationPath::<N>::from_str("m/0/1"),
201 Ok(vec![ChildIndex::normal(0).unwrap(), ChildIndex::normal(1).unwrap()]
202 .try_into()
203 .unwrap())
204 );
205 assert_eq!(
206 EthereumDerivationPath::<N>::from_str("m/0/1/2"),
207 Ok(vec![
208 ChildIndex::normal(0).unwrap(),
209 ChildIndex::normal(1).unwrap(),
210 ChildIndex::normal(2).unwrap()
211 ]
212 .try_into()
213 .unwrap())
214 );
215 assert_eq!(
216 EthereumDerivationPath::<N>::from_str("m/0/1/2/3"),
217 Ok(vec![
218 ChildIndex::normal(0).unwrap(),
219 ChildIndex::normal(1).unwrap(),
220 ChildIndex::normal(2).unwrap(),
221 ChildIndex::normal(3).unwrap()
222 ]
223 .try_into()
224 .unwrap())
225 );
226
227 assert_eq!(
228 EthereumDerivationPath::<N>::from_str("m"),
229 Ok(vec![].try_into().unwrap())
230 );
231 assert_eq!(
232 EthereumDerivationPath::<N>::from_str("m/0'"),
233 Ok(vec![ChildIndex::hardened(0).unwrap()].try_into().unwrap())
234 );
235 assert_eq!(
236 EthereumDerivationPath::<N>::from_str("m/0'/1"),
237 Ok(vec![ChildIndex::hardened(0).unwrap(), ChildIndex::normal(1).unwrap()]
238 .try_into()
239 .unwrap())
240 );
241 assert_eq!(
242 EthereumDerivationPath::<N>::from_str("m/0'/1/2'"),
243 Ok(vec![
244 ChildIndex::hardened(0).unwrap(),
245 ChildIndex::normal(1).unwrap(),
246 ChildIndex::hardened(2).unwrap(),
247 ]
248 .try_into()
249 .unwrap())
250 );
251 assert_eq!(
252 EthereumDerivationPath::<N>::from_str("m/0'/1/2'/3"),
253 Ok(vec![
254 ChildIndex::hardened(0).unwrap(),
255 ChildIndex::normal(1).unwrap(),
256 ChildIndex::hardened(2).unwrap(),
257 ChildIndex::normal(3).unwrap(),
258 ]
259 .try_into()
260 .unwrap())
261 );
262 assert_eq!(
263 EthereumDerivationPath::<N>::from_str("m/0'/1/2'/3/4'"),
264 Ok(vec![
265 ChildIndex::hardened(0).unwrap(),
266 ChildIndex::normal(1).unwrap(),
267 ChildIndex::hardened(2).unwrap(),
268 ChildIndex::normal(3).unwrap(),
269 ChildIndex::hardened(4).unwrap(),
270 ]
271 .try_into()
272 .unwrap())
273 );
274
275 assert_eq!(
276 EthereumDerivationPath::<N>::from_str("m"),
277 Ok(vec![].try_into().unwrap())
278 );
279 assert_eq!(
280 EthereumDerivationPath::<N>::from_str("m/0h"),
281 Ok(vec![ChildIndex::hardened(0).unwrap()].try_into().unwrap())
282 );
283 assert_eq!(
284 EthereumDerivationPath::<N>::from_str("m/0h/1'"),
285 Ok(vec![ChildIndex::hardened(0).unwrap(), ChildIndex::hardened(1).unwrap()]
286 .try_into()
287 .unwrap())
288 );
289 assert_eq!(
290 EthereumDerivationPath::<N>::from_str("m/0'/1h/2'"),
291 Ok(vec![
292 ChildIndex::hardened(0).unwrap(),
293 ChildIndex::hardened(1).unwrap(),
294 ChildIndex::hardened(2).unwrap(),
295 ]
296 .try_into()
297 .unwrap())
298 );
299 assert_eq!(
300 EthereumDerivationPath::<N>::from_str("m/0h/1'/2h/3'"),
301 Ok(vec![
302 ChildIndex::hardened(0).unwrap(),
303 ChildIndex::hardened(1).unwrap(),
304 ChildIndex::hardened(2).unwrap(),
305 ChildIndex::hardened(3).unwrap(),
306 ]
307 .try_into()
308 .unwrap())
309 );
310 assert_eq!(
311 EthereumDerivationPath::<N>::from_str("m/0'/1h/2'/3h/4'"),
312 Ok(vec![
313 ChildIndex::hardened(0).unwrap(),
314 ChildIndex::hardened(1).unwrap(),
315 ChildIndex::hardened(2).unwrap(),
316 ChildIndex::hardened(3).unwrap(),
317 ChildIndex::hardened(4).unwrap(),
318 ]
319 .try_into()
320 .unwrap())
321 );
322 }
323
324 #[test]
325 fn invalid_path() {
326 type N = Mainnet;
327
328 assert_eq!(
329 EthereumDerivationPath::<N>::from_str("n"),
330 Err(DerivationPathError::InvalidDerivationPath("n".try_into().unwrap()))
331 );
332 assert_eq!(
333 EthereumDerivationPath::<N>::from_str("n/0"),
334 Err(DerivationPathError::InvalidDerivationPath("n/0".try_into().unwrap()))
335 );
336 assert_eq!(
337 EthereumDerivationPath::<N>::from_str("n/0/0"),
338 Err(DerivationPathError::InvalidDerivationPath("n/0/0".try_into().unwrap()))
339 );
340
341 assert_eq!(
342 EthereumDerivationPath::<N>::from_str("1"),
343 Err(DerivationPathError::InvalidDerivationPath("1".try_into().unwrap()))
344 );
345 assert_eq!(
346 EthereumDerivationPath::<N>::from_str("1/0"),
347 Err(DerivationPathError::InvalidDerivationPath("1/0".try_into().unwrap()))
348 );
349 assert_eq!(
350 EthereumDerivationPath::<N>::from_str("1/0/0"),
351 Err(DerivationPathError::InvalidDerivationPath("1/0/0".try_into().unwrap()))
352 );
353
354 assert_eq!(
355 EthereumDerivationPath::<N>::from_str("m/0x"),
356 Err(DerivationPathError::InvalidChildNumberFormat)
357 );
358 assert_eq!(
359 EthereumDerivationPath::<N>::from_str("m/0x0"),
360 Err(DerivationPathError::InvalidChildNumberFormat)
361 );
362 assert_eq!(
363 EthereumDerivationPath::<N>::from_str("m/0x00"),
364 Err(DerivationPathError::InvalidChildNumberFormat)
365 );
366
367 assert_eq!(
368 EthereumDerivationPath::<N>::from_str("0/m"),
369 Err(DerivationPathError::InvalidDerivationPath("0/m".try_into().unwrap()))
370 );
371 assert_eq!(
372 EthereumDerivationPath::<N>::from_str("m//0"),
373 Err(DerivationPathError::InvalidChildNumberFormat)
374 );
375 assert_eq!(
376 EthereumDerivationPath::<N>::from_str("m/2147483648"),
377 Err(DerivationPathError::InvalidChildNumber(2147483648))
378 );
379 }
380}