Skip to content

Commit

Permalink
feat: add support for OP_DROP opcode
Browse files Browse the repository at this point in the history
Implemented with the fragment `r:`.
  • Loading branch information
OttoAllmendinger committed Oct 25, 2024
1 parent 98104b9 commit e450ce1
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,13 +682,13 @@ where
Terminal::DupIf(ref _sub) if node_state.n_evaluated == 1 => {
self.stack.push(stack::Element::Satisfied);
}
Terminal::ZeroNotEqual(ref sub) | Terminal::Verify(ref sub)
Terminal::ZeroNotEqual(ref sub) | Terminal::Verify(ref sub) | Terminal::Drop(ref sub)
if node_state.n_evaluated == 0 =>
{
self.push_evaluation_state(node_state.node, 1, 0);
self.push_evaluation_state(sub, 0, 0);
}
Terminal::Verify(ref _sub) if node_state.n_evaluated == 1 => {
Terminal::Verify(ref _sub) | Terminal::Drop(ref _sub) if node_state.n_evaluated == 1 => {
match self.stack.pop() {
Some(stack::Element::Satisfied) => (),
Some(_) => return Some(Err(Error::VerifyFailed)),
Expand Down
3 changes: 3 additions & 0 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk,
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
AndV(ref left, ref right)
Expand Down Expand Up @@ -63,6 +64,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc<Miniscript<
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
AndV(ref left, ref right)
Expand Down Expand Up @@ -93,6 +95,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal<Pk, Ct
| Check(ref sub)
| DupIf(ref sub)
| Verify(ref sub)
| Drop(ref sub)
| NonZero(ref sub)
| ZeroNotEqual(ref sub) => Tree::Unary(sub.as_inner()),
AndV(ref left, ref right)
Expand Down
21 changes: 20 additions & 1 deletion src/miniscript/analyzable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub struct ExtParams {
/// Allow parsing of miniscripts with raw pkh fragments without the pk.
/// This could be obtained when parsing miniscript from script
pub raw_pkh: bool,
/// Allow parsing of miniscripts with drop fragments (`r`)
pub drop: bool,
}

impl ExtParams {
Expand All @@ -51,6 +53,7 @@ impl ExtParams {
malleability: false,
repeated_pk: false,
raw_pkh: false,
drop: false,
}
}

Expand All @@ -68,6 +71,7 @@ impl ExtParams {
malleability: true,
repeated_pk: true,
raw_pkh: false,
drop: true,
}
}

Expand All @@ -80,6 +84,7 @@ impl ExtParams {
malleability: true,
repeated_pk: true,
raw_pkh: true,
drop: true,
}
}

Expand Down Expand Up @@ -118,6 +123,11 @@ impl ExtParams {
self.raw_pkh = true;
self
}

pub fn drop(mut self) -> ExtParams {
self.drop = true;
self
}
}

/// Possible reasons Miniscript guarantees can fail
Expand All @@ -143,6 +153,8 @@ pub enum AnalysisError {
Malleable,
/// Contains partial descriptor raw pkh
ContainsRawPkh,
/// Contains a drop fragment
ContainsDrop,
}

impl fmt::Display for AnalysisError {
Expand All @@ -162,6 +174,7 @@ impl fmt::Display for AnalysisError {
}
AnalysisError::Malleable => f.write_str("Miniscript is malleable"),
AnalysisError::ContainsRawPkh => f.write_str("Miniscript contains raw pkh"),
AnalysisError::ContainsDrop => f.write_str("Miniscript contains drop fragment"),
}
}
}
Expand All @@ -177,7 +190,7 @@ impl error::Error for AnalysisError {
| BranchExceedResouceLimits
| HeightTimelockCombination
| Malleable
| ContainsRawPkh => None,
| ContainsRawPkh | ContainsDrop => None,
}
}
}
Expand Down Expand Up @@ -213,6 +226,10 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
self.iter().any(|ms| matches!(ms.node, Terminal::RawPkH(_)))
}

pub fn contains_drop(&self) -> bool {
self.iter().any(|ms| matches!(ms.node, Terminal::Drop(_)))
}

/// 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.
Expand Down Expand Up @@ -252,6 +269,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
Err(AnalysisError::HeightTimelockCombination)
} else if !ext.raw_pkh && self.contains_raw_pkh() {
Err(AnalysisError::ContainsRawPkh)
} else if !ext.drop && self.contains_drop() {
Err(AnalysisError::ContainsDrop)
} else {
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
.push_astelem(sub)
.push_opcode(opcodes::all::OP_ENDIF),
Terminal::Verify(ref sub) => builder.push_astelem(sub).push_verify(),
Terminal::Drop(ref sub) => builder.push_astelem(sub).push_opcode(opcodes::all::OP_DROP),
Terminal::NonZero(ref sub) => builder
.push_opcode(opcodes::all::OP_SIZE)
.push_opcode(opcodes::all::OP_0NOTEQUAL)
Expand Down
9 changes: 9 additions & 0 deletions src/miniscript/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ enum NonTerm {
Check,
DupIf,
Verify,
Drop,
NonZero,
ZeroNotEqual,
AndV,
Expand Down Expand Up @@ -156,6 +157,8 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
DupIf(Arc<Miniscript<Pk, Ctx>>),
/// `[T] VERIFY`
Verify(Arc<Miniscript<Pk, Ctx>>),
/// `[T] DROP`
Drop(Arc<Miniscript<Pk, Ctx>>),
/// `SIZE 0NOTEQUAL IF [Fn] ENDIF`
NonZero(Arc<Miniscript<Pk, Ctx>>),
/// `[X] 0NOTEQUAL`
Expand Down Expand Up @@ -207,6 +210,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Clone for Terminal<Pk, Ctx> {
Terminal::Check(ref sub) => Terminal::Check(Arc::new(Miniscript::clone(sub))),
Terminal::DupIf(ref sub) => Terminal::DupIf(Arc::new(Miniscript::clone(sub))),
Terminal::Verify(ref sub) => Terminal::Verify(Arc::new(Miniscript::clone(sub))),
Terminal::Drop(ref sub) => Terminal::Drop(Arc::new(Miniscript::clone(sub))),
Terminal::NonZero(ref sub) => Terminal::NonZero(Arc::new(Miniscript::clone(sub))),
Terminal::ZeroNotEqual(ref sub) => {
Terminal::ZeroNotEqual(Arc::new(Miniscript::clone(sub)))
Expand Down Expand Up @@ -460,6 +464,10 @@ pub fn parse<Ctx: ScriptContext>(
non_term.push(NonTerm::Expression);
},
),
Tk::Drop => {
non_term.push(NonTerm::Drop);
non_term.push(NonTerm::Expression);
},
Tk::ZeroNotEqual => {
non_term.push(NonTerm::ZeroNotEqual);
non_term.push(NonTerm::Expression);
Expand Down Expand Up @@ -612,6 +620,7 @@ pub fn parse<Ctx: ScriptContext>(
Some(NonTerm::Check) => term.reduce1(Terminal::Check)?,
Some(NonTerm::DupIf) => term.reduce1(Terminal::DupIf)?,
Some(NonTerm::Verify) => term.reduce1(Terminal::Verify)?,
Some(NonTerm::Drop) => term.reduce1(Terminal::Drop)?,
Some(NonTerm::NonZero) => term.reduce1(Terminal::NonZero)?,
Some(NonTerm::ZeroNotEqual) => term.reduce1(Terminal::ZeroNotEqual)?,
Some(NonTerm::AndV) => {
Expand Down
2 changes: 2 additions & 0 deletions src/miniscript/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for DisplayNode<'a, Pk,
| Terminal::Swap(ref sub)
| Terminal::DupIf(ref sub)
| Terminal::Verify(ref sub)
| Terminal::Drop(ref sub)
| Terminal::NonZero(ref sub)
| Terminal::ZeroNotEqual(ref sub) => {
Tree::Unary(DisplayNode::Node(sub.ty, sub.as_inner()))
Expand Down Expand Up @@ -256,6 +257,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
Terminal::Check(..) => "c",
Terminal::DupIf(..) => "d",
Terminal::Verify(..) => "v",
Terminal::Drop(..) => "r",
Terminal::NonZero(..) => "j",
Terminal::ZeroNotEqual(..) => "n",
Terminal::AndV(_, ref r) if matches!(r.as_inner(), Terminal::True) => "t",
Expand Down
43 changes: 43 additions & 0 deletions src/miniscript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ mod private {
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
Terminal::Drop(..) => Terminal::Drop(stack.pop().unwrap()),
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
Terminal::AndV(..) => {
Expand Down Expand Up @@ -236,6 +237,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
Terminal::After(n) => script_num_size(n.to_consensus_u32() as usize) + 1,
Terminal::Older(n) => script_num_size(n.to_consensus_u32() as usize) + 1,
Terminal::Verify(ref sub) => usize::from(!sub.ext.has_free_verify),
Terminal::Drop(..) => 1,
Terminal::Thresh(ref thresh) => {
script_num_size(thresh.k()) // k
+ 1 // EQUAL
Expand Down Expand Up @@ -553,6 +555,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
Terminal::Check(..) => Terminal::Check(translated.pop().unwrap()),
Terminal::DupIf(..) => Terminal::DupIf(translated.pop().unwrap()),
Terminal::Verify(..) => Terminal::Verify(translated.pop().unwrap()),
Terminal::Drop(..) => Terminal::Drop(translated.pop().unwrap()),
Terminal::NonZero(..) => Terminal::NonZero(translated.pop().unwrap()),
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(translated.pop().unwrap()),
Terminal::AndV(..) => {
Expand Down Expand Up @@ -618,6 +621,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
Terminal::Drop(..) => Terminal::Drop(stack.pop().unwrap()),
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
Terminal::AndV(..) => Terminal::AndV(stack.pop().unwrap(), stack.pop().unwrap()),
Expand Down Expand Up @@ -729,6 +733,7 @@ where
'c' => unwrapped = Terminal::Check(Arc::new(ms)),
'd' => unwrapped = Terminal::DupIf(Arc::new(ms)),
'v' => unwrapped = Terminal::Verify(Arc::new(ms)),
'r' => unwrapped = Terminal::Drop(Arc::new(ms)),
'j' => unwrapped = Terminal::NonZero(Arc::new(ms)),
'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)),
't' => unwrapped = Terminal::AndV(Arc::new(ms), Arc::new(Miniscript::TRUE)),
Expand Down Expand Up @@ -1202,6 +1207,9 @@ mod tests {
assert_eq!(abs.minimum_n_keys(), Some(3));

roundtrip(&ms_str!("older(921)"), "OP_PUSHBYTES_2 9903 OP_CSV");
roundtrip(&ms_str!("and_v(r:after(1024),1)"), "OP_PUSHBYTES_2 0004 OP_CLTV OP_DROP OP_PUSHNUM_1");
roundtrip(&ms_str!("and_v(r:older(1024),1)"), "OP_PUSHBYTES_2 0004 OP_CSV OP_DROP OP_PUSHNUM_1");
roundtrip(&ms_str!("and_v(r:1,1)"), "OP_PUSHNUM_1 OP_DROP OP_PUSHNUM_1");

roundtrip(
&ms_str!("sha256({})",sha256::Hash::hash(&[])),
Expand Down Expand Up @@ -1461,6 +1469,41 @@ mod tests {
assert!(ms_str.is_err());
}

#[test]
fn drop_wrapper() {
type SwMs = Miniscript<String, Segwitv0>;
fn assert_error(s: &str, expected_error: Option<&str>) {
match SwMs::from_str_insane(&s) {
Ok(_) => match expected_error {
Some(e) => {
panic!("Expected error: {}", e);
}
None => {
// do nothing
}
},
Err(e1) => match expected_error {
Some(e2) => assert_eq!(e1.to_string(), e2.to_string()),
None => { panic!("Unexpected error: {}", e1); }
},
}
}

{
assert_error("and_v(r:after(1024),1)", None);
}

{

fn assert_error_cannot_wrap(s: &str, prefix: &str) {
let err_cannot_wrap = format!("typecheck: fragment «{}:after(1024)» cannot wrap a fragment of type V", prefix);
assert_error(s, Some(&err_cannot_wrap));
}
assert_error_cannot_wrap("and_v(rr:after(1024),1)", "rr");
assert_error_cannot_wrap("and_v(rv:after(1024),1)", "rv");
}
}

#[test]
fn translate_tests() {
let ms = Miniscript::<String, Segwitv0>::from_str("pk(A)").unwrap();
Expand Down
9 changes: 8 additions & 1 deletion src/miniscript/ms_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#[cfg(test)]
mod tests {
use core::fmt;

use core::str::FromStr;
use crate::miniscript::types;
use crate::{Miniscript, Segwitv0};

Expand Down Expand Up @@ -15047,6 +15047,7 @@ mod tests {
#[cfg_attr(feature="cargo-fmt", rustfmt_skip)]
fn malleable_tests_from_alloy() {
ms_test("and_v(v:after(500000001),or_d(j:multi(2,A,B,C),multi(2,D,E,F)))", "usB");
ms_test("and_v(r:after(500000001),or_d(j:multi(2,A,B,C),multi(2,D,E,F)))", "usB");
ms_test("or_b(j:multi(2,A,B,C),a:andor(multi(2,D,E,F),multi(2,G,I,J),multi(2,K,L,M)))", "dBesu");
ms_test("andor(or_i(multi(2,A,B,C),0),sha256(c7bcb868ab4db55ca45f8eefe5b1677d9fc2c4111e295baaee1b34ed352c719b),multi(2,D,E,F))", "dBesu");
ms_test("or_d(or_i(0,or_i(multi(2,A,B,C),0)),multi(2,D,E,F))", "dBesu");
Expand Down Expand Up @@ -23855,4 +23856,10 @@ mod tests {

}
}

#[test]
pub fn test_opdrop() {
Miniscript::<String, Segwitv0>::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap();
let ms: Miniscript<String, Segwitv0> = Miniscript::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap();
}
}
2 changes: 2 additions & 0 deletions src/miniscript/satisfy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> {
| Terminal::Swap(ref sub)
| Terminal::Check(ref sub)
| Terminal::Verify(ref sub)
| Terminal::Drop(ref sub)
| Terminal::NonZero(ref sub)
| Terminal::ZeroNotEqual(ref sub) => {
Self::satisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn)
Expand Down Expand Up @@ -1647,6 +1648,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> {
| Terminal::Older(_)
| Terminal::After(_)
| Terminal::Verify(_)
| Terminal::Drop(_)
| Terminal::OrC(..) => Satisfaction {
stack: Witness::Impossible,
has_sig: false,
Expand Down
13 changes: 13 additions & 0 deletions src/miniscript/types/correctness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,19 @@ impl Correctness {
})
}

/// Constructor for the correctness properties of the `r:` fragment.
pub const fn cast_drop(self) -> Result<Self, ErrorKind> {
// from https://bitcoin.sipa.be/miniscript/:
// > Every miniscript expression has one of four basic types:
// > ...
// > "V" Verify expressions. Like "B", these take their inputs from the top of the stack.
// > Upon satisfaction however, they continue without pushing anything.
// > They cannot be dissatisfied (will abort instead).
// So while OP_DROP doesn't actually verify anything, the closest type is still `V`.
// We delegate to `cast_verify` to handle the rest of the properties.
Self::cast_verify(self)
}

/// Constructor for the correctness properties of the `j:` fragment.
pub const fn cast_nonzero(self) -> Result<Self, ErrorKind> {
if !self.input.constfn_eq(Input::OneNonZero) && !self.input.constfn_eq(Input::AnyNonZero) {
Expand Down
7 changes: 7 additions & 0 deletions src/miniscript/types/extra_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ impl ExtData {
}
}

/// Extra properties for the `r:` fragment.
pub fn cast_drop(self) -> Self {
// delegate to `cast_verify` as the properties are the same
self.cast_verify()
}

/// Extra properties for the `j:` fragment.
pub const fn cast_nonzero(self) -> Self {
ExtData {
Expand Down Expand Up @@ -947,6 +953,7 @@ impl ExtData {
Terminal::Check(ref sub) => Self::cast_check(sub.ext),
Terminal::DupIf(ref sub) => Self::cast_dupif(sub.ext),
Terminal::Verify(ref sub) => Self::cast_verify(sub.ext),
Terminal::Drop(ref sub) => Self::cast_drop(sub.ext),
Terminal::NonZero(ref sub) => Self::cast_nonzero(sub.ext),
Terminal::ZeroNotEqual(ref sub) => Self::cast_zeronotequal(sub.ext),
Terminal::AndB(ref l, ref r) => {
Expand Down
8 changes: 7 additions & 1 deletion src/miniscript/types/malleability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,17 @@ impl Malleability {
}
}

/// Constructor for the malleabilitiy properties of the `v:` fragment.
/// Constructor for the malleability properties of the `v:` fragment.
pub const fn cast_verify(self) -> Self {
Malleability { dissat: Dissat::None, safe: self.safe, non_malleable: self.non_malleable }
}

/// Constructor for the malleability properties of the `r:` fragment.
pub const fn cast_drop(self) -> Self {
// delegate to `cast_verify()`
self.cast_verify()
}

/// Constructor for the malleabilitiy properties of the `j:` fragment.
pub const fn cast_nonzero(self) -> Self {
Malleability {
Expand Down
Loading

0 comments on commit e450ce1

Please sign in to comment.