|
| 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 gurantees offered by the library |
| 18 | +//! actually hold. |
| 19 | +
|
| 20 | +use error; |
| 21 | +use miniscript::iter::PkPkh; |
| 22 | +use std::collections::HashSet; |
| 23 | +use std::fmt; |
| 24 | +use {Miniscript, MiniscriptKey, ScriptContext}; |
| 25 | +/// Possible reasons Miniscript guarantees can fail |
| 26 | +/// We currently mark Miniscript as Non-Analyzable if |
| 27 | +/// 1. It is unsafe(does not require a digital signature to spend it) |
| 28 | +/// 2. It contains a unspendable path because of either |
| 29 | +/// a. Resource limitations |
| 30 | +/// b. Timelock Mixing |
| 31 | +/// 3. The script is malleable and thereby some of satisfaction weight |
| 32 | +/// gurantees are not satisfied. |
| 33 | +/// 4. It has repeated publickeys |
| 34 | +#[derive(Debug)] |
| 35 | +pub enum AnalysisError { |
| 36 | + /// Top level is not safe. |
| 37 | + TopLevelNonSafe, |
| 38 | + /// Repeated Pubkeys |
| 39 | + RepeatedPubkeys, |
| 40 | + /// Miniscript contains atleast one path that exceeds resource limits |
| 41 | + ResourceLimitsExceeded, |
| 42 | + /// Contains a combination of heightlock and timelock |
| 43 | + HeightTimeLockCombination, |
| 44 | + /// Malleable script |
| 45 | + Malleable, |
| 46 | +} |
| 47 | + |
| 48 | +impl fmt::Display for AnalysisError { |
| 49 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 50 | + match *self { |
| 51 | + AnalysisError::TopLevelNonSafe => { |
| 52 | + f.write_str("All spend paths must require a signature") |
| 53 | + } |
| 54 | + AnalysisError::RepeatedPubkeys => { |
| 55 | + f.write_str("Miniscript contains repeated pubkeys or pubkeyhashes") |
| 56 | + } |
| 57 | + AnalysisError::ResourceLimitsExceeded => { |
| 58 | + f.write_str("Atleast one spend path exceeds the resource limits(stack depth/satisfaction size..)") |
| 59 | + } |
| 60 | + AnalysisError::HeightTimeLockCombination => { |
| 61 | + f.write_str("Contains a combination of heightlock and timelock") |
| 62 | + } |
| 63 | + AnalysisError::Malleable => f.write_str("Miniscript is malleable") |
| 64 | + } |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +impl error::Error for AnalysisError { |
| 69 | + fn description(&self) -> &str { |
| 70 | + "" |
| 71 | + } |
| 72 | + fn cause(&self) -> Option<&error::Error> { |
| 73 | + None |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> { |
| 78 | + /// Whether all spend paths of miniscript require a signature |
| 79 | + pub fn requires_sig(&self) -> bool { |
| 80 | + self.ty.mall.safe |
| 81 | + } |
| 82 | + |
| 83 | + /// Whether the miniscript is malleable |
| 84 | + pub fn is_non_malleable(&self) -> bool { |
| 85 | + self.ty.mall.non_malleable |
| 86 | + } |
| 87 | + |
| 88 | + /// Whether the miniscript can exceed the resource limits(Opcodes, Stack limit etc) |
| 89 | + // It maybe possible to return a detail error type containing why the miniscript |
| 90 | + // failed. But doing so may require returning a collection of errors |
| 91 | + pub fn within_resource_limits(&self) -> bool { |
| 92 | + match Ctx::check_satisfaction_rules(&self) { |
| 93 | + Ok(_) => true, |
| 94 | + Err(_) => false, |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + /// Whether the miniscript contains a combination of timelocks |
| 99 | + pub fn has_mixed_timelocks(&self) -> bool { |
| 100 | + self.ext.timelock_info.contains_unspendable_path() |
| 101 | + } |
| 102 | + |
| 103 | + /// Whether the miniscript has repeated Pk or Pkh |
| 104 | + pub fn has_repeated_keys(&self) -> bool { |
| 105 | + // Simple way to check whether all of these are correct is |
| 106 | + // to have an iterator |
| 107 | + let all_pkhs_len = self |
| 108 | + .iter_pk_pkh() |
| 109 | + .map(|pk_pkh| match pk_pkh { |
| 110 | + PkPkh::PlainPubkey(pk) => pk.to_pubkeyhash(), |
| 111 | + PkPkh::HashedPubkey(h) => h, |
| 112 | + }) |
| 113 | + .count(); |
| 114 | + |
| 115 | + let unique_pkhs_len = self |
| 116 | + .iter_pk_pkh() |
| 117 | + .map(|pk_pkh| match pk_pkh { |
| 118 | + PkPkh::PlainPubkey(pk) => pk.to_pubkeyhash(), |
| 119 | + PkPkh::HashedPubkey(h) => h, |
| 120 | + }) |
| 121 | + .collect::<HashSet<_>>() |
| 122 | + .len(); |
| 123 | + |
| 124 | + unique_pkhs_len != all_pkhs_len |
| 125 | + } |
| 126 | + |
| 127 | + /// Check whether the underlying Miniscript is safe under the current context |
| 128 | + /// Lifting these polices would create a semantic representation that does |
| 129 | + /// not represent the underlying semantics when miniscript is spent. |
| 130 | + /// Signing logic may not find satisfaction even if one exists. |
| 131 | + /// |
| 132 | + /// For most cases, users should be dealing with safe scripts. |
| 133 | + /// Use this function to check whether the guarantees of library hold. |
| 134 | + /// Most functions of the library like would still |
| 135 | + /// work, but results cannot be relied upon |
| 136 | + pub fn check_safety(&self) -> Result<(), AnalysisError> { |
| 137 | + if !self.requires_sig() { |
| 138 | + Err(AnalysisError::TopLevelNonSafe) |
| 139 | + } else if !self.is_non_malleable() { |
| 140 | + Err(AnalysisError::Malleable) |
| 141 | + } else if !self.within_resource_limits() { |
| 142 | + Err(AnalysisError::ResourceLimitsExceeded) |
| 143 | + } else if self.has_repeated_keys() { |
| 144 | + Err(AnalysisError::RepeatedPubkeys) |
| 145 | + } else if self.has_mixed_timelocks() { |
| 146 | + Err(AnalysisError::HeightTimeLockCombination) |
| 147 | + } else { |
| 148 | + Ok(()) |
| 149 | + } |
| 150 | + } |
| 151 | +} |
0 commit comments