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#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ElectrumWalletFile {
13 addresses: Addresses,
14 wallet_type: WalletType,
15 keystores: Vec<Keystore>,
16}
17
18impl ElectrumWalletFile {
19 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 pub fn addresses(&self) -> &Addresses {
45 &self.addresses
46 }
47
48 pub fn wallet_type(&self) -> &WalletType {
50 &self.wallet_type
51 }
52
53 pub fn keystores(&self) -> &Vec<Keystore> {
55 &self.keystores
56 }
57
58 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 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 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 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 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 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 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 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 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 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#[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#[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 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 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 fn default_type() -> String {
454 "bip32".to_string()
455 }
456}
457
458#[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 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}