1use crate::bitstream::BitWriter;
4use crate::error::Error;
5use crate::header::Header;
6use crate::origin_path::{PathDecl, PathDeclPaths};
7use crate::tlv::TlvSection;
8use crate::tree::{Body, Node, write_node};
9use crate::use_site_path::UseSitePath;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct Descriptor {
18 pub n: u8,
20 pub path_decl: PathDecl,
22 pub use_site_path: UseSitePath,
24 pub tree: Node,
26 pub tlv: TlvSection,
28}
29
30impl Descriptor {
31 pub fn key_index_width(&self) -> u8 {
38 (32 - (self.n as u32).saturating_sub(1).leading_zeros()) as u8
41 }
42
43 pub fn is_wallet_policy(&self) -> bool {
51 matches!(&self.tlv.pubkeys, Some(v) if !v.is_empty())
52 }
53}
54
55pub fn encode_payload(d: &Descriptor) -> Result<(Vec<u8>, usize), Error> {
66 let mut d_canonical = d.clone();
67 crate::canonicalize::canonicalize_placeholder_indices(&mut d_canonical)?;
68 let d = &d_canonical;
69 crate::validate::validate_placeholder_usage(&d.tree, d.n)?;
70 if let Some(overrides) = &d.tlv.use_site_path_overrides {
71 crate::validate::validate_multipath_consistency(&d.use_site_path, overrides)?;
72 }
73 if matches!(d.tree.tag, crate::tag::Tag::Tr) {
74 if let Body::Tr { tree: Some(t), .. } = &d.tree.body {
75 crate::validate::validate_tap_script_tree(t)?;
76 }
77 }
78
79 let mut w = BitWriter::new();
80 let header = Header {
81 version: Header::WF_REDESIGN_VERSION,
82 divergent_paths: matches!(d.path_decl.paths, PathDeclPaths::Divergent(_)),
83 };
84 header.write(&mut w);
85 d.path_decl.write(&mut w)?;
86 d.use_site_path.write(&mut w)?;
87 let kiw = d.key_index_width();
88 write_node(&mut w, &d.tree, kiw)?;
89 d.tlv.write(&mut w, kiw)?;
90 let total_bits = w.bit_len();
91 Ok((w.into_bytes(), total_bits))
92}
93
94pub fn render_codex32_grouped(s: &str, group_size: usize) -> String {
99 if group_size == 0 {
100 return s.to_string();
101 }
102 let mut out = String::new();
103 for (i, ch) in s.chars().enumerate() {
104 if i > 0 && i % group_size == 0 {
105 out.push('-');
106 }
107 out.push(ch);
108 }
109 out
110}
111
112pub fn encode_md1_string(d: &Descriptor) -> Result<String, Error> {
115 let (bytes, bit_len) = encode_payload(d)?;
116 crate::codex32::wrap_payload(&bytes, bit_len)
117}
118
119#[cfg(test)]
120mod render_tests {
121 use super::*;
122
123 #[test]
124 fn render_groups_at_4() {
125 assert_eq!(render_codex32_grouped("md1qpz9r4cy7", 4), "md1q-pz9r-4cy7");
126 }
127
128 #[test]
129 fn render_zero_group_size_no_grouping() {
130 assert_eq!(render_codex32_grouped("md1qpz9r4cy7", 0), "md1qpz9r4cy7");
131 }
132}
133
134#[cfg(test)]
135mod is_wallet_policy_tests {
136 use super::*;
137 use crate::origin_path::OriginPath;
138 use crate::tag::Tag;
139 use crate::tlv::TlvSection;
140
141 fn wpkh_template_only() -> Descriptor {
142 Descriptor {
143 n: 1,
144 path_decl: PathDecl {
145 n: 1,
146 paths: PathDeclPaths::Shared(OriginPath { components: vec![] }),
147 },
148 use_site_path: UseSitePath::standard_multipath(),
149 tree: Node {
150 tag: Tag::Wpkh,
151 body: Body::KeyArg { index: 0 },
152 },
153 tlv: TlvSection::new_empty(),
154 }
155 }
156
157 #[test]
158 fn is_wallet_policy_returns_false_for_template_only() {
159 let d = wpkh_template_only();
161 assert!(!d.is_wallet_policy());
162 }
163
164 #[test]
165 fn is_wallet_policy_returns_false_for_empty_pubkeys() {
166 let mut d = wpkh_template_only();
170 d.tlv.pubkeys = Some(Vec::new());
171 assert!(!d.is_wallet_policy());
172 }
173
174 #[test]
175 fn is_wallet_policy_returns_true_for_populated_pubkeys() {
176 let mut d = wpkh_template_only();
177 d.tlv.pubkeys = Some(vec![(0u8, [0u8; 65])]);
178 assert!(d.is_wallet_policy());
179 }
180}