Skip to main content

bitcoin_bosd/
descriptor.rs

1//! # Bitcoin Output Script Descriptor (BOSD)
2//!
3//! This module implements a BOSD parser and validator.
4//!
5//! The main type is [`Descriptor`].
6//! Check this crate's top-level documentation for the
7//! specification and rationale.
8
9use core::fmt;
10
11use std::{
12    fmt::{Display, Formatter},
13    str::FromStr,
14};
15
16use hex::{DisplayHex, FromHex};
17
18#[cfg(feature = "address")]
19use bitcoin::XOnlyPublicKey;
20
21use crate::error::DescriptorError;
22
23/// `OP_RETURN` type tag.
24pub(crate) const OP_RETURN_TYPE_TAG: u8 = 0;
25
26/// Maximum length of `OP_RETURN` payload.
27///
28/// Bitcoin Core's `datacarriersize` default is 100,000 bytes for the entire script.
29/// See: <https://github.com/bitcoin/bitcoin/blob/v30.0/src/policy/policy.cpp#L146>
30///
31/// Bitcoin Core's `MAX_STANDARD_TX_WEIGHT` is 400,000 weight units.
32/// For non-segwit transactions: max size = 400,000 / 4 = 100,000 bytes.
33/// See: <https://github.com/bitcoin/bitcoin/blob/v28.0/src/policy/policy.h#L27>
34///
35/// A valid transaction requires at least one input. The minimum overhead for a
36/// transaction with a single OP_RETURN output spending a bare `OP_TRUE` output is:
37///
38/// - Version: 4 bytes
39/// - Input count (`varint=1`): 1 byte
40/// - Input (prevout + empty scriptSig + sequence): 41 bytes
41/// - Output count (`varint=1`): 1 byte
42/// - Output value: 8 bytes
43/// - ScriptPubKey length (varint, for scripts `> 65_535`): 5 bytes
44/// - `OP_RETURN` + `OP_PUSHDATA4` + 4-byte length: 6 bytes
45/// - Locktime: 4 bytes
46/// - Total overhead: 70 bytes
47///
48/// Therefore: max payload = 100,000 - 70 = 99,930 bytes
49pub const MAX_OP_RETURN_LEN: usize = 99_930;
50
51/// `P2PKH` type tag.
52pub(crate) const P2PKH_TYPE_TAG: u8 = 1;
53
54/// Exact length of P2PKH payload.
55pub const P2PKH_LEN: usize = 20;
56
57/// `P2SH` type tag.
58pub(crate) const P2SH_TYPE_TAG: u8 = 2;
59
60/// Exact length of P2SH payload.
61pub const P2SH_LEN: usize = 20;
62
63/// `P2WPKH`/`P2WSH` type tag.
64pub(crate) const P2WPKH_P2WSH_TYPE_TAG: u8 = 3;
65
66/// Exact length of P2WPKH payload.
67pub const P2WPKH_LEN: usize = 20;
68
69/// Exact length of P2WSH payload.
70pub const P2WSH_LEN: usize = 32;
71
72/// `P2A`/`P2TR` type tag.
73pub(crate) const P2TR_TYPE_TAG: u8 = 4;
74
75/// Exact length of `P2A` payload.
76pub const P2A_LEN: usize = 0;
77
78/// The exact witness program data bytes for P2A (Pay-to-Anchor) per BIP 433.
79///
80/// P2A is defined as the scriptPubKey `OP_1 OP_PUSH2 <0x4e73>` (hex: `51 02 4e 73`).
81/// This constant contains just the 2-byte program data `[0x4e, 0x73]`, which is what
82/// `WitnessProgram::program().as_bytes()` returns for a P2A output.
83pub const P2A_PROGRAM_BYTES: [u8; 2] = [0x4e, 0x73];
84
85/// Exact length of P2TR payload.
86pub const P2TR_LEN: usize = 32;
87
88/// A Bitcoin Output Script Descriptor (BOSD).
89///
90/// This is a compact binary format consisting of
91/// a `type_tag` that represents a ScriptPubKey that can be
92/// relayed by any node in the Bitcoin network,
93/// due to standardness requirements.
94///
95/// See [the Bitcoin developer guide on Transactions](https://developer.bitcoin.org/devguide/transactions.html)
96/// for more information on standardness.
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98#[repr(C)]
99pub struct Descriptor {
100    /// The type of the descriptor.
101    type_tag: DescriptorType,
102
103    /// The actual underlying data.
104    payload: Vec<u8>,
105}
106
107impl Descriptor {
108    /// Constructs a new [`Descriptor`] from a byte slice.
109    ///
110    /// Users are advised to use the `new_*` methods whenever possible.
111    pub fn from_bytes(bytes: &[u8]) -> Result<Self, DescriptorError> {
112        // Extract the type tag (which must exist) and the payload
113        let (&type_tag, payload) = bytes.split_first().ok_or(DescriptorError::MissingTypeTag)?;
114
115        // Validate the payload length against the type
116        match type_tag {
117            // OP_RETURN must be at most 100KB.
118            OP_RETURN_TYPE_TAG => {
119                let payload_len = payload.len();
120                if payload_len > MAX_OP_RETURN_LEN {
121                    Err(DescriptorError::InvalidPayloadLength(payload_len))
122                } else {
123                    Ok(Self {
124                        type_tag: DescriptorType::OpReturn,
125                        payload: payload.to_vec(),
126                    })
127                }
128            }
129            // P2PKH and P2SH must be exactly 20 bytes.
130            P2PKH_TYPE_TAG => {
131                let payload_len = payload.len();
132                if payload_len != P2PKH_LEN {
133                    Err(DescriptorError::InvalidPayloadLength(payload_len))
134                } else {
135                    Ok(Self {
136                        type_tag: DescriptorType::P2pkh,
137                        payload: payload.to_vec(),
138                    })
139                }
140            }
141            P2SH_TYPE_TAG => {
142                let payload_len = payload.len();
143                if payload_len != P2SH_LEN {
144                    Err(DescriptorError::InvalidPayloadLength(payload_len))
145                } else {
146                    Ok(Self {
147                        type_tag: DescriptorType::P2sh,
148                        payload: payload.to_vec(),
149                    })
150                }
151            }
152            // P2WPKH must be exactly 20 bytes, and P2SH must be exactly 32 bytes.
153            P2WPKH_P2WSH_TYPE_TAG => {
154                let payload_len = payload.len();
155                match payload_len {
156                    P2WPKH_LEN => Ok(Self {
157                        type_tag: DescriptorType::P2wpkh,
158                        payload: payload.to_vec(),
159                    }),
160                    P2WSH_LEN => Ok(Self {
161                        type_tag: DescriptorType::P2wsh,
162                        payload: payload.to_vec(),
163                    }),
164                    _ => Err(DescriptorError::InvalidPayloadLength(payload_len)),
165                }
166            }
167            // P2A must be exactly 0 bytes, and P2TR must be exactly 32 bytes.
168            P2TR_TYPE_TAG => {
169                let payload_len = payload.len();
170                match payload_len {
171                    P2A_LEN => Ok(Self {
172                        type_tag: DescriptorType::P2a,
173                        payload: payload.to_vec(),
174                    }),
175                    #[cfg(feature = "address")]
176                    P2TR_LEN => {
177                        // Validate that the payload is a valid x-only public key
178                        if XOnlyPublicKey::from_slice(payload).is_err() {
179                            return Err(DescriptorError::InvalidXOnlyPublicKey);
180                        }
181                        Ok(Self {
182                            type_tag: DescriptorType::P2tr,
183                            payload: payload.to_vec(),
184                        })
185                    }
186                    #[cfg(not(feature = "address"))]
187                    P2TR_LEN => Ok(Self {
188                        type_tag: DescriptorType::P2tr,
189                        payload: payload.to_vec(),
190                    }),
191                    _ => Err(DescriptorError::InvalidPayloadLength(payload_len)),
192                }
193            }
194            _ => Err(DescriptorError::InvalidDescriptorType(type_tag)),
195        }
196    }
197
198    /// Constructs a new [`Descriptor`] from a byte [`Vec`].
199    ///
200    /// Users are advised to use the `new_*` methods whenever possible.
201    pub fn from_vec(bytes: Vec<u8>) -> Result<Self, DescriptorError> {
202        Self::from_bytes(&bytes)
203    }
204
205    /// Constructs a new [`Descriptor`] from an `OP_RETURN` payload.
206    ///
207    /// The payload is expected to be at most 100KB.
208    ///
209    /// # Example
210    ///
211    /// ```
212    /// # use bitcoin_bosd::{Descriptor, DescriptorType};
213    /// let payload = b"hello world";
214    /// let desc = Descriptor::new_op_return(payload).expect("valid payload that is at most 100KB");
215    /// # assert_eq!(desc.type_tag(), DescriptorType::OpReturn);
216    /// # assert_eq!(desc.payload(), b"hello world");
217    /// ```
218    pub fn new_op_return(payload: &[u8]) -> Result<Self, DescriptorError> {
219        let type_tag = DescriptorType::OpReturn;
220        let payload_len = payload.len();
221        if payload_len > MAX_OP_RETURN_LEN {
222            Err(DescriptorError::InvalidPayloadLength(payload_len))
223        } else {
224            Ok(Self {
225                type_tag,
226                payload: payload.to_vec(),
227            })
228        }
229    }
230
231    /// Constructs a new [`Descriptor`] from a P2PKH payload.
232    ///
233    /// The payload is expected to be a valid 20-byte hash.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2PKH_LEN};
239    /// let payload = [0u8; P2PKH_LEN]; // all zeros, don't use in production
240    /// let desc = Descriptor::new_p2pkh(&payload);
241    /// # assert_eq!(desc.type_tag(), DescriptorType::P2pkh);
242    /// # assert_eq!(desc.payload(), [0u8; P2PKH_LEN]);
243    /// ```
244    pub fn new_p2pkh(payload: &[u8; P2PKH_LEN]) -> Self {
245        let type_tag = DescriptorType::P2pkh;
246        Self {
247            type_tag,
248            payload: payload.to_vec(),
249        }
250    }
251
252    /// Constructs a new [`Descriptor`] from a P2SH payload.
253    ///
254    /// The payload is expected to be a valid 20-byte hash.
255    ///
256    /// # Example
257    ///
258    /// ```
259    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2SH_LEN};
260    /// let payload = [0u8; P2SH_LEN]; // all zeros, don't use in production
261    /// let desc = Descriptor::new_p2sh(&payload);
262    /// # assert_eq!(desc.type_tag(), DescriptorType::P2sh);
263    /// # assert_eq!(desc.payload(), [0u8; P2SH_LEN]);
264    /// ```
265    pub fn new_p2sh(payload: &[u8; P2SH_LEN]) -> Self {
266        let type_tag = DescriptorType::P2sh;
267        Self {
268            type_tag,
269            payload: payload.to_vec(),
270        }
271    }
272
273    /// Constructs a new [`Descriptor`] from a P2WPKH payload.
274    ///
275    /// The payload is expected to be a valid 20-byte hash.
276    ///
277    /// # Example
278    ///
279    /// ```
280    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2WPKH_LEN};
281    /// let payload = [0u8; P2WPKH_LEN]; // all zeros, don't use in production
282    /// let desc = Descriptor::new_p2wpkh(&payload);
283    /// # assert_eq!(desc.type_tag(), DescriptorType::P2wpkh);
284    /// # assert_eq!(desc.payload(), [0u8; P2WPKH_LEN]);
285    /// ```
286    pub fn new_p2wpkh(payload: &[u8; P2WPKH_LEN]) -> Self {
287        let type_tag = DescriptorType::P2wpkh;
288        Self {
289            type_tag,
290            payload: payload.to_vec(),
291        }
292    }
293
294    /// Constructs a new [`Descriptor`] from a P2WSH payload.
295    ///
296    /// The payload is expected to be a valid 32-byte hash.
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2WSH_LEN};
302    /// let payload = [0u8; P2WSH_LEN]; // all zeros, don't use in production
303    /// let desc = Descriptor::new_p2wsh(&payload);
304    /// # assert_eq!(desc.type_tag(), DescriptorType::P2wsh);
305    /// # assert_eq!(desc.payload(), [0u8; P2WSH_LEN]);
306    /// ```
307    pub fn new_p2wsh(payload: &[u8; P2WSH_LEN]) -> Self {
308        let type_tag = DescriptorType::P2wsh;
309        Self {
310            type_tag,
311            payload: payload.to_vec(),
312        }
313    }
314
315    /// Constructs a new [`Descriptor`] from an empty P2A payload.
316    ///
317    /// # Example
318    ///
319    /// ```
320    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2A_LEN};
321    /// let payload = [0u8; P2A_LEN]; // empty payload
322    /// let desc = Descriptor::new_p2a(&payload);
323    /// # assert_eq!(desc.type_tag(), DescriptorType::P2a);
324    /// # assert_eq!(desc.payload(), [0u8; P2A_LEN]);
325    /// ```
326    pub fn new_p2a(payload: &[u8; P2A_LEN]) -> Self {
327        let type_tag = DescriptorType::P2a;
328
329        Self {
330            type_tag,
331            payload: payload.to_vec(),
332        }
333    }
334
335    /// Constructs a new [`Descriptor`] from an _unchecked_ P2TR payload.
336    ///
337    /// The payload is expected to be a valid 32-byte X-only public key.
338    /// You _must_ validate this key on your own; this function will not do it for you.
339    ///
340    /// # Example
341    ///
342    /// ```
343    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2TR_LEN};
344    /// let payload = [2u8; P2TR_LEN]; // valid X-only public key, but don't use in production
345    /// let desc = Descriptor::new_p2tr_unchecked(&payload);
346    /// # assert_eq!(desc.type_tag(), DescriptorType::P2tr);
347    /// # assert_eq!(desc.payload(), [2u8; P2TR_LEN]);
348    /// ```
349    pub fn new_p2tr_unchecked(payload: &[u8; P2TR_LEN]) -> Self {
350        let type_tag = DescriptorType::P2tr;
351
352        Self {
353            type_tag,
354            payload: payload.to_vec(),
355        }
356    }
357
358    /// Constructs a new [`Descriptor`] from a P2TR payload.
359    ///
360    /// The payload is expected to be a valid 32-byte X-only public key.
361    /// This function will validate this key for you, and return an error if validation fails.
362    ///
363    /// # Example
364    ///
365    /// ```
366    /// # use bitcoin_bosd::{Descriptor, DescriptorType, descriptor::P2TR_LEN};
367    /// let payload = [2u8; P2TR_LEN]; // valid X-only public key, but don't use in production
368    /// let desc = Descriptor::new_p2tr(&payload).expect("valid X-only public key");
369    /// # assert_eq!(desc.type_tag(), DescriptorType::P2tr);
370    /// # assert_eq!(desc.payload(), [2u8; P2TR_LEN]);
371    /// ```
372    #[cfg(feature = "address")]
373    pub fn new_p2tr(payload: &[u8; P2TR_LEN]) -> Result<Self, DescriptorError> {
374        let type_tag = DescriptorType::P2tr;
375
376        if XOnlyPublicKey::from_slice(payload).is_err() {
377            Err(DescriptorError::InvalidXOnlyPublicKey)
378        } else {
379            Ok(Self {
380                type_tag,
381                payload: payload.to_vec(),
382            })
383        }
384    }
385
386    /// Returns the bytes representation of the descriptor.
387    ///
388    /// That is:
389    ///
390    /// - 1-byte type tag.
391    /// - arbitrary-sized payload.
392    pub fn to_bytes(&self) -> Vec<u8> {
393        let mut bytes = Vec::with_capacity(1 + self.payload.len());
394        bytes.push(self.type_tag.to_u8());
395        bytes.extend_from_slice(&self.payload);
396        bytes
397    }
398
399    /// Generates fixed bytes of payload of length specified by the generic parameter.
400    ///
401    /// # Notes
402    ///
403    /// - This method is intended for internal use and relies on the caller
404    ///   ensuring that the payload's length matches the size `B`.
405    pub(crate) fn to_fixed_payload_bytes<const B: usize>(&self) -> [u8; B] {
406        debug_assert_eq!(self.payload().len(), B);
407        let mut bytes = [0u8; B];
408        bytes[..].copy_from_slice(self.payload());
409        bytes
410    }
411
412    /// Returns the type tag of the descriptor.
413    pub fn type_tag(&self) -> DescriptorType {
414        self.type_tag
415    }
416
417    /// Returns the payload of the descriptor.
418    ///
419    /// # Warning
420    ///
421    /// It is not advisable to use this method.
422    /// Instead, try to parse it either as a Bitcoin address
423    /// by using [`Descriptor::to_address`] in the case of an address,
424    /// or as a Bitcoin script by using [`Descriptor::to_script`] in
425    /// the case of an `OP_RETURN` payload.
426    pub fn payload(&self) -> &[u8] {
427        self.payload.as_slice()
428    }
429}
430
431impl Display for Descriptor {
432    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
433        let type_tag = self.type_tag().to_u8();
434        write!(f, "{}{}", &[type_tag].as_hex(), self.payload.as_hex())
435    }
436}
437
438impl FromStr for Descriptor {
439    type Err = DescriptorError;
440
441    fn from_str(s: &str) -> Result<Self, Self::Err> {
442        let bytes = Vec::from_hex(s)?;
443        Self::from_bytes(&bytes)
444    }
445}
446
447/// The type tag of a [`Descriptor`].
448///
449/// This is the first byte of the payload.
450#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
451#[non_exhaustive] // Might need more in the future.
452pub enum DescriptorType {
453    /// `OP_RETURN` payload.
454    OpReturn,
455
456    /// P2PKH hash.
457    ///
458    /// It is a 20-byte hash of a public key,
459    /// that is first hashed with SHA-256,
460    /// followed by RIPEMD-160.
461    P2pkh,
462
463    /// P2SH hash.
464    ///
465    /// It is a 20-byte hash of a custom locking script,
466    /// that is first hashed with SHA-256,
467    /// followed by RIPEMD-160.
468    P2sh,
469
470    /// P2WPKH hash.
471    ///
472    /// It is a 20-byte hash of a public key,
473    /// that is first hashed with SHA-256,
474    /// followed by RIPEMD-160.
475    P2wpkh,
476
477    /// P2WSH hash.
478    ///
479    /// It is a 32-byte hash of a custom locking script
480    /// hashed with SHA-256.
481    P2wsh,
482
483    ///  P2A.
484    ///
485    /// An anchor's descriptor has no payload.
486    P2a,
487
488    /// P2TR X-only public key.
489    ///
490    /// It is a 32-byte public key.
491    /// The key might be tweaked by a Merkle root hash
492    /// that represents the underlying taptree of script
493    /// spending conditions.
494    P2tr,
495}
496
497impl DescriptorType {
498    /// Returns the type tag as a byte.
499    pub fn to_u8(self) -> u8 {
500        match self {
501            DescriptorType::OpReturn => 0,
502            DescriptorType::P2pkh => 1,
503            DescriptorType::P2sh => 2,
504            DescriptorType::P2wpkh => 3,
505            DescriptorType::P2wsh => 3,
506            DescriptorType::P2a => 4,
507            DescriptorType::P2tr => 4,
508        }
509    }
510}
511
512impl Display for DescriptorType {
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514        match self {
515            DescriptorType::OpReturn => write!(f, "OP_RETURN"),
516            DescriptorType::P2pkh => write!(f, "P2PKH"),
517            DescriptorType::P2sh => write!(f, "P2SH"),
518            DescriptorType::P2wpkh => write!(f, "P2WPKH"),
519            DescriptorType::P2wsh => write!(f, "P2WSH"),
520            DescriptorType::P2a => write!(f, "P2A"),
521            DescriptorType::P2tr => write!(f, "P2TR"),
522        }
523    }
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529
530    #[cfg(test)]
531    mod proptest_tests {
532        use super::*;
533        use proptest::prelude::*;
534
535        proptest! {
536            /// Test that any valid `OP_RETURN` payload (0-100KB) roundtrips correctly.
537            #[test]
538            fn op_return_roundtrip_property(data in prop::collection::vec(any::<u8>(), 0..=MAX_OP_RETURN_LEN)) {
539                if data.len() <= MAX_OP_RETURN_LEN {
540                    let mut bytes = vec![0u8; data.len() + 1];
541                    bytes[0] = 0; // OP_RETURN type tag
542                    bytes[1..].copy_from_slice(&data);
543
544                    let descriptor = Descriptor::from_bytes(&bytes).expect("valid OP_RETURN should parse");
545                    assert_eq!(descriptor.type_tag(), DescriptorType::OpReturn);
546                    assert_eq!(descriptor.payload(), &data);
547                    assert_eq!(&descriptor.to_bytes(), &bytes);
548                }
549            }
550
551            /// Test that `OP_RETURN` payloads larger than 100KB are rejected.
552            #[test]
553            fn op_return_invalid_size_property(data in prop::collection::vec(any::<u8>(), (MAX_OP_RETURN_LEN + 1)..=(MAX_OP_RETURN_LEN * 2))) {
554                let mut bytes = vec![0u8; data.len() + 1];
555                bytes[0] = 0; // OP_RETURN type tag
556                bytes[1..].copy_from_slice(&data);
557
558                assert!(Descriptor::from_bytes(&bytes).is_err(),
559                    "OP_RETURN payload of {} bytes should be rejected", data.len());
560            }
561
562            /// Test that exactly 100KB `OP_RETURN` payloads are accepted.
563            #[test]
564            fn op_return_max_size_property(data in prop::collection::vec(any::<u8>(), MAX_OP_RETURN_LEN..=MAX_OP_RETURN_LEN)) {
565                let mut bytes = vec![0u8; data.len() + 1];
566                bytes[0] = 0; // OP_RETURN type tag
567                bytes[1..].copy_from_slice(&data);
568
569                let descriptor = Descriptor::from_bytes(&bytes).expect("100KB OP_RETURN should be valid");
570                assert_eq!(descriptor.type_tag(), DescriptorType::OpReturn);
571                assert_eq!(descriptor.payload(), &data);
572                assert_eq!(&descriptor.to_bytes(), &bytes);
573            }
574
575            /// Test that any valid descriptor roundtrips correctly.
576            #[test]
577            fn descriptor_roundtrip_property(data in prop::collection::vec(any::<u8>(), 1..=(MAX_OP_RETURN_LEN + 1))) {
578                if let Ok(descriptor) = Descriptor::from_bytes(&data) {
579                    assert_eq!(&descriptor.to_bytes(), &data);
580                }
581            }
582        }
583    }
584
585    #[test]
586    fn descriptor_from_bytes() {
587        let bytes = [0, 1, 2, 3, 4, 5];
588        let descriptor = Descriptor::from_bytes(&bytes).unwrap();
589        assert_eq!(descriptor.type_tag(), DescriptorType::OpReturn);
590        assert_eq!(descriptor.payload(), &[1, 2, 3, 4, 5]);
591    }
592
593    #[test]
594    fn descriptor_from_bytes_invalid() {
595        // Empty byte slice
596        let bytes = [];
597        assert!(Descriptor::from_bytes(&bytes).is_err());
598
599        // Only tag type byte
600        for type_tag in 0..=u8::MAX {
601            let bytes = [type_tag];
602
603            // An empty payload is currently invalid for all types except `OP_RETURN` and `P2TR` with an empty payload.
604            match type_tag {
605                OP_RETURN_TYPE_TAG => assert!(Descriptor::from_bytes(&bytes).is_ok()),
606                P2TR_TYPE_TAG => assert!(Descriptor::from_bytes(&bytes).is_ok()),
607                _ => assert!(Descriptor::from_bytes(&bytes).is_err()),
608            }
609        }
610
611        // Invalid type tag
612        let bytes = [5, 1, 2, 3, 4, 5, 6];
613        assert!(Descriptor::from_bytes(&bytes).is_err());
614
615        // Invalid payload length
616        // OP_RETURN with 100001 bytes (MAX_OP_RETURN_LEN + 1)
617        let mut bytes = vec![0; MAX_OP_RETURN_LEN + 2]; // 1 byte type tag + (MAX_OP_RETURN_LEN + 1) bytes payload
618        bytes[0] = 0; // OP_RETURN type tag
619        assert!(Descriptor::from_bytes(&bytes).is_err());
620
621        // P2PKH with 19 bytes
622        let bytes = [1; 20];
623        assert!(Descriptor::from_bytes(&bytes).is_err());
624
625        // P2TR with 33 bytes
626        let bytes = [4; 34];
627        assert!(Descriptor::from_bytes(&bytes).is_err());
628    }
629
630    #[test]
631    fn descriptor_to_bytes() {
632        let original: &[u8; 20] = &[
633            0, 99, 104, 97, 114, 108, 101, 121, 32, 108, 111, 118, 101, 115, 32, 104, 101, 105,
634            100, 105,
635        ];
636        let desc = Descriptor::from_str("00636861726c6579206c6f766573206865696469").unwrap();
637        let bytes = desc.to_bytes();
638        assert_eq!(bytes, original);
639    }
640
641    #[test]
642    fn descriptor_type() {
643        assert_eq!(DescriptorType::OpReturn.to_u8(), 0);
644        assert_eq!(DescriptorType::P2pkh.to_u8(), 1);
645        assert_eq!(DescriptorType::P2sh.to_u8(), 2);
646        assert_eq!(DescriptorType::P2wpkh.to_u8(), 3);
647        assert_eq!(DescriptorType::P2wsh.to_u8(), 3);
648        assert_eq!(DescriptorType::P2a.to_u8(), 4);
649        assert_eq!(DescriptorType::P2tr.to_u8(), 4);
650    }
651
652    #[test]
653    fn from_str() {
654        // OP_RETURN in hex string replacing the 6a (`OP_RETURN`)
655        // for a 0x00 (type_tag) byte for `OP_RETURN`.
656        // Source: https://bitcoin.stackexchange.com/a/29555
657        //         and transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
658        let s = "00636861726c6579206c6f766573206865696469";
659        let desc = Descriptor::from_str(s).unwrap();
660        assert_eq!(desc.type_tag(), DescriptorType::OpReturn);
661        assert_eq!(desc.payload(), b"charley loves heidi");
662
663        // P2PKH
664        // Using 0x01 (type_tag) and a 20-byte hash
665        // Source: transaction 8bae12b5f4c088d940733dcd1455efc6a3a69cf9340e17a981286d3778615684
666        // Corresponds to address `1HnhWpkMHMjgt167kvgcPyurMmsCQ2WPgg`
667        let s = "01b8268ce4d481413c4e848ff353cd16104291c45b";
668        let desc = Descriptor::from_str(s).unwrap();
669        assert_eq!(desc.type_tag(), DescriptorType::P2pkh);
670        assert_eq!(
671            desc.payload(),
672            Vec::from_hex("b8268ce4d481413c4e848ff353cd16104291c45b").unwrap()
673        );
674
675        // P2SH
676        // Using 0x02 (type_tag) and a 20-byte hash
677        // Source: transaction a0f1aaa2fb4582c89e0511df0374a5a2833bf95f7314f4a51b55b7b71e90ce0f
678        // Corresponds to address `3CK4fEwbMP7heJarmU4eqA3sMbVJyEnU3V`
679        let s = "02748284390f9e263a4b766a75d0633c50426eb875";
680        let desc = Descriptor::from_str(s).unwrap();
681        assert_eq!(desc.type_tag(), DescriptorType::P2sh);
682        assert_eq!(
683            desc.payload(),
684            Vec::from_hex("748284390f9e263a4b766a75d0633c50426eb875").unwrap()
685        );
686
687        // P2WPKH
688        // Using 0x03 (type_tag) and a 20-byte hash
689        // Source: transaction 7c53ba0f1fc65f021749cac6a9c163e499fcb2e539b08c040802be55c33d32fe
690        // Corresponds to address `bc1qvugyzunmnq5y8alrmdrxnsh4gts9p9hmvhyd40`
691        let s = "03671041727b982843f7e3db4669c2f542e05096fb";
692        let desc = Descriptor::from_str(s).unwrap();
693        assert_eq!(desc.type_tag(), DescriptorType::P2wpkh);
694        assert_eq!(
695            desc.payload(),
696            Vec::from_hex("671041727b982843f7e3db4669c2f542e05096fb").unwrap()
697        );
698
699        // P2WSH
700        // Using 0x03 (type_tag) and a 32-byte hash
701        // Source: transaction fbf3517516ebdf03358a9ef8eb3569f96ac561c162524e37e9088eb13b228849
702        // Corresponds to address `bc1qvhu3557twysq2ldn6dut6rmaj3qk04p60h9l79wk4lzgy0ca8mfsnffz65`
703        let s = "0365f91a53cb7120057db3d378bd0f7d944167d43a7dcbff15d6afc4823f1d3ed3";
704        let desc = Descriptor::from_str(s).unwrap();
705        assert_eq!(desc.type_tag(), DescriptorType::P2wsh);
706        assert_eq!(
707            desc.payload(),
708            Vec::from_hex("65f91a53cb7120057db3d378bd0f7d944167d43a7dcbff15d6afc4823f1d3ed3")
709                .unwrap()
710        );
711
712        // P2A
713        // Using 0x04 (type_tag) and a 0-byte payload.
714        // Source: transaction c054743f0f3ecfac2cf08c40c7dd36fcb38928cf8e07d179693ca2692d041848
715        // Corresponds to address `bc1pfeesrawgf`
716        let s = "04";
717        let desc = Descriptor::from_str(s).unwrap();
718        assert_eq!(desc.type_tag(), DescriptorType::P2a);
719        assert_eq!(desc.payload(), Vec::from_hex("").unwrap());
720
721        // P2TR
722        // Using 0x04 (type_tag) and a 32-byte hash
723        // Source: transaction a7115c7267dbb4aab62b37818d431b784fe731f4d2f9fa0939a9980d581690ec
724        // Corresponds to address `bc1ppuxgmd6n4j73wdp688p08a8rte97dkn5n70r2ym6kgsw0v3c5ensrytduf`
725        let s = "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667";
726        let desc = Descriptor::from_str(s).unwrap();
727        assert_eq!(desc.type_tag(), DescriptorType::P2tr);
728        assert_eq!(
729            desc.payload(),
730            Vec::from_hex("0f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667")
731                .unwrap()
732        );
733    }
734
735    #[test]
736    fn to_string() {
737        let original = "00636861726c6579206c6f766573206865696469";
738        let desc = Descriptor::from_bytes(&[
739            0, 99, 104, 97, 114, 108, 101, 121, 32, 108, 111, 118, 101, 115, 32, 104, 101, 105,
740            100, 105,
741        ])
742        .unwrap();
743        let s = desc.to_string();
744        assert_eq!(s, original);
745    }
746
747    #[test]
748    fn invalid_from_str() {
749        // Invalid type tag
750        let s = "050000000000000000000000000000000000000000000000000000000000000000";
751        assert!(Descriptor::from_str(s).is_err());
752
753        // Invalid payload length
754        // OP_RETURN with 100001 bytes (create a hex string with (MAX_OP_RETURN_LEN + 1)*2 hex chars)
755        let s = "00".to_string() + &"00".repeat(MAX_OP_RETURN_LEN + 1);
756        assert!(Descriptor::from_str(&s).is_err());
757
758        // P2PKH with 19 bytes
759        let s = "0100000000000000000000000000000000000000";
760        assert!(Descriptor::from_str(s).is_err());
761
762        // P2A with 2 bytes
763        let s = "0400";
764        assert!(Descriptor::from_str(s).is_err());
765
766        // P2TR with 33 bytes
767        let s = "04000000000000000000000000000000000000000000000000000000000000000000";
768        assert!(Descriptor::from_str(s).is_err());
769    }
770
771    #[test]
772    fn test_p2a_fixed_bytes() {
773        let desc = Descriptor::from_str("04").unwrap();
774        let bytes = desc.to_fixed_payload_bytes::<P2A_LEN>();
775        assert_eq!(bytes.len(), P2A_LEN);
776    }
777
778    #[test]
779    fn test_p2tr_fixed_bytes() {
780        let desc = Descriptor::from_str(
781            "040f0c8db753acbd17343a39c2f3f4e35e4be6da749f9e35137ab220e7b238a667",
782        )
783        .unwrap();
784        let bytes = desc.to_fixed_payload_bytes::<P2TR_LEN>();
785        assert_eq!(bytes.len(), P2TR_LEN);
786    }
787
788    #[test]
789    fn test_p2pkh_fixed_bytes() {
790        let desc = Descriptor::from_str("01b8268ce4d481413c4e848ff353cd16104291c45b").unwrap();
791        let bytes = desc.to_fixed_payload_bytes::<P2PKH_LEN>();
792        assert_eq!(bytes.len(), P2PKH_LEN);
793    }
794
795    #[cfg(feature = "address")]
796    #[test]
797    fn invalid_new_p2tr() {
798        let invalid_payload = [0; P2TR_LEN];
799        let result = Descriptor::new_p2tr(&invalid_payload);
800        assert!(result.is_err());
801        assert_eq!(
802            result.err().unwrap(),
803            DescriptorError::InvalidXOnlyPublicKey
804        );
805    }
806
807    /// Verify that MAX_OP_RETURN_LEN is correctly calculated to fit within
808    /// Bitcoin Core's MAX_STANDARD_TX_WEIGHT for a minimal valid transaction.
809    #[cfg(feature = "address")]
810    #[test]
811    fn max_op_return_fits_in_standard_tx() {
812        use bitcoin::{
813            absolute::LockTime, blockdata::script::PushBytesBuf, hashes::Hash,
814            transaction::Version, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
815            Txid, Witness,
816        };
817
818        // Create a minimal valid transaction:
819        // - One input spending a bare OP_TRUE output (empty scriptSig)
820        // - One OP_RETURN output with MAX_OP_RETURN_LEN bytes
821        let tx = Transaction {
822            version: Version::ONE,
823            lock_time: LockTime::ZERO,
824            input: vec![TxIn {
825                previous_output: OutPoint {
826                    txid: Txid::all_zeros(),
827                    vout: 0,
828                },
829                script_sig: ScriptBuf::new(), // Empty scriptSig (spending bare OP_TRUE)
830                sequence: Sequence::MAX,
831                witness: Witness::new(),
832            }],
833            output: vec![TxOut {
834                value: Amount::ZERO,
835                script_pubkey: ScriptBuf::new_op_return(
836                    PushBytesBuf::try_from(vec![0u8; MAX_OP_RETURN_LEN]).unwrap(),
837                ),
838            }],
839        };
840
841        // Verify the transaction weight is within the standard limit
842        assert!(
843            tx.weight() <= Transaction::MAX_STANDARD_WEIGHT,
844            "Transaction with MAX_OP_RETURN_LEN ({}) bytes has weight {} which exceeds MAX_STANDARD_WEIGHT ({})",
845            MAX_OP_RETURN_LEN,
846            tx.weight(),
847            Transaction::MAX_STANDARD_WEIGHT
848        );
849
850        // Verify that one more byte would exceed the limit
851        let tx_exceeded = Transaction {
852            version: Version::ONE,
853            lock_time: LockTime::ZERO,
854            input: vec![TxIn {
855                previous_output: OutPoint {
856                    txid: Txid::all_zeros(),
857                    vout: 0,
858                },
859                script_sig: ScriptBuf::new(),
860                sequence: Sequence::MAX,
861                witness: Witness::new(),
862            }],
863            output: vec![TxOut {
864                value: Amount::ZERO,
865                script_pubkey: ScriptBuf::new_op_return(
866                    PushBytesBuf::try_from(vec![0u8; MAX_OP_RETURN_LEN + 1]).unwrap(),
867                ),
868            }],
869        };
870
871        assert!(
872            tx_exceeded.weight() > Transaction::MAX_STANDARD_WEIGHT,
873            "Transaction with MAX_OP_RETURN_LEN + 1 ({}) bytes should exceed MAX_STANDARD_WEIGHT",
874            MAX_OP_RETURN_LEN + 1
875        );
876    }
877}