derivation_path/
lib.rs

1//! A simple struct for dealing with derivation paths as defined by BIP32, BIP44 and BIP49 of the
2//! Bitcoin protocol. This crate provides interfaces for dealing with hardened vs normal child
3//! indexes, as well as display and parsing derivation paths from strings
4//!
5//! # Example
6//!
7//! ```
8//! # use derivation_path::{ChildIndex, DerivationPath, DerivationPathType};
9//! let path = DerivationPath::bip44(0, 1, 0, 1).unwrap();
10//! assert_eq!(&path.to_string(), "m/44'/0'/1'/0/1");
11//! assert_eq!(path.path()[2], ChildIndex::Hardened(1));
12//!
13//! let path: DerivationPath = "m/49'/0'/0'/1/0".parse().unwrap();
14//! assert_eq!(path.path()[4], ChildIndex::Normal(0));
15//! assert_eq!(path.path_type(), DerivationPathType::BIP49);
16//! ```
17
18#![cfg_attr(not(feature = "std"), no_std)]
19
20#[cfg(not(feature = "std"))]
21extern crate alloc;
22
23#[cfg(not(feature = "std"))]
24use alloc::{borrow::ToOwned, boxed::Box, string::String};
25
26use core::fmt;
27use core::iter::IntoIterator;
28use core::slice::Iter;
29use core::str::FromStr;
30
31/// Errors when building a [DerivationPath]
32#[derive(Debug, Clone)]
33pub enum DerivationPathError {
34    PathTooLong,
35    InvalidChildIndex(ChildIndexError),
36}
37
38impl fmt::Display for DerivationPathError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Self::PathTooLong => f.write_str("path too long"),
42            Self::InvalidChildIndex(err) => {
43                f.write_fmt(format_args!("invalid child index: {}", err))
44            }
45        }
46    }
47}
48
49#[cfg(feature = "std")]
50impl std::error::Error for DerivationPathError {
51    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
52        match self {
53            Self::InvalidChildIndex(err) => Some(err),
54            Self::PathTooLong => None,
55        }
56    }
57}
58
59/// Errors when parsing a [DerivationPath] from a [str]
60#[derive(Debug, Clone)]
61pub enum DerivationPathParseError {
62    Empty,
63    InvalidPrefix(String),
64    InvalidChildIndex(ChildIndexParseError),
65}
66
67impl fmt::Display for DerivationPathParseError {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            Self::Empty => f.write_str("empty"),
71            Self::InvalidPrefix(prefix) => f.write_fmt(format_args!("invalid prefix: {}", prefix)),
72            Self::InvalidChildIndex(err) => {
73                f.write_fmt(format_args!("invalid child index: {}", err))
74            }
75        }
76    }
77}
78
79#[cfg(feature = "std")]
80impl std::error::Error for DerivationPathParseError {
81    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
82        match self {
83            Self::InvalidChildIndex(err) => Some(err),
84            Self::Empty | Self::InvalidPrefix(_) => None,
85        }
86    }
87}
88
89/// A list of [ChildIndex] items
90#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct DerivationPath(Box<[ChildIndex]>);
92
93/// [DerivationPath] specifications as defined by BIP's
94#[derive(Copy, Clone, Debug, Eq, PartialEq)]
95pub enum DerivationPathType {
96    None,
97    BIP32,
98    BIP44,
99    BIP49,
100}
101
102impl DerivationPath {
103    /// Build a [DerivationPath] from a list of [ChildIndex] items
104    #[inline]
105    pub fn new<P>(path: P) -> Self
106    where
107        P: Into<Box<[ChildIndex]>>,
108    {
109        DerivationPath(path.into())
110    }
111
112    /// Build a BIP32 style [DerivationPath]. This will fail if the length of the path is greater
113    /// than 255 items
114    pub fn bip32<P>(path: P) -> Result<Self, DerivationPathError>
115    where
116        P: Into<Box<[ChildIndex]>>,
117    {
118        let path = path.into();
119        if path.len() > 255 {
120            return Err(DerivationPathError::PathTooLong);
121        }
122        Ok(Self::new(path))
123    }
124
125    /// Build a BIP44 style [DerivationPath]: `m/44'/coin'/account'/change/address`
126    #[inline]
127    pub fn bip44(
128        coin: u32,
129        account: u32,
130        change: u32,
131        address: u32,
132    ) -> Result<Self, DerivationPathError> {
133        Self::bip4x(44, coin, account, change, address)
134    }
135
136    /// Build a BIP49 style [DerivationPath]: `m/49'/coin'/account'/change/address`
137    #[inline]
138    pub fn bip49(
139        coin: u32,
140        account: u32,
141        change: u32,
142        address: u32,
143    ) -> Result<Self, DerivationPathError> {
144        Self::bip4x(49, coin, account, change, address)
145    }
146
147    #[inline]
148    fn bip4x(
149        purpose: u32,
150        coin: u32,
151        account: u32,
152        change: u32,
153        address: u32,
154    ) -> Result<Self, DerivationPathError> {
155        Ok(Self::new(
156            [
157                ChildIndex::hardened(purpose)?,
158                ChildIndex::hardened(coin)?,
159                ChildIndex::hardened(account)?,
160                ChildIndex::normal(change)?,
161                ChildIndex::normal(address)?,
162            ]
163            .as_ref(),
164        ))
165    }
166
167    /// Get a reference to the list of [ChildIndex] items
168    #[inline]
169    pub fn path(&self) -> &[ChildIndex] {
170        self.0.as_ref()
171    }
172
173    /// Get the [DerivationPathType]. This will check the "purpose" index in BIP44/49 style
174    /// derivation paths or otherwise return BIP32 if the length is less than 255
175    pub fn path_type(&self) -> DerivationPathType {
176        let path = self.path();
177        let len = path.len();
178        if len == 5
179            && path[1].is_hardened()
180            && path[2].is_hardened()
181            && path[3].is_normal()
182            && path[4].is_normal()
183        {
184            match path[0] {
185                ChildIndex::Hardened(44) => DerivationPathType::BIP44,
186                ChildIndex::Hardened(49) => DerivationPathType::BIP49,
187                _ => DerivationPathType::BIP32,
188            }
189        } else if len < 256 {
190            DerivationPathType::BIP32
191        } else {
192            DerivationPathType::None
193        }
194    }
195}
196
197impl fmt::Display for DerivationPath {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        f.write_str("m")?;
200        for index in self.path() {
201            f.write_str("/")?;
202            fmt::Display::fmt(index, f)?;
203        }
204        Ok(())
205    }
206}
207
208impl FromStr for DerivationPath {
209    type Err = DerivationPathParseError;
210
211    fn from_str(s: &str) -> Result<Self, Self::Err> {
212        if s.is_empty() {
213            return Err(DerivationPathParseError::Empty);
214        }
215        let mut parts = s.split('/');
216        match parts.next().unwrap() {
217            "m" => (),
218            prefix => return Err(DerivationPathParseError::InvalidPrefix(prefix.to_owned())),
219        }
220        let path = parts
221            .map(|part| ChildIndex::from_str(part).map_err(|e| e.into()))
222            .collect::<Result<Box<[ChildIndex]>, DerivationPathParseError>>()?;
223        Ok(DerivationPath::new(path))
224    }
225}
226
227impl AsRef<[ChildIndex]> for DerivationPath {
228    fn as_ref(&self) -> &[ChildIndex] {
229        self.path()
230    }
231}
232
233impl<'a> IntoIterator for &'a DerivationPath {
234    type IntoIter = Iter<'a, ChildIndex>;
235    type Item = &'a ChildIndex;
236    fn into_iter(self) -> Self::IntoIter {
237        self.path().iter()
238    }
239}
240
241/// An index in a [DerivationPath]
242#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
243pub enum ChildIndex {
244    Normal(u32),
245    Hardened(u32),
246}
247
248/// Errors when parsing a [ChildIndex] from a [str]
249#[derive(Debug, Clone)]
250pub enum ChildIndexParseError {
251    ParseIntError(core::num::ParseIntError),
252    ChildIndexError(ChildIndexError),
253}
254
255impl fmt::Display for ChildIndexParseError {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        match self {
258            Self::ParseIntError(err) => {
259                f.write_fmt(format_args!("could not parse child index: {}", err))
260            }
261            Self::ChildIndexError(err) => f.write_fmt(format_args!("invalid child index: {}", err)),
262        }
263    }
264}
265
266#[cfg(feature = "std")]
267impl std::error::Error for ChildIndexParseError {
268    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
269        match self {
270            Self::ParseIntError(err) => Some(err),
271            Self::ChildIndexError(err) => Some(err),
272        }
273    }
274}
275
276/// Errors when building a [ChildIndex]
277#[derive(Debug, Clone)]
278pub enum ChildIndexError {
279    NumberTooLarge(u32),
280}
281
282impl fmt::Display for ChildIndexError {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284        match self {
285            Self::NumberTooLarge(num) => f.write_fmt(format_args!("number too large: {}", num)),
286        }
287    }
288}
289
290#[cfg(feature = "std")]
291impl std::error::Error for ChildIndexError {}
292
293impl ChildIndex {
294    /// Create a [ChildIndex::Hardened] instance from a [u32]. This will fail if `num` is not
295    /// in `[0, 2^31 - 1]`
296    pub fn hardened(num: u32) -> Result<Self, ChildIndexError> {
297        Ok(Self::Hardened(Self::check_size(num)?))
298    }
299
300    /// Create a [ChildIndex::Normal] instance from a [u32]. This will fail if `num` is not
301    /// in `[0, 2^31 - 1]`
302    pub fn normal(num: u32) -> Result<Self, ChildIndexError> {
303        Ok(Self::Normal(Self::check_size(num)?))
304    }
305
306    fn check_size(num: u32) -> Result<u32, ChildIndexError> {
307        if num & (1 << 31) == 0 {
308            Ok(num)
309        } else {
310            Err(ChildIndexError::NumberTooLarge(num))
311        }
312    }
313
314    /// Convert [ChildIndex] to its inner [u32]
315    #[inline]
316    pub fn to_u32(self) -> u32 {
317        match self {
318            ChildIndex::Hardened(index) => index,
319            ChildIndex::Normal(index) => index,
320        }
321    }
322
323    /// Convert [ChildIndex] to a [u32] representing the type and a 31 bit number. The highest bit
324    /// is set for a hard derivation and clear for a normal derivation, and the remaining 31 bits are
325    /// the index
326    #[inline]
327    pub fn to_bits(self) -> u32 {
328        match self {
329            ChildIndex::Hardened(index) => (1 << 31) | index,
330            ChildIndex::Normal(index) => index,
331        }
332    }
333
334    /// Build a [ChildIndex] from a [u32] representing the type and a 31 bit number.
335    /// See [ChildIndex::to_bits] for more information
336    #[inline]
337    pub fn from_bits(bits: u32) -> Self {
338        if bits & (1 << 31) == 0 {
339            ChildIndex::Normal(bits)
340        } else {
341            ChildIndex::Hardened(bits & !(1 << 31))
342        }
343    }
344
345    /// Check if the [ChildIndex] is "hardened"
346    #[inline]
347    pub fn is_hardened(self) -> bool {
348        matches!(self, Self::Hardened(_))
349    }
350
351    /// Check if the [ChildIndex] is "normal"
352    #[inline]
353    pub fn is_normal(self) -> bool {
354        matches!(self, Self::Normal(_))
355    }
356}
357
358impl fmt::Display for ChildIndex {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        fmt::Display::fmt(&self.to_u32(), f)?;
361        if self.is_hardened() {
362            f.write_str("'")?;
363        }
364        Ok(())
365    }
366}
367
368impl FromStr for ChildIndex {
369    type Err = ChildIndexParseError;
370
371    fn from_str(s: &str) -> Result<Self, Self::Err> {
372        let mut chars = s.chars();
373        Ok(match chars.next_back() {
374            Some('\'') => Self::hardened(u32::from_str(chars.as_str())?)?,
375            _ => Self::normal(u32::from_str(s)?)?,
376        })
377    }
378}
379
380impl From<core::num::ParseIntError> for ChildIndexParseError {
381    fn from(err: core::num::ParseIntError) -> Self {
382        Self::ParseIntError(err)
383    }
384}
385
386impl From<ChildIndexError> for ChildIndexParseError {
387    fn from(err: ChildIndexError) -> Self {
388        Self::ChildIndexError(err)
389    }
390}
391
392impl From<ChildIndexParseError> for DerivationPathParseError {
393    fn from(err: ChildIndexParseError) -> Self {
394        Self::InvalidChildIndex(err)
395    }
396}
397
398impl From<ChildIndexError> for DerivationPathError {
399    fn from(err: ChildIndexError) -> Self {
400        Self::InvalidChildIndex(err)
401    }
402}
403
404#[cfg(test)]
405mod test {
406    use super::*;
407
408    #[cfg(not(feature = "std"))]
409    use alloc::{string::ToString, vec};
410
411    #[test]
412    fn child_index_is_normal() {
413        assert!(ChildIndex::Hardened(0).is_hardened());
414        assert!(!ChildIndex::Normal(0).is_hardened());
415    }
416
417    #[test]
418    fn child_index_is_hardened() {
419        assert!(!ChildIndex::Hardened(0).is_normal());
420        assert!(ChildIndex::Normal(0).is_normal());
421    }
422
423    #[test]
424    fn child_index_range() {
425        assert!(ChildIndex::normal(0).is_ok());
426        assert!(ChildIndex::normal(1).is_ok());
427        assert!(ChildIndex::normal(100).is_ok());
428        assert!(ChildIndex::normal(1 << 31).is_err());
429
430        assert!(ChildIndex::hardened(0).is_ok());
431        assert!(ChildIndex::hardened(1 << 31).is_err());
432    }
433
434    #[test]
435    fn child_index_to_u32() {
436        assert_eq!(ChildIndex::Normal(0).to_u32(), 0);
437        assert_eq!(ChildIndex::Normal(1).to_u32(), 1);
438        assert_eq!(ChildIndex::Normal(100).to_u32(), 100);
439        assert_eq!(ChildIndex::Hardened(0).to_u32(), 0);
440        assert_eq!(ChildIndex::Hardened(1).to_u32(), 1);
441    }
442
443    #[test]
444    fn child_index_to_bits() {
445        assert_eq!(ChildIndex::Normal(0).to_bits(), 0);
446        assert_eq!(ChildIndex::Normal(1).to_bits(), 1);
447        assert_eq!(ChildIndex::Normal(100).to_bits(), 100);
448        assert_eq!(ChildIndex::Hardened(0).to_bits(), (1 << 31) | 0);
449        assert_eq!(ChildIndex::Hardened(1).to_bits(), (1 << 31) | 1);
450        assert_eq!(ChildIndex::Hardened(100).to_bits(), (1 << 31) | 100);
451    }
452
453    #[test]
454    fn child_index_from_bits() {
455        assert_eq!(ChildIndex::from_bits(0), ChildIndex::Normal(0));
456        assert_eq!(ChildIndex::from_bits(1), ChildIndex::Normal(1));
457        assert_eq!(ChildIndex::from_bits(100), ChildIndex::Normal(100));
458        assert_eq!(
459            ChildIndex::from_bits((1 << 31) | 0),
460            ChildIndex::Hardened(0)
461        );
462        assert_eq!(
463            ChildIndex::from_bits((1 << 31) | 1),
464            ChildIndex::Hardened(1)
465        );
466        assert_eq!(
467            ChildIndex::from_bits((1 << 31) | 100),
468            ChildIndex::Hardened(100)
469        );
470    }
471
472    #[test]
473    fn child_index_to_string() {
474        assert_eq!(&ChildIndex::Normal(0).to_string(), "0");
475        assert_eq!(&ChildIndex::Normal(1).to_string(), "1");
476        assert_eq!(&ChildIndex::Normal(100).to_string(), "100");
477        assert_eq!(&ChildIndex::Hardened(0).to_string(), "0'");
478        assert_eq!(&ChildIndex::Hardened(1).to_string(), "1'");
479        assert_eq!(&ChildIndex::Hardened(100).to_string(), "100'");
480    }
481
482    #[test]
483    fn child_index_from_str() {
484        assert_eq!(ChildIndex::Normal(0), "0".parse().unwrap());
485        assert_eq!(ChildIndex::Normal(1), "1".parse().unwrap());
486        assert_eq!(ChildIndex::Normal(100), "100".parse().unwrap());
487        assert_eq!(ChildIndex::Hardened(0), "0'".parse().unwrap());
488        assert_eq!(ChildIndex::Hardened(1), "1'".parse().unwrap());
489        assert_eq!(ChildIndex::Hardened(100), "100'".parse().unwrap());
490        assert!(matches!(
491            ChildIndex::from_str(""),
492            Err(ChildIndexParseError::ParseIntError(_))
493        ));
494        assert!(matches!(
495            ChildIndex::from_str("a"),
496            Err(ChildIndexParseError::ParseIntError(_))
497        ));
498        assert!(matches!(
499            ChildIndex::from_str("100 "),
500            Err(ChildIndexParseError::ParseIntError(_))
501        ));
502        assert!(matches!(
503            ChildIndex::from_str("99a"),
504            Err(ChildIndexParseError::ParseIntError(_))
505        ));
506        assert!(matches!(
507            ChildIndex::from_str("a10"),
508            Err(ChildIndexParseError::ParseIntError(_))
509        ));
510        assert!(matches!(
511            ChildIndex::from_str(" 10"),
512            Err(ChildIndexParseError::ParseIntError(_))
513        ));
514        assert!(matches!(
515            ChildIndex::from_str(&(1u32 << 31).to_string()),
516            Err(ChildIndexParseError::ChildIndexError(_))
517        ));
518    }
519
520    #[test]
521    fn derivation_path_new() {
522        let path = [
523            ChildIndex::Normal(1),
524            ChildIndex::Hardened(2),
525            ChildIndex::Normal(3),
526        ];
527        assert_eq!(&path, DerivationPath::new(path.as_ref()).path());
528
529        let path: [ChildIndex; 0] = [];
530        assert_eq!(&path, DerivationPath::new(path.as_ref()).path());
531
532        let path = vec![ChildIndex::Normal(0); 256];
533        assert_eq!(path.as_slice(), DerivationPath::new(path.as_ref()).path());
534    }
535
536    #[test]
537    fn derivation_bip32() {
538        let path = [
539            ChildIndex::Normal(1),
540            ChildIndex::Hardened(2),
541            ChildIndex::Normal(3),
542        ];
543        assert_eq!(&path, DerivationPath::bip32(path.as_ref()).unwrap().path());
544
545        let path: [ChildIndex; 0] = [];
546        assert_eq!(&path, DerivationPath::bip32(path.as_ref()).unwrap().path());
547
548        let path = vec![ChildIndex::Normal(0); 256];
549        assert!(matches!(
550            DerivationPath::bip32(path.as_ref()),
551            Err(DerivationPathError::PathTooLong)
552        ));
553    }
554
555    #[test]
556    fn derivation_bip44() {
557        assert_eq!(
558            DerivationPath::bip44(1, 2, 3, 4).unwrap().path(),
559            &[
560                ChildIndex::Hardened(44),
561                ChildIndex::Hardened(1),
562                ChildIndex::Hardened(2),
563                ChildIndex::Normal(3),
564                ChildIndex::Normal(4)
565            ]
566        );
567
568        assert!(matches!(
569            DerivationPath::bip44(1 << 31, 0, 0, 0),
570            Err(DerivationPathError::InvalidChildIndex(_))
571        ));
572    }
573
574    #[test]
575    fn derivation_bip49() {
576        assert_eq!(
577            DerivationPath::bip49(1, 2, 3, 4).unwrap().path(),
578            &[
579                ChildIndex::Hardened(49),
580                ChildIndex::Hardened(1),
581                ChildIndex::Hardened(2),
582                ChildIndex::Normal(3),
583                ChildIndex::Normal(4)
584            ]
585        );
586
587        assert!(matches!(
588            DerivationPath::bip44(1 << 31, 0, 0, 0),
589            Err(DerivationPathError::InvalidChildIndex(_))
590        ));
591    }
592
593    #[test]
594    fn derivation_path_type() {
595        assert_eq!(
596            DerivationPath::new(vec![
597                ChildIndex::Normal(0),
598                ChildIndex::Normal(0),
599                ChildIndex::Normal(0)
600            ])
601            .path_type(),
602            DerivationPathType::BIP32
603        );
604        assert_eq!(
605            DerivationPath::new(vec![]).path_type(),
606            DerivationPathType::BIP32
607        );
608        assert_eq!(
609            DerivationPath::new(vec![
610                ChildIndex::Hardened(44),
611                ChildIndex::Hardened(0),
612                ChildIndex::Hardened(0),
613                ChildIndex::Normal(0),
614                ChildIndex::Normal(0)
615            ])
616            .path_type(),
617            DerivationPathType::BIP44
618        );
619        assert_eq!(
620            DerivationPath::new(vec![
621                ChildIndex::Hardened(44),
622                ChildIndex::Hardened(0),
623                ChildIndex::Hardened(0),
624                ChildIndex::Normal(0),
625            ])
626            .path_type(),
627            DerivationPathType::BIP32
628        );
629        assert_eq!(
630            DerivationPath::new(vec![
631                ChildIndex::Hardened(43),
632                ChildIndex::Hardened(0),
633                ChildIndex::Hardened(0),
634                ChildIndex::Normal(0),
635                ChildIndex::Normal(0)
636            ])
637            .path_type(),
638            DerivationPathType::BIP32
639        );
640        assert_eq!(
641            DerivationPath::new(vec![
642                ChildIndex::Hardened(44),
643                ChildIndex::Hardened(0),
644                ChildIndex::Normal(0),
645                ChildIndex::Normal(0),
646                ChildIndex::Normal(0)
647            ])
648            .path_type(),
649            DerivationPathType::BIP32
650        );
651        assert_eq!(
652            DerivationPath::new(vec![
653                ChildIndex::Hardened(49),
654                ChildIndex::Hardened(0),
655                ChildIndex::Hardened(0),
656                ChildIndex::Normal(0),
657                ChildIndex::Normal(0)
658            ])
659            .path_type(),
660            DerivationPathType::BIP49
661        );
662        assert_eq!(
663            DerivationPath::new(vec![ChildIndex::Normal(0); 256]).path_type(),
664            DerivationPathType::None
665        );
666    }
667
668    #[test]
669    fn derivation_path_to_string() {
670        assert_eq!(
671            &DerivationPath::new(vec![
672                ChildIndex::Hardened(1),
673                ChildIndex::Hardened(2),
674                ChildIndex::Normal(3),
675                ChildIndex::Hardened(4)
676            ])
677            .to_string(),
678            "m/1'/2'/3/4'"
679        );
680        assert_eq!(
681            &DerivationPath::new(vec![
682                ChildIndex::Hardened(1),
683                ChildIndex::Hardened(2),
684                ChildIndex::Hardened(4),
685                ChildIndex::Normal(3),
686            ])
687            .to_string(),
688            "m/1'/2'/4'/3"
689        );
690        assert_eq!(
691            &DerivationPath::new(vec![
692                ChildIndex::Normal(100),
693                ChildIndex::Hardened(2),
694                ChildIndex::Hardened(4),
695                ChildIndex::Normal(3),
696            ])
697            .to_string(),
698            "m/100/2'/4'/3"
699        );
700        assert_eq!(
701            &DerivationPath::new(vec![ChildIndex::Normal(0),]).to_string(),
702            "m/0"
703        );
704        assert_eq!(&DerivationPath::new(vec![]).to_string(), "m");
705    }
706
707    #[test]
708    fn derivation_path_parsing() {
709        assert_eq!(
710            DerivationPath::new(vec![
711                ChildIndex::Hardened(1),
712                ChildIndex::Hardened(2),
713                ChildIndex::Normal(3),
714                ChildIndex::Hardened(4)
715            ]),
716            "m/1'/2'/3/4'".parse().unwrap()
717        );
718        assert_eq!(
719            DerivationPath::new(vec![
720                ChildIndex::Hardened(1),
721                ChildIndex::Hardened(2),
722                ChildIndex::Hardened(4),
723                ChildIndex::Normal(3),
724            ]),
725            "m/1'/2'/4'/3".parse().unwrap()
726        );
727        assert_eq!(
728            DerivationPath::new(vec![
729                ChildIndex::Normal(100),
730                ChildIndex::Hardened(2),
731                ChildIndex::Hardened(4),
732                ChildIndex::Normal(3),
733            ]),
734            "m/100/2'/4'/3".parse().unwrap()
735        );
736        assert_eq!(
737            DerivationPath::new(vec![ChildIndex::Normal(0),]),
738            "m/0".parse().unwrap()
739        );
740        assert_eq!(DerivationPath::new(vec![]), "m".parse().unwrap());
741
742        assert!(matches!(
743            DerivationPath::from_str(""),
744            Err(DerivationPathParseError::Empty)
745        ));
746        assert!(matches!(
747            DerivationPath::from_str("n/0"),
748            Err(DerivationPathParseError::InvalidPrefix(_))
749        ));
750        assert!(matches!(
751            DerivationPath::from_str("mn/0"),
752            Err(DerivationPathParseError::InvalidPrefix(_))
753        ));
754        assert!(matches!(
755            DerivationPath::from_str("m/0/"),
756            Err(DerivationPathParseError::InvalidChildIndex(_))
757        ));
758        assert!(matches!(
759            DerivationPath::from_str("m/0/a/1"),
760            Err(DerivationPathParseError::InvalidChildIndex(_))
761        ));
762        assert!(matches!(
763            DerivationPath::from_str("m/0///1"),
764            Err(DerivationPathParseError::InvalidChildIndex(_))
765        ));
766        assert!(matches!(
767            DerivationPath::from_str(&format!("m/0/{}/1", (1u32 << 31))),
768            Err(DerivationPathParseError::InvalidChildIndex(_))
769        ));
770        assert!(matches!(
771            DerivationPath::from_str("m|1"),
772            Err(DerivationPathParseError::InvalidPrefix(_))
773        ));
774    }
775}