1use std::str::FromStr;
2
3use crate::error::CryptoError;
4
5const HARDENED_BIT: u32 = 1 << 31;
6
7#[derive(Copy, Clone, PartialEq, Eq, Debug)]
9pub struct ChildNumber(u32);
10
11impl ChildNumber {
12 pub fn is_hardened(&self) -> bool {
13 self.0 & HARDENED_BIT == HARDENED_BIT
14 }
15
16 pub fn is_normal(&self) -> bool {
17 self.0 & HARDENED_BIT == 0
18 }
19
20 pub fn to_bytes(&self) -> [u8; 4] {
21 self.0.to_be_bytes()
22 }
23
24 pub fn hardened_from_u32(index: u32) -> Self {
25 ChildNumber(index | HARDENED_BIT)
26 }
27
28 pub fn non_hardened_from_u32(index: u32) -> Self {
29 ChildNumber(index)
30 }
31}
32
33impl FromStr for ChildNumber {
34 type Err = CryptoError;
35
36 fn from_str(child: &str) -> Result<ChildNumber, CryptoError> {
37 let (child, mask) = if child.ends_with('\'') {
38 (&child[..child.len() - 1], HARDENED_BIT)
39 } else {
40 (child, 0)
41 };
42
43 let index: u32 = child
44 .parse()
45 .map_err(|_| CryptoError::InvalidChildNumber.into())?;
46
47 if index & HARDENED_BIT == 0 {
48 Ok(ChildNumber(index | mask))
49 } else {
50 Err(CryptoError::InvalidChildNumber)
51 }
52 }
53}
54
55#[derive(Clone, PartialEq, Eq, Debug, Default)]
56pub struct DerivationPath {
57 path: Vec<ChildNumber>,
58}
59
60impl FromStr for DerivationPath {
61 type Err = CryptoError;
62
63 fn from_str(path: &str) -> Result<DerivationPath, CryptoError> {
64 let mut path = path.split('/');
65
66 if path.next() != Some("m") {
67 return Err(CryptoError::InvalidDerivationPath);
68 }
69
70 Ok(DerivationPath {
71 path: path
72 .map(str::parse)
73 .collect::<Result<Vec<ChildNumber>, CryptoError>>()?,
74 })
75 }
76}
77
78impl DerivationPath {
79 pub fn as_ref(&self) -> &[ChildNumber] {
80 &self.path
81 }
82
83 pub fn iter(&self) -> impl Iterator<Item = &ChildNumber> {
84 self.path.iter()
85 }
86}
87
88pub trait IntoDerivationPath {
89 fn into(self) -> Result<DerivationPath, CryptoError>;
90}
91
92impl IntoDerivationPath for DerivationPath {
93 fn into(self) -> Result<DerivationPath, CryptoError> {
94 Ok(self)
95 }
96}
97
98impl IntoDerivationPath for &str {
99 fn into(self) -> Result<DerivationPath, CryptoError> {
100 self.parse()
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn derive_path() {
110 let path: DerivationPath = "m/44'/60'/0'/0/0".parse().unwrap();
111
112 assert_eq!(
113 path,
114 DerivationPath {
115 path: vec![
116 ChildNumber(44 | HARDENED_BIT),
117 ChildNumber(60 | HARDENED_BIT),
118 ChildNumber(0 | HARDENED_BIT),
119 ChildNumber(0),
120 ChildNumber(0),
121 ],
122 }
123 );
124 }
125}