Skip to content

Commit a620f67

Browse files
feat: add support for OP_DROP opcode
Implemented with the fragment `r:`.
1 parent d8db3fa commit a620f67

16 files changed

+191
-5
lines changed

src/interpreter/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -682,13 +682,17 @@ where
682682
Terminal::DupIf(ref _sub) if node_state.n_evaluated == 1 => {
683683
self.stack.push(stack::Element::Satisfied);
684684
}
685-
Terminal::ZeroNotEqual(ref sub) | Terminal::Verify(ref sub)
685+
Terminal::ZeroNotEqual(ref sub)
686+
| Terminal::Verify(ref sub)
687+
| Terminal::Drop(ref sub)
686688
if node_state.n_evaluated == 0 =>
687689
{
688690
self.push_evaluation_state(node_state.node, 1, 0);
689691
self.push_evaluation_state(sub, 0, 0);
690692
}
691-
Terminal::Verify(ref _sub) if node_state.n_evaluated == 1 => {
693+
Terminal::Verify(ref _sub) | Terminal::Drop(ref _sub)
694+
if node_state.n_evaluated == 1 =>
695+
{
692696
match self.stack.pop() {
693697
Some(stack::Element::Satisfied) => (),
694698
Some(_) => return Some(Err(Error::VerifyFailed)),

src/iter/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript<Pk,
3333
| Check(ref sub)
3434
| DupIf(ref sub)
3535
| Verify(ref sub)
36+
| Drop(ref sub)
3637
| NonZero(ref sub)
3738
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
3839
AndV(ref left, ref right)
@@ -63,6 +64,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc<Miniscript<
6364
| Check(ref sub)
6465
| DupIf(ref sub)
6566
| Verify(ref sub)
67+
| Drop(ref sub)
6668
| NonZero(ref sub)
6769
| ZeroNotEqual(ref sub) => Tree::Unary(sub),
6870
AndV(ref left, ref right)
@@ -93,6 +95,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal<Pk, Ct
9395
| Check(ref sub)
9496
| DupIf(ref sub)
9597
| Verify(ref sub)
98+
| Drop(ref sub)
9699
| NonZero(ref sub)
97100
| ZeroNotEqual(ref sub) => Tree::Unary(sub.as_inner()),
98101
AndV(ref left, ref right)

src/miniscript/analyzable.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub struct ExtParams {
3939
/// Allow parsing of miniscripts with raw pkh fragments without the pk.
4040
/// This could be obtained when parsing miniscript from script
4141
pub raw_pkh: bool,
42+
/// Allow parsing of miniscripts with drop fragments (`r`)
43+
pub drop: bool,
4244
}
4345

4446
impl ExtParams {
@@ -51,6 +53,7 @@ impl ExtParams {
5153
malleability: false,
5254
repeated_pk: false,
5355
raw_pkh: false,
56+
drop: false,
5457
}
5558
}
5659

@@ -68,6 +71,7 @@ impl ExtParams {
6871
malleability: true,
6972
repeated_pk: true,
7073
raw_pkh: false,
74+
drop: true,
7175
}
7276
}
7377

@@ -80,6 +84,7 @@ impl ExtParams {
8084
malleability: true,
8185
repeated_pk: true,
8286
raw_pkh: true,
87+
drop: true,
8388
}
8489
}
8590

@@ -118,6 +123,11 @@ impl ExtParams {
118123
self.raw_pkh = true;
119124
self
120125
}
126+
127+
pub fn drop(mut self) -> ExtParams {
128+
self.drop = true;
129+
self
130+
}
121131
}
122132

123133
/// Possible reasons Miniscript guarantees can fail
@@ -143,6 +153,8 @@ pub enum AnalysisError {
143153
Malleable,
144154
/// Contains partial descriptor raw pkh
145155
ContainsRawPkh,
156+
/// Contains a drop fragment
157+
ContainsDrop,
146158
}
147159

148160
impl fmt::Display for AnalysisError {
@@ -162,6 +174,7 @@ impl fmt::Display for AnalysisError {
162174
}
163175
AnalysisError::Malleable => f.write_str("Miniscript is malleable"),
164176
AnalysisError::ContainsRawPkh => f.write_str("Miniscript contains raw pkh"),
177+
AnalysisError::ContainsDrop => f.write_str("Miniscript contains drop fragment"),
165178
}
166179
}
167180
}
@@ -177,7 +190,8 @@ impl error::Error for AnalysisError {
177190
| BranchExceedResouceLimits
178191
| HeightTimelockCombination
179192
| Malleable
180-
| ContainsRawPkh => None,
193+
| ContainsRawPkh
194+
| ContainsDrop => None,
181195
}
182196
}
183197
}
@@ -213,6 +227,10 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
213227
self.iter().any(|ms| matches!(ms.node, Terminal::RawPkH(_)))
214228
}
215229

230+
pub fn contains_drop(&self) -> bool {
231+
self.iter().any(|ms| matches!(ms.node, Terminal::Drop(_)))
232+
}
233+
216234
/// Check whether the underlying Miniscript is safe under the current context
217235
/// Lifting these polices would create a semantic representation that does
218236
/// not represent the underlying semantics when miniscript is spent.
@@ -252,6 +270,8 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
252270
Err(AnalysisError::HeightTimelockCombination)
253271
} else if !ext.raw_pkh && self.contains_raw_pkh() {
254272
Err(AnalysisError::ContainsRawPkh)
273+
} else if !ext.drop && self.contains_drop() {
274+
Err(AnalysisError::ContainsDrop)
255275
} else {
256276
Ok(())
257277
}

src/miniscript/astelem.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
193193
.push_astelem(sub)
194194
.push_opcode(opcodes::all::OP_ENDIF),
195195
Terminal::Verify(ref sub) => builder.push_astelem(sub).push_verify(),
196+
Terminal::Drop(ref sub) => builder.push_astelem(sub).push_opcode(opcodes::all::OP_DROP),
196197
Terminal::NonZero(ref sub) => builder
197198
.push_opcode(opcodes::all::OP_SIZE)
198199
.push_opcode(opcodes::all::OP_0NOTEQUAL)

src/miniscript/decode.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ enum NonTerm {
9090
Check,
9191
DupIf,
9292
Verify,
93+
Drop,
9394
NonZero,
9495
ZeroNotEqual,
9596
AndV,
@@ -156,6 +157,8 @@ pub enum Terminal<Pk: MiniscriptKey, Ctx: ScriptContext> {
156157
DupIf(Arc<Miniscript<Pk, Ctx>>),
157158
/// `[T] VERIFY`
158159
Verify(Arc<Miniscript<Pk, Ctx>>),
160+
/// `[T] DROP`
161+
Drop(Arc<Miniscript<Pk, Ctx>>),
159162
/// `SIZE 0NOTEQUAL IF [Fn] ENDIF`
160163
NonZero(Arc<Miniscript<Pk, Ctx>>),
161164
/// `[X] 0NOTEQUAL`
@@ -207,6 +210,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Clone for Terminal<Pk, Ctx> {
207210
Terminal::Check(ref sub) => Terminal::Check(Arc::new(Miniscript::clone(sub))),
208211
Terminal::DupIf(ref sub) => Terminal::DupIf(Arc::new(Miniscript::clone(sub))),
209212
Terminal::Verify(ref sub) => Terminal::Verify(Arc::new(Miniscript::clone(sub))),
213+
Terminal::Drop(ref sub) => Terminal::Drop(Arc::new(Miniscript::clone(sub))),
210214
Terminal::NonZero(ref sub) => Terminal::NonZero(Arc::new(Miniscript::clone(sub))),
211215
Terminal::ZeroNotEqual(ref sub) => {
212216
Terminal::ZeroNotEqual(Arc::new(Miniscript::clone(sub)))
@@ -460,6 +464,10 @@ pub fn parse<Ctx: ScriptContext>(
460464
non_term.push(NonTerm::Expression);
461465
},
462466
),
467+
Tk::Drop => {
468+
non_term.push(NonTerm::Drop);
469+
non_term.push(NonTerm::Expression);
470+
},
463471
Tk::ZeroNotEqual => {
464472
non_term.push(NonTerm::ZeroNotEqual);
465473
non_term.push(NonTerm::Expression);
@@ -612,6 +620,7 @@ pub fn parse<Ctx: ScriptContext>(
612620
Some(NonTerm::Check) => term.reduce1(Terminal::Check)?,
613621
Some(NonTerm::DupIf) => term.reduce1(Terminal::DupIf)?,
614622
Some(NonTerm::Verify) => term.reduce1(Terminal::Verify)?,
623+
Some(NonTerm::Drop) => term.reduce1(Terminal::Drop)?,
615624
Some(NonTerm::NonZero) => term.reduce1(Terminal::NonZero)?,
616625
Some(NonTerm::ZeroNotEqual) => term.reduce1(Terminal::ZeroNotEqual)?,
617626
Some(NonTerm::AndV) => {

src/miniscript/display.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for DisplayNode<'a, Pk,
8282
| Terminal::Swap(ref sub)
8383
| Terminal::DupIf(ref sub)
8484
| Terminal::Verify(ref sub)
85+
| Terminal::Drop(ref sub)
8586
| Terminal::NonZero(ref sub)
8687
| Terminal::ZeroNotEqual(ref sub) => {
8788
Tree::Unary(DisplayNode::Node(sub.ty, sub.as_inner()))
@@ -256,6 +257,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
256257
Terminal::Check(..) => "c",
257258
Terminal::DupIf(..) => "d",
258259
Terminal::Verify(..) => "v",
260+
Terminal::Drop(..) => "r",
259261
Terminal::NonZero(..) => "j",
260262
Terminal::ZeroNotEqual(..) => "n",
261263
Terminal::AndV(_, ref r) if matches!(r.as_inner(), Terminal::True) => "t",

src/miniscript/mod.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ mod private {
103103
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
104104
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
105105
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
106+
Terminal::Drop(..) => Terminal::Drop(stack.pop().unwrap()),
106107
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
107108
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
108109
Terminal::AndV(..) => {
@@ -236,6 +237,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
236237
Terminal::After(n) => script_num_size(n.to_consensus_u32() as usize) + 1,
237238
Terminal::Older(n) => script_num_size(n.to_consensus_u32() as usize) + 1,
238239
Terminal::Verify(ref sub) => usize::from(!sub.ext.has_free_verify),
240+
Terminal::Drop(..) => 1,
239241
Terminal::Thresh(ref thresh) => {
240242
script_num_size(thresh.k()) // k
241243
+ 1 // EQUAL
@@ -573,6 +575,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
573575
Terminal::Check(..) => Terminal::Check(translated.pop().unwrap()),
574576
Terminal::DupIf(..) => Terminal::DupIf(translated.pop().unwrap()),
575577
Terminal::Verify(..) => Terminal::Verify(translated.pop().unwrap()),
578+
Terminal::Drop(..) => Terminal::Drop(translated.pop().unwrap()),
576579
Terminal::NonZero(..) => Terminal::NonZero(translated.pop().unwrap()),
577580
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(translated.pop().unwrap()),
578581
Terminal::AndV(..) => {
@@ -638,6 +641,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
638641
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
639642
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
640643
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
644+
Terminal::Drop(..) => Terminal::Drop(stack.pop().unwrap()),
641645
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
642646
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
643647
Terminal::AndV(..) => Terminal::AndV(stack.pop().unwrap(), stack.pop().unwrap()),
@@ -749,6 +753,7 @@ where
749753
'c' => unwrapped = Terminal::Check(Arc::new(ms)),
750754
'd' => unwrapped = Terminal::DupIf(Arc::new(ms)),
751755
'v' => unwrapped = Terminal::Verify(Arc::new(ms)),
756+
'r' => unwrapped = Terminal::Drop(Arc::new(ms)),
752757
'j' => unwrapped = Terminal::NonZero(Arc::new(ms)),
753758
'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)),
754759
't' => unwrapped = Terminal::AndV(Arc::new(ms), Arc::new(Miniscript::TRUE)),
@@ -1222,6 +1227,15 @@ mod tests {
12221227
assert_eq!(abs.minimum_n_keys(), Some(3));
12231228

12241229
roundtrip(&ms_str!("older(921)"), "OP_PUSHBYTES_2 9903 OP_CSV");
1230+
roundtrip(
1231+
&ms_str!("and_v(r:after(1024),1)"),
1232+
"OP_PUSHBYTES_2 0004 OP_CLTV OP_DROP OP_PUSHNUM_1",
1233+
);
1234+
roundtrip(
1235+
&ms_str!("and_v(r:older(1024),1)"),
1236+
"OP_PUSHBYTES_2 0004 OP_CSV OP_DROP OP_PUSHNUM_1",
1237+
);
1238+
roundtrip(&ms_str!("and_v(r:1,1)"), "OP_PUSHNUM_1 OP_DROP OP_PUSHNUM_1");
12251239

12261240
roundtrip(
12271241
&ms_str!("sha256({})",sha256::Hash::hash(&[])),
@@ -1481,6 +1495,45 @@ mod tests {
14811495
assert!(ms_str.is_err());
14821496
}
14831497

1498+
#[test]
1499+
fn drop_wrapper() {
1500+
type SwMs = Miniscript<String, Segwitv0>;
1501+
fn assert_error(s: &str, expected_error: Option<&str>) {
1502+
match SwMs::from_str_insane(&s) {
1503+
Ok(_) => match expected_error {
1504+
Some(e) => {
1505+
panic!("Expected error: {}", e);
1506+
}
1507+
None => {
1508+
// do nothing
1509+
}
1510+
},
1511+
Err(e1) => match expected_error {
1512+
Some(e2) => assert_eq!(e1.to_string(), e2.to_string()),
1513+
None => {
1514+
panic!("Unexpected error: {}", e1);
1515+
}
1516+
},
1517+
}
1518+
}
1519+
1520+
{
1521+
assert_error("and_v(r:after(1024),1)", None);
1522+
}
1523+
1524+
{
1525+
fn assert_error_cannot_wrap(s: &str, prefix: &str) {
1526+
let err_cannot_wrap = format!(
1527+
"typecheck: fragment «{}:after(1024)» cannot wrap a fragment of type V",
1528+
prefix
1529+
);
1530+
assert_error(s, Some(&err_cannot_wrap));
1531+
}
1532+
assert_error_cannot_wrap("and_v(rr:after(1024),1)", "rr");
1533+
assert_error_cannot_wrap("and_v(rv:after(1024),1)", "rv");
1534+
}
1535+
}
1536+
14841537
#[test]
14851538
fn translate_tests() {
14861539
let ms = Miniscript::<String, Segwitv0>::from_str("pk(A)").unwrap();

src/miniscript/ms_tests.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#[cfg(test)]
99
mod tests {
1010
use core::fmt;
11+
use core::str::FromStr;
1112

1213
use crate::miniscript::types;
1314
use crate::{Miniscript, Segwitv0};
@@ -15047,6 +15048,7 @@ mod tests {
1504715048
#[cfg_attr(feature="cargo-fmt", rustfmt_skip)]
1504815049
fn malleable_tests_from_alloy() {
1504915050
ms_test("and_v(v:after(500000001),or_d(j:multi(2,A,B,C),multi(2,D,E,F)))", "usB");
15051+
ms_test("and_v(r:after(500000001),or_d(j:multi(2,A,B,C),multi(2,D,E,F)))", "usB");
1505015052
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");
1505115053
ms_test("andor(or_i(multi(2,A,B,C),0),sha256(c7bcb868ab4db55ca45f8eefe5b1677d9fc2c4111e295baaee1b34ed352c719b),multi(2,D,E,F))", "dBesu");
1505215054
ms_test("or_d(or_i(0,or_i(multi(2,A,B,C),0)),multi(2,D,E,F))", "dBesu");
@@ -23855,4 +23857,11 @@ mod tests {
2385523857

2385623858
}
2385723859
}
23860+
23861+
#[test]
23862+
pub fn test_opdrop() {
23863+
Miniscript::<String, Segwitv0>::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap();
23864+
let ms: Miniscript<String, Segwitv0> =
23865+
Miniscript::from_str("and_v(v:after(100000),multi(1,A,B))").unwrap();
23866+
}
2385823867
}

src/miniscript/satisfy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> {
13381338
| Terminal::Swap(ref sub)
13391339
| Terminal::Check(ref sub)
13401340
| Terminal::Verify(ref sub)
1341+
| Terminal::Drop(ref sub)
13411342
| Terminal::NonZero(ref sub)
13421343
| Terminal::ZeroNotEqual(ref sub) => {
13431344
Self::satisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn)
@@ -1647,6 +1648,7 @@ impl<Pk: MiniscriptKey + ToPublicKey> Satisfaction<Placeholder<Pk>> {
16471648
| Terminal::Older(_)
16481649
| Terminal::After(_)
16491650
| Terminal::Verify(_)
1651+
| Terminal::Drop(_)
16501652
| Terminal::OrC(..) => Satisfaction {
16511653
stack: Witness::Impossible,
16521654
has_sig: false,

src/miniscript/types/correctness.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,19 @@ impl Correctness {
238238
})
239239
}
240240

241+
/// Constructor for the correctness properties of the `r:` fragment.
242+
pub const fn cast_drop(self) -> Result<Self, ErrorKind> {
243+
// from https://bitcoin.sipa.be/miniscript/:
244+
// > Every miniscript expression has one of four basic types:
245+
// > ...
246+
// > "V" Verify expressions. Like "B", these take their inputs from the top of the stack.
247+
// > Upon satisfaction however, they continue without pushing anything.
248+
// > They cannot be dissatisfied (will abort instead).
249+
// So while OP_DROP doesn't actually verify anything, the closest type is still `V`.
250+
// We delegate to `cast_verify` to handle the rest of the properties.
251+
Self::cast_verify(self)
252+
}
253+
241254
/// Constructor for the correctness properties of the `j:` fragment.
242255
pub const fn cast_nonzero(self) -> Result<Self, ErrorKind> {
243256
if !self.input.constfn_eq(Input::OneNonZero) && !self.input.constfn_eq(Input::AnyNonZero) {

src/miniscript/types/extra_props.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,12 @@ impl ExtData {
488488
}
489489
}
490490

491+
/// Extra properties for the `r:` fragment.
492+
pub fn cast_drop(self) -> Self {
493+
// delegate to `cast_verify` as the properties are the same
494+
self.cast_verify()
495+
}
496+
491497
/// Extra properties for the `j:` fragment.
492498
pub const fn cast_nonzero(self) -> Self {
493499
ExtData {
@@ -947,6 +953,7 @@ impl ExtData {
947953
Terminal::Check(ref sub) => Self::cast_check(sub.ext),
948954
Terminal::DupIf(ref sub) => Self::cast_dupif(sub.ext),
949955
Terminal::Verify(ref sub) => Self::cast_verify(sub.ext),
956+
Terminal::Drop(ref sub) => Self::cast_drop(sub.ext),
950957
Terminal::NonZero(ref sub) => Self::cast_nonzero(sub.ext),
951958
Terminal::ZeroNotEqual(ref sub) => Self::cast_zeronotequal(sub.ext),
952959
Terminal::AndB(ref l, ref r) => {

0 commit comments

Comments
 (0)