Skip to main content

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}