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