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
// Miniscript Analysis
// Written in 2018 by
// Andrew Poelstra <apoelstra@wpsoftware.net>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! Miniscript Analysis
//!
//! Tools for determining whether the guarantees offered by the library
//! actually hold.
use error;
use miniscript::iter::PkPkh;
use std::collections::HashSet;
use std::fmt;
use {Miniscript, MiniscriptKey, ScriptContext};
/// 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)]
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,
}
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")
}
}
}
impl error::Error for AnalysisError {}
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 {
match Ctx::check_local_validity(&self) {
Ok(_) => true,
Err(_) => false,
}
}
/// 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_pkh().count();
let unique_pkhs_len = self
.iter_pk_pkh()
.map(|pk_pkh| match pk_pkh {
PkPkh::PlainPubkey(pk) => pk.to_pubkeyhash(),
PkPkh::HashedPubkey(h) => h,
})
.collect::<HashSet<_>>()
.len();
unique_pkhs_len != all_pkhs_len
}
/// 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(())
}
}
}