anya_core/bitcoin/taproot/
mod.rs

1//! Taproot implementation for Bitcoin
2//!
3//! This module provides functionality for working with Taproot, including
4//! key generation, asset creation, and transaction building.
5//!
6//! [AIR-3][AIS-3][BPC-3][RES-3] Implementation follows official Bitcoin Improvement Proposals (BIPs)
7
8// [AIR-3][AIS-3][BPC-3][RES-3] Taproot module for Bitcoin asset management
9use bitcoin::hashes::sha256;
10use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
11use bitcoin::taproot::TaprootBuilder;
12use bitcoin::ScriptBuf;
13use serde::{Deserialize, Serialize};
14use std::fmt;
15
16use crate::bitcoin::error::BitcoinError;
17
18/// Errors that can occur during Taproot operations
19#[derive(Debug)]
20pub enum TaprootError {
21    /// Error related to key operations
22    KeyError(String),
23    /// Error related to script operations
24    ScriptError(String),
25    /// Error from Taproot operations
26    TaprootError(String),
27    /// Error from Taproot builder
28    BuilderError(String),
29    /// Error from Bitcoin operations
30    BitcoinError(String),
31    /// Error from secp256k1 operations
32    Secp256k1Error(secp256k1::Error),
33    /// Error from hex operations
34    HexError(hex::FromHexError),
35    /// Error from input validation
36    ValidationError(String),
37}
38
39impl fmt::Display for TaprootError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            Self::KeyError(e) => write!(f, "Key error: {e}"),
43            Self::ScriptError(e) => write!(f, "Script error: {e}"),
44            Self::TaprootError(e) => write!(f, "Taproot error: {e}"),
45            Self::BuilderError(e) => write!(f, "Builder error: {e}"),
46            Self::BitcoinError(e) => write!(f, "Bitcoin error: {e}"),
47            Self::Secp256k1Error(e) => write!(f, "Secp256k1 error: {e}"),
48            Self::HexError(e) => write!(f, "Hex error: {e}"),
49            Self::ValidationError(e) => write!(f, "Validation error: {e}"),
50        }
51    }
52}
53
54impl std::error::Error for TaprootError {}
55
56impl From<bitcoin::secp256k1::Error> for TaprootError {
57    fn from(e: bitcoin::secp256k1::Error) -> Self {
58        Self::Secp256k1Error(e)
59    }
60}
61
62impl From<hex::FromHexError> for TaprootError {
63    fn from(e: hex::FromHexError) -> Self {
64        Self::HexError(e)
65    }
66}
67
68impl From<BitcoinError> for TaprootError {
69    fn from(e: BitcoinError) -> Self {
70        Self::BitcoinError(e.to_string())
71    }
72}
73
74impl From<bitcoin::taproot::TaprootError> for TaprootError {
75    fn from(e: bitcoin::taproot::TaprootError) -> Self {
76        Self::TaprootError(e.to_string())
77    }
78}
79
80impl From<bitcoin::taproot::IncompleteBuilderError> for TaprootError {
81    fn from(e: bitcoin::taproot::IncompleteBuilderError) -> Self {
82        Self::BuilderError(e.to_string())
83    }
84}
85
86/// Taproot Asset structure
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct TaprootAsset {
89    /// Unique identifier for the asset (SHA-256 hash of asset properties)
90    pub asset_id: [u8; 32],
91    /// Name of the asset (1-32 characters)
92    pub name: String,
93    /// Total supply of the asset (must be > 0)
94    pub supply: u64,
95    /// Number of decimal places (0-8)
96    pub precision: u8,
97    /// Additional metadata (max 1024 bytes)
98    pub metadata: String,
99    /// Whether the asset has been issued on-chain
100    pub issued: bool,
101    /// Taproot script leaves for the asset
102    pub leaves: Vec<ScriptBuf>,
103    /// Number of script leaves
104    pub num_leaves: u32,
105    /// Issuer's public key (x-only, 32 bytes)
106    pub issuer_pubkey: [u8; 32],
107}
108
109impl TaprootAsset {
110    /// Create a new Taproot asset with the given parameters
111    pub fn new(
112        name: &str,
113        supply: u64,
114        precision: u8,
115        metadata: &str,
116        issuer_secret_key: &[u8],
117    ) -> Result<Self, TaprootError> {
118        // Validate inputs
119        if name.is_empty() || name.len() > 32 {
120            return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
121                "Asset name must be 1-32 characters".to_string(),
122            ));
123        }
124
125        if supply == 0 {
126            return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
127                "Supply must be greater than 0".to_string(),
128            ));
129        }
130
131        if precision > 8 {
132            return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
133                "Precision must be 0-8".to_string(),
134            ));
135        }
136
137        if metadata.len() > 1024 {
138            return Err(crate::bitcoin::taproot::TaprootError::ValidationError(
139                "Metadata too large (max 1024 bytes)".to_string(),
140            ));
141        }
142
143        // Generate asset ID
144        let asset_id = generate_asset_id(name, supply, precision, metadata)?;
145
146        // Create the asset script
147        let mut builder = bitcoin::blockdata::script::Builder::new();
148        builder = builder.push_opcode(bitcoin::opcodes::all::OP_RETURN);
149        builder = push_bytes_to_script(builder, &asset_id);
150
151        // Initialize the leaves vector with the asset script
152        let leaves = vec![builder.into_script()];
153
154        // Parse the secret key to get the public key
155        let secp = Secp256k1::new();
156        let secret_key = SecretKey::from_slice(issuer_secret_key)
157            .map_err(|e| crate::bitcoin::taproot::TaprootError::KeyError(e.to_string()))?;
158        let (x_only_pubkey, _) = secret_key.public_key(&secp).x_only_public_key();
159
160        Ok(Self {
161            asset_id,
162            name: name.to_string(),
163            supply,
164            precision,
165            metadata: metadata.to_string(),
166            issued: true,
167            leaves,
168            num_leaves: 1,
169            issuer_pubkey: x_only_pubkey.serialize(),
170        })
171    }
172
173    /// Create a Taproot-compatible asset script
174    ///
175    /// # Returns
176    /// A `ScriptBuf` containing the asset script or a `TaprootError` if creation fails
177    ///
178    /// # Compliance
179    /// - BIP-341/342 (Taproot)
180    /// - BIP-352 (Asset protocols)
181    pub fn create_asset_script(&self) -> Result<ScriptBuf, TaprootError> {
182        use bitcoin::blockdata::opcodes;
183        use bitcoin::blockdata::script::Builder;
184
185        let mut builder = Builder::new();
186
187        // Start with OP_RETURN
188        builder = builder.push_opcode(opcodes::all::OP_RETURN);
189
190        // Push the asset ID bytes
191        builder = push_bytes_to_script(builder, &self.asset_id);
192
193        // Push name as bytes (limited to 32 bytes)
194        let name_bytes = self.name.as_bytes();
195        let name_slice = if name_bytes.len() > 32 {
196            &name_bytes[..32]
197        } else {
198            name_bytes
199        };
200        builder = push_bytes_to_script(builder, name_slice);
201
202        // Push supply as 8-byte little-endian
203        let supply_bytes = self.supply.to_le_bytes();
204        builder = push_bytes_to_script(builder, &supply_bytes);
205
206        // Push precision as single byte
207        builder = builder.push_int(self.precision as i64);
208
209        Ok(builder.into_script())
210    }
211}
212
213/// Generate a new Taproot key pair
214///
215/// # Returns
216/// A tuple containing the secret key and corresponding public key
217///
218/// # Compliance
219/// - BIP-341/342 (Taproot)
220pub fn generate_keypair() -> Result<(SecretKey, bitcoin::key::XOnlyPublicKey), TaprootError> {
221    let secp = Secp256k1::new();
222    let mut rng = rand::thread_rng();
223    let (secret_key, _) = secp.generate_keypair(&mut rng);
224
225    // Convert to x-only public key for Taproot
226    let x_only = secret_key.x_only_public_key(&secp);
227    Ok((secret_key, x_only.0))
228}
229
230/// Generate a unique asset ID from the asset's properties
231///
232/// # Arguments
233/// * `name` - Name of the asset
234/// * `supply` - Total supply of the asset
235/// * `precision` - Decimal precision of the asset
236/// * `metadata` - Additional metadata for the asset
237///
238/// # Returns
239/// A 32-byte array representing the asset ID
240fn generate_asset_id(
241    name: &str,
242    supply: u64,
243    precision: u8,
244    metadata: &str,
245) -> Result<[u8; 32], TaprootError> {
246    use bitcoin::hashes::{Hash, HashEngine};
247
248    let mut engine = sha256::Hash::engine();
249    engine.input(name.as_bytes());
250    engine.input(&supply.to_le_bytes());
251    engine.input(&[precision]);
252    engine.input(metadata.as_bytes());
253
254    let hash = sha256::Hash::from_engine(engine);
255    Ok(hash.to_byte_array())
256}
257
258/// Helper method to push bytes to a script
259fn push_bytes_to_script(
260    mut builder: bitcoin::blockdata::script::Builder,
261    data: &[u8],
262) -> bitcoin::blockdata::script::Builder {
263    for &byte in data {
264        builder = builder.push_int(byte as i64);
265    }
266    builder
267}
268
269/// Create a new Taproot asset
270///
271/// Creates a new Taproot asset with the given parameters
272///
273/// # Arguments
274/// * `name` - Name of the asset (1-32 characters)
275/// * `supply` - Total supply of the asset (must be > 0)
276/// * `precision` - Number of decimal places (0-8)
277/// * `metadata` - Additional metadata as a JSON string (max 1024 bytes)
278/// * `issuer_secret_key` - Secret key of the asset issuer (32 bytes)
279///
280/// # Returns
281/// A new instance of TaprootAsset or an error if validation fails
282///
283/// # Compliance
284/// - BIP-341/342 (Taproot)
285/// - BIP-352 (Asset protocols)
286///
287/// # Example
288/// ```
289/// use anya_core::bitcoin::taproot::{create_asset, TaprootAsset};
290/// use bitcoin::secp256k1::rand;
291///
292/// let mut rng = rand::thread_rng();
293/// let secret_key = bitcoin::secp256k1::SecretKey::new(&mut rng);
294///
295/// let asset = create_asset(
296///     "MY_ASSET",
297///     1000000,
298///     8,
299///     "My test asset",
300///     &secret_key[..],
301/// ).unwrap();
302/// ```
303pub fn create_asset(
304    name: &str,
305    supply: u64,
306    precision: u8,
307    metadata: &str,
308    issuer_secret_key: &[u8],
309) -> Result<TaprootAsset, BitcoinError> {
310    // Validate inputs
311    if name.is_empty() || name.len() > 32 {
312        return Err(BitcoinError::InvalidScript(
313            "Asset name must be 1-32 characters".to_string(),
314        ));
315    }
316
317    if supply == 0 {
318        return Err(BitcoinError::InvalidScript(
319            "Asset supply must be greater than 0".to_string(),
320        ));
321    }
322
323    if precision > 8 {
324        return Err(BitcoinError::InvalidScript(
325            "Precision must be between 0 and 8".to_string(),
326        ));
327    }
328
329    if metadata.len() > 1024 {
330        return Err(BitcoinError::InvalidScript(
331            "Metadata exceeds maximum length of 1024 bytes".to_string(),
332        ));
333    }
334
335    if issuer_secret_key.len() != 32 {
336        return Err(BitcoinError::InvalidPrivateKey);
337    }
338
339    // Create the asset with the provided issuer secret key
340    TaprootAsset::new(name, supply, precision, metadata, issuer_secret_key)
341        .map_err(|e| BitcoinError::TaprootError(e.to_string()))
342}
343
344/// Issue a Taproot asset
345///
346/// Creates a transaction that issues the asset to the specified address.
347///
348/// # Arguments
349/// * `asset` - The TaprootAsset to issue
350/// * `issuer_secret_key` - The issuer's secret key (32 bytes)
351///
352/// # Returns
353/// The hex-encoded Taproot output script that locks the asset
354///
355/// # Compliance
356/// - BIP-341/342 (Taproot)
357/// - BIP-352 (Asset protocols)
358pub fn issue_asset(asset: &TaprootAsset, issuer_secret_key: &[u8]) -> Result<String, TaprootError> {
359    // Create secp256k1 context
360    let secp = Secp256k1::new();
361
362    // Parse the secret key
363    let secret_key = secp256k1::SecretKey::from_slice(issuer_secret_key)
364        .map_err(|e| TaprootError::KeyError(e.to_string()))?;
365
366    // Get the x-only public key
367    let (x_only_pubkey, _) = secret_key.public_key(&secp).x_only_public_key();
368
369    // Create the asset script
370    let asset_script = asset.create_asset_script()?;
371
372    // Create a Taproot tree with the asset script following the tr(KEY,{SILENT_LEAF}) pattern
373    let mut builder = TaprootBuilder::new();
374
375    // Add the asset script as a leaf with depth 1 (SILENT_LEAF)
376    builder = builder
377        .add_leaf(1, asset_script.clone())
378        .map_err(|e| TaprootError::BuilderError(format!("Failed to add leaf: {e}")))?;
379
380    // Finalize the Taproot tree with the internal key (KEY)
381    let taproot_spend_info = builder
382        .finalize(&secp, x_only_pubkey)
383        .map_err(|e| TaprootError::TaprootError(format!("Failed to finalize builder: {e:?}")))?;
384
385    // Get the Taproot output script
386    let output_key = taproot_spend_info.output_key();
387    let output_script = bitcoin::ScriptBuf::new_p2tr(&secp, output_key.into(), None);
388
389    // [AIR-3][AIS-3][BPC-3][RES-3] Verify the output script follows tr(KEY,{SILENT_LEAF}) pattern
390    let script_bytes = output_script.as_bytes();
391    if script_bytes.len() != 34 || !script_bytes.starts_with(&[0x51, 0x20]) {
392        return Err(TaprootError::BuilderError(
393            "Output script does not match tr(KEY,{SILENT_LEAF}) pattern".to_string(),
394        ));
395    }
396
397    Ok(hex::encode(output_script.as_bytes()))
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use bitcoin::secp256k1::rand::rngs::OsRng;
404    use bitcoin::secp256k1::Secp256k1;
405
406    #[test]
407    fn test_generate_keypair() {
408        let result = generate_keypair();
409        assert!(result.is_ok());
410
411        let (secret_key, public_key) = result.unwrap();
412
413        // Verify the keys are related
414        let secp = Secp256k1::new();
415        let derived_pubkey = secret_key.x_only_public_key(&secp).0;
416        assert_eq!(public_key, derived_pubkey);
417    }
418
419    #[test]
420    fn test_generate_asset_id() {
421        let asset_id1 = generate_asset_id("TEST", 1000, 8, "metadata").unwrap();
422        let asset_id2 = generate_asset_id("TEST", 1000, 8, "metadata").unwrap();
423        let asset_id3 = generate_asset_id("TEST", 1001, 8, "metadata").unwrap();
424
425        // Same inputs should produce same ID
426        assert_eq!(asset_id1, asset_id2);
427
428        // Different inputs should produce different IDs
429        assert_ne!(asset_id1, asset_id3);
430
431        // Asset ID should be 32 bytes
432        assert_eq!(asset_id1.len(), 32);
433    }
434
435    #[test]
436    fn test_taproot_asset_creation() {
437        let secp = Secp256k1::new();
438        let mut rng = OsRng;
439        let (secret_key, _) = secp.generate_keypair(&mut rng);
440
441        let asset = TaprootAsset::new(
442            "TESTCOIN",
443            1000000,
444            8,
445            "Test asset metadata",
446            &secret_key[..],
447        );
448
449        assert!(asset.is_ok());
450        let asset = asset.unwrap();
451
452        assert_eq!(asset.name, "TESTCOIN");
453        assert_eq!(asset.supply, 1000000);
454        assert_eq!(asset.precision, 8);
455        assert_eq!(asset.metadata, "Test asset metadata");
456        assert!(asset.issued);
457        assert_eq!(asset.num_leaves, 1);
458        assert_eq!(asset.leaves.len(), 1);
459    }
460
461    #[test]
462    fn test_asset_validation() {
463        let secp = Secp256k1::new();
464        let mut rng = OsRng;
465        let (secret_key, _) = secp.generate_keypair(&mut rng);
466
467        // Test empty name
468        let result = TaprootAsset::new("", 1000, 8, "metadata", &secret_key[..]);
469        assert!(result.is_err());
470
471        // Test name too long
472        let long_name = "a".repeat(33);
473        let result = TaprootAsset::new(&long_name, 1000, 8, "metadata", &secret_key[..]);
474        assert!(result.is_err());
475
476        // Test zero supply
477        let result = TaprootAsset::new("TEST", 0, 8, "metadata", &secret_key[..]);
478        assert!(result.is_err());
479
480        // Test precision too high
481        let result = TaprootAsset::new("TEST", 1000, 9, "metadata", &secret_key[..]);
482        assert!(result.is_err());
483
484        // Test metadata too large
485        let large_metadata = "a".repeat(1025);
486        let result = TaprootAsset::new("TEST", 1000, 8, &large_metadata, &secret_key[..]);
487        assert!(result.is_err());
488    }
489
490    #[test]
491    fn test_create_asset_script() {
492        let secp = Secp256k1::new();
493        let mut rng = OsRng;
494        let (secret_key, _) = secp.generate_keypair(&mut rng);
495
496        let asset = TaprootAsset::new("TEST", 1000, 8, "metadata", &secret_key[..]).unwrap();
497
498        let script = asset.create_asset_script();
499        assert!(script.is_ok());
500
501        let script = script.unwrap();
502        assert!(!script.is_empty());
503
504        // Should start with OP_RETURN
505        let script_bytes = script.as_bytes();
506        assert_eq!(script_bytes[0], 0x6a); // OP_RETURN opcode
507    }
508
509    #[test]
510    fn test_create_asset_function() {
511        let secp = Secp256k1::new();
512        let mut rng = OsRng;
513        let (secret_key, _) = secp.generate_keypair(&mut rng);
514
515        let result = create_asset("TESTCOIN", 1000000, 8, "Test metadata", &secret_key[..]);
516
517        assert!(result.is_ok());
518        let asset = result.unwrap();
519
520        assert_eq!(asset.name, "TESTCOIN");
521        assert_eq!(asset.supply, 1000000);
522        assert_eq!(asset.precision, 8);
523        assert_eq!(asset.metadata, "Test metadata");
524        assert!(asset.issued);
525        assert_eq!(asset.num_leaves, 1);
526        assert_eq!(asset.leaves.len(), 1);
527    }
528}