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}