Skip to main content

kaccy_bitcoin/
tapscript_utils.rs

1//! Tapscript utilities for advanced Taproot features
2//!
3//! Provides tools for building complex script trees, generating Merkle proofs,
4//! and optimizing script paths using Huffman coding.
5
6use crate::error::BitcoinError;
7use bitcoin::hashes::Hash;
8use bitcoin::{Script, TapLeafHash};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// A leaf in the Tapscript tree
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TapscriptLeaf {
15    /// The script
16    pub script: Vec<u8>,
17    /// Leaf version (typically 0xc0 for Tapscript)
18    pub version: u8,
19    /// Weight (for Huffman optimization)
20    pub weight: u32,
21}
22
23impl TapscriptLeaf {
24    /// Create a new tapscript leaf
25    pub fn new(script: Vec<u8>, weight: u32) -> Self {
26        Self {
27            script,
28            version: 0xc0, // Tapscript version
29            weight,
30        }
31    }
32
33    /// Calculate the leaf hash
34    pub fn leaf_hash(&self) -> Result<TapLeafHash, BitcoinError> {
35        use bitcoin::hashes::{HashEngine, sha256};
36        let script = Script::from_bytes(&self.script);
37
38        // Manually compute TapLeaf hash: SHA256(SHA256(0xc0 || script))
39        let mut engine = sha256::Hash::engine();
40        engine.input(&[self.version]);
41        engine.input(script.as_bytes());
42        let hash = sha256::Hash::from_engine(engine);
43
44        Ok(TapLeafHash::from_byte_array(hash.to_byte_array()))
45    }
46}
47
48/// A node in the Tapscript tree
49#[derive(Debug, Clone)]
50pub enum TapscriptNode {
51    /// Leaf node containing a script
52    Leaf(TapscriptLeaf),
53    /// Branch node with left and right children
54    Branch {
55        /// Left child of the branch
56        left: Box<TapscriptNode>,
57        /// Right child of the branch
58        right: Box<TapscriptNode>,
59    },
60}
61
62impl TapscriptNode {
63    /// Calculate the hash of this node
64    pub fn node_hash(&self) -> Result<[u8; 32], BitcoinError> {
65        match self {
66            TapscriptNode::Leaf(leaf) => {
67                let hash = leaf.leaf_hash()?;
68                Ok(*hash.as_byte_array())
69            }
70            TapscriptNode::Branch { left, right } => {
71                let left_hash = left.node_hash()?;
72                let right_hash = right.node_hash()?;
73
74                // Lexicographic ordering for branches
75                let (first, second) = if left_hash < right_hash {
76                    (left_hash, right_hash)
77                } else {
78                    (right_hash, left_hash)
79                };
80
81                // TapBranch hash = SHA256(SHA256(0x01 || first || second))
82                use bitcoin::hashes::{Hash, HashEngine, sha256};
83                let mut engine = sha256::Hash::engine();
84                engine.input(&[0x01]); // Branch tag
85                engine.input(&first);
86                engine.input(&second);
87                let hash = sha256::Hash::from_engine(engine);
88
89                Ok(hash.to_byte_array())
90            }
91        }
92    }
93
94    /// Get total weight of the tree
95    pub fn total_weight(&self) -> u32 {
96        match self {
97            TapscriptNode::Leaf(leaf) => leaf.weight,
98            TapscriptNode::Branch { left, right } => left.total_weight() + right.total_weight(),
99        }
100    }
101
102    /// Get tree depth
103    pub fn depth(&self) -> usize {
104        match self {
105            TapscriptNode::Leaf(_) => 1,
106            TapscriptNode::Branch { left, right } => 1 + std::cmp::max(left.depth(), right.depth()),
107        }
108    }
109}
110
111/// Merkle proof for a specific script path
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct MerkleProof {
114    /// The script being proven
115    pub script: Vec<u8>,
116    /// Proof hashes (path from leaf to root)
117    pub proof_hashes: Vec<[u8; 32]>,
118    /// Leaf version
119    pub leaf_version: u8,
120}
121
122impl MerkleProof {
123    /// Create a new Merkle proof
124    pub fn new(script: Vec<u8>, proof_hashes: Vec<[u8; 32]>, leaf_version: u8) -> Self {
125        Self {
126            script,
127            proof_hashes,
128            leaf_version,
129        }
130    }
131
132    /// Verify the proof against a root hash
133    pub fn verify(&self, root_hash: &[u8; 32]) -> Result<bool, BitcoinError> {
134        let leaf = TapscriptLeaf {
135            script: self.script.clone(),
136            version: self.leaf_version,
137            weight: 1,
138        };
139
140        let mut current_hash = *leaf.leaf_hash()?.as_byte_array();
141
142        for proof_hash in &self.proof_hashes {
143            let (first, second) = if current_hash < *proof_hash {
144                (current_hash, *proof_hash)
145            } else {
146                (*proof_hash, current_hash)
147            };
148
149            use bitcoin::hashes::{Hash, HashEngine, sha256};
150            let mut engine = sha256::Hash::engine();
151            engine.input(&[0x01]);
152            engine.input(&first);
153            engine.input(&second);
154            current_hash = sha256::Hash::from_engine(engine).to_byte_array();
155        }
156
157        Ok(current_hash == *root_hash)
158    }
159}
160
161/// Builder for complex Tapscript trees
162pub struct TapscriptTreeBuilder {
163    leaves: Vec<TapscriptLeaf>,
164}
165
166impl TapscriptTreeBuilder {
167    /// Create a new tree builder
168    pub fn new() -> Self {
169        Self { leaves: Vec::new() }
170    }
171
172    /// Add a script leaf with weight
173    pub fn add_leaf(&mut self, script: Vec<u8>, weight: u32) {
174        self.leaves.push(TapscriptLeaf::new(script, weight));
175    }
176
177    /// Add a simple script leaf with default weight
178    pub fn add_simple_leaf(&mut self, script: Vec<u8>) {
179        self.add_leaf(script, 1);
180    }
181
182    /// Build a balanced tree (not optimized)
183    pub fn build_balanced(&self) -> Result<TapscriptNode, BitcoinError> {
184        if self.leaves.is_empty() {
185            return Err(BitcoinError::InvalidTransaction(
186                "No leaves to build tree".to_string(),
187            ));
188        }
189
190        let mut nodes: Vec<TapscriptNode> = self
191            .leaves
192            .iter()
193            .map(|leaf| TapscriptNode::Leaf(leaf.clone()))
194            .collect();
195
196        while nodes.len() > 1 {
197            let mut new_nodes = Vec::new();
198
199            for chunk in nodes.chunks(2) {
200                if chunk.len() == 2 {
201                    new_nodes.push(TapscriptNode::Branch {
202                        left: Box::new(chunk[0].clone()),
203                        right: Box::new(chunk[1].clone()),
204                    });
205                } else {
206                    new_nodes.push(chunk[0].clone());
207                }
208            }
209
210            nodes = new_nodes;
211        }
212
213        Ok(nodes.into_iter().next().unwrap())
214    }
215
216    /// Build a Huffman-coded tree (optimized for weights)
217    ///
218    /// Creates a tree where more frequently used scripts (higher weight)
219    /// have shorter paths, reducing witness size.
220    pub fn build_huffman(&self) -> Result<TapscriptNode, BitcoinError> {
221        if self.leaves.is_empty() {
222            return Err(BitcoinError::InvalidTransaction(
223                "No leaves to build tree".to_string(),
224            ));
225        }
226
227        if self.leaves.len() == 1 {
228            return Ok(TapscriptNode::Leaf(self.leaves[0].clone()));
229        }
230
231        // Create initial nodes
232        let mut nodes: Vec<TapscriptNode> = self
233            .leaves
234            .iter()
235            .map(|leaf| TapscriptNode::Leaf(leaf.clone()))
236            .collect();
237
238        // Build Huffman tree by repeatedly combining lowest weight nodes
239        while nodes.len() > 1 {
240            // Sort by weight (ascending)
241            nodes.sort_by_key(|n| n.total_weight());
242
243            // Take two nodes with lowest weight
244            let left = nodes.remove(0);
245            let right = nodes.remove(0);
246
247            // Create branch node
248            let branch = TapscriptNode::Branch {
249                left: Box::new(left),
250                right: Box::new(right),
251            };
252
253            // Insert back into list
254            nodes.push(branch);
255        }
256
257        Ok(nodes.into_iter().next().unwrap())
258    }
259
260    /// Generate Merkle proof for a specific script
261    pub fn generate_proof(
262        &self,
263        tree: &TapscriptNode,
264        target_script: &[u8],
265    ) -> Result<MerkleProof, BitcoinError> {
266        let mut proof_hashes = Vec::new();
267
268        if !self.find_proof(tree, target_script, &mut proof_hashes)? {
269            return Err(BitcoinError::InvalidTransaction(
270                "Script not found in tree".to_string(),
271            ));
272        }
273
274        Ok(MerkleProof::new(target_script.to_vec(), proof_hashes, 0xc0))
275    }
276
277    /// Helper to recursively find proof path
278    fn find_proof(
279        &self,
280        node: &TapscriptNode,
281        target: &[u8],
282        proof: &mut Vec<[u8; 32]>,
283    ) -> Result<bool, BitcoinError> {
284        match node {
285            TapscriptNode::Leaf(leaf) => Ok(leaf.script == target),
286            TapscriptNode::Branch { left, right } => {
287                if self.find_proof(left, target, proof)? {
288                    proof.push(right.node_hash()?);
289                    Ok(true)
290                } else if self.find_proof(right, target, proof)? {
291                    proof.push(left.node_hash()?);
292                    Ok(true)
293                } else {
294                    Ok(false)
295                }
296            }
297        }
298    }
299}
300
301impl Default for TapscriptTreeBuilder {
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307/// Analyzer for script path optimization
308pub struct ScriptPathOptimizer {
309    /// Script usage frequencies
310    script_frequencies: HashMap<Vec<u8>, u32>,
311}
312
313impl ScriptPathOptimizer {
314    /// Create a new optimizer
315    pub fn new() -> Self {
316        Self {
317            script_frequencies: HashMap::new(),
318        }
319    }
320
321    /// Record script usage
322    pub fn record_usage(&mut self, script: Vec<u8>) {
323        *self.script_frequencies.entry(script).or_insert(0) += 1;
324    }
325
326    /// Build optimized tree based on usage patterns
327    pub fn build_optimized_tree(&self) -> Result<TapscriptNode, BitcoinError> {
328        let mut builder = TapscriptTreeBuilder::new();
329
330        for (script, frequency) in &self.script_frequencies {
331            builder.add_leaf(script.clone(), *frequency);
332        }
333
334        builder.build_huffman()
335    }
336
337    /// Calculate expected witness size for a tree
338    pub fn calculate_expected_witness_size(&self, tree: &TapscriptNode) -> f64 {
339        let total_frequency: u32 = self.script_frequencies.values().sum();
340        if total_frequency == 0 {
341            return 0.0;
342        }
343
344        let mut expected_size = 0.0;
345
346        for (script, frequency) in &self.script_frequencies {
347            let depth = self.get_script_depth(tree, script).unwrap_or(0);
348            let probability = *frequency as f64 / total_frequency as f64;
349            expected_size += probability * (script.len() + depth * 32) as f64;
350        }
351
352        expected_size
353    }
354
355    /// Get depth of a script in the tree
356    fn get_script_depth(&self, node: &TapscriptNode, target: &[u8]) -> Option<usize> {
357        match node {
358            TapscriptNode::Leaf(leaf) => {
359                if leaf.script == target {
360                    Some(0)
361                } else {
362                    None
363                }
364            }
365            TapscriptNode::Branch { left, right } => {
366                if let Some(depth) = self.get_script_depth(left, target) {
367                    Some(depth + 1)
368                } else {
369                    self.get_script_depth(right, target).map(|depth| depth + 1)
370                }
371            }
372        }
373    }
374}
375
376impl Default for ScriptPathOptimizer {
377    fn default() -> Self {
378        Self::new()
379    }
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn test_tapscript_leaf() {
388        let script = vec![0x51]; // OP_1
389        let leaf = TapscriptLeaf::new(script.clone(), 1);
390
391        assert_eq!(leaf.script, script);
392        assert_eq!(leaf.version, 0xc0);
393        assert_eq!(leaf.weight, 1);
394    }
395
396    #[test]
397    fn test_tree_builder_balanced() {
398        let mut builder = TapscriptTreeBuilder::new();
399        builder.add_simple_leaf(vec![0x51]); // OP_1
400        builder.add_simple_leaf(vec![0x52]); // OP_2
401
402        let tree = builder.build_balanced();
403        assert!(tree.is_ok());
404    }
405
406    #[test]
407    fn test_tree_builder_huffman() {
408        let mut builder = TapscriptTreeBuilder::new();
409        builder.add_leaf(vec![0x51], 10); // High frequency
410        builder.add_leaf(vec![0x52], 1); // Low frequency
411
412        let tree = builder.build_huffman();
413        assert!(tree.is_ok());
414    }
415
416    #[test]
417    fn test_script_path_optimizer() {
418        let mut optimizer = ScriptPathOptimizer::new();
419        optimizer.record_usage(vec![0x51]);
420        optimizer.record_usage(vec![0x51]);
421        optimizer.record_usage(vec![0x52]);
422
423        let tree = optimizer.build_optimized_tree();
424        assert!(tree.is_ok());
425    }
426
427    #[test]
428    fn test_merkle_proof_creation() {
429        let mut builder = TapscriptTreeBuilder::new();
430        let script1 = vec![0x51];
431        let script2 = vec![0x52];
432
433        builder.add_simple_leaf(script1.clone());
434        builder.add_simple_leaf(script2.clone());
435
436        let tree = builder.build_balanced().unwrap();
437        let proof = builder.generate_proof(&tree, &script1);
438
439        assert!(proof.is_ok());
440    }
441
442    #[test]
443    fn test_node_depth() {
444        let leaf = TapscriptNode::Leaf(TapscriptLeaf::new(vec![0x51], 1));
445        assert_eq!(leaf.depth(), 1);
446
447        let branch = TapscriptNode::Branch {
448            left: Box::new(leaf.clone()),
449            right: Box::new(leaf),
450        };
451        assert_eq!(branch.depth(), 2);
452    }
453
454    #[test]
455    fn test_node_total_weight() {
456        let leaf1 = TapscriptNode::Leaf(TapscriptLeaf::new(vec![0x51], 5));
457        let leaf2 = TapscriptNode::Leaf(TapscriptLeaf::new(vec![0x52], 3));
458
459        let branch = TapscriptNode::Branch {
460            left: Box::new(leaf1),
461            right: Box::new(leaf2),
462        };
463
464        assert_eq!(branch.total_weight(), 8);
465    }
466}