use crate::bitstream::BitWriter;
use crate::error::Error;
use crate::header::Header;
use crate::origin_path::{PathDecl, PathDeclPaths};
use crate::tlv::TlvSection;
use crate::tree::{Body, Node, write_node};
use crate::use_site_path::UseSitePath;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Descriptor {
pub n: u8,
pub path_decl: PathDecl,
pub use_site_path: UseSitePath,
pub tree: Node,
pub tlv: TlvSection,
}
impl Descriptor {
pub fn key_index_width(&self) -> u8 {
(32 - (self.n as u32).saturating_sub(1).leading_zeros()) as u8
}
pub fn is_wallet_policy(&self) -> bool {
matches!(&self.tlv.pubkeys, Some(v) if !v.is_empty())
}
}
pub fn encode_payload(d: &Descriptor) -> Result<(Vec<u8>, usize), Error> {
let mut d_canonical = d.clone();
crate::canonicalize::canonicalize_placeholder_indices(&mut d_canonical)?;
let d = &d_canonical;
crate::validate::validate_placeholder_usage(&d.tree, d.n)?;
if let Some(overrides) = &d.tlv.use_site_path_overrides {
crate::validate::validate_multipath_consistency(&d.use_site_path, overrides)?;
}
if matches!(d.tree.tag, crate::tag::Tag::Tr) {
if let Body::Tr { tree: Some(t), .. } = &d.tree.body {
crate::validate::validate_tap_script_tree(t)?;
}
}
let mut w = BitWriter::new();
let header = Header {
version: Header::WF_REDESIGN_VERSION,
divergent_paths: matches!(d.path_decl.paths, PathDeclPaths::Divergent(_)),
};
header.write(&mut w);
d.path_decl.write(&mut w)?;
d.use_site_path.write(&mut w)?;
let kiw = d.key_index_width();
write_node(&mut w, &d.tree, kiw)?;
d.tlv.write(&mut w, kiw)?;
let total_bits = w.bit_len();
Ok((w.into_bytes(), total_bits))
}
pub fn render_codex32_grouped(s: &str, group_size: usize) -> String {
if group_size == 0 {
return s.to_string();
}
let mut out = String::new();
for (i, ch) in s.chars().enumerate() {
if i > 0 && i % group_size == 0 {
out.push('-');
}
out.push(ch);
}
out
}
pub fn encode_md1_string(d: &Descriptor) -> Result<String, Error> {
let (bytes, bit_len) = encode_payload(d)?;
crate::codex32::wrap_payload(&bytes, bit_len)
}
#[cfg(test)]
mod render_tests {
use super::*;
#[test]
fn render_groups_at_4() {
assert_eq!(render_codex32_grouped("md1qpz9r4cy7", 4), "md1q-pz9r-4cy7");
}
#[test]
fn render_zero_group_size_no_grouping() {
assert_eq!(render_codex32_grouped("md1qpz9r4cy7", 0), "md1qpz9r4cy7");
}
}
#[cfg(test)]
mod is_wallet_policy_tests {
use super::*;
use crate::origin_path::OriginPath;
use crate::tag::Tag;
use crate::tlv::TlvSection;
fn wpkh_template_only() -> Descriptor {
Descriptor {
n: 1,
path_decl: PathDecl {
n: 1,
paths: PathDeclPaths::Shared(OriginPath { components: vec![] }),
},
use_site_path: UseSitePath::standard_multipath(),
tree: Node {
tag: Tag::Wpkh,
body: Body::KeyArg { index: 0 },
},
tlv: TlvSection::new_empty(),
}
}
#[test]
fn is_wallet_policy_returns_false_for_template_only() {
let d = wpkh_template_only();
assert!(!d.is_wallet_policy());
}
#[test]
fn is_wallet_policy_returns_false_for_empty_pubkeys() {
let mut d = wpkh_template_only();
d.tlv.pubkeys = Some(Vec::new());
assert!(!d.is_wallet_policy());
}
#[test]
fn is_wallet_policy_returns_true_for_populated_pubkeys() {
let mut d = wpkh_template_only();
d.tlv.pubkeys = Some(vec![(0u8, [0u8; 65])]);
assert!(d.is_wallet_policy());
}
}