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(())
        }
    }
}