use crate::encode::Descriptor;
use crate::nums::NUMS_H_POINT_X_ONLY_HEX;
use crate::tag::Tag;
use crate::tree::{Body, Node};
use crate::use_site_path::UseSitePath;
use std::fmt::Write as _;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RenderError {
MalformedTree(String),
}
impl std::fmt::Display for RenderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RenderError::MalformedTree(m) => write!(f, "malformed descriptor tree: {m}"),
}
}
}
impl std::error::Error for RenderError {}
pub fn descriptor_to_template(d: &Descriptor) -> Result<String, RenderError> {
let mut out = String::new();
render_node(
&d.tree,
d.n,
&d.use_site_path,
d.tlv.use_site_path_overrides.as_deref(),
&mut out,
)?;
Ok(out)
}
fn render_node(
node: &Node,
n: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
match node.tag {
Tag::Wpkh => render_wrapper("wpkh", node, n, default_usp, overrides, out),
Tag::Pkh => render_wrapper("pkh", node, n, default_usp, overrides, out),
Tag::Wsh => render_wrapper("wsh", node, n, default_usp, overrides, out),
Tag::Sh => render_wrapper("sh", node, n, default_usp, overrides, out),
Tag::Tr => {
out.push_str("tr(");
match &node.body {
Body::Tr {
is_nums,
key_index,
tree,
} => {
if *is_nums {
out.push_str(NUMS_H_POINT_X_ONLY_HEX);
} else {
render_key(*key_index, default_usp, overrides, out)?;
}
if let Some(t) = tree {
out.push(',');
render_tap_node(t, n, default_usp, overrides, out)?;
}
}
_ => {
return Err(RenderError::MalformedTree(
"Tag::Tr without Body::Tr".into(),
));
}
}
out.push(')');
Ok(())
}
Tag::Multi => render_multi("multi", node, default_usp, overrides, out),
Tag::SortedMulti => render_multi("sortedmulti", node, default_usp, overrides, out),
Tag::MultiA => render_multi("multi_a", node, default_usp, overrides, out),
Tag::SortedMultiA => render_multi("sortedmulti_a", node, default_usp, overrides, out),
Tag::PkK | Tag::PkH => match node.body {
Body::KeyArg { index } => {
if matches!(node.tag, Tag::PkH) {
out.push_str("pkh(");
} else {
out.push_str("pk(");
}
render_key(index, default_usp, overrides, out)?;
out.push(')');
Ok(())
}
_ => Err(RenderError::MalformedTree(
"PkK/PkH without KeyArg body".into(),
)),
},
Tag::AndV => {
let kids = match &node.body {
Body::Children(v) if v.len() == 2 => v,
_ => {
return Err(RenderError::MalformedTree(
"AndV body must be Children([2])".into(),
));
}
};
out.push_str("and_v(");
render_node(&kids[0], n, default_usp, overrides, out)?;
out.push(',');
render_node(&kids[1], n, default_usp, overrides, out)?;
out.push(')');
Ok(())
}
Tag::Verify => {
let inner = match &node.body {
Body::Children(v) if v.len() == 1 => &v[0],
_ => {
return Err(RenderError::MalformedTree(
"Verify body must be Children([1])".into(),
));
}
};
out.push_str("v:");
render_node(inner, n, default_usp, overrides, out)
}
Tag::Older => {
let v = match node.body {
Body::Timelock(v) => v,
_ => {
return Err(RenderError::MalformedTree(
"Older body must be Timelock".into(),
));
}
};
write!(out, "older({v})").unwrap();
Ok(())
}
Tag::After => {
let v = match node.body {
Body::Timelock(v) => v,
_ => {
return Err(RenderError::MalformedTree(
"After body must be Timelock".into(),
));
}
};
write!(out, "after({v})").unwrap();
Ok(())
}
Tag::AndB => render_binary("and_b", node, n, default_usp, overrides, out),
Tag::OrB => render_binary("or_b", node, n, default_usp, overrides, out),
Tag::OrC => render_binary("or_c", node, n, default_usp, overrides, out),
Tag::OrD => render_binary("or_d", node, n, default_usp, overrides, out),
Tag::OrI => render_binary("or_i", node, n, default_usp, overrides, out),
Tag::AndOr => {
let kids = match &node.body {
Body::Children(v) if v.len() == 3 => v,
_ => {
return Err(RenderError::MalformedTree(
"AndOr body must be Children([3])".into(),
));
}
};
out.push_str("andor(");
render_node(&kids[0], n, default_usp, overrides, out)?;
out.push(',');
render_node(&kids[1], n, default_usp, overrides, out)?;
out.push(',');
render_node(&kids[2], n, default_usp, overrides, out)?;
out.push(')');
Ok(())
}
Tag::Sha256 => render_hash256("sha256", &node.body, out),
Tag::Hash256 => render_hash256("hash256", &node.body, out),
Tag::Ripemd160 => render_hash160("ripemd160", &node.body, out),
Tag::Hash160 => render_hash160("hash160", &node.body, out),
Tag::Check | Tag::Swap | Tag::Alt | Tag::DupIf | Tag::NonZero | Tag::ZeroNotEqual => {
render_wrapper_chain(node, n, default_usp, overrides, out)
}
Tag::True => {
out.push('1');
Ok(())
}
Tag::False => {
out.push('0');
Ok(())
}
Tag::RawPkH => {
let h = match &node.body {
Body::Hash160Body(h) => h,
_ => {
return Err(RenderError::MalformedTree(
"RawPkH body must be Hash160Body".into(),
));
}
};
out.push_str("expr_raw_pkh(");
for byte in h {
write!(out, "{byte:02x}").unwrap();
}
out.push(')');
Ok(())
}
Tag::Thresh => {
let (k, children) = match &node.body {
Body::Variable { k, children } => (*k, children),
_ => {
return Err(RenderError::MalformedTree(
"Thresh body must be Variable".into(),
));
}
};
write!(out, "thresh({k}").unwrap();
for child in children {
out.push(',');
render_node(child, n, default_usp, overrides, out)?;
}
out.push(')');
Ok(())
}
other => Err(RenderError::MalformedTree(format!(
"unsupported tag in render: {other:?}"
))),
}
}
fn render_hash256(name: &str, body: &Body, out: &mut String) -> Result<(), RenderError> {
let h = match body {
Body::Hash256Body(h) => h,
_ => {
return Err(RenderError::MalformedTree(format!(
"{name} body must be Hash256Body"
)));
}
};
out.push_str(name);
out.push('(');
for byte in h {
write!(out, "{byte:02x}").unwrap();
}
out.push(')');
Ok(())
}
fn render_hash160(name: &str, body: &Body, out: &mut String) -> Result<(), RenderError> {
let h = match body {
Body::Hash160Body(h) => h,
_ => {
return Err(RenderError::MalformedTree(format!(
"{name} body must be Hash160Body"
)));
}
};
out.push_str(name);
out.push('(');
for byte in h {
write!(out, "{byte:02x}").unwrap();
}
out.push(')');
Ok(())
}
fn render_wrapper_chain(
node: &Node,
n: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
debug_assert!(
matches!(
node.tag,
Tag::Check | Tag::Swap | Tag::Alt | Tag::DupIf | Tag::NonZero | Tag::ZeroNotEqual
),
"render_wrapper_chain called on non-wrapper tag {:?}",
node.tag
);
let mut prefix = String::new();
let mut current = node;
loop {
let letter = match current.tag {
Tag::Check => Some('c'),
Tag::Swap => Some('s'),
Tag::Alt => Some('a'),
Tag::DupIf => Some('d'),
Tag::NonZero => Some('j'),
Tag::ZeroNotEqual => Some('n'),
_ => None,
};
match letter {
Some(c) => {
prefix.push(c);
current = match ¤t.body {
Body::Children(v) if v.len() == 1 => &v[0],
_ => {
return Err(RenderError::MalformedTree(format!(
"{c}: wrapper body must be Children([1])"
)));
}
};
}
None => break,
}
}
if prefix.ends_with('c') && matches!(current.tag, Tag::PkK | Tag::PkH) {
let prefix_no_c = &prefix[..prefix.len() - 1];
if !prefix_no_c.is_empty() {
out.push_str(prefix_no_c);
out.push(':');
}
let idx = match current.body {
Body::KeyArg { index } => index,
_ => {
return Err(RenderError::MalformedTree(
"Check(PkK/PkH) inner body must be KeyArg".into(),
));
}
};
if matches!(current.tag, Tag::PkH) {
out.push_str("pkh(");
} else {
out.push_str("pk(");
}
render_key(idx, default_usp, overrides, out)?;
out.push(')');
return Ok(());
}
out.push_str(&prefix);
out.push(':');
render_node(current, n, default_usp, overrides, out)
}
fn render_binary(
name: &str,
node: &Node,
n: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
let kids = match &node.body {
Body::Children(v) if v.len() == 2 => v,
_ => {
return Err(RenderError::MalformedTree(format!(
"{name} body must be Children([2])"
)));
}
};
out.push_str(name);
out.push('(');
render_node(&kids[0], n, default_usp, overrides, out)?;
out.push(',');
render_node(&kids[1], n, default_usp, overrides, out)?;
out.push(')');
Ok(())
}
fn render_wrapper(
name: &str,
node: &Node,
n: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
out.push_str(name);
out.push('(');
match &node.body {
Body::KeyArg { index } => render_key(*index, default_usp, overrides, out)?,
Body::Children(v) if v.len() == 1 => render_node(&v[0], n, default_usp, overrides, out)?,
_ => {
return Err(RenderError::MalformedTree(format!(
"{name} body must be KeyArg or Children([1])"
)));
}
}
out.push(')');
Ok(())
}
fn render_multi(
name: &str,
node: &Node,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
let (k, indices) = match &node.body {
Body::MultiKeys { k, indices } => (*k, indices),
_ => {
return Err(RenderError::MalformedTree(format!(
"{name} body must be MultiKeys"
)));
}
};
write!(out, "{name}({k}").unwrap();
for idx in indices {
out.push(',');
render_key(*idx, default_usp, overrides, out)?;
}
out.push(')');
Ok(())
}
fn render_tap_node(
node: &Node,
n: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
if matches!(node.tag, Tag::TapTree) {
let children = match &node.body {
Body::Children(v) if v.len() == 2 => v,
_ => {
return Err(RenderError::MalformedTree(
"TapTree must have Children([2])".into(),
));
}
};
out.push('{');
render_tap_node(&children[0], n, default_usp, overrides, out)?;
out.push(',');
render_tap_node(&children[1], n, default_usp, overrides, out)?;
out.push('}');
Ok(())
} else {
render_node(node, n, default_usp, overrides, out)
}
}
fn render_key(
idx: u8,
default_usp: &UseSitePath,
overrides: Option<&[(u8, UseSitePath)]>,
out: &mut String,
) -> Result<(), RenderError> {
let usp = overrides
.and_then(|v| v.iter().find(|(i, _)| *i == idx).map(|(_, u)| u))
.unwrap_or(default_usp);
write!(out, "@{idx}").unwrap();
if let Some(alts) = &usp.multipath {
out.push_str("/<");
for (n, alt) in alts.iter().enumerate() {
if n > 0 {
out.push(';');
}
write!(out, "{}", alt.value).unwrap();
if alt.hardened {
out.push('\'');
}
}
out.push_str(">/*");
} else {
out.push_str("/*");
}
if usp.wildcard_hardened {
out.push('\'');
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_bare_rawpkh_emits_expr_raw_pkh() {
let node = Node {
tag: Tag::RawPkH,
body: Body::Hash160Body([0u8; 20]),
};
let usp = UseSitePath::standard_multipath();
let mut out = String::new();
render_node(
&node, 1, &usp, None, &mut out,
)
.expect("render_node Tag::RawPkH must succeed");
assert_eq!(
out,
"expr_raw_pkh(0000000000000000000000000000000000000000)",
);
}
}