use bitcoin::address::Address;
use bitcoin::key::Secp256k1;
use bitcoin::secp256k1::rand::rngs::OsRng;
use bitcoin::secp256k1::{All, PublicKey, SecretKey, XOnlyPublicKey};
use bitcoin::taproot::{TapLeafHash, TapNodeHash, TaprootBuilder, TaprootSpendInfo};
use bitcoin::{Amount, Network, ScriptBuf, Transaction, TxOut};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;
use crate::error::{BitcoinError, Result};
#[derive(Debug, Clone)]
pub struct TaprootConfig {
pub network: Network,
pub enable_script_path: bool,
pub max_tree_depth: u8,
}
impl Default for TaprootConfig {
fn default() -> Self {
Self {
network: Network::Bitcoin,
enable_script_path: true,
max_tree_depth: 128,
}
}
}
pub struct TaprootManager {
config: TaprootConfig,
secp: Secp256k1<All>,
}
#[derive(Debug, Clone)]
pub struct TaprootKeyPair {
pub internal_key: XOnlyPublicKey,
#[allow(dead_code)]
secret_key: Option<SecretKey>,
}
#[derive(Debug, Clone)]
pub struct TaprootScriptTree {
pub root: Option<TapNodeHash>,
pub leaves: Vec<TaprootScriptLeaf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaprootScriptLeaf {
pub script: ScriptBuf,
pub version: u8,
pub leaf_hash: TapLeafHash,
}
#[derive(Debug, Clone)]
pub struct TaprootAddress {
pub address: Address,
pub spend_info: TaprootSpendInfo,
pub internal_key: XOnlyPublicKey,
}
#[derive(Debug, Clone)]
pub enum TaprootSpendPath {
KeyPath,
ScriptPath {
leaf_index: usize,
},
}
impl TaprootManager {
pub fn new(config: TaprootConfig) -> Self {
Self {
config,
secp: Secp256k1::new(),
}
}
pub fn generate_keypair(&self) -> Result<TaprootKeyPair> {
let secret_key = SecretKey::new(&mut OsRng);
let public_key = PublicKey::from_secret_key(&self.secp, &secret_key);
let (x_only_pubkey, _parity) = public_key.x_only_public_key();
Ok(TaprootKeyPair {
internal_key: x_only_pubkey,
secret_key: Some(secret_key),
})
}
pub fn create_key_path_address(&self, internal_key: XOnlyPublicKey) -> Result<TaprootAddress> {
let builder = TaprootBuilder::new();
let spend_info = builder
.finalize(&self.secp, internal_key)
.map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
let _output_key = spend_info.output_key();
let address = Address::p2tr(&self.secp, internal_key, None, self.config.network);
Ok(TaprootAddress {
address,
spend_info,
internal_key,
})
}
pub fn create_script_path_address(
&self,
internal_key: XOnlyPublicKey,
scripts: Vec<ScriptBuf>,
) -> Result<TaprootAddress> {
if !self.config.enable_script_path {
return Err(BitcoinError::Validation(
"Script path spending is disabled".to_string(),
));
}
if scripts.is_empty() {
return Err(BitcoinError::Validation(
"At least one script is required".to_string(),
));
}
let mut builder = TaprootBuilder::new();
for script in scripts {
builder = builder
.add_leaf(0, script)
.map_err(|e| BitcoinError::Validation(format!("Failed to add leaf: {}", e)))?;
}
let spend_info = builder
.finalize(&self.secp, internal_key)
.map_err(|_| BitcoinError::Validation("Taproot finalization failed".to_string()))?;
let merkle_root = spend_info.merkle_root();
let address = Address::p2tr(&self.secp, internal_key, merkle_root, self.config.network);
Ok(TaprootAddress {
address,
spend_info,
internal_key,
})
}
pub fn validate_address(&self, address: &str) -> Result<bool> {
let addr = Address::from_str(address)
.map_err(|e| BitcoinError::InvalidAddress(format!("Invalid address: {}", e)))?
.require_network(self.config.network)
.map_err(|_| BitcoinError::InvalidAddress("Network mismatch".to_string()))?;
Ok(addr.script_pubkey().is_p2tr())
}
pub fn is_taproot_address(&self, address: &Address) -> bool {
address.script_pubkey().is_p2tr()
}
pub fn extract_internal_key(&self, address: &Address) -> Result<XOnlyPublicKey> {
if !address.script_pubkey().is_p2tr() {
return Err(BitcoinError::InvalidAddress(
"Address is not a Taproot address".to_string(),
));
}
let script_pubkey = address.script_pubkey();
if script_pubkey.len() != 34 {
return Err(BitcoinError::InvalidAddress(
"Invalid Taproot script pubkey length".to_string(),
));
}
let pubkey_bytes = &script_pubkey.as_bytes()[2..34];
XOnlyPublicKey::from_slice(pubkey_bytes)
.map_err(|e| BitcoinError::InvalidAddress(format!("Invalid x-only pubkey: {}", e)))
}
}
pub struct TaprootTxBuilder {
#[allow(dead_code)]
manager: Arc<TaprootManager>,
}
impl TaprootTxBuilder {
pub fn new(manager: Arc<TaprootManager>) -> Self {
Self { manager }
}
pub fn create_key_path_spend(
&self,
_prev_output: TxOut,
_destination: Address,
_amount: Amount,
) -> Result<Transaction> {
Err(BitcoinError::Validation(
"Key path spending not yet implemented".to_string(),
))
}
pub fn create_script_path_spend(
&self,
_prev_output: TxOut,
_destination: Address,
_amount: Amount,
_leaf_index: usize,
) -> Result<Transaction> {
Err(BitcoinError::Validation(
"Script path spending not yet implemented".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_taproot_config_defaults() {
let config = TaprootConfig::default();
assert_eq!(config.network, Network::Bitcoin);
assert!(config.enable_script_path);
assert_eq!(config.max_tree_depth, 128);
}
#[test]
fn test_generate_keypair() {
let config = TaprootConfig {
network: Network::Testnet,
..Default::default()
};
let manager = TaprootManager::new(config);
let keypair = manager.generate_keypair().unwrap();
assert!(keypair.secret_key.is_some());
}
#[test]
fn test_create_key_path_address() {
let config = TaprootConfig {
network: Network::Testnet,
..Default::default()
};
let manager = TaprootManager::new(config);
let keypair = manager.generate_keypair().unwrap();
let address = manager
.create_key_path_address(keypair.internal_key)
.unwrap();
assert!(manager.is_taproot_address(&address.address));
}
#[test]
fn test_create_script_path_address() {
let config = TaprootConfig {
network: Network::Testnet,
..Default::default()
};
let manager = TaprootManager::new(config);
let keypair = manager.generate_keypair().unwrap();
let script = ScriptBuf::from_bytes(vec![0x51]);
let address = manager
.create_script_path_address(keypair.internal_key, vec![script])
.unwrap();
assert!(manager.is_taproot_address(&address.address));
}
#[test]
fn test_validate_taproot_address() {
let config = TaprootConfig {
network: Network::Testnet,
..Default::default()
};
let manager = TaprootManager::new(config);
let keypair = manager.generate_keypair().unwrap();
let address = manager
.create_key_path_address(keypair.internal_key)
.unwrap();
let is_valid = manager
.validate_address(&address.address.to_string())
.unwrap();
assert!(is_valid);
}
#[test]
fn test_script_path_disabled() {
let config = TaprootConfig {
network: Network::Testnet,
enable_script_path: false,
..Default::default()
};
let manager = TaprootManager::new(config);
let keypair = manager.generate_keypair().unwrap();
let script = ScriptBuf::from_bytes(vec![0x51]);
let result = manager.create_script_path_address(keypair.internal_key, vec![script]);
assert!(result.is_err());
}
}