rustywallet_descriptor/
lib.rs

1//! # rustywallet-descriptor
2//!
3//! Output descriptors (BIP380-386) for Bitcoin wallet development.
4//!
5//! This crate provides functionality to:
6//! - Parse output descriptor strings
7//! - Derive addresses from descriptors
8//! - Generate script pubkeys
9//! - Support ranged descriptors with wildcards
10//!
11//! ## Supported Descriptors
12//!
13//! | Type | Description | Example |
14//! |------|-------------|---------|
15//! | `pk()` | Pay to pubkey | `pk(KEY)` |
16//! | `pkh()` | Pay to pubkey hash (P2PKH) | `pkh(KEY)` |
17//! | `wpkh()` | Pay to witness pubkey hash (P2WPKH) | `wpkh(KEY)` |
18//! | `sh()` | Pay to script hash (P2SH) | `sh(wpkh(KEY))` |
19//! | `wsh()` | Pay to witness script hash (P2WSH) | `wsh(multi(2,KEY,KEY))` |
20//! | `tr()` | Pay to Taproot (P2TR) | `tr(KEY)` |
21//! | `multi()` | k-of-n multisig | `multi(2,KEY,KEY,KEY)` |
22//! | `sortedmulti()` | Sorted k-of-n multisig | `sortedmulti(2,KEY,KEY,KEY)` |
23//!
24//! ## Example
25//!
26//! ```rust
27//! use rustywallet_descriptor::{Descriptor, derive_address};
28//! use rustywallet_address::Network;
29//!
30//! // Parse a descriptor
31//! let desc = Descriptor::parse("wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)").unwrap();
32//!
33//! // Derive an address
34//! let address = derive_address(&desc, Network::BitcoinMainnet, 0).unwrap();
35//! println!("Address: {}", address);
36//! ```
37//!
38//! ## Ranged Descriptors
39//!
40//! ```rust
41//! use rustywallet_descriptor::{Descriptor, derive_addresses};
42//! use rustywallet_address::Network;
43//!
44//! // Parse a ranged descriptor with wildcard
45//! let desc = Descriptor::parse(
46//!     "wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/*)"
47//! ).unwrap();
48//!
49//! // Derive multiple addresses
50//! let addresses = derive_addresses(&desc, Network::BitcoinMainnet, 0, 10).unwrap();
51//! for (i, addr) in addresses.iter().enumerate() {
52//!     println!("Address {}: {}", i, addr);
53//! }
54//! ```
55//!
56//! ## Checksum
57//!
58//! Descriptors can include a checksum for error detection:
59//!
60//! ```rust
61//! use rustywallet_descriptor::{Descriptor, add_checksum};
62//!
63//! let desc = "wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
64//! let with_checksum = add_checksum(desc);
65//! println!("With checksum: {}", with_checksum);
66//!
67//! // Parse validates checksum automatically
68//! let parsed = Descriptor::parse(&with_checksum).unwrap();
69//! ```
70
71pub mod address;
72pub mod checksum;
73pub mod descriptor;
74pub mod error;
75pub mod key;
76pub mod script;
77
78// Re-exports
79pub use address::{derive_address, derive_addresses};
80pub use checksum::{add_checksum, compute_checksum, strip_checksum, verify_checksum};
81pub use descriptor::Descriptor;
82pub use error::DescriptorError;
83pub use key::{parse_key, DescriptorKey, KeyOrigin, Wildcard};
84pub use script::{generate_script_pubkey, ScriptPubkey, ScriptType};
85
86/// Prelude module for convenient imports
87pub mod prelude {
88    pub use crate::address::{derive_address, derive_addresses};
89    pub use crate::checksum::add_checksum;
90    pub use crate::descriptor::Descriptor;
91    pub use crate::error::DescriptorError;
92    pub use crate::key::{DescriptorKey, Wildcard};
93    pub use crate::script::{ScriptPubkey, ScriptType};
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use rustywallet_address::Network;
100
101    #[test]
102    fn test_basic_workflow() {
103        // Parse descriptor
104        let desc = Descriptor::parse(
105            "wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)",
106        )
107        .unwrap();
108
109        // Generate script
110        let script = generate_script_pubkey(&desc, 0).unwrap();
111        assert_eq!(script.script_type(), ScriptType::P2wpkh);
112
113        // Derive address
114        let address = derive_address(&desc, Network::BitcoinMainnet, 0).unwrap();
115        assert!(address.starts_with("bc1q"));
116    }
117
118    #[test]
119    fn test_checksum_workflow() {
120        let desc = "pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)";
121
122        // Add checksum
123        let with_checksum = add_checksum(desc);
124        assert!(with_checksum.contains('#'));
125
126        // Parse with checksum
127        let parsed = Descriptor::parse(&with_checksum).unwrap();
128        assert_eq!(parsed.descriptor_type(), "pkh");
129    }
130
131    #[test]
132    fn test_nested_descriptor() {
133        let desc = Descriptor::parse(
134            "sh(wpkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5))",
135        )
136        .unwrap();
137
138        assert!(desc.is_segwit());
139        assert_eq!(desc.descriptor_type(), "sh");
140
141        let address = derive_address(&desc, Network::BitcoinMainnet, 0).unwrap();
142        assert!(address.starts_with('3'));
143    }
144}