md_codec/tag.rs
1//! v0.30 Tag enum per SPEC §3.
2//!
3//! 36 operators in primary 6-bit space (0x00..=0x23). Primary range
4//! 0x24..=0x3E is reserved for future operators per SPEC §3.2's semantic
5//! ranges. Primary value 0x3F is the extension prefix; 4-bit subcodes
6//! 0x00..=0x0F are all reserved in v0.30 (no extension variants allocated).
7//! TLV section tag space is SEPARATE and stays at 5-bit width per the Q13
8//! split — decoder dispatches tag-width by context (bytecode vs TLV).
9
10use crate::bitstream::{BitReader, BitWriter};
11use crate::error::Error;
12
13/// Operator tag identifying a descriptor/Miniscript fragment kind.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Tag {
16 /// `wpkh` — P2WPKH descriptor.
17 Wpkh,
18 /// `tr` — Taproot descriptor.
19 Tr,
20 /// `wsh` — P2WSH descriptor.
21 Wsh,
22 /// `sh` — P2SH descriptor.
23 Sh,
24 /// `pkh` — P2PKH descriptor.
25 Pkh,
26 /// Taproot tree node.
27 TapTree,
28 /// `multi` — k-of-n multisig.
29 Multi,
30 /// `sortedmulti` — sorted-key multisig.
31 SortedMulti,
32 /// `multi_a` — Tapscript multisig with `OP_CHECKSIGADD`.
33 MultiA,
34 /// `sortedmulti_a` — sorted-key Tapscript multisig.
35 SortedMultiA,
36 /// Miniscript `pk_k` — bare public key check.
37 PkK,
38 /// Miniscript `pk_h` — public-key-hash check.
39 PkH,
40 /// Miniscript `c:` wrapper (CHECKSIG).
41 Check,
42 /// Miniscript `v:` wrapper (VERIFY).
43 Verify,
44 /// Miniscript `s:` wrapper (SWAP).
45 Swap,
46 /// Miniscript `a:` wrapper (TOALTSTACK).
47 Alt,
48 /// Miniscript `d:` wrapper (DUPIF).
49 DupIf,
50 /// Miniscript `j:` wrapper (NONZERO).
51 NonZero,
52 /// Miniscript `n:` wrapper (ZERONOTEQUAL).
53 ZeroNotEqual,
54 /// Miniscript `and_v`.
55 AndV,
56 /// Miniscript `and_b`.
57 AndB,
58 /// Miniscript `andor`.
59 AndOr,
60 /// Miniscript `or_b`.
61 OrB,
62 /// Miniscript `or_c`.
63 OrC,
64 /// Miniscript `or_d`.
65 OrD,
66 /// Miniscript `or_i`.
67 OrI,
68 /// Miniscript `thresh`.
69 Thresh,
70 /// Miniscript `after` — absolute timelock.
71 After,
72 /// Miniscript `older` — relative timelock.
73 Older,
74 /// Miniscript `sha256`.
75 Sha256,
76 /// Miniscript `hash160`.
77 Hash160,
78
79 /// Miniscript `hash256` (primary 0x1F in v0.30; promoted from v0.x extension).
80 Hash256,
81 /// Miniscript `ripemd160` (primary 0x20 in v0.30; promoted from v0.x extension).
82 Ripemd160,
83 /// Raw public-key hash variant (primary 0x21 in v0.30; promoted from v0.x extension).
84 RawPkH,
85 /// Miniscript `0` literal (primary 0x22 in v0.30; promoted from v0.x extension).
86 False,
87 /// Miniscript `1` literal (primary 0x23 in v0.30; promoted from v0.x extension).
88 True,
89}
90
91const EXTENSION_PREFIX_6BIT: u8 = 0x3F;
92
93impl Tag {
94 /// Returns `(primary_code, extension_code_opt)`. In v0.30 every allocated
95 /// variant returns `(primary, None)` — the entire 4-bit extension subspace
96 /// (behind primary prefix `0x3F`) is reserved for future operators. The
97 /// `Option<u8>` shape is preserved for forward compatibility.
98 pub(crate) fn codes(&self) -> (u8, Option<u8>) {
99 match self {
100 Tag::Wpkh => (0x00, None),
101 Tag::Tr => (0x01, None),
102 Tag::Wsh => (0x02, None),
103 Tag::Sh => (0x03, None),
104 Tag::Pkh => (0x04, None),
105 Tag::TapTree => (0x05, None),
106 Tag::Multi => (0x06, None),
107 Tag::SortedMulti => (0x07, None),
108 Tag::MultiA => (0x08, None),
109 Tag::SortedMultiA => (0x09, None),
110 Tag::PkK => (0x0A, None),
111 Tag::PkH => (0x0B, None),
112 Tag::Check => (0x0C, None),
113 Tag::Verify => (0x0D, None),
114 Tag::Swap => (0x0E, None),
115 Tag::Alt => (0x0F, None),
116 Tag::DupIf => (0x10, None),
117 Tag::NonZero => (0x11, None),
118 Tag::ZeroNotEqual => (0x12, None),
119 Tag::AndV => (0x13, None),
120 Tag::AndB => (0x14, None),
121 Tag::AndOr => (0x15, None),
122 Tag::OrB => (0x16, None),
123 Tag::OrC => (0x17, None),
124 Tag::OrD => (0x18, None),
125 Tag::OrI => (0x19, None),
126 Tag::Thresh => (0x1A, None),
127 Tag::After => (0x1B, None),
128 Tag::Older => (0x1C, None),
129 Tag::Sha256 => (0x1D, None),
130 Tag::Hash160 => (0x1E, None),
131 Tag::Hash256 => (0x1F, None),
132 Tag::Ripemd160 => (0x20, None),
133 Tag::RawPkH => (0x21, None),
134 Tag::False => (0x22, None),
135 Tag::True => (0x23, None),
136 }
137 }
138
139 /// Encode this tag (6 bits primary, plus 4 more if extension) into `w`.
140 pub fn write(&self, w: &mut BitWriter) {
141 let (primary, ext) = self.codes();
142 w.write_bits(u64::from(primary), 6);
143 if let Some(e) = ext {
144 w.write_bits(u64::from(e), 4);
145 }
146 }
147
148 /// Decode a tag from `r`, consuming 6 bits (or 10 for extension).
149 ///
150 /// Per SPEC v0.30 §3.2 + §11.1: 6-bit primary values 0x24..=0x3E are the
151 /// reserved range and produce `Error::TagOutOfRange { primary }`. Primary
152 /// value 0x3F is the extension prefix; the decoder consumes the following
153 /// 4-bit subcode and returns `Error::TagOutOfRange { primary: 0x3F }`
154 /// because no extension variants are allocated in v0.30 (the subcode is
155 /// consumed but not reported in the error payload).
156 pub fn read(r: &mut BitReader) -> Result<Self, Error> {
157 let primary = r.read_bits(6)? as u8;
158 if primary == EXTENSION_PREFIX_6BIT {
159 // Consume the 4-bit subcode and reject — v0.30 allocates none.
160 let _subcode = r.read_bits(4)?;
161 return Err(Error::TagOutOfRange { primary });
162 }
163 match primary {
164 0x00 => Ok(Tag::Wpkh),
165 0x01 => Ok(Tag::Tr),
166 0x02 => Ok(Tag::Wsh),
167 0x03 => Ok(Tag::Sh),
168 0x04 => Ok(Tag::Pkh),
169 0x05 => Ok(Tag::TapTree),
170 0x06 => Ok(Tag::Multi),
171 0x07 => Ok(Tag::SortedMulti),
172 0x08 => Ok(Tag::MultiA),
173 0x09 => Ok(Tag::SortedMultiA),
174 0x0A => Ok(Tag::PkK),
175 0x0B => Ok(Tag::PkH),
176 0x0C => Ok(Tag::Check),
177 0x0D => Ok(Tag::Verify),
178 0x0E => Ok(Tag::Swap),
179 0x0F => Ok(Tag::Alt),
180 0x10 => Ok(Tag::DupIf),
181 0x11 => Ok(Tag::NonZero),
182 0x12 => Ok(Tag::ZeroNotEqual),
183 0x13 => Ok(Tag::AndV),
184 0x14 => Ok(Tag::AndB),
185 0x15 => Ok(Tag::AndOr),
186 0x16 => Ok(Tag::OrB),
187 0x17 => Ok(Tag::OrC),
188 0x18 => Ok(Tag::OrD),
189 0x19 => Ok(Tag::OrI),
190 0x1A => Ok(Tag::Thresh),
191 0x1B => Ok(Tag::After),
192 0x1C => Ok(Tag::Older),
193 0x1D => Ok(Tag::Sha256),
194 0x1E => Ok(Tag::Hash160),
195 0x1F => Ok(Tag::Hash256),
196 0x20 => Ok(Tag::Ripemd160),
197 0x21 => Ok(Tag::RawPkH),
198 0x22 => Ok(Tag::False),
199 0x23 => Ok(Tag::True),
200 _ => Err(Error::TagOutOfRange { primary }),
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 fn round_trip(t: Tag) {
210 let mut w = BitWriter::new();
211 t.write(&mut w);
212 let bytes = w.into_bytes();
213 let mut r = BitReader::new(&bytes);
214 assert_eq!(Tag::read(&mut r).unwrap(), t);
215 }
216
217 #[test]
218 fn tag_wpkh() {
219 round_trip(Tag::Wpkh);
220 }
221 #[test]
222 fn tag_tr() {
223 round_trip(Tag::Tr);
224 }
225 #[test]
226 fn tag_taptree() {
227 round_trip(Tag::TapTree);
228 }
229 #[test]
230 fn tag_thresh() {
231 round_trip(Tag::Thresh);
232 }
233 #[test]
234 fn tag_hash256() {
235 round_trip(Tag::Hash256);
236 }
237 #[test]
238 fn tag_false() {
239 round_trip(Tag::False);
240 }
241 #[test]
242 fn tag_true() {
243 round_trip(Tag::True);
244 }
245
246 /// SPEC v0.30 §3.2 + §11.1: the entire primary reserved range 0x24..=0x3E
247 /// and the entire extension subspace 0x00..=0x0F (behind prefix 0x3F) are
248 /// rejected with `Error::TagOutOfRange`. The variant's `primary` field
249 /// carries the raw 6-bit value read off the wire — 0x3F for extension-
250 /// subspace failures (the 4-bit subcode is consumed but not reported).
251 #[test]
252 fn tag_reserved_range_rejected() {
253 // Arm 1: primary reserved-range low boundary 0x24
254 let mut w = BitWriter::new();
255 w.write_bits(0x24, 6);
256 let bytes = w.into_bytes();
257 let mut r = BitReader::new(&bytes);
258 assert!(matches!(
259 Tag::read(&mut r),
260 Err(Error::TagOutOfRange { primary: 0x24 })
261 ));
262
263 // Arm 2: primary reserved-range high boundary 0x3E
264 let mut w = BitWriter::new();
265 w.write_bits(0x3E, 6);
266 let bytes = w.into_bytes();
267 let mut r = BitReader::new(&bytes);
268 assert!(matches!(
269 Tag::read(&mut r),
270 Err(Error::TagOutOfRange { primary: 0x3E })
271 ));
272
273 // Arm 3: extension prefix 0x3F + subcode 0x00 (low boundary)
274 let mut w = BitWriter::new();
275 w.write_bits(0x3F, 6);
276 w.write_bits(0x00, 4);
277 let bytes = w.into_bytes();
278 let mut r = BitReader::new(&bytes);
279 assert!(matches!(
280 Tag::read(&mut r),
281 Err(Error::TagOutOfRange { primary: 0x3F })
282 ));
283
284 // Arm 4: extension prefix 0x3F + subcode 0x0F (high boundary)
285 let mut w = BitWriter::new();
286 w.write_bits(0x3F, 6);
287 w.write_bits(0x0F, 4);
288 let bytes = w.into_bytes();
289 let mut r = BitReader::new(&bytes);
290 assert!(matches!(
291 Tag::read(&mut r),
292 Err(Error::TagOutOfRange { primary: 0x3F })
293 ));
294 }
295}