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