Merge pull request #644 from sanket1729/tap_opcodes

Add OP_CHECKSIGADD and OP_SUCCESSxxx
This commit is contained in:
Andrew Poelstra 2021-09-24 22:47:48 +00:00 committed by GitHub
commit 9fe840c20e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 47 deletions

View File

@ -417,8 +417,8 @@ pub mod all {
/// Does nothing /// Does nothing
pub const OP_NOP10: All = All {code: 0xb9}; pub const OP_NOP10: All = All {code: 0xb9};
// Every other opcode acts as OP_RETURN // Every other opcode acts as OP_RETURN
/// Synonym for OP_RETURN /// OP_CHECKSIGADD post tapscript
pub const OP_RETURN_186: All = All {code: 0xba}; pub const OP_CHECKSIGADD: All = All {code: 0xba};
/// Synonym for OP_RETURN /// Synonym for OP_RETURN
pub const OP_RETURN_187: All = All {code: 0xbb}; pub const OP_RETURN_187: All = All {code: 0xbb};
/// Synonym for OP_RETURN /// Synonym for OP_RETURN
@ -556,7 +556,7 @@ pub mod all {
/// Synonym for OP_RETURN /// Synonym for OP_RETURN
pub const OP_RETURN_254: All = All {code: 0xfe}; pub const OP_RETURN_254: All = All {code: 0xfe};
/// Synonym for OP_RETURN /// Synonym for OP_RETURN
pub const OP_RETURN_255: All = All {code: 0xff}; pub const OP_INVALIDOPCODE: All = All {code: 0xff};
} }
impl fmt::Debug for All { impl fmt::Debug for All {
@ -652,48 +652,84 @@ impl fmt::Debug for All {
all::OP_CLTV => write!(f, "CLTV"), all::OP_CLTV => write!(f, "CLTV"),
all::OP_CSV => write!(f, "CSV"), all::OP_CSV => write!(f, "CSV"),
All {code: x} if x >= all::OP_NOP1.code && x <= all::OP_NOP10.code => write!(f, "NOP{}", x - all::OP_NOP1.code + 1), All {code: x} if x >= all::OP_NOP1.code && x <= all::OP_NOP10.code => write!(f, "NOP{}", x - all::OP_NOP1.code + 1),
all::OP_INVALIDOPCODE => write!(f, "INVALIDOPCODE"),
all::OP_CHECKSIGADD => write!(f, "CHECKSIGADD"),
All {code: x} => write!(f, "RETURN_{}", x), All {code: x} => write!(f, "RETURN_{}", x),
} }
} }
} }
/// Classification context for the opcode. Some opcodes like `OP_RESERVED`
/// abort the script in [`ClassifyContext::Legacy`] context, but will act as OP_SUCCESS in tapscript
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ClassifyContext {
/// Opcode used in tapscript context
TapScript,
/// Opcode used in legacy context
Legacy,
}
impl All { impl All {
/// Classifies an Opcode into a broad class /// Classifies an Opcode into a broad class
#[inline] #[inline]
pub fn classify(self) -> Class { pub fn classify(self, ctx: ClassifyContext) -> Class {
// 17 opcodes use self::all::*;
if self == all::OP_VERIF || self == all::OP_VERNOTIF || match (self, ctx) {
self == all::OP_CAT || self == all::OP_SUBSTR || // 3 opcodes illegal in all contexts
self == all::OP_LEFT || self == all::OP_RIGHT || (OP_VERIF, _) | (OP_VERNOTIF, _) | (OP_INVALIDOPCODE, _) => Class::IllegalOp,
self == all::OP_INVERT || self == all::OP_AND ||
self == all::OP_OR || self == all::OP_XOR || // 15 opcodes illegal in Legacy context
self == all::OP_2MUL || self == all::OP_2DIV || (OP_CAT, ctx) | (OP_SUBSTR, ctx)
self == all::OP_MUL || self == all::OP_DIV || self == all::OP_MOD || | (OP_LEFT, ctx) | (OP_RIGHT, ctx)
self == all::OP_LSHIFT || self == all::OP_RSHIFT { | (OP_INVERT, ctx)
Class::IllegalOp | (OP_AND, ctx) | (OP_OR, ctx) | (OP_XOR, ctx)
// 11 opcodes | (OP_2MUL, ctx) | (OP_2DIV, ctx)
} else if self == all::OP_NOP || | (OP_MUL, ctx) | (OP_DIV, ctx) | (OP_MOD, ctx)
(all::OP_NOP1.code <= self.code && | (OP_LSHIFT, ctx) | (OP_RSHIFT, ctx) if ctx == ClassifyContext::Legacy => Class::IllegalOp,
self.code <= all::OP_NOP10.code) {
Class::NoOp // 87 opcodes of SuccessOp class only in TapScript context
// 75 opcodes (op, ClassifyContext::TapScript)
} else if self == all::OP_RESERVED || self == all::OP_VER || self == all::OP_RETURN || if op.code == 80 || op.code == 98 ||
self == all::OP_RESERVED1 || self == all::OP_RESERVED2 || (op.code >= 126 && op.code <= 129) ||
self.code >= all::OP_RETURN_186.code { (op.code >= 131 && op.code <= 134) ||
Class::ReturnOp (op.code >= 137 && op.code <= 138) ||
// 1 opcode (op.code >= 141 && op.code <= 142) ||
} else if self == all::OP_PUSHNUM_NEG1 { (op.code >= 149 && op.code <= 153) ||
Class::PushNum(-1) (op.code >= 187 && op.code <= 254) => Class::SuccessOp,
// 16 opcodes
} else if all::OP_PUSHNUM_1.code <= self.code && // 11 opcodes of NoOp class
self.code <= all::OP_PUSHNUM_16.code { (OP_NOP, _) => Class::NoOp,
Class::PushNum(1 + self.code as i32 - all::OP_PUSHNUM_1.code as i32) (op, _) if op.code >= OP_NOP1.code && op.code <= OP_NOP10.code => Class::NoOp,
// 76 opcodes
} else if self.code <= all::OP_PUSHBYTES_75.code { // 1 opcode for `OP_RETURN`
Class::PushBytes(self.code as u32) (OP_RETURN, _) => Class::ReturnOp,
// 60 opcodes
} else { // 4 opcodes operating equally to `OP_RETURN` only in Legacy context
Class::Ordinary(Ordinary::try_from_all(self).unwrap()) (OP_RESERVED, ctx)
| (OP_RESERVED1, ctx) | (OP_RESERVED2, ctx)
| (OP_VER, ctx) if ctx == ClassifyContext::Legacy => Class::ReturnOp,
// 71 opcodes operating equally to `OP_RETURN` only in Legacy context
(op, ClassifyContext::Legacy) if op.code >= OP_CHECKSIGADD.code => Class::ReturnOp,
// 2 opcodes operating equally to `OP_RETURN` only in TapScript context
(OP_CHECKMULTISIG, ClassifyContext::TapScript)
| (OP_CHECKMULTISIGVERIFY, ClassifyContext::TapScript) => Class::ReturnOp,
// 1 opcode of PushNum class
(OP_PUSHNUM_NEG1, _) => Class::PushNum(-1),
// 16 opcodes of PushNum class
(op, _) if op.code >= OP_PUSHNUM_1.code && op.code <= OP_PUSHNUM_16.code => {
Class::PushNum(1 + self.code as i32 - OP_PUSHNUM_1.code as i32)
},
// 76 opcodes of PushBytes class
(op, _) if op.code <= OP_PUSHBYTES_75.code => Class::PushBytes(self.code as u32),
// opcodes of Ordinary class: 61 for Legacy and 60 for TapScript context
(_, _) => Class::Ordinary(Ordinary::with(self)),
} }
} }
@ -743,6 +779,8 @@ pub enum Class {
PushBytes(u32), PushBytes(u32),
/// Fails the script if executed /// Fails the script if executed
ReturnOp, ReturnOp,
/// Succeeds the script even if not executed
SuccessOp,
/// Fails the script even if not executed /// Fails the script even if not executed
IllegalOp, IllegalOp,
/// Does nothing /// Does nothing
@ -774,6 +812,13 @@ macro_rules! ordinary_opcode {
} }
impl Ordinary { impl Ordinary {
fn with(b: All) -> Self {
match b {
$( all::$op => { Ordinary::$op } ),*
_ => unreachable!("construction of `Ordinary` type from non-ordinary opcode {}", b),
}
}
/// Try to create from an All /// Try to create from an All
pub fn try_from_all(b: All) -> Option<Self> { pub fn try_from_all(b: All) -> Option<Self> {
match b { match b {
@ -807,7 +852,8 @@ ordinary_opcode! {
// crypto // crypto
OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256, OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256,
OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CODESEPARATOR, OP_CHECKSIG, OP_CHECKSIGVERIFY,
OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY,
OP_CHECKSIGADD
} }
impl Ordinary { impl Ordinary {
@ -836,6 +882,25 @@ mod tests {
} }
} }
#[test]
fn classify_test() {
let op174 = all::OP_CHECKMULTISIG;
assert_eq!(op174.classify(ClassifyContext::Legacy), Class::Ordinary(Ordinary::OP_CHECKMULTISIG));
assert_eq!(op174.classify(ClassifyContext::TapScript), Class::ReturnOp);
let op175 = all::OP_CHECKMULTISIGVERIFY;
assert_eq!(op175.classify(ClassifyContext::Legacy), Class::Ordinary(Ordinary::OP_CHECKMULTISIGVERIFY));
assert_eq!(op175.classify(ClassifyContext::TapScript), Class::ReturnOp);
let op186 = all::OP_CHECKSIGADD;
assert_eq!(op186.classify(ClassifyContext::Legacy), Class::ReturnOp);
assert_eq!(op186.classify(ClassifyContext::TapScript), Class::Ordinary(Ordinary::OP_CHECKSIGADD));
let op187 = all::OP_RETURN_187;
assert_eq!(op187.classify(ClassifyContext::Legacy), Class::ReturnOp);
assert_eq!(op187.classify(ClassifyContext::TapScript), Class::SuccessOp);
}
#[test] #[test]
fn str_roundtrip() { fn str_roundtrip() {
let mut unique = HashSet::new(); let mut unique = HashSet::new();
@ -1025,7 +1090,7 @@ mod tests {
roundtrip!(unique, OP_NOP8); roundtrip!(unique, OP_NOP8);
roundtrip!(unique, OP_NOP9); roundtrip!(unique, OP_NOP9);
roundtrip!(unique, OP_NOP10); roundtrip!(unique, OP_NOP10);
roundtrip!(unique, OP_RETURN_186); roundtrip!(unique, OP_CHECKSIGADD);
roundtrip!(unique, OP_RETURN_187); roundtrip!(unique, OP_RETURN_187);
roundtrip!(unique, OP_RETURN_188); roundtrip!(unique, OP_RETURN_188);
roundtrip!(unique, OP_RETURN_189); roundtrip!(unique, OP_RETURN_189);
@ -1094,7 +1159,7 @@ mod tests {
roundtrip!(unique, OP_RETURN_252); roundtrip!(unique, OP_RETURN_252);
roundtrip!(unique, OP_RETURN_253); roundtrip!(unique, OP_RETURN_253);
roundtrip!(unique, OP_RETURN_254); roundtrip!(unique, OP_RETURN_254);
roundtrip!(unique, OP_RETURN_255); roundtrip!(unique, OP_INVALIDOPCODE);
assert_eq!(unique.len(), 256); assert_eq!(unique.len(), 256);
} }
} }

View File

@ -407,8 +407,9 @@ impl Script {
/// Whether a script can be proven to have no satisfying input /// Whether a script can be proven to have no satisfying input
pub fn is_provably_unspendable(&self) -> bool { pub fn is_provably_unspendable(&self) -> bool {
!self.0.is_empty() && (opcodes::All::from(self.0[0]).classify() == opcodes::Class::ReturnOp || !self.0.is_empty() &&
opcodes::All::from(self.0[0]).classify() == opcodes::Class::IllegalOp) (opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::ReturnOp ||
opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::IllegalOp)
} }
/// Gets the minimum value an output with this script should have in order to be /// Gets the minimum value an output with this script should have in order to be
@ -481,7 +482,7 @@ impl Script {
let opcode = opcodes::All::from(script[index]); let opcode = opcodes::All::from(script[index]);
index += 1; index += 1;
let data_len = if let opcodes::Class::PushBytes(n) = opcode.classify() { let data_len = if let opcodes::Class::PushBytes(n) = opcode.classify(opcodes::ClassifyContext::Legacy) {
n as usize n as usize
} else { } else {
match opcode { match opcode {
@ -594,7 +595,9 @@ impl<'a> Iterator for Instructions<'a> {
return None; return None;
} }
match opcodes::All::from(self.data[0]).classify() { // classify parameter does not really matter here since we are only using
// it for pushes and nums
match opcodes::All::from(self.data[0]).classify(opcodes::ClassifyContext::Legacy) {
opcodes::Class::PushBytes(n) => { opcodes::Class::PushBytes(n) => {
let n = n as usize; let n = n as usize;
if self.data.len() < n + 1 { if self.data.len() < n + 1 {

View File

@ -133,7 +133,7 @@ impl Template {
pub fn first_push_as_number(&self) -> Option<usize> { pub fn first_push_as_number(&self) -> Option<usize> {
if !self.0.is_empty() { if !self.0.is_empty() {
if let TemplateElement::Op(op) = self.0[0] { if let TemplateElement::Op(op) = self.0[0] {
if let opcodes::Class::PushNum(n) = op.classify() { if let opcodes::Class::PushNum(n) = op.classify(opcodes::ClassifyContext::Legacy) {
if n >= 0 { if n >= 0 {
return Some(n as usize); return Some(n as usize);
} }
@ -249,7 +249,7 @@ pub fn untemplate(script: &script::Script) -> Result<(Template, Vec<PublicKey>),
} }
} }
script::Instruction::Op(op) => { script::Instruction::Op(op) => {
match op.classify() { match op.classify(opcodes::ClassifyContext::Legacy) {
// CHECKSIG should only come after a list of keys // CHECKSIG should only come after a list of keys
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIG) | opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIG) |
opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIGVERIFY) => { opcodes::Class::Ordinary(opcodes::Ordinary::OP_CHECKSIGVERIFY) => {

View File

@ -231,7 +231,7 @@ pub fn script_find_and_remove(haystack: &mut Vec<u8>, needle: &[u8]) -> usize {
top = top.wrapping_sub(needle.len()); top = top.wrapping_sub(needle.len());
if overflow { break; } if overflow { break; }
} else { } else {
i += match opcodes::All::from((*haystack)[i]).classify() { i += match opcodes::All::from((*haystack)[i]).classify(opcodes::ClassifyContext::Legacy) {
opcodes::Class::PushBytes(n) => n as usize + 1, opcodes::Class::PushBytes(n) => n as usize + 1,
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => 2, opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => 2,
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => 3, opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => 3,