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 vec![
36 (
37 [0x04u8, 0x35, 0x83, 0x94],
38 Network::Testnet,
39 "pkh".to_string(),
40 ), (
42 [0x04u8, 0x4a, 0x4e, 0x28],
43 Network::Testnet,
44 "sh(wpkh".to_string(),
45 ), (
47 [0x02u8, 0x42, 0x85, 0xb5],
48 Network::Testnet,
49 "sh(wsh".to_string(),
50 ), (
52 [0x04u8, 0x5f, 0x18, 0xbc],
53 Network::Testnet,
54 "wpkh".to_string(),
55 ), (
57 [0x02u8, 0x57, 0x50, 0x48],
58 Network::Testnet,
59 "wsh".to_string(),
60 ), (
62 [0x04u8, 0x88, 0xad, 0xE4],
63 Network::Bitcoin,
64 "pkh".to_string(),
65 ), (
67 [0x04u8, 0x9d, 0x78, 0x78],
68 Network::Bitcoin,
69 "sh(wpkh".to_string(),
70 ), (
72 [0x02u8, 0x95, 0xb0, 0x05],
73 Network::Bitcoin,
74 "sh(wsh".to_string(),
75 ), (
77 [0x04u8, 0xb2, 0x43, 0x0c],
78 Network::Bitcoin,
79 "wpkh".to_string(),
80 ), (
82 [0x02u8, 0xaa, 0x7a, 0x99],
83 Network::Bitcoin,
84 "wsh".to_string(),
85 ), ]
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 fn kind(&self) -> &str {
119 &self.kind
120 }
121
122 fn xkey_str(&self) -> String {
124 self.xprv.to_string()
125 }
126
127 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 pub fn new(xprv: Xpriv, kind: String) -> Self {
140 ElectrumExtendedPrivKey { xprv, kind }
141 }
142
143 pub fn xprv(&self) -> &Xpriv {
145 &self.xprv
146 }
147
148 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}