penumbra_sdk_keys/keys/
bip44.rs

1/// Penumbra's registered coin type.
2/// See: https://github.com/satoshilabs/slips/pull/1592
3const PENUMBRA_COIN_TYPE: u32 = 6532;
4
5/// Represents a BIP44 derivation path.
6///
7/// BIP43 defines the purpose constant used for BIP44 derivation.
8///
9/// BIP43: https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki
10/// BIP44: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
11pub struct Bip44Path {
12    purpose: u32,
13    coin_type: u32,
14    account: u32,
15    change: Option<u32>,
16    address_index: Option<u32>,
17}
18
19impl Bip44Path {
20    /// Create a new BIP44 path for a Penumbra wallet.
21    pub fn new(account: u32) -> Self {
22        Self {
23            purpose: 44,
24            coin_type: PENUMBRA_COIN_TYPE,
25            account,
26            change: None,
27            address_index: None,
28        }
29    }
30
31    /// Create a new generic BIP44 path.
32    pub fn new_generic(
33        purpose: u32,
34        coin_type: u32,
35        account: u32,
36        change: Option<u32>,
37        address_index: Option<u32>,
38    ) -> Self {
39        Self {
40            purpose,
41            coin_type,
42            account,
43            change,
44            address_index,
45        }
46    }
47
48    /// Per BIP43, purpose is typically a constant set to 44' or 0x8000002C.
49    pub fn purpose(&self) -> u32 {
50        self.purpose
51    }
52
53    /// Per BIP44, coin type is a constant set for each currency.
54    pub fn coin_type(&self) -> u32 {
55        self.coin_type
56    }
57
58    /// Per BIP44, account splits the key space into independent user identities.
59    pub fn account(&self) -> u32 {
60        self.account
61    }
62
63    /// Change is set to 1 to denote change addresses. None if unset.
64    pub fn change(&self) -> Option<u32> {
65        self.change
66    }
67
68    /// Addresses are numbered starting from index 0. None if unset.
69    pub fn address_index(&self) -> Option<u32> {
70        self.address_index
71    }
72
73    pub fn path(&self) -> String {
74        let mut path = format!("m/44'/{}'/{}'", self.coin_type(), self.account());
75        if self.change().is_some() {
76            path = format!("{}/{}", path, self.change().expect("change will exist"));
77        }
78        if self.address_index().is_some() {
79            path = format!(
80                "{}/{}",
81                path,
82                self.address_index().expect("address index will exist")
83            );
84        }
85        path
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_bip44_path_full() {
95        let path = Bip44Path::new_generic(44, 6532, 0, Some(0), Some(0));
96        assert_eq!(path.path(), "m/44'/6532'/0'/0/0");
97    }
98
99    #[test]
100    fn test_bip44_path_account_level() {
101        let path = Bip44Path::new(0);
102        assert_eq!(path.path(), "m/44'/6532'/0'");
103    }
104}