kaccy_bitcoin/
miniscript_support.rs

1//! Miniscript support for Bitcoin scripts
2//!
3//! Miniscript is a language for writing Bitcoin scripts in a structured way,
4//! making them easier to analyze, compose, and reason about.
5
6use bitcoin::{Network, ScriptBuf};
7use serde::{Deserialize, Serialize};
8
9use crate::error::{BitcoinError, Result};
10
11/// Miniscript policy types
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum MiniscriptPolicy {
14    /// Public key policy: pk(KEY)
15    PublicKey(String),
16    /// Hash preimage: hash256(H), sha256(H), ripemd160(H), hash160(H)
17    Hash { hash_type: HashType, hash: String },
18    /// Timelock: after(n), older(n)
19    Timelock {
20        timelock_type: TimelockType,
21        value: u32,
22    },
23    /// Threshold: thresh(k, X1, ..., Xn)
24    Threshold {
25        k: usize,
26        policies: Vec<MiniscriptPolicy>,
27    },
28    /// Logical AND: and(X, Y)
29    And(Box<MiniscriptPolicy>, Box<MiniscriptPolicy>),
30    /// Logical OR: or(X, Y)
31    Or(Box<MiniscriptPolicy>, Box<MiniscriptPolicy>),
32    /// Multi-signature: multi(k, KEY1, ..., KEYn)
33    Multi { k: usize, keys: Vec<String> },
34}
35
36/// Hash types for miniscript
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum HashType {
39    /// SHA256
40    Sha256,
41    /// HASH256 (double SHA256)
42    Hash256,
43    /// RIPEMD160
44    Ripemd160,
45    /// HASH160 (SHA256 then RIPEMD160)
46    Hash160,
47}
48
49/// Timelock types for miniscript
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum TimelockType {
52    /// Absolute block height or timestamp
53    After,
54    /// Relative block height or timestamp
55    Older,
56}
57
58/// Miniscript descriptor
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct MiniscriptDescriptor {
61    /// The miniscript policy
62    pub policy: MiniscriptPolicy,
63    /// Network for address generation
64    pub network: Network,
65    /// Script type (WSH, SH, etc.)
66    pub script_type: MiniscriptScriptType,
67}
68
69/// Script types for miniscript
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71pub enum MiniscriptScriptType {
72    /// Bare (legacy)
73    Bare,
74    /// Pay-to-Script-Hash (P2SH)
75    P2SH,
76    /// Pay-to-Witness-Script-Hash (P2WSH)
77    P2WSH,
78    /// Pay-to-Script-Hash Witness Script Hash (P2SH-P2WSH)
79    P2SHP2WSH,
80}
81
82impl MiniscriptDescriptor {
83    /// Create a new miniscript descriptor
84    pub fn new(
85        policy: MiniscriptPolicy,
86        network: Network,
87        script_type: MiniscriptScriptType,
88    ) -> Self {
89        Self {
90            policy,
91            network,
92            script_type,
93        }
94    }
95
96    /// Compile the policy to a Bitcoin script (simplified version)
97    pub fn compile(&self) -> Result<ScriptBuf> {
98        // This is a simplified compilation - in production, you'd use the miniscript crate
99        self.compile_policy(&self.policy)
100    }
101
102    #[allow(clippy::only_used_in_recursion)]
103    fn compile_policy(&self, policy: &MiniscriptPolicy) -> Result<ScriptBuf> {
104        match policy {
105            MiniscriptPolicy::PublicKey(_key) => {
106                // In a real implementation, this would compile to a proper pk() script
107                Ok(ScriptBuf::new())
108            }
109            MiniscriptPolicy::Hash { .. } => {
110                // Compile hash preimage check
111                Ok(ScriptBuf::new())
112            }
113            MiniscriptPolicy::Timelock { .. } => {
114                // Compile timelock
115                Ok(ScriptBuf::new())
116            }
117            MiniscriptPolicy::Threshold { k: _, policies: _ } => {
118                // Compile threshold
119                Ok(ScriptBuf::new())
120            }
121            MiniscriptPolicy::And(left, right) => {
122                // Compile AND
123                let _left_script = self.compile_policy(left)?;
124                let _right_script = self.compile_policy(right)?;
125                Ok(ScriptBuf::new())
126            }
127            MiniscriptPolicy::Or(left, right) => {
128                // Compile OR
129                let _left_script = self.compile_policy(left)?;
130                let _right_script = self.compile_policy(right)?;
131                Ok(ScriptBuf::new())
132            }
133            MiniscriptPolicy::Multi { k: _, keys: _ } => {
134                // Compile multi-sig
135                Ok(ScriptBuf::new())
136            }
137        }
138    }
139
140    /// Analyze the policy for properties
141    pub fn analyze(&self) -> PolicyAnalysis {
142        PolicyAnalysis {
143            is_malleable: self.check_malleability(),
144            is_sane: self.check_sanity(),
145            max_satisfaction_weight: self.estimate_weight(),
146            requires_timelock: self.requires_timelock(),
147            requires_hash_preimage: self.requires_hash_preimage(),
148        }
149    }
150
151    fn check_malleability(&self) -> bool {
152        // Simplified malleability check
153        false
154    }
155
156    fn check_sanity(&self) -> bool {
157        // Check if the policy makes sense
158        true
159    }
160
161    fn estimate_weight(&self) -> usize {
162        // Estimate worst-case witness weight
163        match &self.policy {
164            MiniscriptPolicy::PublicKey(_) => 73, // signature
165            MiniscriptPolicy::Multi { k, keys } => {
166                // k signatures plus multi-sig overhead
167                73 * k + 34 * keys.len()
168            }
169            MiniscriptPolicy::Threshold { k: _, policies } => {
170                // Sum of worst-case weights for k policies
171                policies.iter().map(|_| 100).sum()
172            }
173            _ => 100, // Conservative estimate
174        }
175    }
176
177    fn requires_timelock(&self) -> bool {
178        self.check_policy_for_feature(&self.policy, |p| {
179            matches!(p, MiniscriptPolicy::Timelock { .. })
180        })
181    }
182
183    fn requires_hash_preimage(&self) -> bool {
184        self.check_policy_for_feature(&self.policy, |p| matches!(p, MiniscriptPolicy::Hash { .. }))
185    }
186
187    #[allow(clippy::only_used_in_recursion)]
188    fn check_policy_for_feature<F>(&self, policy: &MiniscriptPolicy, check: F) -> bool
189    where
190        F: Fn(&MiniscriptPolicy) -> bool + Copy,
191    {
192        if check(policy) {
193            return true;
194        }
195
196        match policy {
197            MiniscriptPolicy::And(left, right) | MiniscriptPolicy::Or(left, right) => {
198                self.check_policy_for_feature(left, check)
199                    || self.check_policy_for_feature(right, check)
200            }
201            MiniscriptPolicy::Threshold { policies, .. } => policies
202                .iter()
203                .any(|p| self.check_policy_for_feature(p, check)),
204            _ => false,
205        }
206    }
207}
208
209/// Policy analysis results
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct PolicyAnalysis {
212    /// Whether the policy is malleable
213    pub is_malleable: bool,
214    /// Whether the policy is sane (makes logical sense)
215    pub is_sane: bool,
216    /// Maximum satisfaction weight in witness units
217    pub max_satisfaction_weight: usize,
218    /// Whether the policy requires a timelock
219    pub requires_timelock: bool,
220    /// Whether the policy requires a hash preimage
221    pub requires_hash_preimage: bool,
222}
223
224/// Miniscript compiler and analyzer
225pub struct MiniscriptCompiler {
226    network: Network,
227}
228
229impl MiniscriptCompiler {
230    /// Create a new miniscript compiler
231    pub fn new(network: Network) -> Self {
232        Self { network }
233    }
234
235    /// Parse a miniscript policy string (simplified parser)
236    pub fn parse_policy(&self, policy_str: &str) -> Result<MiniscriptPolicy> {
237        // Simplified parser - in production, use miniscript crate's parser
238        if policy_str.starts_with("pk(") {
239            let key = policy_str
240                .trim_start_matches("pk(")
241                .trim_end_matches(')')
242                .to_string();
243            Ok(MiniscriptPolicy::PublicKey(key))
244        } else if policy_str.starts_with("multi(") {
245            // Parse multi(k, key1, key2, ...)
246            let inner = policy_str
247                .trim_start_matches("multi(")
248                .trim_end_matches(')');
249            let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
250
251            if parts.len() < 2 {
252                return Err(BitcoinError::Validation(
253                    "Invalid multi policy: need at least k and one key".to_string(),
254                ));
255            }
256
257            let k = parts[0]
258                .parse::<usize>()
259                .map_err(|_| BitcoinError::Validation("Invalid threshold k".to_string()))?;
260            let keys = parts[1..].iter().map(|s| s.to_string()).collect();
261
262            Ok(MiniscriptPolicy::Multi { k, keys })
263        } else if policy_str.starts_with("after(") {
264            let value_str = policy_str
265                .trim_start_matches("after(")
266                .trim_end_matches(')');
267            let value = value_str
268                .parse::<u32>()
269                .map_err(|_| BitcoinError::Validation("Invalid timelock value".to_string()))?;
270
271            Ok(MiniscriptPolicy::Timelock {
272                timelock_type: TimelockType::After,
273                value,
274            })
275        } else if policy_str.starts_with("older(") {
276            let value_str = policy_str
277                .trim_start_matches("older(")
278                .trim_end_matches(')');
279            let value = value_str
280                .parse::<u32>()
281                .map_err(|_| BitcoinError::Validation("Invalid timelock value".to_string()))?;
282
283            Ok(MiniscriptPolicy::Timelock {
284                timelock_type: TimelockType::Older,
285                value,
286            })
287        } else {
288            Err(BitcoinError::Validation(format!(
289                "Unsupported policy: {}",
290                policy_str
291            )))
292        }
293    }
294
295    /// Compile a policy to a descriptor
296    pub fn compile(
297        &self,
298        policy: MiniscriptPolicy,
299        script_type: MiniscriptScriptType,
300    ) -> Result<MiniscriptDescriptor> {
301        Ok(MiniscriptDescriptor::new(policy, self.network, script_type))
302    }
303
304    /// Optimize a policy (simplified)
305    pub fn optimize(&self, policy: MiniscriptPolicy) -> MiniscriptPolicy {
306        // In production, this would perform various optimizations
307        policy
308    }
309}
310
311/// Policy template builder for common use cases
312pub struct PolicyTemplateBuilder {
313    #[allow(dead_code)]
314    network: Network,
315}
316
317impl PolicyTemplateBuilder {
318    /// Create a new policy template builder
319    pub fn new(network: Network) -> Self {
320        Self { network }
321    }
322
323    /// Create a simple single-key policy
324    pub fn single_key(&self, pubkey: String) -> MiniscriptPolicy {
325        MiniscriptPolicy::PublicKey(pubkey)
326    }
327
328    /// Create a multi-signature policy
329    pub fn multisig(&self, k: usize, keys: Vec<String>) -> MiniscriptPolicy {
330        MiniscriptPolicy::Multi { k, keys }
331    }
332
333    /// Create a timelock policy (absolute)
334    pub fn absolute_timelock(&self, value: u32) -> MiniscriptPolicy {
335        MiniscriptPolicy::Timelock {
336            timelock_type: TimelockType::After,
337            value,
338        }
339    }
340
341    /// Create a timelock policy (relative)
342    pub fn relative_timelock(&self, value: u32) -> MiniscriptPolicy {
343        MiniscriptPolicy::Timelock {
344            timelock_type: TimelockType::Older,
345            value,
346        }
347    }
348
349    /// Create a hash preimage policy
350    pub fn hash_preimage(&self, hash_type: HashType, hash: String) -> MiniscriptPolicy {
351        MiniscriptPolicy::Hash { hash_type, hash }
352    }
353
354    /// Create an AND policy
355    pub fn and(&self, left: MiniscriptPolicy, right: MiniscriptPolicy) -> MiniscriptPolicy {
356        MiniscriptPolicy::And(Box::new(left), Box::new(right))
357    }
358
359    /// Create an OR policy
360    pub fn or(&self, left: MiniscriptPolicy, right: MiniscriptPolicy) -> MiniscriptPolicy {
361        MiniscriptPolicy::Or(Box::new(left), Box::new(right))
362    }
363
364    /// Create a threshold policy
365    pub fn threshold(&self, k: usize, policies: Vec<MiniscriptPolicy>) -> MiniscriptPolicy {
366        MiniscriptPolicy::Threshold { k, policies }
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn test_single_key_policy() {
376        let builder = PolicyTemplateBuilder::new(Network::Bitcoin);
377        let policy = builder.single_key(
378            "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".to_string(),
379        );
380
381        match policy {
382            MiniscriptPolicy::PublicKey(key) => {
383                assert!(key.starts_with("0279BE667"));
384            }
385            _ => panic!("Expected PublicKey policy"),
386        }
387    }
388
389    #[test]
390    fn test_multisig_policy() {
391        let builder = PolicyTemplateBuilder::new(Network::Bitcoin);
392        let keys = vec!["key1".to_string(), "key2".to_string(), "key3".to_string()];
393        let policy = builder.multisig(2, keys.clone());
394
395        match policy {
396            MiniscriptPolicy::Multi {
397                k,
398                keys: policy_keys,
399            } => {
400                assert_eq!(k, 2);
401                assert_eq!(policy_keys, keys);
402            }
403            _ => panic!("Expected Multi policy"),
404        }
405    }
406
407    #[test]
408    fn test_timelock_policy() {
409        let builder = PolicyTemplateBuilder::new(Network::Bitcoin);
410        let policy = builder.absolute_timelock(500000);
411
412        match policy {
413            MiniscriptPolicy::Timelock {
414                timelock_type,
415                value,
416            } => {
417                assert_eq!(timelock_type, TimelockType::After);
418                assert_eq!(value, 500000);
419            }
420            _ => panic!("Expected Timelock policy"),
421        }
422    }
423
424    #[test]
425    fn test_and_policy() {
426        let builder = PolicyTemplateBuilder::new(Network::Bitcoin);
427        let left = builder.single_key("key1".to_string());
428        let right = builder.absolute_timelock(500000);
429        let policy = builder.and(left, right);
430
431        match policy {
432            MiniscriptPolicy::And(_, _) => {}
433            _ => panic!("Expected And policy"),
434        }
435    }
436
437    #[test]
438    fn test_policy_parsing() {
439        let compiler = MiniscriptCompiler::new(Network::Bitcoin);
440
441        let policy = compiler.parse_policy("pk(key1)").unwrap();
442        assert!(matches!(policy, MiniscriptPolicy::PublicKey(_)));
443
444        let policy = compiler.parse_policy("multi(2, key1, key2, key3)").unwrap();
445        if let MiniscriptPolicy::Multi { k, keys } = policy {
446            assert_eq!(k, 2);
447            assert_eq!(keys.len(), 3);
448        } else {
449            panic!("Expected Multi policy");
450        }
451
452        let policy = compiler.parse_policy("after(500000)").unwrap();
453        assert!(matches!(
454            policy,
455            MiniscriptPolicy::Timelock {
456                timelock_type: TimelockType::After,
457                ..
458            }
459        ));
460    }
461
462    #[test]
463    fn test_descriptor_creation() {
464        let policy = MiniscriptPolicy::PublicKey("key1".to_string());
465        let descriptor =
466            MiniscriptDescriptor::new(policy, Network::Bitcoin, MiniscriptScriptType::P2WSH);
467
468        assert_eq!(descriptor.script_type, MiniscriptScriptType::P2WSH);
469        assert_eq!(descriptor.network, Network::Bitcoin);
470    }
471
472    #[test]
473    fn test_policy_analysis() {
474        let policy = MiniscriptPolicy::Multi {
475            k: 2,
476            keys: vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
477        };
478        let descriptor =
479            MiniscriptDescriptor::new(policy, Network::Bitcoin, MiniscriptScriptType::P2WSH);
480
481        let analysis = descriptor.analyze();
482        assert!(!analysis.is_malleable);
483        assert!(analysis.is_sane);
484        assert!(analysis.max_satisfaction_weight > 0);
485    }
486
487    #[test]
488    fn test_requires_timelock_detection() {
489        let builder = PolicyTemplateBuilder::new(Network::Bitcoin);
490
491        let policy_with_timelock = builder.and(
492            builder.single_key("key1".to_string()),
493            builder.absolute_timelock(500000),
494        );
495
496        let descriptor = MiniscriptDescriptor::new(
497            policy_with_timelock,
498            Network::Bitcoin,
499            MiniscriptScriptType::P2WSH,
500        );
501
502        let analysis = descriptor.analyze();
503        assert!(analysis.requires_timelock);
504    }
505}