use crate::bitstream::BitReader;
use crate::encode::Descriptor;
use crate::error::{ContextKind, Error};
use crate::header::Header;
use crate::origin_path::PathDecl;
use crate::tag::Tag;
use crate::tlv::TlvSection;
use crate::tree::read_node;
use crate::use_site_path::UseSitePath;
pub fn decode_payload(bytes: &[u8], total_bits: usize) -> Result<Descriptor, Error> {
let mut r = BitReader::with_bit_limit(bytes, total_bits);
let header = Header::read(&mut r)?;
let path_decl = PathDecl::read(&mut r, header.divergent_paths)?;
let use_site_path = UseSitePath::read(&mut r)?;
let key_index_width = (32 - (path_decl.n as u32).saturating_sub(1).leading_zeros()) as u8;
let tree = read_node(&mut r, key_index_width)?;
if !matches!(
tree.tag,
Tag::Sh | Tag::Wsh | Tag::Wpkh | Tag::Pkh | Tag::Tr
) {
return Err(Error::OperatorContextViolation {
tag: tree.tag,
context: ContextKind::TopLevel,
});
}
let tlv = TlvSection::read(&mut r, key_index_width, path_decl.n)?;
let descriptor = Descriptor {
n: path_decl.n,
path_decl,
use_site_path,
tree,
tlv,
};
crate::validate::validate_placeholder_usage(&descriptor.tree, descriptor.n)?;
if let Some(overrides) = &descriptor.tlv.use_site_path_overrides {
crate::validate::validate_multipath_consistency(&descriptor.use_site_path, overrides)?;
crate::validate::validate_use_site_overrides_canonical(
&descriptor.use_site_path,
overrides,
)?;
}
if matches!(descriptor.tree.tag, crate::tag::Tag::Tr) {
if let crate::tree::Body::Tr { tree: Some(t), .. } = &descriptor.tree.body {
crate::validate::validate_tap_script_tree(t)?;
}
}
crate::validate::validate_explicit_origin_required(&descriptor)?;
crate::validate::validate_xpub_bytes(&descriptor)?;
Ok(descriptor)
}
pub fn decode_md1_string(s: &str) -> Result<Descriptor, Error> {
let (bytes, symbol_aligned_bit_count) = crate::codex32::unwrap_string(s)?;
let chunked_flag = bytes.first().map(|b| (b >> 3) & 0x01).unwrap_or(0);
if chunked_flag == 1 {
return crate::chunk::reassemble(&[s]);
}
decode_payload(&bytes, symbol_aligned_bit_count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encode::encode_payload;
use crate::origin_path::{OriginPath, PathComponent, PathDeclPaths};
use crate::tlv::TlvSection;
use crate::tree::{Body, Node};
#[test]
fn decode_rejects_non_canonical_root_tag() {
let d = Descriptor {
n: 1,
path_decl: PathDecl {
n: 1,
paths: PathDeclPaths::Shared(OriginPath {
components: vec![PathComponent {
hardened: true,
value: 84,
}],
}),
},
use_site_path: UseSitePath::standard_multipath(),
tree: Node {
tag: Tag::AndV,
body: Body::Children(vec![
Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 0 },
},
Node {
tag: Tag::PkK,
body: Body::KeyArg { index: 0 },
},
]),
},
tlv: TlvSection::new_empty(),
};
let (bytes, total_bits) = encode_payload(&d).expect("encode AndV-rooted ok");
let err = decode_payload(&bytes, total_bits).expect_err("decode must reject");
assert!(
matches!(
err,
Error::OperatorContextViolation {
tag: Tag::AndV,
context: ContextKind::TopLevel,
}
),
"expected OperatorContextViolation{{TopLevel}}, got {err:?}"
);
}
}