Skip to content

Commit 9fe840c

Browse files
authored
Merge pull request rust-bitcoin#644 from sanket1729/tap_opcodes
Add OP_CHECKSIGADD and OP_SUCCESSxxx
2 parents 72dbe1d + c75f3ef commit 9fe840c

File tree

4 files changed

+115
-47
lines changed

4 files changed

+115
-47
lines changed

src/blockdata/opcodes.rs

Lines changed: 105 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,8 @@ pub mod all {
417417
/// Does nothing
418418
pub const OP_NOP10: All = All {code: 0xb9};
419419
// Every other opcode acts as OP_RETURN
420-
/// Synonym for OP_RETURN
421-
pub const OP_RETURN_186: All = All {code: 0xba};
420+
/// OP_CHECKSIGADD post tapscript
421+
pub const OP_CHECKSIGADD: All = All {code: 0xba};
422422
/// Synonym for OP_RETURN
423423
pub const OP_RETURN_187: All = All {code: 0xbb};
424424
/// Synonym for OP_RETURN
@@ -556,7 +556,7 @@ pub mod all {
556556
/// Synonym for OP_RETURN
557557
pub const OP_RETURN_254: All = All {code: 0xfe};
558558
/// Synonym for OP_RETURN
559-
pub const OP_RETURN_255: All = All {code: 0xff};
559+
pub const OP_INVALIDOPCODE: All = All {code: 0xff};
560560
}
561561

562562
impl fmt::Debug for All {
@@ -652,48 +652,84 @@ impl fmt::Debug for All {
652652
all::OP_CLTV => write!(f, "CLTV"),
653653
all::OP_CSV => write!(f, "CSV"),
654654
All {code: x} if x >= all::OP_NOP1.code && x <= all::OP_NOP10.code => write!(f, "NOP{}", x - all::OP_NOP1.code + 1),
655+
all::OP_INVALIDOPCODE => write!(f, "INVALIDOPCODE"),
656+
all::OP_CHECKSIGADD => write!(f, "CHECKSIGADD"),
655657
All {code: x} => write!(f, "RETURN_{}", x),
656658
}
657659
}
658660
}
659661

662+
663+
/// Classification context for the opcode. Some opcodes like `OP_RESERVED`
664+
/// abort the script in [`ClassifyContext::Legacy`] context, but will act as OP_SUCCESS in tapscript
665+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
666+
pub enum ClassifyContext {
667+
/// Opcode used in tapscript context
668+
TapScript,
669+
/// Opcode used in legacy context
670+
Legacy,
671+
}
672+
660673
impl All {
661674
/// Classifies an Opcode into a broad class
662675
#[inline]
663-
pub fn classify(self) -> Class {
664-
// 17 opcodes
665-
if self == all::OP_VERIF || self == all::OP_VERNOTIF ||
666-
self == all::OP_CAT || self == all::OP_SUBSTR ||
667-
self == all::OP_LEFT || self == all::OP_RIGHT ||
668-
self == all::OP_INVERT || self == all::OP_AND ||
669-
self == all::OP_OR || self == all::OP_XOR ||
670-
self == all::OP_2MUL || self == all::OP_2DIV ||
671-
self == all::OP_MUL || self == all::OP_DIV || self == all::OP_MOD ||
672-
self == all::OP_LSHIFT || self == all::OP_RSHIFT {
673-
Class::IllegalOp
674-
// 11 opcodes
675-
} else if self == all::OP_NOP ||
676-
(all::OP_NOP1.code <= self.code &&
677-
self.code <= all::OP_NOP10.code) {
678-
Class::NoOp
679-
// 75 opcodes
680-
} else if self == all::OP_RESERVED || self == all::OP_VER || self == all::OP_RETURN ||
681-
self == all::OP_RESERVED1 || self == all::OP_RESERVED2 ||
682-
self.code >= all::OP_RETURN_186.code {
683-
Class::ReturnOp
684-
// 1 opcode
685-
} else if self == all::OP_PUSHNUM_NEG1 {
686-
Class::PushNum(-1)
687-
// 16 opcodes
688-
} else if all::OP_PUSHNUM_1.code <= self.code &&
689-
self.code <= all::OP_PUSHNUM_16.code {
690-
Class::PushNum(1 + self.code as i32 - all::OP_PUSHNUM_1.code as i32)
691-
// 76 opcodes
692-
} else if self.code <= all::OP_PUSHBYTES_75.code {
693-
Class::PushBytes(self.code as u32)
694-
// 60 opcodes
695-
} else {
696-
Class::Ordinary(Ordinary::try_from_all(self).unwrap())
676+
pub fn classify(self, ctx: ClassifyContext) -> Class {
677+
use self::all::*;
678+
match (self, ctx) {
679+
// 3 opcodes illegal in all contexts
680+
(OP_VERIF, _) | (OP_VERNOTIF, _) | (OP_INVALIDOPCODE, _) => Class::IllegalOp,
681+
682+
// 15 opcodes illegal in Legacy context
683+
(OP_CAT, ctx) | (OP_SUBSTR, ctx)
684+
| (OP_LEFT, ctx) | (OP_RIGHT, ctx)
685+
| (OP_INVERT, ctx)
686+
| (OP_AND, ctx) | (OP_OR, ctx) | (OP_XOR, ctx)
687+
| (OP_2MUL, ctx) | (OP_2DIV, ctx)
688+
| (OP_MUL, ctx) | (OP_DIV, ctx) | (OP_MOD, ctx)
689+
| (OP_LSHIFT, ctx) | (OP_RSHIFT, ctx) if ctx == ClassifyContext::Legacy => Class::IllegalOp,
690+
691+
// 87 opcodes of SuccessOp class only in TapScript context
692+
(op, ClassifyContext::TapScript)
693+
if op.code == 80 || op.code == 98 ||
694+
(op.code >= 126 && op.code <= 129) ||
695+
(op.code >= 131 && op.code <= 134) ||
696+
(op.code >= 137 && op.code <= 138) ||
697+
(op.code >= 141 && op.code <= 142) ||
698+
(op.code >= 149 && op.code <= 153) ||
699+
(op.code >= 187 && op.code <= 254) => Class::SuccessOp,
700+
701+
// 11 opcodes of NoOp class
702+
(OP_NOP, _) => Class::NoOp,
703+
(op, _) if op.code >= OP_NOP1.code && op.code <= OP_NOP10.code => Class::NoOp,
704+
705+
// 1 opcode for `OP_RETURN`
706+
(OP_RETURN, _) => Class::ReturnOp,
707+
708+
// 4 opcodes operating equally to `OP_RETURN` only in Legacy context
709+
(OP_RESERVED, ctx)
710+
| (OP_RESERVED1, ctx) | (OP_RESERVED2, ctx)
711+
| (OP_VER, ctx) if ctx == ClassifyContext::Legacy => Class::ReturnOp,
712+
713+
// 71 opcodes operating equally to `OP_RETURN` only in Legacy context
714+
(op, ClassifyContext::Legacy) if op.code >= OP_CHECKSIGADD.code => Class::ReturnOp,
715+
716+
// 2 opcodes operating equally to `OP_RETURN` only in TapScript context
717+
(OP_CHECKMULTISIG, ClassifyContext::TapScript)
718+
| (OP_CHECKMULTISIGVERIFY, ClassifyContext::TapScript) => Class::ReturnOp,
719+
720+
// 1 opcode of PushNum class
721+
(OP_PUSHNUM_NEG1, _) => Class::PushNum(-1),
722+
723+
// 16 opcodes of PushNum class
724+
(op, _) if op.code >= OP_PUSHNUM_1.code && op.code <= OP_PUSHNUM_16.code => {
725+
Class::PushNum(1 + self.code as i32 - OP_PUSHNUM_1.code as i32)
726+
},
727+
728+
// 76 opcodes of PushBytes class
729+
(op, _) if op.code <= OP_PUSHBYTES_75.code => Class::PushBytes(self.code as u32),
730+
731+
// opcodes of Ordinary class: 61 for Legacy and 60 for TapScript context
732+
(_, _) => Class::Ordinary(Ordinary::with(self)),
697733
}
698734
}
699735

@@ -743,6 +779,8 @@ pub enum Class {
743779
PushBytes(u32),
744780
/// Fails the script if executed
745781
ReturnOp,
782+
/// Succeeds the script even if not executed
783+
SuccessOp,
746784
/// Fails the script even if not executed
747785
IllegalOp,
748786
/// Does nothing
@@ -774,6 +812,13 @@ macro_rules! ordinary_opcode {
774812
}
775813

776814
impl Ordinary {
815+
fn with(b: All) -> Self {
816+
match b {
817+
$( all::$op => { Ordinary::$op } ),*
818+
_ => unreachable!("construction of `Ordinary` type from non-ordinary opcode {}", b),
819+
}
820+
}
821+
777822
/// Try to create from an All
778823
pub fn try_from_all(b: All) -> Option<Self> {
779824
match b {
@@ -807,7 +852,8 @@ ordinary_opcode! {
807852
// crypto
808853
OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256,
809854
OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY,
810-
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
855+
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY,
856+
OP_CHECKSIGADD
811857
}
812858

813859
impl Ordinary {
@@ -836,6 +882,25 @@ mod tests {
836882
}
837883
}
838884

885+
#[test]
886+
fn classify_test() {
887+
let op174 = all::OP_CHECKMULTISIG;
888+
assert_eq!(op174.classify(ClassifyContext::Legacy), Class::Ordinary(Ordinary::OP_CHECKMULTISIG));
889+
assert_eq!(op174.classify(ClassifyContext::TapScript), Class::ReturnOp);
890+
891+
let op175 = all::OP_CHECKMULTISIGVERIFY;
892+
assert_eq!(op175.classify(ClassifyContext::Legacy), Class::Ordinary(Ordinary::OP_CHECKMULTISIGVERIFY));
893+
assert_eq!(op175.classify(ClassifyContext::TapScript), Class::ReturnOp);
894+
895+
let op186 = all::OP_CHECKSIGADD;
896+
assert_eq!(op186.classify(ClassifyContext::Legacy), Class::ReturnOp);
897+
assert_eq!(op186.classify(ClassifyContext::TapScript), Class::Ordinary(Ordinary::OP_CHECKSIGADD));
898+
899+
let op187 = all::OP_RETURN_187;
900+
assert_eq!(op187.classify(ClassifyContext::Legacy), Class::ReturnOp);
901+
assert_eq!(op187.classify(ClassifyContext::TapScript), Class::SuccessOp);
902+
}
903+
839904
#[test]
840905
fn str_roundtrip() {
841906
let mut unique = HashSet::new();
@@ -1025,7 +1090,7 @@ mod tests {
10251090
roundtrip!(unique, OP_NOP8);
10261091
roundtrip!(unique, OP_NOP9);
10271092
roundtrip!(unique, OP_NOP10);
1028-
roundtrip!(unique, OP_RETURN_186);
1093+
roundtrip!(unique, OP_CHECKSIGADD);
10291094
roundtrip!(unique, OP_RETURN_187);
10301095
roundtrip!(unique, OP_RETURN_188);
10311096
roundtrip!(unique, OP_RETURN_189);
@@ -1094,7 +1159,7 @@ mod tests {
10941159
roundtrip!(unique, OP_RETURN_252);
10951160
roundtrip!(unique, OP_RETURN_253);
10961161
roundtrip!(unique, OP_RETURN_254);
1097-
roundtrip!(unique, OP_RETURN_255);
1162+
roundtrip!(unique, OP_INVALIDOPCODE);
10981163
assert_eq!(unique.len(), 256);
10991164
}
11001165
}

src/blockdata/script.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,9 @@ impl Script {
407407

408408
/// Whether a script can be proven to have no satisfying input
409409
pub fn is_provably_unspendable(&self) -> bool {
410-
!self.0.is_empty() && (opcodes::All::from(self.0[0]).classify() == opcodes::Class::ReturnOp ||
411-
opcodes::All::from(self.0[0]).classify() == opcodes::Class::IllegalOp)
410+
!self.0.is_empty() &&
411+
(opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::ReturnOp ||
412+
opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::IllegalOp)
412413
}
413414

414415
/// Gets the minimum value an output with this script should have in order to be
@@ -481,7 +482,7 @@ impl Script {
481482
let opcode = opcodes::All::from(script[index]);
482483
index += 1;
483484

484-
let data_len = if let opcodes::Class::PushBytes(n) = opcode.classify() {
485+
let data_len = if let opcodes::Class::PushBytes(n) = opcode.classify(opcodes::ClassifyContext::Legacy) {
485486
n as usize
486487
} else {
487488
match opcode {
@@ -594,7 +595,9 @@ impl<'a> Iterator for Instructions<'a> {
594595
return None;
595596
}
596597

597-
match opcodes::All::from(self.data[0]).classify() {
598+
// classify parameter does not really matter here since we are only using
599+
// it for pushes and nums
600+
match opcodes::All::from(self.data[0]).classify(opcodes::ClassifyContext::Legacy) {
598601
opcodes::Class::PushBytes(n) => {
599602
let n = n as usize;
600603
if self.data.len() < n + 1 {

src/util/contracthash.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl Template {
133133
pub fn first_push_as_number(&self) -> Option<usize> {
134134
if !self.0.is_empty() {
135135
if let TemplateElement::Op(op) = self.0[0] {
136-
if let opcodes::Class::PushNum(n) = op.classify() {
136+
if let opcodes::Class::PushNum(n) = op.classify(opcodes::ClassifyContext::Legacy) {
137137
if n >= 0 {
138138
return Some(n as usize);
139139
}
@@ -249,7 +249,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
249249
}
250250
}
251251
script::Instruction::Op(op) => {
252-
match op.classify() {
252+
match op.classify(opcodes::ClassifyContext::Legacy) {
253253
// CHECKSIG should only come after a list of keys
254254
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIG) |
255255
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIGVERIFY) => {

src/util/misc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ pub fn script_find_and_remove(haystack: &mut Vec<u8>, needle: &[u8]) -> usize {
231231
top = top.wrapping_sub(needle.len());
232232
if overflow { break; }
233233
} else {
234-
i += match opcodes::All::from((*haystack)[i]).classify() {
234+
i += match opcodes::All::from((*haystack)[i]).classify(opcodes::ClassifyContext::Legacy) {
235235
opcodes::Class::PushBytes(n) => n as usize + 1,
236236
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => 2,
237237
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => 3,

0 commit comments

Comments
 (0)