1use crate::canonicalize::{ExpandedKey, expand_per_at_n};
12use crate::derive::xpub_from_tlv_bytes;
13use crate::encode::Descriptor;
14use crate::error::Error;
15use crate::origin_path::OriginPath;
16use crate::tag::Tag;
17use crate::tree::{Body, Node};
18use crate::use_site_path::UseSitePath;
19
20use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint};
21use miniscript::descriptor::{
22 DescriptorPublicKey, DescriptorXKey, SinglePub, SinglePubKey, Wildcard,
23};
24use miniscript::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG};
25use miniscript::{
26 AbsLockTime, Legacy, Miniscript, RelLockTime, ScriptContext, Segwitv0, Tap, Terminal, Threshold,
27};
28use std::str::FromStr;
29use std::sync::Arc;
30
31const NUMS_H_POINT_X_ONLY_HEX: &str =
34 "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
35
36pub fn to_miniscript_descriptor(
54 d: &Descriptor,
55 chain: u32,
56) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
57 let expanded = expand_per_at_n(d)?;
58 let mut keys: Vec<DescriptorPublicKey> = Vec::with_capacity(expanded.len());
59 for e in &expanded {
60 keys.push(build_descriptor_public_key(e, &d.use_site_path, chain)?);
61 }
62 node_to_descriptor(&d.tree, &keys)
63}
64
65fn build_descriptor_public_key(
68 e: &ExpandedKey,
69 use_site: &UseSitePath,
70 chain: u32,
71) -> Result<DescriptorPublicKey, Error> {
72 let xpub_bytes = e.xpub.ok_or(Error::MissingPubkey { idx: e.idx })?;
73 let xkey = xpub_from_tlv_bytes(e.idx, &xpub_bytes)?;
74
75 let origin = e.fingerprint.map(|fp| {
76 (
77 Fingerprint::from(fp),
78 origin_path_to_derivation(&e.origin_path),
79 )
80 });
81
82 let derivation_path = use_site_to_derivation_path(use_site, chain)?;
85
86 Ok(DescriptorPublicKey::XPub(DescriptorXKey {
87 origin,
88 xkey,
89 derivation_path,
90 wildcard: Wildcard::Unhardened,
91 }))
92}
93
94fn origin_path_to_derivation(p: &OriginPath) -> DerivationPath {
96 let children: Vec<ChildNumber> = p
97 .components
98 .iter()
99 .map(|c| {
100 if c.hardened {
101 ChildNumber::from_hardened_idx(c.value)
102 .unwrap_or(ChildNumber::Hardened { index: c.value })
103 } else {
104 ChildNumber::from_normal_idx(c.value)
105 .unwrap_or(ChildNumber::Normal { index: c.value })
106 }
107 })
108 .collect();
109 DerivationPath::from(children)
110}
111
112fn use_site_to_derivation_path(u: &UseSitePath, chain: u32) -> Result<DerivationPath, Error> {
117 let mut comps: Vec<ChildNumber> = Vec::new();
118 if let Some(alts) = &u.multipath {
119 let alt = alts
120 .get(chain as usize)
121 .ok_or(Error::ChainIndexOutOfRange {
122 chain,
123 alt_count: alts.len(),
124 })?;
125 if alt.hardened {
126 return Err(Error::HardenedPublicDerivation);
127 }
128 comps.push(ChildNumber::Normal { index: alt.value });
129 }
130 Ok(DerivationPath::from(comps))
131}
132
133fn node_to_descriptor(
135 node: &Node,
136 keys: &[DescriptorPublicKey],
137) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
138 match (&node.tag, &node.body) {
139 (Tag::Pkh, Body::KeyArg { index }) => {
140 let pk = lookup_key(keys, *index)?;
141 miniscript::Descriptor::new_pkh(pk).map_err(|e| failed(e.to_string()))
142 }
143 (Tag::Wpkh, Body::KeyArg { index }) => {
144 let pk = lookup_key(keys, *index)?;
145 miniscript::Descriptor::new_wpkh(pk).map_err(|e| failed(e.to_string()))
146 }
147 (Tag::Sh, Body::Children(children)) if children.len() == 1 => {
148 sh_inner_to_descriptor(&children[0], keys)
149 }
150 (Tag::Wsh, Body::Children(children)) if children.len() == 1 => {
151 wsh_inner_to_descriptor(&children[0], keys)
152 }
153 (
154 Tag::Tr,
155 Body::Tr {
156 is_nums,
157 key_index,
158 tree,
159 },
160 ) => {
161 let internal_key = if *is_nums {
162 build_nums_internal_key()?
163 } else {
164 lookup_key(keys, *key_index)?
165 };
166 let script_tree = if let Some(t) = tree {
167 Some(tree_to_taptree(t, keys)?)
168 } else {
169 None
170 };
171 miniscript::Descriptor::new_tr(internal_key, script_tree)
172 .map_err(|e| failed(e.to_string()))
173 }
174 _ => Err(failed(format!(
175 "unsupported top-level tag {:?} with body shape",
176 node.tag
177 ))),
178 }
179}
180
181fn build_nums_internal_key() -> Result<DescriptorPublicKey, Error> {
184 let x_only = bitcoin::secp256k1::XOnlyPublicKey::from_str(NUMS_H_POINT_X_ONLY_HEX)
185 .map_err(|e| failed(format!("NUMS x-only parse: {e}")))?;
186 Ok(DescriptorPublicKey::Single(SinglePub {
187 origin: None,
188 key: SinglePubKey::XOnly(x_only),
189 }))
190}
191
192fn wsh_inner_to_descriptor(
195 inner: &Node,
196 keys: &[DescriptorPublicKey],
197) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
198 if let (Tag::SortedMulti, Body::MultiKeys { k, indices }) = (&inner.tag, &inner.body) {
199 let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
200 *k,
201 indices,
202 keys,
203 "wsh-sortedmulti",
204 )?;
205 return miniscript::Descriptor::new_wsh_sortedmulti(thresh)
206 .map_err(|e| failed(e.to_string()));
207 }
208 let ms = node_to_miniscript::<Segwitv0>(inner, keys)?;
209 miniscript::Descriptor::new_wsh(ms).map_err(|e| failed(e.to_string()))
210}
211
212fn sh_inner_to_descriptor(
216 inner: &Node,
217 keys: &[DescriptorPublicKey],
218) -> Result<miniscript::Descriptor<DescriptorPublicKey>, Error> {
219 match (&inner.tag, &inner.body) {
220 (Tag::Wsh, Body::Children(grand)) if grand.len() == 1 => {
221 let grandchild = &grand[0];
222 if let (Tag::SortedMulti, Body::MultiKeys { k, indices }) =
223 (&grandchild.tag, &grandchild.body)
224 {
225 let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
226 *k,
227 indices,
228 keys,
229 "sh-wsh-sortedmulti",
230 )?;
231 return miniscript::Descriptor::new_sh_wsh_sortedmulti(thresh)
232 .map_err(|e| failed(e.to_string()));
233 }
234 let ms = node_to_miniscript::<Segwitv0>(grandchild, keys)?;
235 miniscript::Descriptor::new_sh_wsh(ms).map_err(|e| failed(e.to_string()))
236 }
237 (Tag::Wpkh, Body::KeyArg { index }) => {
238 let pk = lookup_key(keys, *index)?;
239 miniscript::Descriptor::new_sh_wpkh(pk).map_err(|e| failed(e.to_string()))
240 }
241 (Tag::SortedMulti, Body::MultiKeys { k, indices }) => {
242 let thresh = build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(
243 *k,
244 indices,
245 keys,
246 "sh-sortedmulti",
247 )?;
248 miniscript::Descriptor::new_sh_sortedmulti(thresh).map_err(|e| failed(e.to_string()))
249 }
250 _ => {
251 let ms = node_to_miniscript::<Legacy>(inner, keys)?;
252 miniscript::Descriptor::new_sh(ms).map_err(|e| failed(e.to_string()))
253 }
254 }
255}
256
257fn tree_to_taptree(
259 node: &Node,
260 keys: &[DescriptorPublicKey],
261) -> Result<miniscript::descriptor::TapTree<DescriptorPublicKey>, Error> {
262 if let (Tag::TapTree, Body::Children(children)) = (&node.tag, &node.body) {
263 if children.len() != 2 {
264 return Err(failed(format!(
265 "Tag::TapTree expected 2 children, got {}",
266 children.len()
267 )));
268 }
269 let l = tree_to_taptree(&children[0], keys)?;
270 let r = tree_to_taptree(&children[1], keys)?;
271 return miniscript::descriptor::TapTree::combine(l, r)
272 .map_err(|e| failed(format!("TapTree depth: {e}")));
273 }
274 let ms = node_to_miniscript::<Tap>(node, keys)?;
278 Ok(miniscript::descriptor::TapTree::leaf(Arc::new(ms)))
279}
280
281fn node_to_miniscript<Ctx>(
283 node: &Node,
284 keys: &[DescriptorPublicKey],
285) -> Result<Miniscript<DescriptorPublicKey, Ctx>, Error>
286where
287 Ctx: ScriptContext,
288{
289 let term: Terminal<DescriptorPublicKey, Ctx> = match (&node.tag, &node.body) {
290 (Tag::PkK, Body::KeyArg { index }) => {
291 let pk = lookup_key(keys, *index)?;
296 let inner = Miniscript::from_ast(Terminal::PkK(pk)).map_err(into_failed)?;
297 Terminal::Check(Arc::new(inner))
298 }
299 (Tag::PkH, Body::KeyArg { index }) => {
300 let pk = lookup_key(keys, *index)?;
301 let inner = Miniscript::from_ast(Terminal::PkH(pk)).map_err(into_failed)?;
302 Terminal::Check(Arc::new(inner))
303 }
304 (Tag::Check, Body::Children(children)) => {
305 arity_eq(node.tag, children.len(), 1)?;
306 Terminal::Check(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
307 }
308 (Tag::Verify, Body::Children(children)) => {
309 arity_eq(node.tag, children.len(), 1)?;
310 Terminal::Verify(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
311 }
312 (Tag::Swap, Body::Children(children)) => {
313 arity_eq(node.tag, children.len(), 1)?;
314 Terminal::Swap(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
315 }
316 (Tag::Alt, Body::Children(children)) => {
317 arity_eq(node.tag, children.len(), 1)?;
318 Terminal::Alt(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
319 }
320 (Tag::DupIf, Body::Children(children)) => {
321 arity_eq(node.tag, children.len(), 1)?;
322 Terminal::DupIf(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
323 }
324 (Tag::NonZero, Body::Children(children)) => {
325 arity_eq(node.tag, children.len(), 1)?;
326 Terminal::NonZero(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
327 }
328 (Tag::ZeroNotEqual, Body::Children(children)) => {
329 arity_eq(node.tag, children.len(), 1)?;
330 Terminal::ZeroNotEqual(Arc::new(node_to_miniscript::<Ctx>(&children[0], keys)?))
331 }
332 (Tag::AndV, Body::Children(children)) => {
333 arity_eq(node.tag, children.len(), 2)?;
334 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
335 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
336 Terminal::AndV(Arc::new(l), Arc::new(r))
337 }
338 (Tag::AndB, Body::Children(children)) => {
339 arity_eq(node.tag, children.len(), 2)?;
340 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
341 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
342 Terminal::AndB(Arc::new(l), Arc::new(r))
343 }
344 (Tag::AndOr, Body::Children(children)) => {
345 arity_eq(node.tag, children.len(), 3)?;
346 let a = node_to_miniscript::<Ctx>(&children[0], keys)?;
347 let b = node_to_miniscript::<Ctx>(&children[1], keys)?;
348 let c = node_to_miniscript::<Ctx>(&children[2], keys)?;
349 Terminal::AndOr(Arc::new(a), Arc::new(b), Arc::new(c))
350 }
351 (Tag::OrB, Body::Children(children)) => {
352 arity_eq(node.tag, children.len(), 2)?;
353 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
354 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
355 Terminal::OrB(Arc::new(l), Arc::new(r))
356 }
357 (Tag::OrC, Body::Children(children)) => {
358 arity_eq(node.tag, children.len(), 2)?;
359 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
360 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
361 Terminal::OrC(Arc::new(l), Arc::new(r))
362 }
363 (Tag::OrD, Body::Children(children)) => {
364 arity_eq(node.tag, children.len(), 2)?;
365 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
366 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
367 Terminal::OrD(Arc::new(l), Arc::new(r))
368 }
369 (Tag::OrI, Body::Children(children)) => {
370 arity_eq(node.tag, children.len(), 2)?;
371 let l = node_to_miniscript::<Ctx>(&children[0], keys)?;
372 let r = node_to_miniscript::<Ctx>(&children[1], keys)?;
373 Terminal::OrI(Arc::new(l), Arc::new(r))
374 }
375 (Tag::Thresh, Body::Variable { k, children }) => {
376 let mut subs: Vec<Arc<Miniscript<DescriptorPublicKey, Ctx>>> =
377 Vec::with_capacity(children.len());
378 for c in children {
379 subs.push(Arc::new(node_to_miniscript::<Ctx>(c, keys)?));
380 }
381 let thresh =
382 Threshold::<_, 0>::new(*k as usize, subs).map_err(|e| failed(e.to_string()))?;
383 Terminal::Thresh(thresh)
384 }
385 (Tag::Multi, Body::MultiKeys { k, indices }) => {
386 let thresh =
391 build_multi_threshold::<{ MAX_PUBKEYS_PER_MULTISIG }>(*k, indices, keys, "multi")?;
392 Terminal::Multi(thresh)
393 }
394 (Tag::MultiA, Body::MultiKeys { k, indices }) => {
395 let thresh = build_multi_threshold::<{ MAX_PUBKEYS_IN_CHECKSIGADD }>(
396 *k, indices, keys, "multi_a",
397 )?;
398 Terminal::MultiA(thresh)
399 }
400 (Tag::SortedMulti, Body::MultiKeys { .. }) => {
401 return Err(failed(
402 "Tag::SortedMulti must be the sole child of wsh/sh; cannot appear as a miniscript leaf"
403 .to_string(),
404 ));
405 }
406 (Tag::SortedMultiA, Body::MultiKeys { .. }) => {
407 return Err(failed(
408 "Tag::SortedMultiA must be a tap-leaf root child; rust-miniscript v13 has no Terminal::SortedMultiA fragment"
409 .to_string(),
410 ));
411 }
412 (Tag::After, Body::Timelock(v)) => {
413 let lt = AbsLockTime::from_consensus(*v).map_err(|e| failed(e.to_string()))?;
414 Terminal::After(lt)
415 }
416 (Tag::Older, Body::Timelock(v)) => {
417 let lt = RelLockTime::from_consensus(*v).map_err(|e| failed(e.to_string()))?;
418 Terminal::Older(lt)
419 }
420 (Tag::Sha256, Body::Hash256Body(h)) => {
421 let hash = sha256_from_bytes(h)?;
422 Terminal::Sha256(hash)
423 }
424 (Tag::Hash256, Body::Hash256Body(h)) => {
425 let hash = hash256_from_bytes(h)?;
426 Terminal::Hash256(hash)
427 }
428 (Tag::Ripemd160, Body::Hash160Body(h)) => {
429 let hash = ripemd160_from_bytes(h)?;
430 Terminal::Ripemd160(hash)
431 }
432 (Tag::Hash160, Body::Hash160Body(h)) => {
433 let hash = hash160_from_bytes(h)?;
434 Terminal::Hash160(hash)
435 }
436 (Tag::RawPkH, Body::Hash160Body(_)) => {
437 return Err(failed(
438 "Tag::RawPkH is not constructible through miniscript's public API".to_string(),
439 ));
440 }
441 (Tag::False, Body::Empty) => Terminal::False,
442 (Tag::True, Body::Empty) => Terminal::True,
443 (Tag::TapTree, _) => {
444 return Err(failed(
445 "Tag::TapTree is a tap-tree internal node, not a miniscript leaf".to_string(),
446 ));
447 }
448 (Tag::Tr, _) | (Tag::Wsh, _) | (Tag::Sh, _) | (Tag::Wpkh, _) | (Tag::Pkh, _) => {
449 return Err(failed(format!(
450 "top-level wrapper {:?} cannot appear inside a miniscript context",
451 node.tag
452 )));
453 }
454 _ => {
455 return Err(failed(format!(
456 "tag {:?} unsupported with body shape",
457 node.tag
458 )));
459 }
460 };
461 Miniscript::from_ast(term).map_err(into_failed)
462}
463
464fn lookup_key(keys: &[DescriptorPublicKey], idx: u8) -> Result<DescriptorPublicKey, Error> {
465 keys.get(idx as usize)
466 .cloned()
467 .ok_or_else(|| failed(format!("@{idx} out of range")))
468}
469
470fn build_multi_threshold<const MAX: usize>(
471 k: u8,
472 indices: &[u8],
473 keys: &[DescriptorPublicKey],
474 label: &str,
475) -> Result<Threshold<DescriptorPublicKey, MAX>, Error> {
476 let pks: Vec<DescriptorPublicKey> = indices
477 .iter()
478 .map(|i| lookup_key(keys, *i))
479 .collect::<Result<_, _>>()?;
480 Threshold::<DescriptorPublicKey, MAX>::new(k as usize, pks)
481 .map_err(|e| failed(format!("{label} threshold: {e}")))
482}
483
484fn arity_eq(tag: Tag, got: usize, expected: usize) -> Result<(), Error> {
485 if got != expected {
486 return Err(failed(format!(
487 "{tag:?} expected {expected} children, got {got}"
488 )));
489 }
490 Ok(())
491}
492
493fn failed(detail: String) -> Error {
494 Error::AddressDerivationFailed { detail }
495}
496
497fn into_failed(e: miniscript::Error) -> Error {
498 failed(e.to_string())
499}
500
501fn sha256_from_bytes(h: &[u8; 32]) -> Result<bitcoin::hashes::sha256::Hash, Error> {
504 use bitcoin::hashes::Hash;
505 Ok(bitcoin::hashes::sha256::Hash::from_byte_array(*h))
506}
507
508fn hash256_from_bytes(h: &[u8; 32]) -> Result<miniscript::hash256::Hash, Error> {
509 use bitcoin::hashes::Hash;
510 Ok(miniscript::hash256::Hash::from_byte_array(*h))
511}
512
513fn ripemd160_from_bytes(h: &[u8; 20]) -> Result<bitcoin::hashes::ripemd160::Hash, Error> {
514 use bitcoin::hashes::Hash;
515 Ok(bitcoin::hashes::ripemd160::Hash::from_byte_array(*h))
516}
517
518fn hash160_from_bytes(h: &[u8; 20]) -> Result<bitcoin::hashes::hash160::Hash, Error> {
519 use bitcoin::hashes::Hash;
520 Ok(bitcoin::hashes::hash160::Hash::from_byte_array(*h))
521}