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}