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