elements_miniscript/miniscript/
analyzable.rs

1// Written in 2018 by Andrew Poelstra <apoelstra@wpsoftware.net>
2// SPDX-License-Identifier: CC0-1.0
3
4//!  Miniscript Analysis
5//!
6//! Tools for determining whether the guarantees offered by the library
7//! actually hold.
8
9use std::collections::HashSet;
10use std::fmt;
11
12use crate::{error, Extension, Miniscript, MiniscriptKey, ScriptContext, Terminal};
13
14/// Params for parsing miniscripts that either non-sane or non-specified(experimental) in the spec.
15/// Used as a parameter [`Miniscript::from_str_ext`] and [`Miniscript::parse_with_ext`].
16///
17/// This allows parsing miniscripts if
18/// 1. It is unsafe(does not require a digital signature to spend it)
19/// 2. It contains a unspendable path because of either
20///     a. Resource limitations
21///     b. Timelock Mixing
22/// 3. The script is malleable and thereby some of satisfaction weight
23///    guarantees are not satisfied.
24/// 4. It has repeated public keys
25/// 5. raw pkh fragments without the pk. This could be obtained when parsing miniscript from script
26#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
27pub struct ExtParams {
28    /// Allow parsing of non-safe miniscripts
29    pub top_unsafe: bool,
30    /// Allow parsing of miniscripts with unspendable paths
31    pub resource_limitations: bool,
32    /// Allow parsing of miniscripts with timelock mixing
33    pub timelock_mixing: bool,
34    /// Allow parsing of malleable miniscripts
35    pub malleability: bool,
36    /// Allow parsing of miniscripts with repeated public keys
37    pub repeated_pk: bool,
38    /// Allow parsing of miniscripts with raw pkh fragments without the pk.
39    /// This could be obtained when parsing miniscript from script
40    pub raw_pkh: bool,
41}
42
43impl ExtParams {
44    /// Create a new ExtParams that with all the sanity rules
45    pub fn new() -> ExtParams {
46        ExtParams {
47            top_unsafe: false,
48            resource_limitations: false,
49            timelock_mixing: false,
50            malleability: false,
51            repeated_pk: false,
52            raw_pkh: false,
53        }
54    }
55
56    /// Create a new ExtParams that allows all the sanity rules
57    pub fn sane() -> ExtParams {
58        ExtParams::new()
59    }
60
61    /// Create a new ExtParams that insanity rules
62    /// This enables parsing well specified but "insane" miniscripts.
63    /// Refer to the [`ExtParams`] documentation for more details on "insane" miniscripts.
64    pub fn insane() -> ExtParams {
65        ExtParams {
66            top_unsafe: true,
67            resource_limitations: true,
68            timelock_mixing: true,
69            malleability: true,
70            repeated_pk: true,
71            raw_pkh: false,
72        }
73    }
74
75    /// Enable all non-sane rules and experimental rules
76    pub fn allow_all() -> ExtParams {
77        ExtParams {
78            top_unsafe: true,
79            resource_limitations: true,
80            timelock_mixing: true,
81            malleability: true,
82            repeated_pk: true,
83            raw_pkh: true,
84        }
85    }
86
87    /// Builder that allows non-safe miniscripts.
88    pub fn top_unsafe(mut self) -> ExtParams {
89        self.top_unsafe = true;
90        self
91    }
92
93    /// Builder that allows miniscripts with exceed resource limitations.
94    pub fn exceed_resource_limitations(mut self) -> ExtParams {
95        self.resource_limitations = true;
96        self
97    }
98
99    /// Builder that allows miniscripts with timelock mixing.
100    pub fn timelock_mixing(mut self) -> ExtParams {
101        self.timelock_mixing = true;
102        self
103    }
104
105    /// Builder that allows malleable miniscripts.
106    pub fn malleability(mut self) -> ExtParams {
107        self.malleability = true;
108        self
109    }
110
111    /// Builder that allows miniscripts with repeated public keys.
112    pub fn repeated_pk(mut self) -> ExtParams {
113        self.repeated_pk = true;
114        self
115    }
116
117    /// Builder that allows miniscripts with raw pkh fragments.
118    pub fn raw_pkh(mut self) -> ExtParams {
119        self.raw_pkh = true;
120        self
121    }
122}
123
124/// We currently mark Miniscript as Non-Analyzable if
125/// 1. It is unsafe(does not require a digital signature to spend it)
126/// 2. It contains a unspendable path because of either
127///     a. Resource limitations
128///     b. Timelock Mixing
129/// 3. The script is malleable and thereby some of satisfaction weight
130///    guarantees are not satisfied.
131/// 4. It has repeated publickeys
132#[derive(Debug, PartialEq)]
133pub enum AnalysisError {
134    /// Top level is not safe.
135    SiglessBranch,
136    /// Repeated Pubkeys
137    RepeatedPubkeys,
138    /// Miniscript contains at least one path that exceeds resource limits
139    BranchExceedResouceLimits,
140    /// Contains a combination of heightlock and timelock
141    HeightTimelockCombination,
142    /// Malleable script
143    Malleable,
144    /// Contains partial descriptor raw pkh
145    ContainsRawPkh,
146}
147
148impl fmt::Display for AnalysisError {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        match *self {
151            AnalysisError::SiglessBranch => {
152                f.write_str("All spend paths must require a signature")
153            }
154            AnalysisError::RepeatedPubkeys => {
155                f.write_str("Miniscript contains repeated pubkeys or pubkeyhashes")
156            }
157            AnalysisError::BranchExceedResouceLimits => {
158                f.write_str("At least one spend path exceeds the resource limits(stack depth/satisfaction size..)")
159            }
160            AnalysisError::HeightTimelockCombination => {
161                f.write_str("Contains a combination of heightlock and timelock")
162            }
163            AnalysisError::Malleable => f.write_str("Miniscript is malleable"),
164            AnalysisError::ContainsRawPkh => f.write_str("Miniscript contains raw pkh"),
165        }
166    }
167}
168
169impl error::Error for AnalysisError {
170    fn cause(&self) -> Option<&dyn error::Error> {
171        use self::AnalysisError::*;
172
173        match self {
174            SiglessBranch
175            | RepeatedPubkeys
176            | BranchExceedResouceLimits
177            | HeightTimelockCombination
178            | Malleable
179            | ContainsRawPkh => None,
180        }
181    }
182}
183
184impl<Pk: MiniscriptKey, Ctx: ScriptContext, Ext: Extension> Miniscript<Pk, Ctx, Ext> {
185    /// Whether all spend paths of miniscript require a signature
186    pub fn requires_sig(&self) -> bool {
187        self.ty.mall.safe
188    }
189
190    /// Whether the miniscript is malleable
191    pub fn is_non_malleable(&self) -> bool {
192        self.ty.mall.non_malleable
193    }
194
195    /// Whether the miniscript can exceed the resource limits(Opcodes, Stack limit etc)
196    // It maybe possible to return a detail error type containing why the miniscript
197    // failed. But doing so may require returning a collection of errors
198    pub fn within_resource_limits(&self) -> bool {
199        Ctx::check_local_validity(self).is_ok()
200    }
201
202    /// Whether the miniscript contains a combination of timelocks
203    pub fn has_mixed_timelocks(&self) -> bool {
204        self.ext.timelock_info.contains_unspendable_path()
205    }
206
207    /// Whether the miniscript has repeated Pk or Pkh
208    pub fn has_repeated_keys(&self) -> bool {
209        // Simple way to check whether all of these are correct is
210        // to have an iterator
211        let all_pkhs_len = self.iter_pk().count();
212
213        let unique_pkhs_len = self.iter_pk().collect::<HashSet<_>>().len();
214
215        unique_pkhs_len != all_pkhs_len
216    }
217
218    /// Whether the given miniscript contains a raw pkh fragment
219    pub fn contains_raw_pkh(&self) -> bool {
220        self.iter()
221            .any(|ms| matches!(ms.node, Terminal::RawPkH(..)))
222    }
223
224    /// Check whether the underlying Miniscript is safe under the current context
225    /// Lifting these polices would create a semantic representation that does
226    /// not represent the underlying semantics when miniscript is spent.
227    /// Signing logic may not find satisfaction even if one exists.
228    ///
229    /// For most cases, users should be dealing with safe scripts.
230    /// Use this function to check whether the guarantees of library hold.
231    /// Most functions of the library like would still
232    /// work, but results cannot be relied upon
233    pub fn sanity_check(&self) -> Result<(), AnalysisError> {
234        if !self.requires_sig() {
235            Err(AnalysisError::SiglessBranch)
236        } else if !self.is_non_malleable() {
237            Err(AnalysisError::Malleable)
238        } else if !self.within_resource_limits() {
239            Err(AnalysisError::BranchExceedResouceLimits)
240        } else if self.has_repeated_keys() {
241            Err(AnalysisError::RepeatedPubkeys)
242        } else if self.has_mixed_timelocks() {
243            Err(AnalysisError::HeightTimelockCombination)
244        } else {
245            Ok(())
246        }
247    }
248
249    /// Check whether the miniscript follows the given Extra policy [`ExtParams`]
250    pub fn ext_check(&self, ext: &ExtParams) -> Result<(), AnalysisError> {
251        if !ext.top_unsafe && !self.requires_sig() {
252            Err(AnalysisError::SiglessBranch)
253        } else if !ext.malleability && !self.is_non_malleable() {
254            Err(AnalysisError::Malleable)
255        } else if !ext.resource_limitations && !self.within_resource_limits() {
256            Err(AnalysisError::BranchExceedResouceLimits)
257        } else if !ext.repeated_pk && self.has_repeated_keys() {
258            Err(AnalysisError::RepeatedPubkeys)
259        } else if !ext.timelock_mixing && self.has_mixed_timelocks() {
260            Err(AnalysisError::HeightTimelockCombination)
261        } else if !ext.raw_pkh && self.contains_raw_pkh() {
262            Err(AnalysisError::ContainsRawPkh)
263        } else {
264            Ok(())
265        }
266    }
267}