use crate::checksum::{strip_checksum, verify_checksum, add_checksum};
use crate::error::DescriptorError;
use crate::key::{parse_key, DescriptorKey};
use std::fmt;
use std::str::FromStr;
#[derive(Clone, Debug)]
pub enum Descriptor {
Pk(DescriptorKey),
Pkh(DescriptorKey),
Wpkh(DescriptorKey),
Sh(Box<Descriptor>),
Wsh(Box<Descriptor>),
Tr(DescriptorKey),
Multi {
threshold: usize,
keys: Vec<DescriptorKey>,
},
SortedMulti {
threshold: usize,
keys: Vec<DescriptorKey>,
},
}
impl Descriptor {
pub fn parse(s: &str) -> Result<Self, DescriptorError> {
let s = s.trim();
if s.is_empty() {
return Err(DescriptorError::EmptyDescriptor);
}
let desc_str = if s.contains('#') {
verify_checksum(s)?;
strip_checksum(s)
} else {
s
};
parse_descriptor_inner(desc_str, 0)
}
pub fn to_string_with_checksum(&self) -> String {
add_checksum(&self.to_string())
}
pub fn has_wildcard(&self) -> bool {
match self {
Self::Pk(key) | Self::Pkh(key) | Self::Wpkh(key) | Self::Tr(key) => {
key.has_wildcard()
}
Self::Sh(inner) | Self::Wsh(inner) => inner.has_wildcard(),
Self::Multi { keys, .. } | Self::SortedMulti { keys, .. } => {
keys.iter().any(|k| k.has_wildcard())
}
}
}
pub fn descriptor_type(&self) -> &'static str {
match self {
Self::Pk(_) => "pk",
Self::Pkh(_) => "pkh",
Self::Wpkh(_) => "wpkh",
Self::Sh(_) => "sh",
Self::Wsh(_) => "wsh",
Self::Tr(_) => "tr",
Self::Multi { .. } => "multi",
Self::SortedMulti { .. } => "sortedmulti",
}
}
pub fn is_segwit(&self) -> bool {
match self {
Self::Wpkh(_) | Self::Wsh(_) | Self::Tr(_) => true,
Self::Sh(inner) => matches!(inner.as_ref(), Self::Wpkh(_) | Self::Wsh(_)),
_ => false,
}
}
pub fn is_taproot(&self) -> bool {
matches!(self, Self::Tr(_))
}
}
impl fmt::Display for Descriptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pk(key) => write!(f, "pk({})", key),
Self::Pkh(key) => write!(f, "pkh({})", key),
Self::Wpkh(key) => write!(f, "wpkh({})", key),
Self::Sh(inner) => write!(f, "sh({})", inner),
Self::Wsh(inner) => write!(f, "wsh({})", inner),
Self::Tr(key) => write!(f, "tr({})", key),
Self::Multi { threshold, keys } => {
write!(f, "multi({}", threshold)?;
for key in keys {
write!(f, ",{}", key)?;
}
write!(f, ")")
}
Self::SortedMulti { threshold, keys } => {
write!(f, "sortedmulti({}", threshold)?;
for key in keys {
write!(f, ",{}", key)?;
}
write!(f, ")")
}
}
}
}
impl FromStr for Descriptor {
type Err = DescriptorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
fn parse_descriptor_inner(s: &str, pos: usize) -> Result<Descriptor, DescriptorError> {
let s = s.trim();
let open_paren = s.find('(')
.ok_or_else(|| DescriptorError::parse_error(pos, "Expected '('"))?;
let func_name = &s[..open_paren];
let content = extract_parentheses_content(s, open_paren)?;
match func_name {
"pk" => {
let key = parse_key(content)?;
Ok(Descriptor::Pk(key))
}
"pkh" => {
let key = parse_key(content)?;
Ok(Descriptor::Pkh(key))
}
"wpkh" => {
let key = parse_key(content)?;
Ok(Descriptor::Wpkh(key))
}
"sh" => {
let inner = parse_descriptor_inner(content, pos + open_paren + 1)?;
Ok(Descriptor::Sh(Box::new(inner)))
}
"wsh" => {
let inner = parse_descriptor_inner(content, pos + open_paren + 1)?;
Ok(Descriptor::Wsh(Box::new(inner)))
}
"tr" => {
let key = parse_key(content)?;
Ok(Descriptor::Tr(key))
}
"multi" => {
parse_multi(content, false)
}
"sortedmulti" => {
parse_multi(content, true)
}
_ => Err(DescriptorError::UnsupportedType(func_name.to_string())),
}
}
fn extract_parentheses_content(s: &str, open_pos: usize) -> Result<&str, DescriptorError> {
let mut depth = 0;
let mut close_pos = None;
for (i, c) in s[open_pos..].char_indices() {
match c {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
close_pos = Some(open_pos + i);
break;
}
}
_ => {}
}
}
let close = close_pos.ok_or_else(|| DescriptorError::parse_error(open_pos, "Unmatched '('"))?;
Ok(&s[open_pos + 1..close])
}
fn parse_multi(content: &str, sorted: bool) -> Result<Descriptor, DescriptorError> {
let parts: Vec<&str> = split_top_level(content, ',');
if parts.is_empty() {
return Err(DescriptorError::parse_error(0, "Empty multi descriptor"));
}
let threshold: usize = parts[0].trim().parse()
.map_err(|_| DescriptorError::parse_error(0, "Invalid threshold"))?;
let mut keys = Vec::new();
for part in &parts[1..] {
let key = parse_key(part.trim())?;
keys.push(key);
}
if threshold == 0 || threshold > keys.len() {
return Err(DescriptorError::InvalidThreshold {
k: threshold,
n: keys.len(),
});
}
if sorted {
Ok(Descriptor::SortedMulti { threshold, keys })
} else {
Ok(Descriptor::Multi { threshold, keys })
}
}
fn split_top_level(s: &str, delimiter: char) -> Vec<&str> {
let mut parts = Vec::new();
let mut depth = 0;
let mut bracket_depth = 0;
let mut start = 0;
for (i, c) in s.char_indices() {
match c {
'(' => depth += 1,
')' => depth -= 1,
'[' => bracket_depth += 1,
']' => bracket_depth -= 1,
c if c == delimiter && depth == 0 && bracket_depth == 0 => {
parts.push(&s[start..i]);
start = i + 1;
}
_ => {}
}
}
if start < s.len() {
parts.push(&s[start..]);
}
parts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_pk() {
let desc = "pk(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let parsed = Descriptor::parse(desc).unwrap();
assert_eq!(parsed.descriptor_type(), "pk");
assert!(!parsed.has_wildcard());
}
#[test]
fn test_parse_pkh() {
let desc = "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let parsed = Descriptor::parse(desc).unwrap();
assert_eq!(parsed.descriptor_type(), "pkh");
}
#[test]
fn test_parse_wpkh() {
let desc = "wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let parsed = Descriptor::parse(desc).unwrap();
assert_eq!(parsed.descriptor_type(), "wpkh");
assert!(parsed.is_segwit());
}
#[test]
fn test_parse_sh_wpkh() {
let desc = "sh(wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5))";
let parsed = Descriptor::parse(desc).unwrap();
assert_eq!(parsed.descriptor_type(), "sh");
assert!(parsed.is_segwit());
}
#[test]
fn test_parse_tr() {
let desc = "tr(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let parsed = Descriptor::parse(desc).unwrap();
assert_eq!(parsed.descriptor_type(), "tr");
assert!(parsed.is_taproot());
}
#[test]
fn test_parse_multi() {
let desc = "multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)";
let parsed = Descriptor::parse(desc).unwrap();
match parsed {
Descriptor::Multi { threshold, keys } => {
assert_eq!(threshold, 2);
assert_eq!(keys.len(), 2);
}
_ => panic!("Expected Multi"),
}
}
#[test]
fn test_parse_with_checksum() {
let desc = "wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let with_checksum = add_checksum(desc);
let parsed = Descriptor::parse(&with_checksum).unwrap();
assert_eq!(parsed.descriptor_type(), "wpkh");
}
#[test]
fn test_descriptor_roundtrip() {
let desc = "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
let parsed = Descriptor::parse(desc).unwrap();
let displayed = parsed.to_string();
let reparsed = Descriptor::parse(&displayed).unwrap();
assert_eq!(parsed.to_string(), reparsed.to_string());
}
#[test]
fn test_invalid_threshold() {
let desc = "multi(3,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)";
let result = Descriptor::parse(desc);
assert!(matches!(result, Err(DescriptorError::InvalidThreshold { .. })));
}
#[test]
fn test_parse_xpub_descriptor() {
let desc = "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/*)";
let parsed = Descriptor::parse(desc).unwrap();
assert!(parsed.has_wildcard());
}
}