Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/miniscript/analyzable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,36 @@
//! Tools for determining whether the guarantees offered by the library
//! actually hold.

use crate::miniscript::types::Malleability;
use crate::prelude::*;
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal};

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.signed }
/// Whether all spend paths of miniscript require a signature.
///
/// Returns `false` for a malleable miniscript. The "s" property only
/// describes the satisfactions of an expression that is non-malleable to
/// begin with, so nothing is known for a malleable one, and `false` is the
/// assumption that keeps a caller from relying on a signature being
/// required.
#[deprecated(since = "TBD", note = "use non_malleable_and_requires_sig instead")]
pub fn requires_sig(&self) -> bool { self.non_malleable_and_requires_sig() }

/// Whether the miniscript is non-malleable and all of its spend paths
/// require a signature.
///
/// Returns `false` for a malleable miniscript. The "s" property only
/// describes the satisfactions of an expression that is non-malleable to
/// begin with, so nothing is known for a malleable one, and `false` is the
/// assumption that keeps a caller from relying on a signature being
/// required.
pub fn non_malleable_and_requires_sig(&self) -> bool {
matches!(self.ty.mall, Malleability::NonMalleable { signed: true, .. })
}

/// Whether the miniscript is malleable
pub fn is_non_malleable(&self) -> bool { self.ty.mall.non_malleable }
/// Whether the miniscript is guaranteed to have a non-malleable
/// satisfaction, if it has a satisfaction at all.
pub fn is_non_malleable(&self) -> bool { self.ty.mall.is_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
Expand Down
53 changes: 46 additions & 7 deletions src/miniscript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ mod private {
}

// Sigless branches can be fixed by adding a conjunction with a signature.
if !params.allow_sigless_branch && !self.requires_sig() {
if !params.allow_sigless_branch && !self.non_malleable_and_requires_sig() {
return Err(ValidationError::SiglessBranch);
}

Expand Down Expand Up @@ -636,7 +636,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
let satisfaction = satisfy::Satisfaction::satisfy(
self,
&satisfier,
self.ty.mall.signed,
self.non_malleable_and_requires_sig(),
self.leaf_hash_internal(),
);
self._satisfy(satisfaction)
Expand All @@ -654,7 +654,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
let satisfaction = satisfy::Satisfaction::satisfy_mall(
self,
&satisfier,
self.ty.mall.signed,
self.non_malleable_and_requires_sig(),
self.leaf_hash_internal(),
);
self._satisfy(satisfaction)
Expand Down Expand Up @@ -683,7 +683,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
satisfy::Satisfaction::build_template(
self,
provider,
self.ty.mall.signed,
self.non_malleable_and_requires_sig(),
self.leaf_hash_internal(),
)
}
Expand All @@ -699,7 +699,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
satisfy::Satisfaction::build_template_mall(
self,
provider,
self.ty.mall.signed,
self.non_malleable_and_requires_sig(),
self.leaf_hash_internal(),
)
}
Expand Down Expand Up @@ -1345,8 +1345,8 @@ mod tests {
match (ms, valid) {
(Ok(ms), true) => {
assert_eq!(format!("{:x}", ms.encode()), expected_hex);
assert_eq!(ms.ty.mall.non_malleable, non_mal);
assert_eq!(ms.ty.mall.signed, need_sig);
assert_eq!(ms.is_non_malleable(), non_mal);
assert_eq!(ms.non_malleable_and_requires_sig(), non_mal && need_sig);
assert_eq!(ms.ext.static_ops + ms.ext.sat_data.unwrap().max_exec_op_count, ops);
}
(Err(_), false) => {}
Expand Down Expand Up @@ -2233,4 +2233,43 @@ mod tests {
);
}
}
#[test]
fn malleable_has_no_malleability_properties() {
// or_b requires one of its children to be signed. Two hash fragments
// are not, so the expression is valid but malleable, and then it
// carries none of the three malleability properties.
let hash = sha256::Hash::hash(&[]);
let malleable = Miniscript::<String, Segwitv0>::from_str_insane(&format!(
"or_b(sha256({}),a:sha256({}))",
hash, hash
))
.unwrap();
assert_eq!(malleable.ty.mall, types::Malleability::Malleable);
assert!(!malleable.is_non_malleable());
assert!(!malleable.non_malleable_and_requires_sig());
assert_eq!(malleable.ty.to_string(), "B/du");

// The same shape with signed children keeps all of them.
let sane = Miniscript::<String, Segwitv0>::from_str_insane("or_b(pk(A),a:pk(B))").unwrap();
assert_eq!(
sane.ty.mall,
types::Malleability::NonMalleable { dissat: types::Dissat::Unique, signed: true }
);
assert!(sane.is_non_malleable());
assert!(sane.non_malleable_and_requires_sig());
assert_eq!(sane.ty.to_string(), "B/duesm");

// The or_b above was never signed, so it only pins the erasure of the
// dissatisfaction property. This one did carry "s": and_b is signed if
// either child is, and the multi is, so it rendered as "B/ndus" while
// still being malleable.
let signed_but_malleable = Miniscript::<String, Segwitv0>::from_str_insane(
"j:and_b(multi(2,A,B),s:or_i(older(1),older(4252898)))",
)
.unwrap();
assert_eq!(signed_but_malleable.ty.mall, types::Malleability::Malleable);
assert!(!signed_but_malleable.is_non_malleable());
assert!(!signed_but_malleable.non_malleable_and_requires_sig());
assert_eq!(signed_but_malleable.ty.to_string(), "B/ndu");
}
}
Loading
Loading