keechain_core/command/
export.rs1use std::path::PathBuf;
5use std::str::FromStr;
6
7use bdk::miniscript::descriptor::Descriptor;
8use bitcoin::secp256k1::Secp256k1;
9use bitcoin::util::bip32::{
10 ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
11};
12use bitcoin::Network;
13use serde_json::json;
14
15use crate::error::{Error, Result};
16use crate::types::{
17 BitcoinCoreDescriptor, Descriptors, ElectrumExportSupportedScripts, ElectrumJsonWallet, Seed,
18};
19use crate::util::bip::bip32::{self, Bip32RootKey};
20use crate::util::dir;
21
22pub fn descriptor(
23 root_fingerprint: Fingerprint,
24 pubkey: ExtendedPubKey,
25 path: &DerivationPath,
26 change: bool,
27) -> Result<Descriptor<String>> {
28 let mut iter_path = path.into_iter();
29
30 let purpose: &ChildNumber = match iter_path.next() {
31 Some(child) => child,
32 None => {
33 return Err(Error::Generic(
34 "Invalid derivation path: purpose not provided".to_string(),
35 ))
36 }
37 };
38
39 let coin: &ChildNumber = match iter_path.next() {
40 Some(ChildNumber::Hardened { index: 0 }) => &ChildNumber::Hardened { index: 0 },
41 Some(ChildNumber::Hardened { index: 1 }) => &ChildNumber::Hardened { index: 1 },
42 _ => {
43 return Err(Error::Generic(
44 "Invalid derivation path: coin invalid or not provided".to_string(),
45 ))
46 }
47 };
48
49 let account: &ChildNumber = match iter_path.next() {
50 Some(child) => child,
51 None => &ChildNumber::Hardened { index: 0 },
52 };
53
54 let descriptor: String = format!(
55 "[{}/{:#}/{:#}/{:#}]{}/{}/*",
56 root_fingerprint,
57 purpose,
58 coin,
59 account,
60 pubkey,
61 i32::from(change)
62 );
63
64 let descriptor: String = match purpose {
65 ChildNumber::Hardened { index: 44 } => format!("pkh({})", descriptor),
66 ChildNumber::Hardened { index: 49 } => format!("sh(wpkh({}))", descriptor),
67 ChildNumber::Hardened { index: 84 } => format!("wpkh({})", descriptor),
68 ChildNumber::Hardened { index: 86 } => format!("tr({})", descriptor),
69 _ => return Err(Error::Generic("Unsupported derivation path".to_string())),
70 };
71
72 Descriptor::from_str(&descriptor)
73 .map_err(|e| Error::Parse(format!("Impossible to parse descriptor: {}", e)))
74}
75
76pub fn descriptors(seed: Seed, network: Network, account: Option<u32>) -> Result<Descriptors> {
77 let root: ExtendedPrivKey = seed.to_bip32_root_key(network)?;
78 let secp = Secp256k1::new();
79 let root_fingerprint = root.fingerprint(&secp);
80
81 let paths: Vec<DerivationPath> = vec![
82 bip32::account_extended_path(44, network, account)?,
83 bip32::account_extended_path(49, network, account)?,
84 bip32::account_extended_path(84, network, account)?,
85 bip32::account_extended_path(86, network, account)?,
86 ];
87
88 let capacity: usize = paths.len();
89 let mut descriptors = Descriptors {
90 external: Vec::with_capacity(capacity),
91 internal: Vec::with_capacity(capacity),
92 };
93
94 for path in paths.iter() {
95 let derived_private_key: ExtendedPrivKey = root.derive_priv(&secp, path)?;
96 let derived_public_key: ExtendedPubKey =
97 ExtendedPubKey::from_priv(&secp, &derived_private_key);
98
99 descriptors.external.push(descriptor(
100 root_fingerprint,
101 derived_public_key,
102 path,
103 false,
104 )?);
105 descriptors.internal.push(descriptor(
106 root_fingerprint,
107 derived_public_key,
108 path,
109 true,
110 )?);
111 }
112
113 Ok(descriptors)
114}
115
116pub fn bitcoin_core(seed: Seed, network: Network, account: Option<u32>) -> Result<String> {
117 let descriptors: Descriptors = descriptors(seed, network, account)?;
118 let mut bitcoin_core_descriptors: Vec<BitcoinCoreDescriptor> = Vec::new();
119
120 for desc in descriptors.external.into_iter() {
121 bitcoin_core_descriptors.push(BitcoinCoreDescriptor::new(desc, false));
122 }
123
124 for desc in descriptors.internal.into_iter() {
125 bitcoin_core_descriptors.push(BitcoinCoreDescriptor::new(desc, true));
126 }
127
128 Ok(format!(
129 "\nimportdescriptors '{}'\n",
130 json!(bitcoin_core_descriptors)
131 ))
132}
133
134pub fn electrum(
135 seed: Seed,
136 network: Network,
137 script: ElectrumExportSupportedScripts,
138 account: Option<u32>,
139) -> Result<PathBuf> {
140 let electrum_json_wallet = ElectrumJsonWallet::new(seed, network, script, account)?;
141 let home_dir: PathBuf = dir::home();
142 electrum_json_wallet.save_to_file(home_dir)
143}