libelectrum2descriptors/
electrum_extended_priv_key.rs

1use crate::{Descriptors, Electrum2DescriptorError, ElectrumExtendedKey};
2use bitcoin::base58;
3use bitcoin::bip32::{ChainCode, ChildNumber, Fingerprint, Xpriv};
4use bitcoin::secp256k1;
5use bitcoin::{Network, NetworkKind};
6use std::convert::TryInto;
7use std::str::FromStr;
8
9pub struct ElectrumExtendedPrivKey {
10    xprv: Xpriv,
11    kind: String,
12}
13
14type SentinelMap = Vec<([u8; 4], Network, String)>;
15fn initialize_sentinels() -> SentinelMap {
16    // electrum testnet
17    // https://github.com/spesmilo/electrum/blob/928e43fc530ba5befa062db788e4e04d56324161/electrum/constants.py#L110-L116
18    //     XPRV_HEADERS = {
19    //         'standard':    0x04358394,  # tprv
20    //         'p2wpkh-p2sh': 0x044a4e28,  # uprv
21    //         'p2wsh-p2sh':  0x024285b5,  # Uprv
22    //         'p2wpkh':      0x045f18bc,  # vprv
23    //         'p2wsh':       0x02575048,  # Vprv
24    //     }
25    // electrum mainnet
26    // https://github.com/spesmilo/electrum/blob/928e43fc530ba5befa062db788e4e04d56324161/electrum/constants.py#L74-L80
27    //     XPRV_HEADERS = {
28    //         'standard':    0x0488ade4,  # xprv
29    //         'p2wpkh-p2sh': 0x049d7878,  # yprv
30    //         'p2wsh-p2sh':  0x0295b005,  # Yprv
31    //         'p2wpkh':      0x04b2430c,  # zprv
32    //         'p2wsh':       0x02aa7a99,  # Zprv
33    //     }
34
35    vec![
36        (
37            [0x04u8, 0x35, 0x83, 0x94],
38            Network::Testnet,
39            "pkh".to_string(),
40        ), // tprv
41        (
42            [0x04u8, 0x4a, 0x4e, 0x28],
43            Network::Testnet,
44            "sh(wpkh".to_string(),
45        ), // uprv
46        (
47            [0x02u8, 0x42, 0x85, 0xb5],
48            Network::Testnet,
49            "sh(wsh".to_string(),
50        ), // Uprv
51        (
52            [0x04u8, 0x5f, 0x18, 0xbc],
53            Network::Testnet,
54            "wpkh".to_string(),
55        ), // vprv
56        (
57            [0x02u8, 0x57, 0x50, 0x48],
58            Network::Testnet,
59            "wsh".to_string(),
60        ), // Vprv
61        (
62            [0x04u8, 0x88, 0xad, 0xE4],
63            Network::Bitcoin,
64            "pkh".to_string(),
65        ), // xprv
66        (
67            [0x04u8, 0x9d, 0x78, 0x78],
68            Network::Bitcoin,
69            "sh(wpkh".to_string(),
70        ), // yprv
71        (
72            [0x02u8, 0x95, 0xb0, 0x05],
73            Network::Bitcoin,
74            "sh(wsh".to_string(),
75        ), // Yprv
76        (
77            [0x04u8, 0xb2, 0x43, 0x0c],
78            Network::Bitcoin,
79            "wpkh".to_string(),
80        ), // zprv
81        (
82            [0x02u8, 0xaa, 0x7a, 0x99],
83            Network::Bitcoin,
84            "wsh".to_string(),
85        ), // Zprv
86    ]
87}
88
89impl FromStr for ElectrumExtendedPrivKey {
90    type Err = Electrum2DescriptorError;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        let data = base58::decode_check(s)?;
94
95        if data.len() != 78 {
96            return Err(Electrum2DescriptorError::InvalidLength(data.len()));
97        }
98
99        let cn_int = u32::from_be_bytes(data[9..13].try_into().unwrap());
100        let child_number: ChildNumber = ChildNumber::from(cn_int);
101        let (network, kind) = match_electrum_xprv(&data[0..4])?;
102        let key = secp256k1::SecretKey::from_slice(&data[46..78])?;
103
104        let xprv = Xpriv {
105            network: network.into(),
106            depth: data[4],
107            parent_fingerprint: Fingerprint::from(&data[5..9].try_into().unwrap()),
108            child_number,
109            chain_code: ChainCode::from(&data[13..45].try_into().unwrap()),
110            private_key: key,
111        };
112        Ok(ElectrumExtendedPrivKey { xprv, kind })
113    }
114}
115
116impl ElectrumExtendedKey for ElectrumExtendedPrivKey {
117    /// Returns the kind
118    fn kind(&self) -> &str {
119        &self.kind
120    }
121
122    /// Returns the xprv as String
123    fn xkey_str(&self) -> String {
124        self.xprv.to_string()
125    }
126
127    /// Returns internal and external descriptor
128    fn to_descriptors(&self) -> Descriptors {
129        let xprv = self.xprv.to_string();
130        let closing_parenthesis = if self.kind.contains('(') { ")" } else { "" };
131        let [external, change] =
132            [0, 1].map(|i| format!("{}({}/{}/*){}", self.kind, xprv, i, closing_parenthesis));
133        Descriptors { external, change }
134    }
135}
136
137impl ElectrumExtendedPrivKey {
138    /// Constructs a new instance
139    pub fn new(xprv: Xpriv, kind: String) -> Self {
140        ElectrumExtendedPrivKey { xprv, kind }
141    }
142
143    /// Returns the xprv
144    pub fn xprv(&self) -> &Xpriv {
145        &self.xprv
146    }
147
148    /// converts to electrum format
149    pub fn electrum_xprv(&self) -> Result<String, Electrum2DescriptorError> {
150        let sentinels = initialize_sentinels();
151        let sentinel = sentinels
152            .iter()
153            .find(|sent| NetworkKind::from(sent.1) == self.xprv.network && sent.2 == self.kind)
154            .ok_or_else(|| Electrum2DescriptorError::UnknownType)?;
155        let mut data = Vec::from(&sentinel.0[..]);
156        data.push(self.xprv.depth);
157        data.extend(self.xprv.parent_fingerprint.as_bytes());
158        let child_number: u32 = self.xprv.child_number.into();
159        data.extend(child_number.to_be_bytes());
160        data.extend(self.xprv.chain_code.as_bytes());
161        data.push(0u8);
162        data.extend(self.xprv.private_key.as_ref());
163
164        if data.len() != 78 {
165            return Err(Electrum2DescriptorError::InvalidLength(data.len()));
166        }
167
168        Ok(base58::encode_check(&data))
169    }
170}
171
172fn match_electrum_xprv(version: &[u8]) -> Result<(Network, String), Electrum2DescriptorError> {
173    let sentinels = initialize_sentinels();
174    let sentinel = sentinels
175        .iter()
176        .find(|sent| sent.0 == version)
177        .ok_or_else(|| {
178            Electrum2DescriptorError::InvalidExtendedKeyVersion(version[0..4].try_into().unwrap())
179        })?;
180    Ok((sentinel.1, sentinel.2.clone()))
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186    use std::str::FromStr;
187
188    #[test]
189    fn test_vprv_from_electrum() {
190        let electrum_xprv = ElectrumExtendedPrivKey::from_str("yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF").unwrap();
191        assert_eq!(electrum_xprv.xprv.to_string(),"xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
192        assert_eq!(electrum_xprv.kind, "sh(wpkh");
193        let descriptors = electrum_xprv.to_descriptors();
194        assert_eq!(descriptors.external, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/0/*))");
195        assert_eq!(descriptors.change, "sh(wpkh(xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD/1/*))");
196        let xprv = electrum_xprv.xprv();
197        assert_eq!(xprv.to_string(), "xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD");
198    }
199
200    #[test]
201    fn test_vprv_to_electrum() {
202        let electrum_xprv = ElectrumExtendedPrivKey::new(
203            Xpriv::from_str("xprv9y7S1RkggDtZnP1RSzJ7PwUR4MUfF66Wz2jGv9TwJM52WLGmnnrQLLzBSTi7rNtBk4SGeQHBj5G4CuQvPXSn58BmhvX9vk6YzcMm37VuNYD").unwrap(),
204            "sh(wpkh".to_string(),
205        );
206        assert_eq!(electrum_xprv.electrum_xprv().unwrap(), "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF");
207    }
208
209    #[test]
210    fn test_vprv_roundtrip() {
211        let elxprv = "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF";
212        let electrum_xprv = ElectrumExtendedPrivKey::from_str(elxprv).unwrap();
213        assert_eq!(electrum_xprv.electrum_xprv().unwrap(), elxprv);
214        assert_ne!(electrum_xprv.xprv.to_string(), elxprv);
215    }
216}