stellar_ledger/
hd_path.rs1use crate::{Error, HD_PATH_ELEMENTS_COUNT};
2
3const HARDENED_OFFSET: u32 = 1 << 31;
4const PURPOSE: u32 = 44;
5const COIN_TYPE: u32 = 148;
6
7#[derive(Clone, Copy)]
8pub struct HdPath(pub u32);
9
10impl HdPath {
11 #[must_use]
12 pub fn depth(&self) -> u8 {
13 HD_PATH_ELEMENTS_COUNT
14 }
15}
16
17impl From<u32> for HdPath {
18 fn from(index: u32) -> Self {
19 HdPath(index)
20 }
21}
22
23impl From<&u32> for HdPath {
24 fn from(index: &u32) -> Self {
25 HdPath(*index)
26 }
27}
28
29impl HdPath {
30 pub fn to_vec(&self) -> Result<Vec<u8>, Error> {
34 hd_path_to_bytes(*self)
35 }
36}
37
38fn hd_path_to_bytes(hd_path: HdPath) -> Result<Vec<u8>, Error> {
39 let index = hardened(hd_path.0, hd_path)?;
40 let result = [
41 hardened(PURPOSE, hd_path)?,
42 hardened(COIN_TYPE, hd_path)?,
43 index,
44 ];
45 Ok(result.into_iter().flat_map(u32::to_be_bytes).collect())
46}
47
48fn hardened(value: u32, hd_path: HdPath) -> Result<u32, Error> {
49 value
50 .checked_add(HARDENED_OFFSET)
51 .ok_or_else(|| Error::Bip32PathError(path_string(hd_path)))
52}
53
54fn path_string(hd_path: HdPath) -> String {
55 format!("m/{PURPOSE}'/{COIN_TYPE}'/{}'", hd_path.0)
56}
57
58#[cfg(test)]
59mod test {
60 use super::*;
61
62 #[test]
63 fn test_depth() {
64 assert_eq!(HdPath(7).depth(), HD_PATH_ELEMENTS_COUNT);
65 }
66
67 #[test]
68 fn test_to_vec() {
69 assert_eq!(
70 HdPath(7).to_vec().unwrap(),
71 vec![0x80, 0x00, 0x00, 0x2c, 0x80, 0x00, 0x00, 0x94, 0x80, 0x00, 0x00, 0x07,]
72 );
73 }
74
75 #[test]
76 fn test_to_vec_rejects_out_of_range_index() {
77 let err = HdPath(HARDENED_OFFSET).to_vec().unwrap_err();
78 assert!(matches!(err, Error::Bip32PathError(_)));
79 assert_eq!(
80 err.to_string(),
81 format!(
82 "Error occurred while parsing BIP32 path: {}",
83 path_string(HdPath(HARDENED_OFFSET))
84 )
85 );
86 }
87}