1use bitcoin::{Network, ScriptBuf};
7use serde::{Deserialize, Serialize};
8
9use crate::error::{BitcoinError, Result};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum MiniscriptPolicy {
14 PublicKey(String),
16 Hash { hash_type: HashType, hash: String },
18 Timelock {
20 timelock_type: TimelockType,
21 value: u32,
22 },
23 Threshold {
25 k: usize,
26 policies: Vec<MiniscriptPolicy>,
27 },
28 And(Box<MiniscriptPolicy>, Box<MiniscriptPolicy>),
30 Or(Box<MiniscriptPolicy>, Box<MiniscriptPolicy>),
32 Multi { k: usize, keys: Vec<String> },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum HashType {
39 Sha256,
41 Hash256,
43 Ripemd160,
45 Hash160,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum TimelockType {
52 After,
54 Older,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct MiniscriptDescriptor {
61 pub policy: MiniscriptPolicy,
63 pub network: Network,
65 pub script_type: MiniscriptScriptType,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
71pub enum MiniscriptScriptType {
72 Bare,
74 P2SH,
76 P2WSH,
78 P2SHP2WSH,
80}
81
82impl MiniscriptDescriptor {
83 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 pub fn compile(&self) -> Result<ScriptBuf> {
98 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 Ok(ScriptBuf::new())
108 }
109 MiniscriptPolicy::Hash { .. } => {
110 Ok(ScriptBuf::new())
112 }
113 MiniscriptPolicy::Timelock { .. } => {
114 Ok(ScriptBuf::new())
116 }
117 MiniscriptPolicy::Threshold { k: _, policies: _ } => {
118 Ok(ScriptBuf::new())
120 }
121 MiniscriptPolicy::And(left, right) => {
122 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 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 Ok(ScriptBuf::new())
136 }
137 }
138 }
139
140 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 false
154 }
155
156 fn check_sanity(&self) -> bool {
157 true
159 }
160
161 fn estimate_weight(&self) -> usize {
162 match &self.policy {
164 MiniscriptPolicy::PublicKey(_) => 73, MiniscriptPolicy::Multi { k, keys } => {
166 73 * k + 34 * keys.len()
168 }
169 MiniscriptPolicy::Threshold { k: _, policies } => {
170 policies.iter().map(|_| 100).sum()
172 }
173 _ => 100, }
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#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct PolicyAnalysis {
212 pub is_malleable: bool,
214 pub is_sane: bool,
216 pub max_satisfaction_weight: usize,
218 pub requires_timelock: bool,
220 pub requires_hash_preimage: bool,
222}
223
224pub struct MiniscriptCompiler {
226 network: Network,
227}
228
229impl MiniscriptCompiler {
230 pub fn new(network: Network) -> Self {
232 Self { network }
233 }
234
235 pub fn parse_policy(&self, policy_str: &str) -> Result<MiniscriptPolicy> {
237 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 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 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 pub fn optimize(&self, policy: MiniscriptPolicy) -> MiniscriptPolicy {
306 policy
308 }
309}
310
311pub struct PolicyTemplateBuilder {
313 #[allow(dead_code)]
314 network: Network,
315}
316
317impl PolicyTemplateBuilder {
318 pub fn new(network: Network) -> Self {
320 Self { network }
321 }
322
323 pub fn single_key(&self, pubkey: String) -> MiniscriptPolicy {
325 MiniscriptPolicy::PublicKey(pubkey)
326 }
327
328 pub fn multisig(&self, k: usize, keys: Vec<String>) -> MiniscriptPolicy {
330 MiniscriptPolicy::Multi { k, keys }
331 }
332
333 pub fn absolute_timelock(&self, value: u32) -> MiniscriptPolicy {
335 MiniscriptPolicy::Timelock {
336 timelock_type: TimelockType::After,
337 value,
338 }
339 }
340
341 pub fn relative_timelock(&self, value: u32) -> MiniscriptPolicy {
343 MiniscriptPolicy::Timelock {
344 timelock_type: TimelockType::Older,
345 value,
346 }
347 }
348
349 pub fn hash_preimage(&self, hash_type: HashType, hash: String) -> MiniscriptPolicy {
351 MiniscriptPolicy::Hash { hash_type, hash }
352 }
353
354 pub fn and(&self, left: MiniscriptPolicy, right: MiniscriptPolicy) -> MiniscriptPolicy {
356 MiniscriptPolicy::And(Box::new(left), Box::new(right))
357 }
358
359 pub fn or(&self, left: MiniscriptPolicy, right: MiniscriptPolicy) -> MiniscriptPolicy {
361 MiniscriptPolicy::Or(Box::new(left), Box::new(right))
362 }
363
364 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}