use crate::canonicalize::{ExpandedKey, expand_per_at_n};
use crate::derive::xpub_from_tlv_bytes;
use crate::encode::Descriptor;
use crate::error::Error;
use crate::origin_path::OriginPath;
use crate::tag::Tag;
use crate::tree::{Body, Node};
use crate::use_site_path::UseSitePath;
use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint};
use miniscript::descriptor::{
DescriptorPublicKey, DescriptorXKey, SinglePub, SinglePubKey, Wildcard,
};
use miniscript::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG};
use miniscript::{
AbsLockTime, Legacy, Miniscript, RelLockTime, ScriptContext, Segwitv0, Tap, Terminal, Threshold,
};
use std::str::FromStr;
use std::sync::Arc;
const NUMS_H_POINT_X_ONLY_HEX: &str =
"50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
pub fn to_miniscript_descriptor(
d: &Descriptor,
chain: u32,
) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
let expanded = expand_per_at_n(d)?;
let mut keys: Vec<DescriptorPublicKey> = Vec::with_capacity(expanded.len());
for e in &expanded {
keys.push(build_descriptor_public_key(e, &d.use_site_path, chain)?);
}
node_to_descriptor(&d.tree, &keys)
}
fn build_descriptor_public_key(
e: &ExpandedKey,
use_site: &UseSitePath,
chain: u32,
) -> Result<DescriptorPublicKey, Error> {
let xpub_bytes = e.xpub.ok_or(Error::MissingPubkey { idx: e.idx })?;
let xkey = xpub_from_tlv_bytes(e.idx, &xpub_bytes)?;
let origin = e.fingerprint.map(|fp| {
(
Fingerprint::from(fp),
origin_path_to_derivation(&e.origin_path),
)
});
let derivation_path = use_site_to_derivation_path(use_site, chain)?;
Ok(DescriptorPublicKey::XPub(DescriptorXKey {
origin,
xkey,
derivation_path,
wildcard: Wildcard::Unhardened,
}))
}
fn origin_path_to_derivation(p: &OriginPath) -> DerivationPath {
let children: Vec<ChildNumber> = p
.components
.iter()
.map(|c| {
if c.hardened {
ChildNumber::from_hardened_idx(c.value)
.unwrap_or(ChildNumber::Hardened { index: c.value })
} else {
ChildNumber::from_normal_idx(c.value)
.unwrap_or(ChildNumber::Normal { index: c.value })
}
})
.collect();
DerivationPath::from(children)
}
fn use_site_to_derivation_path(u: &UseSitePath, chain: u32) -> Result<DerivationPath, Error> {
let mut comps: Vec<ChildNumber> = Vec::new();
if let Some(alts) = &u.multipath {
let alt = alts
.get(chain as usize)
.ok_or(Error::ChainIndexOutOfRange {
chain,
alt_count: alts.len(),
})?;
if alt.hardened {
return Err(Error::HardenedPublicDerivation);
}
comps.push(ChildNumber::Normal { index: alt.value });
}
Ok(DerivationPath::from(comps))
}
fn node_to_descriptor(
node: &Node,
keys: &[DescriptorPublicKey],
) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
match (&node.tag, &node.body) {
(Tag::Pkh, Body::KeyArg { index }) => {
let pk = lookup_key(keys, *index)?;
miniscript::Descriptor::new_pkh(pk).map_err(|e| failed(e.to_string()))
}
(Tag::Wpkh, Body::KeyArg { index }) => {
let pk = lookup_key(keys, *index)?;
miniscript::Descriptor::new_wpkh(pk).map_err(|e| failed(e.to_string()))
}
(Tag::Sh, Body::Children(children)) if children.len() == 1 => {
sh_inner_to_descriptor(&children[0], keys)
}
(Tag::Wsh, Body::Children(children)) if children.len() == 1 => {
wsh_inner_to_descriptor(&children[0], keys)
}
(
Tag::Tr,
Body::Tr {
is_nums,
key_index,
tree,
},
) => {
let internal_key = if *is_nums {
build_nums_internal_key()?
} else {
lookup_key(keys, *key_index)?
};
let script_tree = if let Some(t) = tree {
Some(tree_to_taptree(t, keys)?)
} else {
None
};
miniscript::Descriptor::new_tr(internal_key, script_tree)
.map_err(|e| failed(e.to_string()))
}
_ => Err(failed(format!(
"unsupported top-level tag {:?} with body shape",
node.tag
))),
}
}
fn build_nums_internal_key() -> Result<DescriptorPublicKey, Error> {
let x_only = bitcoin::secp256k1::XOnlyPublicKey::from_str(NUMS_H_POINT_X_ONLY_HEX)
.map_err(|e| failed(format!("NUMS x-only parse: {e}")))?;
Ok(DescriptorPublicKey::Single(SinglePub {
origin: None,
key: SinglePubKey::XOnly(x_only),
}))
}
fn wsh_inner_to_descriptor(
inner: &Node,
keys: &[DescriptorPublicKey],
) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
if let (Tag::SortedMulti, Body::MultiKeys { k, indices }) = (&inner.tag, &inner.body) {
let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
*k,
indices,
keys,
"wsh-sortedmulti",
)?;
return miniscript::Descriptor::new_wsh_sortedmulti(thresh)
.map_err(|e| failed(e.to_string()));
}
let ms = node_to_miniscript::<Segwitv0>(inner, keys)?;
miniscript::Descriptor::new_wsh(ms).map_err(|e| failed(e.to_string()))
}
fn sh_inner_to_descriptor(
inner: &Node,
keys: &[DescriptorPublicKey],
) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
match (&inner.tag, &inner.body) {
(Tag::Wsh, Body::Children(grand)) if grand.len() == 1 => {
let grandchild = &grand[0];
if let (Tag::SortedMulti, Body::MultiKeys { k, indices }) =
(&grandchild.tag, &grandchild.body)
{
let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
*k,
indices,
keys,
"sh-wsh-sortedmulti",
)?;
return miniscript::Descriptor::new_sh_wsh_sortedmulti(thresh)
.map_err(|e| failed(e.to_string()));
}
let ms = node_to_miniscript::<Segwitv0>(grandchild, keys)?;
miniscript::Descriptor::new_sh_wsh(ms).map_err(|e| failed(e.to_string()))
}
(Tag::Wpkh, Body::KeyArg { index }) => {
let pk = lookup_key(keys, *index)?;
miniscript::Descriptor::new_sh_wpkh(pk).map_err(|e| failed(e.to_string()))
}
(Tag::SortedMulti, Body::MultiKeys { k, indices }) => {
let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
*k,
indices,
keys,
"sh-sortedmulti",
)?;
miniscript::Descriptor::new_sh_sortedmulti(thresh).map_err(|e| failed(e.to_string()))
}
_ => {
let ms = node_to_miniscript::<Legacy>(inner, keys)?;
miniscript::Descriptor::new_sh(ms).map_err(|e| failed(e.to_string()))
}
}
}
fn tree_to_taptree(
node: &Node,
keys: &[DescriptorPublicKey],
) -> Result<miniscript::descriptor::TapTree<DescriptorPublicKey>, Error> {
if let (Tag::TapTree, Body::Children(children)) = (&node.tag, &node.body) {
if children.len() != 2 {
return Err(failed(format!(
"Tag::TapTree expected 2 children, got {}",
children.len()
)));
}
let l = tree_to_taptree(&children[0], keys)?;
let r = tree_to_taptree(&children[1], keys)?;
return miniscript::descriptor::TapTree::combine(l, r)
.map_err(|e| failed(format!("TapTree depth: {e}")));
}
let ms = node_to_miniscript::<Tap>(node, keys)?;
Ok(miniscript::descriptor::TapTree::leaf(Arc::new(ms)))
}
fn node_to_miniscript<Ctx>(
node: &Node,
keys: &[DescriptorPublicKey],
) -> Result<Miniscript<DescriptorPublicKey, Ctx>, Error>
where
Ctx: ScriptContext,
{
let term: Terminal<DescriptorPublicKey, Ctx> = match (&node.tag, &node.body) {
(Tag::PkK, Body::KeyArg { index }) => {
let pk = lookup_key(keys, *index)?;
let inner = Miniscript::from_ast(Terminal::PkK(pk)).map_err(into_failed)?;
Terminal::Check(Arc::new(inner))
}
(Tag::PkH, Body::KeyArg { index }) => {
let pk = lookup_key(keys, *index)?;
let inner = Miniscript::from_ast(Terminal::PkH(pk)).map_err(into_failed)?;
Terminal::Check(Arc::new(inner))
}
(Tag::Check, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::Check(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::Verify, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::Verify(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::Swap, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::Swap(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::Alt, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::Alt(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::DupIf, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::DupIf(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::NonZero, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::NonZero(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::ZeroNotEqual, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 1)?;
Terminal::ZeroNotEqual(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
}
(Tag::AndV, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::AndV(Arc::new(l), Arc::new(r))
}
(Tag::AndB, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::AndB(Arc::new(l), Arc::new(r))
}
(Tag::AndOr, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 3)?;
let a = node_to_miniscript::<Ctx>(&children[0], keys)?;
let b = node_to_miniscript::<Ctx>(&children[1], keys)?;
let c = node_to_miniscript::<Ctx>(&children[2], keys)?;
Terminal::AndOr(Arc::new(a), Arc::new(b), Arc::new(c))
}
(Tag::OrB, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::OrB(Arc::new(l), Arc::new(r))
}
(Tag::OrC, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::OrC(Arc::new(l), Arc::new(r))
}
(Tag::OrD, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::OrD(Arc::new(l), Arc::new(r))
}
(Tag::OrI, Body::Children(children)) => {
arity_eq(node.tag, children.len(), 2)?;
let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
Terminal::OrI(Arc::new(l), Arc::new(r))
}
(Tag::Thresh, Body::Variable { k, children }) => {
let mut subs: Vec<Arc<Miniscript<DescriptorPublicKey, Ctx>>> =
Vec::with_capacity(children.len());
for c in children {
subs.push(Arc::new(node_to_miniscript::<Ctx>(c, keys)?));
}
let thresh =
Threshold::<_, 0>::new(*k as usize, subs).map_err(|e| failed(e.to_string()))?;
Terminal::Thresh(thresh)
}
(Tag::Multi, Body::MultiKeys { k, indices }) => {
let thresh =
build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(*k, indices, keys, "multi")?;
Terminal::Multi(thresh)
}
(Tag::MultiA, Body::MultiKeys { k, indices }) => {
let thresh = build_multi_threshold::<{ MAX_PUBKEYS_IN_CHECKSIGADD }>(
*k, indices, keys, "multi_a",
)?;
Terminal::MultiA(thresh)
}
(Tag::SortedMulti, Body::MultiKeys { .. }) => {
return Err(failed(
"Tag::SortedMulti must be the sole child of wsh/sh; cannot appear as a miniscript leaf"
.to_string(),
));
}
(Tag::SortedMultiA, Body::MultiKeys { .. }) => {
return Err(failed(
"Tag::SortedMultiA must be a tap-leaf root child; rust-miniscript v13 has no Terminal::SortedMultiA fragment"
.to_string(),
));
}
(Tag::After, Body::Timelock(v)) => {
let lt = AbsLockTime::from_consensus(*v).map_err(|e| failed(e.to_string()))?;
Terminal::After(lt)
}
(Tag::Older, Body::Timelock(v)) => {
let lt = RelLockTime::from_consensus(*v).map_err(|e| failed(e.to_string()))?;
Terminal::Older(lt)
}
(Tag::Sha256, Body::Hash256Body(h)) => {
let hash = sha256_from_bytes(h)?;
Terminal::Sha256(hash)
}
(Tag::Hash256, Body::Hash256Body(h)) => {
let hash = hash256_from_bytes(h)?;
Terminal::Hash256(hash)
}
(Tag::Ripemd160, Body::Hash160Body(h)) => {
let hash = ripemd160_from_bytes(h)?;
Terminal::Ripemd160(hash)
}
(Tag::Hash160, Body::Hash160Body(h)) => {
let hash = hash160_from_bytes(h)?;
Terminal::Hash160(hash)
}
(Tag::RawPkH, Body::Hash160Body(_)) => {
return Err(failed(
"Tag::RawPkH is not constructible through miniscript's public API".to_string(),
));
}
(Tag::False, Body::Empty) => Terminal::False,
(Tag::True, Body::Empty) => Terminal::True,
(Tag::TapTree, _) => {
return Err(failed(
"Tag::TapTree is a tap-tree internal node, not a miniscript leaf".to_string(),
));
}
(Tag::Tr, _) | (Tag::Wsh, _) | (Tag::Sh, _) | (Tag::Wpkh, _) | (Tag::Pkh, _) => {
return Err(failed(format!(
"top-level wrapper {:?} cannot appear inside a miniscript context",
node.tag
)));
}
_ => {
return Err(failed(format!(
"tag {:?} unsupported with body shape",
node.tag
)));
}
};
Miniscript::from_ast(term).map_err(into_failed)
}
fn lookup_key(keys: &[DescriptorPublicKey], idx: u8) -> Result<DescriptorPublicKey, Error> {
keys.get(idx as usize)
.cloned()
.ok_or_else(|| failed(format!("@{idx} out of range")))
}
fn build_multi_threshold<const MAX: usize>(
k: u8,
indices: &[u8],
keys: &[DescriptorPublicKey],
label: &str,
) -> Result<Threshold<DescriptorPublicKey, MAX>, Error> {
let pks: Vec<DescriptorPublicKey> = indices
.iter()
.map(|i| lookup_key(keys, *i))
.collect::<Result<_, _>>()?;
Threshold::<DescriptorPublicKey, MAX>::new(k as usize, pks)
.map_err(|e| failed(format!("{label} threshold: {e}")))
}
fn arity_eq(tag: Tag, got: usize, expected: usize) -> Result<(), Error> {
if got != expected {
return Err(failed(format!(
"{tag:?} expected {expected} children, got {got}"
)));
}
Ok(())
}
fn failed(detail: String) -> Error {
Error::AddressDerivationFailed { detail }
}
fn into_failed(e: miniscript::Error) -> Error {
failed(e.to_string())
}
fn sha256_from_bytes(h: &[u8; 32]) -> Result<bitcoin::hashes::sha256::Hash, Error> {
use bitcoin::hashes::Hash;
Ok(bitcoin::hashes::sha256::Hash::from_byte_array(*h))
}
fn hash256_from_bytes(h: &[u8; 32]) -> Result<miniscript::hash256::Hash, Error> {
use bitcoin::hashes::Hash;
Ok(miniscript::hash256::Hash::from_byte_array(*h))
}
fn ripemd160_from_bytes(h: &[u8; 20]) -> Result<bitcoin::hashes::ripemd160::Hash, Error> {
use bitcoin::hashes::Hash;
Ok(bitcoin::hashes::ripemd160::Hash::from_byte_array(*h))
}
fn hash160_from_bytes(h: &[u8; 20]) -> Result<bitcoin::hashes::hash160::Hash, Error> {
use bitcoin::hashes::Hash;
Ok(bitcoin::hashes::hash160::Hash::from_byte_array(*h))
}