penumbra_keys/keys/
bip44.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/// Penumbra's registered coin type.
/// See: https://github.com/satoshilabs/slips/pull/1592
const PENUMBRA_COIN_TYPE: u32 = 6532;

/// Represents a BIP44 derivation path.
///
/// BIP43 defines the purpose constant used for BIP44 derivation.
///
/// BIP43: https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki
/// BIP44: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
pub struct Bip44Path {
    purpose: u32,
    coin_type: u32,
    account: u32,
    change: Option<u32>,
    address_index: Option<u32>,
}

impl Bip44Path {
    /// Create a new BIP44 path for a Penumbra wallet.
    pub fn new(account: u32) -> Self {
        Self {
            purpose: 44,
            coin_type: PENUMBRA_COIN_TYPE,
            account,
            change: None,
            address_index: None,
        }
    }

    /// Create a new generic BIP44 path.
    pub fn new_generic(
        purpose: u32,
        coin_type: u32,
        account: u32,
        change: Option<u32>,
        address_index: Option<u32>,
    ) -> Self {
        Self {
            purpose,
            coin_type,
            account,
            change,
            address_index,
        }
    }

    /// Per BIP43, purpose is typically a constant set to 44' or 0x8000002C.
    pub fn purpose(&self) -> u32 {
        self.purpose
    }

    /// Per BIP44, coin type is a constant set for each currency.
    pub fn coin_type(&self) -> u32 {
        self.coin_type
    }

    /// Per BIP44, account splits the key space into independent user identities.
    pub fn account(&self) -> u32 {
        self.account
    }

    /// Change is set to 1 to denote change addresses. None if unset.
    pub fn change(&self) -> Option<u32> {
        self.change
    }

    /// Addresses are numbered starting from index 0. None if unset.
    pub fn address_index(&self) -> Option<u32> {
        self.address_index
    }

    pub fn path(&self) -> String {
        let mut path = format!("m/44'/{}'/{}'", self.coin_type(), self.account());
        if self.change().is_some() {
            path = format!("{}/{}", path, self.change().expect("change will exist"));
        }
        if self.address_index().is_some() {
            path = format!(
                "{}/{}",
                path,
                self.address_index().expect("address index will exist")
            );
        }
        path
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bip44_path_full() {
        let path = Bip44Path::new_generic(44, 6532, 0, Some(0), Some(0));
        assert_eq!(path.path(), "m/44'/6532'/0'/0/0");
    }

    #[test]
    fn test_bip44_path_account_level() {
        let path = Bip44Path::new(0);
        assert_eq!(path.path(), "m/44'/6532'/0'");
    }
}