1use crate::{Descriptors, Electrum2DescriptorError, ElectrumExtendedKey};
2use bitcoin::base58;
3use bitcoin::bip32::{ChainCode, ChildNumber, Fingerprint, Xpub};
4use bitcoin::secp256k1;
5use bitcoin::{Network, NetworkKind};
6use std::convert::TryInto;
7use std::str::FromStr;
8
9pub struct ElectrumExtendedPubKey {
10 xpub: Xpub,
11 kind: String,
12}
13
14type SentinelMap = Vec<([u8; 4], Network, String)>;
15fn initialize_sentinels() -> SentinelMap {
16 vec![
36 (
37 [0x04u8, 0x35, 0x87, 0xcf],
38 Network::Testnet,
39 "pkh".to_string(),
40 ), (
42 [0x04u8, 0x4a, 0x52, 0x62],
43 Network::Testnet,
44 "sh(wpkh".to_string(),
45 ), (
47 [0x02u8, 0x42, 0x89, 0xef],
48 Network::Testnet,
49 "sh(wsh".to_string(),
50 ), (
52 [0x04u8, 0x5f, 0x1c, 0xf6],
53 Network::Testnet,
54 "wpkh".to_string(),
55 ), (
57 [0x02u8, 0x57, 0x54, 0x83],
58 Network::Testnet,
59 "wsh".to_string(),
60 ), (
62 [0x04u8, 0x88, 0xB2, 0x1E],
63 Network::Bitcoin,
64 "pkh".to_string(),
65 ), (
67 [0x04u8, 0x9d, 0x7c, 0xb2],
68 Network::Bitcoin,
69 "sh(wpkh".to_string(),
70 ), (
72 [0x02u8, 0x95, 0xb4, 0x3f],
73 Network::Bitcoin,
74 "sh(wsh".to_string(),
75 ), (
77 [0x04u8, 0xb2, 0x47, 0x46],
78 Network::Bitcoin,
79 "wpkh".to_string(),
80 ), (
82 [0x02u8, 0xaa, 0x7e, 0xd3],
83 Network::Bitcoin,
84 "wsh".to_string(),
85 ), ]
87}
88
89impl FromStr for ElectrumExtendedPubKey {
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_xpub(&data[0..4])?;
102
103 let xpub = Xpub {
104 network: network.into(),
105 depth: data[4],
106 parent_fingerprint: Fingerprint::from(&data[5..9].try_into().unwrap()),
107 child_number,
108 chain_code: ChainCode::from(&data[13..45].try_into().unwrap()),
109 public_key: secp256k1::PublicKey::from_slice(&data[45..78])?,
110 };
111 Ok(ElectrumExtendedPubKey { xpub, kind })
112 }
113}
114
115impl ElectrumExtendedKey for ElectrumExtendedPubKey {
116 fn kind(&self) -> &str {
118 &self.kind
119 }
120
121 fn xkey_str(&self) -> String {
123 self.xpub.to_string()
124 }
125
126 fn to_descriptors(&self) -> Descriptors {
128 let xpub = self.xpub.to_string();
129 let closing_parenthesis = if self.kind.contains('(') { ")" } else { "" };
130 let [external, change] =
131 [0, 1].map(|i| format!("{}({}/{}/*){}", self.kind, xpub, i, closing_parenthesis));
132 Descriptors { external, change }
133 }
134}
135
136impl ElectrumExtendedPubKey {
137 pub fn new(xpub: Xpub, kind: String) -> Self {
139 ElectrumExtendedPubKey { xpub, kind }
140 }
141
142 pub fn xpub(&self) -> &Xpub {
144 &self.xpub
145 }
146
147 pub fn electrum_xpub(&self) -> Result<String, Electrum2DescriptorError> {
149 let sentinels = initialize_sentinels();
150 let sentinel = sentinels
151 .iter()
152 .find(|sent| NetworkKind::from(sent.1) == self.xpub.network && sent.2 == self.kind)
153 .ok_or_else(|| Electrum2DescriptorError::UnknownType)?;
154 let mut data = Vec::from(&sentinel.0[..]);
155 data.push(self.xpub.depth);
156 data.extend(self.xpub.parent_fingerprint.as_bytes());
157 let child_number: u32 = self.xpub.child_number.into();
158 data.extend(child_number.to_be_bytes());
159 data.extend(self.xpub.chain_code.as_bytes());
160 data.extend(&self.xpub.public_key.serialize()); if data.len() != 78 {
163 return Err(Electrum2DescriptorError::InvalidLength(data.len()));
164 }
165
166 Ok(base58::encode_check(&data))
167 }
168}
169
170fn match_electrum_xpub(version: &[u8]) -> Result<(Network, String), Electrum2DescriptorError> {
171 let sentinels = initialize_sentinels();
172 let sentinel = sentinels
173 .iter()
174 .find(|sent| sent.0 == version)
175 .ok_or_else(|| {
176 Electrum2DescriptorError::InvalidExtendedKeyVersion(version[0..4].try_into().unwrap())
177 })?;
178 Ok((sentinel.1, sentinel.2.clone()))
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use miniscript::bitcoin::secp256k1::Secp256k1;
185 use miniscript::descriptor::DescriptorPublicKey;
186 use std::str::FromStr;
187
188 #[test]
189 fn test_vpub_from_electrum() {
190 let electrum_xpub = ElectrumExtendedPubKey::from_str("vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv").unwrap();
191 assert_eq!(electrum_xpub.xpub.to_string(),"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
192 assert_eq!(electrum_xpub.kind, "wpkh");
193 let descriptors = electrum_xpub.to_descriptors();
194 assert_eq!(descriptors.external, "wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/0/*)");
195 assert_eq!(descriptors.change, "wpkh(tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp/1/*)");
196 let xpub = electrum_xpub.xpub();
197 assert_eq!(xpub.to_string(), "tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
198 }
199
200 #[test]
201 fn test_vpub_to_electrum() {
202 let electrum_xpub = ElectrumExtendedPubKey::new(
203 Xpub::from_str("tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp").unwrap(),
204 "wpkh".to_string(),
205 );
206 assert_eq!(electrum_xpub.xpub.to_string(),"tpubD9ZjaMn3rbP1cAVwJy6UcEjFfTLT7W6DbfHdS3Wn48meExtVfKmiH9meWCrSmE9qXLYbGcHC5LxLcdfLZTzwme23qAJoRzRhzbd68dHeyjp");
207 assert_eq!(electrum_xpub.kind, "wpkh");
208 assert_eq!(electrum_xpub.electrum_xpub().unwrap(), "vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv");
209 }
210
211 #[test]
212 fn test_vpub_roundtrip() {
213 let elxpub = "vpub5VXaSncXqxLbdmvrC4Y8z9CszPwuEscADoetWhfrxDFzPUbL5nbVtanYDkrVEutkv9n5A5aCcvRC9swbjDKgHjCZ2tAeae8VsBuPbS8KpXv";
214 let electrum_xpub = ElectrumExtendedPubKey::from_str(elxpub).unwrap();
215 assert_eq!(electrum_xpub.electrum_xpub().unwrap(), elxpub);
216 assert_ne!(elxpub, electrum_xpub.xpub.to_string());
217 }
218
219 #[test]
220 fn test_slip121_vectors() {
221 test_first_address("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj","1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA");
223 test_first_address("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP","37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf");
224 test_first_address("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs","bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu");
225 }
226
227 fn test_first_address(electrum_xpub: &str, expected_first_address: &str) {
228 let electrum_xpub = ElectrumExtendedPubKey::from_str(electrum_xpub).unwrap();
229 assert_eq!(electrum_xpub.xpub.network, Network::Bitcoin.into());
230 let descriptors = electrum_xpub.to_descriptors();
231 let descriptor: miniscript::Descriptor<DescriptorPublicKey> =
232 descriptors.external.parse().unwrap();
233 let secp = Secp256k1::verification_only();
234 let first_address = descriptor
235 .at_derivation_index(0)
236 .unwrap()
237 .derived_descriptor(&secp)
238 .unwrap()
239 .address(miniscript::bitcoin::Network::Bitcoin)
240 .unwrap()
241 .to_string();
242 assert_eq!(expected_first_address, first_address);
243 }
244}