Skip to main content

kaccy_bitcoin/
taproot.rs

1//! Taproot (BIP 341/342) support
2//!
3//! This module provides support for Taproot addresses and transactions,
4//! enabling enhanced privacy, lower fees, and more flexible smart contracts.
5
6use bitcoin::address::Address;
7use bitcoin::key::Secp256k1;
8use bitcoin::secp256k1::rand::rngs::OsRng;
9use bitcoin::secp256k1::{All, PublicKey, SecretKey, XOnlyPublicKey};
10use bitcoin::taproot::{TapLeafHash, TapNodeHash, TaprootBuilder, TaprootSpendInfo};
11use bitcoin::{Amount, Network, ScriptBuf, Transaction, TxOut};
12use serde::{Deserialize, Serialize};
13use std::str::FromStr;
14use std::sync::Arc;
15
16use crate::error::{BitcoinError, Result};
17
18/// Taproot configuration
19#[derive(Debug, Clone)]
20pub struct TaprootConfig {
21    /// Bitcoin network
22    pub network: Network,
23    /// Enable script path spending
24    pub enable_script_path: bool,
25    /// Maximum script tree depth
26    pub max_tree_depth: u8,
27}
28
29impl Default for TaprootConfig {
30    fn default() -> Self {
31        Self {
32            network: Network::Bitcoin,
33            enable_script_path: true,
34            max_tree_depth: 128,
35        }
36    }
37}
38
39/// Taproot address manager
40///
41/// Manages Taproot addresses and transactions (BIP 341/342).
42///
43/// # Examples
44///
45/// ```
46/// use kaccy_bitcoin::{TaprootManager, TaprootConfig};
47/// use bitcoin::Network;
48///
49/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
50/// let config = TaprootConfig {
51///     network: Network::Testnet,
52///     enable_script_path: true,
53///     max_tree_depth: 128,
54/// };
55///
56/// let manager = TaprootManager::new(config);
57///
58/// // Generate a Taproot key pair
59/// let keypair = manager.generate_keypair()?;
60///
61/// // Create a key-path address (most private and efficient)
62/// let address = manager.create_key_path_address(keypair.internal_key)?;
63/// println!("Taproot address: {}", address.address);
64/// # Ok(())
65/// # }
66/// ```
67pub struct TaprootManager {
68    config: TaprootConfig,
69    secp: Secp256k1<All>,
70}
71
72/// Taproot key pair
73#[derive(Debug, Clone)]
74pub struct TaprootKeyPair {
75    /// Internal key (x-only public key)
76    pub internal_key: XOnlyPublicKey,
77    /// Secret key (for signing)
78    #[allow(dead_code)]
79    secret_key: Option<SecretKey>,
80}
81
82/// Taproot script tree
83#[derive(Debug, Clone)]
84pub struct TaprootScriptTree {
85    /// Root of the script tree
86    pub root: Option<TapNodeHash>,
87    /// Leaves in the tree
88    pub leaves: Vec<TaprootScriptLeaf>,
89}
90
91/// A leaf in the Taproot script tree
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct TaprootScriptLeaf {
94    /// The script for this leaf
95    pub script: ScriptBuf,
96    /// Leaf version (typically 0xc0 for Tapscript)
97    pub version: u8,
98    /// Leaf hash
99    pub leaf_hash: TapLeafHash,
100}
101
102/// Taproot address with spending info
103#[derive(Debug, Clone)]
104pub struct TaprootAddress {
105    /// The Bitcoin address
106    pub address: Address,
107    /// Spending information
108    pub spend_info: TaprootSpendInfo,
109    /// Internal key
110    pub internal_key: XOnlyPublicKey,
111}
112
113/// Taproot spending path
114#[derive(Debug, Clone)]
115pub enum TaprootSpendPath {
116    /// Key path (most private and efficient)
117    KeyPath,
118    /// Script path with specific leaf
119    ScriptPath {
120        /// Index of the script leaf to use
121        leaf_index: usize,
122    },
123}
124
125impl TaprootManager {
126    /// Create a new Taproot manager
127    pub fn new(config: TaprootConfig) -> Self {
128        Self {
129            config,
130            secp: Secp256k1::new(),
131        }
132    }
133
134    /// Generate a new Taproot key pair
135    pub fn generate_keypair(&self) -> Result<TaprootKeyPair> {
136        let secret_key = SecretKey::new(&mut OsRng);
137        let public_key = PublicKey::from_secret_key(&self.secp, &secret_key);
138        let (x_only_pubkey, _parity) = public_key.x_only_public_key();
139
140        Ok(TaprootKeyPair {
141            internal_key: x_only_pubkey,
142            secret_key: Some(secret_key),
143        })
144    }
145
146    /// Create a Taproot address from an internal key (key-path only)
147    pub fn create_key_path_address(&self, internal_key: XOnlyPublicKey) -> Result<TaprootAddress> {
148        // For key-path only, we create a simple taproot without script tree
149        let builder = TaprootBuilder::new();
150        let spend_info = builder
151            .finalize(&self.secp, internal_key)
152            .map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
153
154        let _output_key = spend_info.output_key();
155        let address = Address::p2tr(&self.secp, internal_key, None, self.config.network);
156
157        Ok(TaprootAddress {
158            address,
159            spend_info,
160            internal_key,
161        })
162    }
163
164    /// Create a Taproot address with script tree
165    pub fn create_script_path_address(
166        &self,
167        internal_key: XOnlyPublicKey,
168        scripts: Vec<ScriptBuf>,
169    ) -> Result<TaprootAddress> {
170        if !self.config.enable_script_path {
171            return Err(BitcoinError::Validation(
172                "Script path spending is disabled".to_string(),
173            ));
174        }
175
176        if scripts.is_empty() {
177            return Err(BitcoinError::Validation(
178                "At least one script is required".to_string(),
179            ));
180        }
181
182        // Build the script tree
183        let mut builder = TaprootBuilder::new();
184        for script in scripts {
185            builder = builder
186                .add_leaf(0, script)
187                .map_err(|e| BitcoinError::Validation(format!("Failed to add leaf: {}", e)))?;
188        }
189
190        let spend_info = builder
191            .finalize(&self.secp, internal_key)
192            .map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
193
194        let merkle_root = spend_info.merkle_root();
195        let address = Address::p2tr(&self.secp, internal_key, merkle_root, self.config.network);
196
197        Ok(TaprootAddress {
198            address,
199            spend_info,
200            internal_key,
201        })
202    }
203
204    /// Validate a Taproot address
205    pub fn validate_address(&self, address: &str) -> Result<bool> {
206        let addr = Address::from_str(address)
207            .map_err(|e| BitcoinError::InvalidAddress(format!("Invalid address: {}", e)))?
208            .require_network(self.config.network)
209            .map_err(|_| BitcoinError::InvalidAddress("Network mismatch".to_string()))?;
210
211        // Check if the address script_pubkey is P2TR (32 bytes + OP_1)
212        Ok(addr.script_pubkey().is_p2tr())
213    }
214
215    /// Check if an address is a Taproot address
216    pub fn is_taproot_address(&self, address: &Address) -> bool {
217        address.script_pubkey().is_p2tr()
218    }
219
220    /// Extract internal key from address
221    pub fn extract_internal_key(&self, address: &Address) -> Result<XOnlyPublicKey> {
222        if !address.script_pubkey().is_p2tr() {
223            return Err(BitcoinError::InvalidAddress(
224                "Address is not a Taproot address".to_string(),
225            ));
226        }
227
228        // Note: This is a simplified version. In production, you'd need to extract
229        // the actual internal key from the address structure
230        let script_pubkey = address.script_pubkey();
231        if script_pubkey.len() != 34 {
232            return Err(BitcoinError::InvalidAddress(
233                "Invalid Taproot script pubkey length".to_string(),
234            ));
235        }
236
237        // The x-only pubkey is bytes 2-34 in the script pubkey
238        let pubkey_bytes = &script_pubkey.as_bytes()[2..34];
239        XOnlyPublicKey::from_slice(pubkey_bytes)
240            .map_err(|e| BitcoinError::InvalidAddress(format!("Invalid x-only pubkey: {}", e)))
241    }
242}
243
244/// Taproot transaction builder
245pub struct TaprootTxBuilder {
246    #[allow(dead_code)]
247    manager: Arc<TaprootManager>,
248}
249
250impl TaprootTxBuilder {
251    /// Create a new Taproot transaction builder
252    pub fn new(manager: Arc<TaprootManager>) -> Self {
253        Self { manager }
254    }
255
256    /// Create a simple key-path spend
257    pub fn create_key_path_spend(
258        &self,
259        _prev_output: TxOut,
260        _destination: Address,
261        _amount: Amount,
262    ) -> Result<Transaction> {
263        // This would create a transaction spending from a Taproot output via key path
264        // For now, this is a placeholder for the full implementation
265        Err(BitcoinError::Validation(
266            "Key path spending not yet implemented".to_string(),
267        ))
268    }
269
270    /// Create a script-path spend
271    pub fn create_script_path_spend(
272        &self,
273        _prev_output: TxOut,
274        _destination: Address,
275        _amount: Amount,
276        _leaf_index: usize,
277    ) -> Result<Transaction> {
278        // This would create a transaction spending from a Taproot output via script path
279        // For now, this is a placeholder for the full implementation
280        Err(BitcoinError::Validation(
281            "Script path spending not yet implemented".to_string(),
282        ))
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_taproot_config_defaults() {
292        let config = TaprootConfig::default();
293        assert_eq!(config.network, Network::Bitcoin);
294        assert!(config.enable_script_path);
295        assert_eq!(config.max_tree_depth, 128);
296    }
297
298    #[test]
299    fn test_generate_keypair() {
300        let config = TaprootConfig {
301            network: Network::Testnet,
302            ..Default::default()
303        };
304        let manager = TaprootManager::new(config);
305
306        let keypair = manager.generate_keypair().unwrap();
307        assert!(keypair.secret_key.is_some());
308    }
309
310    #[test]
311    fn test_create_key_path_address() {
312        let config = TaprootConfig {
313            network: Network::Testnet,
314            ..Default::default()
315        };
316        let manager = TaprootManager::new(config);
317
318        let keypair = manager.generate_keypair().unwrap();
319        let address = manager
320            .create_key_path_address(keypair.internal_key)
321            .unwrap();
322
323        assert!(manager.is_taproot_address(&address.address));
324    }
325
326    #[test]
327    fn test_create_script_path_address() {
328        let config = TaprootConfig {
329            network: Network::Testnet,
330            ..Default::default()
331        };
332        let manager = TaprootManager::new(config);
333
334        let keypair = manager.generate_keypair().unwrap();
335
336        // Create a simple script (OP_TRUE for testing)
337        let script = ScriptBuf::from_bytes(vec![0x51]); // OP_TRUE
338
339        let address = manager
340            .create_script_path_address(keypair.internal_key, vec![script])
341            .unwrap();
342
343        assert!(manager.is_taproot_address(&address.address));
344    }
345
346    #[test]
347    fn test_validate_taproot_address() {
348        let config = TaprootConfig {
349            network: Network::Testnet,
350            ..Default::default()
351        };
352        let manager = TaprootManager::new(config);
353
354        let keypair = manager.generate_keypair().unwrap();
355        let address = manager
356            .create_key_path_address(keypair.internal_key)
357            .unwrap();
358
359        let is_valid = manager
360            .validate_address(&address.address.to_string())
361            .unwrap();
362        assert!(is_valid);
363    }
364
365    #[test]
366    fn test_script_path_disabled() {
367        let config = TaprootConfig {
368            network: Network::Testnet,
369            enable_script_path: false,
370            ..Default::default()
371        };
372        let manager = TaprootManager::new(config);
373
374        let keypair = manager.generate_keypair().unwrap();
375        let script = ScriptBuf::from_bytes(vec![0x51]);
376
377        let result = manager.create_script_path_address(keypair.internal_key, vec![script]);
378        assert!(result.is_err());
379    }
380}