libelectrum2descriptors/
electrum_wallet_file.rs

1use crate::{
2    Descriptors, Electrum2DescriptorError, ElectrumExtendedKey, ElectrumExtendedPrivKey,
3    ElectrumExtendedPubKey,
4};
5use bitcoin::bip32::{Xpriv, Xpub};
6use regex::Regex;
7use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
8use std::{fmt, io::BufReader, path::Path, str::FromStr, string::ToString};
9
10/// Representation of an electrum wallet file. Has custom serialization and de-serialization routines to more accurately represent what we need, and the electrum wallet file format.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ElectrumWalletFile {
13    addresses: Addresses,
14    wallet_type: WalletType,
15    keystores: Vec<Keystore>,
16}
17
18impl ElectrumWalletFile {
19    /// Construct a wallet
20    pub fn new(
21        keystores: &[Keystore],
22        min_signatures: u8,
23    ) -> Result<Self, Electrum2DescriptorError> {
24        let wallet = if keystores.len() == 1 {
25            ElectrumWalletFile {
26                addresses: Addresses::new(),
27                wallet_type: WalletType::Standard,
28                keystores: keystores.to_vec(),
29            }
30        } else if keystores.len() >= 255 {
31            return Err(Electrum2DescriptorError::TooManyKeyStores(keystores.len()));
32        } else {
33            ElectrumWalletFile {
34                addresses: Addresses::new(),
35                wallet_type: WalletType::Multisig(min_signatures, keystores.len() as u8),
36                keystores: keystores.to_vec(),
37            }
38        };
39        wallet.validate()?;
40        Ok(wallet)
41    }
42
43    /// Getter for addresses
44    pub fn addresses(&self) -> &Addresses {
45        &self.addresses
46    }
47
48    /// Getter for wallet_type
49    pub fn wallet_type(&self) -> &WalletType {
50        &self.wallet_type
51    }
52
53    /// Getter for keystores
54    pub fn keystores(&self) -> &Vec<Keystore> {
55        &self.keystores
56    }
57
58    /// Parse an electrum wallet file
59    pub fn from_file(wallet_file: &Path) -> Result<Self, Electrum2DescriptorError> {
60        let file = std::fs::File::open(wallet_file)?;
61        let reader = BufReader::new(file);
62        let wallet = serde_json::from_reader(reader)?;
63        Ok(wallet)
64    }
65
66    /// Write to an electrum wallet file
67    pub fn to_file(&self, wallet_file: &Path) -> Result<(), Electrum2DescriptorError> {
68        let file = std::fs::File::create(wallet_file)?;
69        Ok(serde_json::to_writer_pretty(file, self)?)
70    }
71
72    /// Construct from an output descriptor. Only the external descriptor is needed, the change descriptor is implied.
73    pub fn from_descriptor(desc: &str) -> Result<Self, Electrum2DescriptorError> {
74        let wallet = if desc.contains("(sortedmulti(") {
75            ElectrumWalletFile::from_descriptor_multisig(desc)
76        } else {
77            ElectrumWalletFile::from_descriptor_singlesig(desc)
78        }?;
79        wallet.validate()?;
80        Ok(wallet)
81    }
82
83    /// Construct from a single signature output descriptor. Only the external descriptor is needed, the change descriptor is implied.
84    fn from_descriptor_singlesig(desc: &str) -> Result<Self, Electrum2DescriptorError> {
85        let re =
86            Regex::new(r#"(pkh|sh\(wpkh|sh\(wsh|wpkh|wsh)\((([tx]p(ub|rv)[0-9A-Za-z]+)/0/\*)\)+"#)?;
87        let captures = re.captures(desc).map(|captures| {
88            captures
89                .iter()
90                .skip(1)
91                .take(3)
92                .flatten()
93                .map(|c| c.as_str())
94                .collect::<Vec<_>>()
95        });
96        let keystore = match captures.as_deref() {
97            Some([kind, _, xkey]) => Keystore::new(kind, xkey)?,
98            _ => {
99                return Err(Electrum2DescriptorError::UnknownDescriptorFormat(format!(
100                    "{:?}",
101                    captures
102                )))
103            }
104        };
105
106        Ok(ElectrumWalletFile {
107            addresses: Addresses::new(),
108            keystores: vec![keystore],
109            wallet_type: WalletType::Standard,
110        })
111    }
112
113    /// Construct from a multisig output descriptor. Only the external descriptor is needed, the change descriptor is implied.
114    fn from_descriptor_multisig(desc: &str) -> Result<Self, Electrum2DescriptorError> {
115        let re = Regex::new(
116            r#"(sh|sh\(wsh|wsh)\(sortedmulti\((\d),([tx]p(ub|rv)[0-9A-Za-z]+/0/\*,?)+\)+"#,
117        )?;
118        let captures = re.captures(desc).map(|captures| {
119            captures
120                .iter()
121                .skip(1)
122                .take(2)
123                .flatten()
124                .map(|c| c.as_str())
125                .collect::<Vec<_>>()
126        });
127        if let Some([kind, x]) = captures.as_deref() {
128            let kind = match *kind {
129                "wsh" => "wsh",
130                "sh" => "pkh",
131                "sh(wsh" => "sh(wsh",
132                _ => {
133                    return Err(Electrum2DescriptorError::UnknownScriptKind(
134                        kind.to_string(),
135                    ))
136                }
137            };
138            let re = Regex::new(r#"[tx]p[ur][bv][0-9A-Za-z]+"#)?;
139            let keystores = re
140                .captures_iter(desc)
141                .map(|cap| Keystore::new(kind, &cap[0]))
142                .collect::<Result<Vec<Keystore>, _>>()?;
143            let y = keystores.len();
144            if y < 2 {
145                return Err(Electrum2DescriptorError::MultisigFewSigners);
146            }
147
148            Ok(ElectrumWalletFile {
149                addresses: Addresses::new(),
150                keystores,
151                wallet_type: WalletType::Multisig(x.parse().unwrap(), y as u8),
152            })
153        } else {
154            Err(Electrum2DescriptorError::UnknownDescriptorFormat(format!(
155                "{:?}",
156                captures
157            )))
158        }
159    }
160
161    /// Generate output descriptors matching the electrum wallet
162    pub fn to_descriptors(&self) -> Result<Descriptors, Electrum2DescriptorError> {
163        match self.wallet_type {
164            WalletType::Standard => {
165                let exkey = self.keystores[0].get_xkey()?;
166                let desc_ext = exkey.kind().to_string() + "(" + &exkey.xkey_str() + "/0/*)";
167                let desc_chg = exkey.kind().to_string() + "(" + &exkey.xkey_str() + "/1/*)";
168
169                Ok(Descriptors {
170                    external: desc_ext,
171                    change: desc_chg,
172                })
173            }
174            WalletType::Multisig(x, _y) => {
175                let xkeys = self
176                    .keystores
177                    .iter()
178                    .map(|ks| ks.get_xkey())
179                    .collect::<Result<Vec<Box<dyn ElectrumExtendedKey>>, _>>()?;
180                let prefix = match xkeys[0].kind() as &str {
181                    "pkh" => "sh",
182                    kind => kind,
183                }
184                .to_string();
185                let prefix = format!("{}(sortedmulti({}", prefix, x);
186
187                let mut desc = xkeys.iter().fold(prefix, |acc, exkey| {
188                    acc + &(",".to_string() + &exkey.xkey_str() + "/0/*")
189                });
190                desc += "))";
191                let opening = desc.matches('(').count();
192                let closing = desc.matches(')').count();
193                if opening > closing {
194                    desc += ")"
195                };
196                let desc_chg = desc.replace("/0/*", "/1/*");
197
198                Ok(Descriptors {
199                    external: desc,
200                    change: desc_chg,
201                })
202            }
203        }
204    }
205
206    /// validate the internal structure
207    fn validate(&self) -> Result<(), Electrum2DescriptorError> {
208        let expected_keystores: usize = match self.wallet_type {
209            WalletType::Standard => 1,
210            WalletType::Multisig(_x, y) => y.into(),
211        };
212
213        if self.keystores.len() != expected_keystores {
214            return Err(Electrum2DescriptorError::WrongNumberOfKeyStores(
215                self.keystores.len(),
216                expected_keystores,
217            ));
218        }
219
220        if let WalletType::Multisig(x, _y) = self.wallet_type {
221            if x as usize > expected_keystores {
222                return Err(Electrum2DescriptorError::NumberSignaturesKeyStores(
223                    x,
224                    expected_keystores,
225                ));
226            }
227        }
228
229        Ok(())
230    }
231}
232
233impl FromStr for ElectrumWalletFile {
234    type Err = Electrum2DescriptorError;
235
236    /// Parse an electrum wallet file from string
237    fn from_str(wallet_file: &str) -> Result<Self, Electrum2DescriptorError> {
238        Ok(serde_json::from_str(wallet_file)?)
239    }
240}
241
242impl std::fmt::Display for ElectrumWalletFile {
243    /// Write to a string as electrum wallet file format
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        let json = serde_json::to_string_pretty(self).map_err(|_| std::fmt::Error {})?;
246        write!(f, "{}", json)
247    }
248}
249
250impl Serialize for ElectrumWalletFile {
251    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
252    where
253        S: Serializer,
254    {
255        // We don't know the length of the map at this point, so it's None
256        let mut map = serializer.serialize_map(None)?;
257        map.serialize_entry("addresses", &self.addresses)?;
258        map.serialize_entry("wallet_type", &self.wallet_type)?;
259        match self.wallet_type {
260            WalletType::Standard => {
261                map.serialize_entry("keystore", &self.keystores[0])?;
262            }
263            WalletType::Multisig(_x, _y) => {
264                self.keystores
265                    .iter()
266                    .enumerate()
267                    .map(|(i, keystore)| {
268                        let key = format!("x{}/", i + 1);
269                        map.serialize_entry(&key, &keystore)
270                    })
271                    .collect::<Result<Vec<_>, _>>()?;
272            }
273        }
274        map.end()
275    }
276}
277
278impl<'de> Deserialize<'de> for ElectrumWalletFile {
279    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280    where
281        D: Deserializer<'de>,
282    {
283        enum Field {
284            Addrs,
285            Keyst,
286            WalTyp,
287            Ignore,
288        }
289
290        impl<'de> Deserialize<'de> for Field {
291            fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
292            where
293                D: Deserializer<'de>,
294            {
295                struct FieldVisitor;
296
297                impl<'de> de::Visitor<'de> for FieldVisitor {
298                    type Value = Field;
299
300                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
301                        formatter.write_str(
302                            "`addresses` or `keystore` or `wallet_type` or 'x1/` or `x2/`",
303                        )
304                    }
305
306                    fn visit_str<E>(self, value: &str) -> Result<Field, E>
307                    where
308                        E: de::Error,
309                    {
310                        let re = Regex::new(r#"(x)(\d+)(/)|([a-z_\-0-9]+)"#).unwrap();
311                        let captures = re.captures(value).map(|captures| {
312                            captures
313                                .iter()
314                                .skip(1)
315                                .flatten()
316                                .map(|c| c.as_str())
317                                .collect::<Vec<_>>()
318                        });
319                        match captures.as_deref() {
320                            Some(["x", _i, "/"]) => Ok(Field::Keyst),
321                            Some(["keystore"]) => Ok(Field::Keyst),
322                            Some(["addresses"]) => Ok(Field::Addrs),
323                            Some(["wallet_type"]) => Ok(Field::WalTyp),
324                            _ => Ok(Field::Ignore),
325                        }
326                    }
327                }
328
329                deserializer.deserialize_identifier(FieldVisitor)
330            }
331        }
332
333        struct ElectrumWalletFileVisitor;
334
335        impl<'de> de::Visitor<'de> for ElectrumWalletFileVisitor {
336            type Value = ElectrumWalletFile;
337
338            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
339                formatter.write_str("struct ElectrumWalletFile")
340            }
341
342            fn visit_map<V>(self, mut map: V) -> Result<ElectrumWalletFile, V::Error>
343            where
344                V: de::MapAccess<'de>,
345            {
346                let mut addresses = Addresses::new();
347                let mut keystores = Vec::new();
348                let mut wallet_type = WalletType::Standard;
349
350                while let Some(key) = map.next_key()? {
351                    match key {
352                        Field::Addrs => {
353                            addresses = map.next_value()?;
354                        }
355                        Field::Keyst => {
356                            keystores.push(map.next_value()?);
357                        }
358                        Field::WalTyp => {
359                            wallet_type = map.next_value()?;
360                        }
361                        Field::Ignore => {
362                            let _ignore = map.next_value::<de::IgnoredAny>()?;
363                        }
364                    }
365                }
366
367                let wallet = ElectrumWalletFile {
368                    addresses,
369                    keystores,
370                    wallet_type,
371                };
372                wallet.validate().map_err(de::Error::custom)?;
373                Ok(wallet)
374            }
375        }
376
377        const FIELDS: &[&str] = &[
378            "addresses",
379            "addr_history",
380            "channel_backups",
381            "keystore",
382            "wallet_type",
383            "x1/",
384            "x2/",
385            "x3/",
386        ];
387        deserializer.deserialize_struct("ElectrumWalletFile", FIELDS, ElectrumWalletFileVisitor)
388    }
389}
390
391/// Representation of the addresses section of an electrum wallet file
392#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
393pub struct Addresses {
394    pub change: Vec<String>,
395    pub receiving: Vec<String>,
396}
397
398impl Addresses {
399    fn new() -> Self {
400        Addresses {
401            change: Vec::new(),
402            receiving: Vec::new(),
403        }
404    }
405}
406
407/// Representation of a keystore section of an electrum wallet file. Can be single sig "keystore" or multisig "x1/" "x2/" ...
408#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
409pub struct Keystore {
410    #[serde(default = "Keystore::default_type")]
411    pub r#type: String,
412    pub xprv: Option<String>,
413    pub xpub: String,
414}
415
416impl Keystore {
417    /// Construct a Keystore from script kind and xpub or xprv
418    fn new(kind: &str, xkey: &str) -> Result<Self, Electrum2DescriptorError> {
419        let xprv = Xpriv::from_str(xkey);
420        let exprv = if let Ok(xprv) = xprv {
421            Some(ElectrumExtendedPrivKey::new(xprv, kind.to_string()).electrum_xprv()?)
422        } else {
423            None
424        };
425
426        let expub = if let Ok(xprv) = xprv {
427            let secp = bitcoin::secp256k1::Secp256k1::new();
428            ElectrumExtendedPubKey::new(Xpub::from_priv(&secp, &xprv), kind.to_string())
429        } else {
430            ElectrumExtendedPubKey::new(Xpub::from_str(xkey)?, kind.to_string())
431        }
432        .electrum_xpub()?;
433
434        Ok(Keystore {
435            r#type: Keystore::default_type(),
436            xprv: exprv,
437            xpub: expub,
438        })
439    }
440
441    /// Get the xprv if available or else the xpub.
442    fn get_xkey(&self) -> Result<Box<dyn ElectrumExtendedKey>, Electrum2DescriptorError> {
443        if let Some(xprv) = &self.xprv {
444            let exprv = ElectrumExtendedPrivKey::from_str(xprv)?;
445            return Ok(Box::new(exprv));
446        }
447
448        let expub = ElectrumExtendedPubKey::from_str(&self.xpub)?;
449        Ok(Box::new(expub))
450    }
451
452    /// Default keystore type to use if nothing else was specified
453    fn default_type() -> String {
454        "bip32".to_string()
455    }
456}
457
458/// Representation of the wallet_type section of an electrum wallet file. Has custom serialization and de-serialization implementatoin.
459#[derive(Clone, Debug, PartialEq, Eq)]
460pub enum WalletType {
461    Standard,
462    Multisig(u8, u8),
463}
464
465impl fmt::Display for WalletType {
466    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467        write!(f, "{:?}", self)
468    }
469}
470
471impl FromStr for WalletType {
472    type Err = Electrum2DescriptorError;
473
474    /// Parse WalletType from a string representation
475    fn from_str(wallet_type: &str) -> Result<Self, Self::Err> {
476        let re = Regex::new(r#"(standard)|(\d+)(of)(\d+)"#)?;
477        let captures = re.captures(wallet_type).map(|captures| {
478            captures
479                .iter()
480                .skip(1)
481                .flatten()
482                .map(|c| c.as_str())
483                .collect::<Vec<_>>()
484        });
485        match captures.as_deref() {
486            Some(["standard"]) => Ok(WalletType::Standard),
487            Some([x, "of", y]) => Ok(WalletType::Multisig(x.parse().unwrap(), y.parse().unwrap())),
488            _ => Err(Electrum2DescriptorError::UnknownWalletType(
489                wallet_type.to_string(),
490            )),
491        }
492    }
493}
494
495impl<'de> Deserialize<'de> for WalletType {
496    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
497    where
498        D: Deserializer<'de>,
499    {
500        let s = String::deserialize(deserializer)?;
501        WalletType::from_str(&s).map_err(de::Error::custom)
502    }
503}
504
505impl Serialize for WalletType {
506    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
507    where
508        S: serde::Serializer,
509    {
510        let s = match *self {
511            WalletType::Standard => "standard".to_string(),
512            WalletType::Multisig(x, y) => format!("{}of{}", x, y),
513        };
514        serializer.serialize_str(&s)
515    }
516}