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