use core::fmt::Display;
use bitcoin::bip32::{DerivationPath, Fingerprint, Xpub};
use miniscript::descriptor::{DescriptorPublicKey, WalletPolicy, WalletPolicyError};
pub fn extract_parts(
policy: &WalletPolicy,
) -> Result<(String, Vec<DescriptorPublicKey>), WalletPolicyError> {
let template = format!("{policy:#}");
let descriptor = policy.clone().into_descriptor()?;
let mut keys: Vec<DescriptorPublicKey> = Vec::new();
let mut indices = Vec::new();
for key in descriptor.iter_pk() {
let info = format_key_info(&key);
let index = keys
.iter()
.position(|k| format_key_info(k) == info)
.unwrap_or_else(|| {
keys.push(key);
keys.len() - 1
});
indices.push(index);
}
let mut renumbered = String::with_capacity(template.len());
let mut occurrences = indices.iter();
let mut chars = template.chars().peekable();
while let Some(c) = chars.next() {
if c != '@' {
renumbered.push(c);
continue;
}
while chars.peek().is_some_and(char::is_ascii_digit) {
chars.next();
}
let index = occurrences
.next()
.ok_or(WalletPolicyError::WalletPolicyInvalidKeyInfo)?;
renumbered.push('@');
renumbered.push_str(&index.to_string());
}
if occurrences.next().is_some() {
return Err(WalletPolicyError::WalletPolicyInvalidKeyInfo);
}
Ok((renumbered, keys))
}
pub fn format_key_info(key: &DescriptorPublicKey) -> String {
match key {
DescriptorPublicKey::Single(_) => key.to_string(),
DescriptorPublicKey::XPub(xpub) => format_origin_xkey(&xpub.origin, &xpub.xkey),
DescriptorPublicKey::MultiXPub(xpub) => format_origin_xkey(&xpub.origin, &xpub.xkey),
}
}
fn format_origin_xkey<K: Display>(
origin: &Option<(Fingerprint, DerivationPath)>,
xkey: &K,
) -> String {
match origin {
Some((fp, path)) if !path.as_ref().is_empty() => format!("[{fp}/{path}]{xkey}"),
Some((fp, _)) => format!("[{fp}]{xkey}"),
None => xkey.to_string(),
}
}
pub fn xpub_origin(
key: &DescriptorPublicKey,
) -> Option<(Option<Fingerprint>, Option<DerivationPath>, Xpub)> {
let (origin, xkey) = match key {
DescriptorPublicKey::XPub(x) => (&x.origin, x.xkey),
DescriptorPublicKey::MultiXPub(x) => (&x.origin, x.xkey),
DescriptorPublicKey::Single(_) => return None,
};
let fingerprint = origin.as_ref().map(|(fp, _)| *fp);
let path = origin.as_ref().map(|(_, path)| path.clone());
Some((fingerprint, path, xkey))
}