Skip to main content

stellar_ledger/
hd_path.rs

1use 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    /// # Errors
31    ///
32    /// Could fail to convert the path to bytes
33    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}