Skip to content

Commit d60e820

Browse files
committed
Added miniscript check_safety
1 parent 72bf613 commit d60e820

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ pub enum Error {
350350
/// Anything but c:pk(key) (P2PK), c:pk_h(key) (P2PKH), and thresh_m(k,...)
351351
/// up to n=3 is invalid by standardness (bare)
352352
NonStandardBareScript,
353+
/// Analysis Error
354+
AnalysisError(miniscript::analyzable::AnalysisError),
353355
}
354356

355357
#[doc(hidden)]
@@ -370,6 +372,13 @@ impl From<miniscript::context::ScriptContextError> for Error {
370372
}
371373
}
372374

375+
#[doc(hidden)]
376+
impl From<miniscript::analyzable::AnalysisError> for Error {
377+
fn from(e: miniscript::analyzable::AnalysisError) -> Error {
378+
Error::AnalysisError(e)
379+
}
380+
}
381+
373382
#[doc(hidden)]
374383
impl From<bitcoin::secp256k1::Error> for Error {
375384
fn from(e: bitcoin::secp256k1::Error) -> Error {
@@ -467,6 +476,7 @@ impl fmt::Display for Error {
467476
up to n=3 is invalid by standardness (bare).
468477
"
469478
),
479+
Error::AnalysisError(ref e) => e.fmt(f),
470480
}
471481
}
472482
}

src/miniscript/analyzable.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
}

src/miniscript/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use bitcoin::blockdata::script;
3232

3333
pub use self::context::{Bare, Legacy, Segwitv0};
3434

35+
pub mod analyzable;
3536
pub mod astelem;
3637
pub(crate) mod context;
3738
pub mod decode;

0 commit comments

Comments
 (0)