miniscript_debug/miniscript/
analyzable.rs

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