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}