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 { leaf_index: usize },
120}
121
122impl TaprootManager {
123    /// Create a new Taproot manager
124    pub fn new(config: TaprootConfig) -> Self {
125        Self {
126            config,
127            secp: Secp256k1::new(),
128        }
129    }
130
131    /// Generate a new Taproot key pair
132    pub fn generate_keypair(&self) -> Result<TaprootKeyPair> {
133        let secret_key = SecretKey::new(&mut OsRng);
134        let public_key = PublicKey::from_secret_key(&self.secp, &secret_key);
135        let (x_only_pubkey, _parity) = public_key.x_only_public_key();
136
137        Ok(TaprootKeyPair {
138            internal_key: x_only_pubkey,
139            secret_key: Some(secret_key),
140        })
141    }
142
143    /// Create a Taproot address from an internal key (key-path only)
144    pub fn create_key_path_address(&self, internal_key: XOnlyPublicKey) -> Result<TaprootAddress> {
145        // For key-path only, we create a simple taproot without script tree
146        let builder = TaprootBuilder::new();
147        let spend_info = builder
148            .finalize(&self.secp, internal_key)
149            .map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
150
151        let _output_key = spend_info.output_key();
152        let address = Address::p2tr(&self.secp, internal_key, None, self.config.network);
153
154        Ok(TaprootAddress {
155            address,
156            spend_info,
157            internal_key,
158        })
159    }
160
161    /// Create a Taproot address with script tree
162    pub fn create_script_path_address(
163        &self,
164        internal_key: XOnlyPublicKey,
165        scripts: Vec<ScriptBuf>,
166    ) -> Result<TaprootAddress> {
167        if !self.config.enable_script_path {
168            return Err(BitcoinError::Validation(
169                "Script path spending is disabled".to_string(),
170            ));
171        }
172
173        if scripts.is_empty() {
174            return Err(BitcoinError::Validation(
175                "At least one script is required".to_string(),
176            ));
177        }
178
179        // Build the script tree
180        let mut builder = TaprootBuilder::new();
181        for script in scripts {
182            builder = builder
183                .add_leaf(0, script)
184                .map_err(|e| BitcoinError::Validation(format!("Failed to add leaf: {}", e)))?;
185        }
186
187        let spend_info = builder
188            .finalize(&self.secp, internal_key)
189            .map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
190
191        let merkle_root = spend_info.merkle_root();
192        let address = Address::p2tr(&self.secp, internal_key, merkle_root, self.config.network);
193
194        Ok(TaprootAddress {
195            address,
196            spend_info,
197            internal_key,
198        })
199    }
200
201    /// Validate a Taproot address
202    pub fn validate_address(&self, address: &str) -> Result<bool> {
203        let addr = Address::from_str(address)
204            .map_err(|e| BitcoinError::InvalidAddress(format!("Invalid address: {}", e)))?
205            .require_network(self.config.network)
206            .map_err(|_| BitcoinError::InvalidAddress("Network mismatch".to_string()))?;
207
208        // Check if the address script_pubkey is P2TR (32 bytes + OP_1)
209        Ok(addr.script_pubkey().is_p2tr())
210    }
211
212    /// Check if an address is a Taproot address
213    pub fn is_taproot_address(&self, address: &Address) -> bool {
214        address.script_pubkey().is_p2tr()
215    }
216
217    /// Extract internal key from address
218    pub fn extract_internal_key(&self, address: &Address) -> Result<XOnlyPublicKey> {
219        if !address.script_pubkey().is_p2tr() {
220            return Err(BitcoinError::InvalidAddress(
221                "Address is not a Taproot address".to_string(),
222            ));
223        }
224
225        // Note: This is a simplified version. In production, you'd need to extract
226        // the actual internal key from the address structure
227        let script_pubkey = address.script_pubkey();
228        if script_pubkey.len() != 34 {
229            return Err(BitcoinError::InvalidAddress(
230                "Invalid Taproot script pubkey length".to_string(),
231            ));
232        }
233
234        // The x-only pubkey is bytes 2-34 in the script pubkey
235        let pubkey_bytes = &script_pubkey.as_bytes()[2..34];
236        XOnlyPublicKey::from_slice(pubkey_bytes)
237            .map_err(|e| BitcoinError::InvalidAddress(format!("Invalid x-only pubkey: {}", e)))
238    }
239}
240
241/// Taproot transaction builder
242pub struct TaprootTxBuilder {
243    #[allow(dead_code)]
244    manager: Arc<TaprootManager>,
245}
246
247impl TaprootTxBuilder {
248    /// Create a new Taproot transaction builder
249    pub fn new(manager: Arc<TaprootManager>) -> Self {
250        Self { manager }
251    }
252
253    /// Create a simple key-path spend
254    pub fn create_key_path_spend(
255        &self,
256        _prev_output: TxOut,
257        _destination: Address,
258        _amount: Amount,
259    ) -> Result<Transaction> {
260        // This would create a transaction spending from a Taproot output via key path
261        // For now, this is a placeholder for the full implementation
262        Err(BitcoinError::Validation(
263            "Key path spending not yet implemented".to_string(),
264        ))
265    }
266
267    /// Create a script-path spend
268    pub fn create_script_path_spend(
269        &self,
270        _prev_output: TxOut,
271        _destination: Address,
272        _amount: Amount,
273        _leaf_index: usize,
274    ) -> Result<Transaction> {
275        // This would create a transaction spending from a Taproot output via script path
276        // For now, this is a placeholder for the full implementation
277        Err(BitcoinError::Validation(
278            "Script path spending not yet implemented".to_string(),
279        ))
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_taproot_config_defaults() {
289        let config = TaprootConfig::default();
290        assert_eq!(config.network, Network::Bitcoin);
291        assert!(config.enable_script_path);
292        assert_eq!(config.max_tree_depth, 128);
293    }
294
295    #[test]
296    fn test_generate_keypair() {
297        let config = TaprootConfig {
298            network: Network::Testnet,
299            ..Default::default()
300        };
301        let manager = TaprootManager::new(config);
302
303        let keypair = manager.generate_keypair().unwrap();
304        assert!(keypair.secret_key.is_some());
305    }
306
307    #[test]
308    fn test_create_key_path_address() {
309        let config = TaprootConfig {
310            network: Network::Testnet,
311            ..Default::default()
312        };
313        let manager = TaprootManager::new(config);
314
315        let keypair = manager.generate_keypair().unwrap();
316        let address = manager
317            .create_key_path_address(keypair.internal_key)
318            .unwrap();
319
320        assert!(manager.is_taproot_address(&address.address));
321    }
322
323    #[test]
324    fn test_create_script_path_address() {
325        let config = TaprootConfig {
326            network: Network::Testnet,
327            ..Default::default()
328        };
329        let manager = TaprootManager::new(config);
330
331        let keypair = manager.generate_keypair().unwrap();
332
333        // Create a simple script (OP_TRUE for testing)
334        let script = ScriptBuf::from_bytes(vec![0x51]); // OP_TRUE
335
336        let address = manager
337            .create_script_path_address(keypair.internal_key, vec![script])
338            .unwrap();
339
340        assert!(manager.is_taproot_address(&address.address));
341    }
342
343    #[test]
344    fn test_validate_taproot_address() {
345        let config = TaprootConfig {
346            network: Network::Testnet,
347            ..Default::default()
348        };
349        let manager = TaprootManager::new(config);
350
351        let keypair = manager.generate_keypair().unwrap();
352        let address = manager
353            .create_key_path_address(keypair.internal_key)
354            .unwrap();
355
356        let is_valid = manager
357            .validate_address(&address.address.to_string())
358            .unwrap();
359        assert!(is_valid);
360    }
361
362    #[test]
363    fn test_script_path_disabled() {
364        let config = TaprootConfig {
365            network: Network::Testnet,
366            enable_script_path: false,
367            ..Default::default()
368        };
369        let manager = TaprootManager::new(config);
370
371        let keypair = manager.generate_keypair().unwrap();
372        let script = ScriptBuf::from_bytes(vec![0x51]);
373
374        let result = manager.create_script_path_address(keypair.internal_key, vec![script]);
375        assert!(result.is_err());
376    }
377}