#![deny(missing_docs)]
use crate::error::Error;
use crate::html::{paper_wallets, to_data_url, WalletData};
use bitcoin::secp256k1::{Secp256k1, Signing};
use bitcoin::{self, secp256k1, Address, Network, PublicKey};
use log::debug;
use miniscript::bitcoin::PrivateKey;
use miniscript::{self, Descriptor, DescriptorTrait, MiniscriptKey, TranslatePk};
use std::collections::BTreeMap;
mod error;
mod html;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct WifAndHexPub {
wif: String,
hex_pub: String,
}
pub fn process(descriptor: String, network: Network) -> Result<String> {
let descriptor_string: Descriptor<String> = descriptor.parse()?;
debug!("descriptor_string: {}", descriptor_string);
let (keys, address) = create_key_pairs_and_address(&descriptor_string, network)?;
let wallet_data = create_wallet_data(address, keys, &descriptor_string)?;
let html = paper_wallets(&wallet_data)?;
Ok(to_data_url(&html, "text/html"))
}
#[allow(clippy::unnecessary_wraps)] fn alias_to_key<T: Signing>(
alias: &str,
keys_map: &mut BTreeMap<String, WifAndHexPub>,
network: Network,
secp: &Secp256k1<T>,
) -> Result<PublicKey> {
let inner = secp256k1::SecretKey::new(&mut bitcoin::secp256k1::rand::thread_rng());
let sk = PrivateKey {
compressed: true,
network,
inner,
};
let key = PublicKey::from_private_key(secp, &sk);
keys_map.insert(
alias.to_string(),
WifAndHexPub {
wif: sk.to_wif(),
hex_pub: key.to_string(),
},
);
Ok(key)
}
pub fn create_key_pairs_and_address(
descriptor_string: &Descriptor<String>,
network: Network,
) -> Result<(BTreeMap<String, WifAndHexPub>, Address)> {
let secp = secp256k1::Secp256k1::signing_only();
let mut keys = BTreeMap::new();
let mut keys_2 = BTreeMap::new();
let descriptor_keys: Descriptor<PublicKey> = descriptor_string.translate_pk(
|alias| alias_to_key(alias, &mut keys, network, &secp),
|alias| Ok(alias_to_key(alias, &mut keys_2, network, &secp)?.to_pubkeyhash()),
)?;
keys.extend(keys_2);
debug!("descriptor_keys: {}", descriptor_keys);
debug!("key_map: {:?}", keys);
let address = descriptor_keys.address(network)?;
debug!("address: {}", address.to_string());
Ok((keys, address))
}
#[allow(clippy::unnecessary_wraps)] fn alias_to_wif_or_pub(alias: &str, legend: &BTreeMap<&String, String>) -> Result<String> {
let alias = alias.to_string();
legend
.get(&alias)
.cloned()
.ok_or(Error::MissingMappedKey(alias))
}
fn create_wallet_data(
address: Address,
keys: BTreeMap<String, WifAndHexPub>,
descriptor_alias: &Descriptor<String>,
) -> Result<Vec<WalletData>> {
let descriptor_alias_string = descriptor_alias
.to_string()
.split('#')
.next()
.map(|s| s.to_string())
.unwrap();
let mut results = vec![];
for alias in keys.keys() {
let legend = keys
.iter()
.map(|(alias_internal, wif_and_pub)| {
if alias == alias_internal {
(alias_internal, wif_and_pub.wif.clone())
} else {
(alias_internal, wif_and_pub.hex_pub.clone())
}
})
.collect::<BTreeMap<_, _>>();
debug!("legend: {:?}", legend);
let descriptor_qr = descriptor_alias
.translate_pk(
|alias| alias_to_wif_or_pub(alias, &legend),
|alias| Ok(alias_to_wif_or_pub(alias, &legend)?.to_pubkeyhash()),
)?
.to_string();
let checksum = descriptor_qr
.split('#')
.nth(1)
.ok_or(Error::MissingChecksum)?;
let wallet_data = WalletData {
alias: alias.clone(),
address: address.to_string(),
address_qr: address.to_qr_uri(),
descriptor_alias: format!("{}#{}", descriptor_alias_string, checksum),
legend_rows: legend
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.collect(),
descriptor_qr,
};
results.push(wallet_data);
}
Ok(results)
}