keechain_core/command/
export.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Distributed under the MIT software license
3
4use 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}