From c7a457a212259dc6b32c4dc32b5769396b88acf8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 6 Jan 2022 18:28:11 -0800 Subject: [PATCH] txscript: implement OP_CHECKSIGADD In this commit, we implement OP_CHECKSIGADD which replaces OP_CHECKMULTISIG* in the tapscript execution environment. --- txscript/opcode.go | 82 ++++++++++++++++++++++++++++++++++++++++- txscript/opcode_test.go | 12 ++++-- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 867a415caf3..e6d41b54653 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -227,7 +227,7 @@ const ( OP_NOP8 = 0xb7 // 183 OP_NOP9 = 0xb8 // 184 OP_NOP10 = 0xb9 // 185 - OP_UNKNOWN186 = 0xba // 186 + OP_CHECKSIGADD = 0xba // 186 OP_UNKNOWN187 = 0xbb // 187 OP_UNKNOWN188 = 0xbc // 188 OP_UNKNOWN189 = 0xbd // 189 @@ -501,6 +501,7 @@ var opcodeArray = [256]opcode{ OP_CHECKSIGVERIFY: {OP_CHECKSIGVERIFY, "OP_CHECKSIGVERIFY", 1, opcodeCheckSigVerify}, OP_CHECKMULTISIG: {OP_CHECKMULTISIG, "OP_CHECKMULTISIG", 1, opcodeCheckMultiSig}, OP_CHECKMULTISIGVERIFY: {OP_CHECKMULTISIGVERIFY, "OP_CHECKMULTISIGVERIFY", 1, opcodeCheckMultiSigVerify}, + OP_CHECKSIGADD: {OP_CHECKSIGADD, "OP_CHECKSIGADD", 1, opcodeCheckSigAdd}, // Reserved opcodes. OP_NOP1: {OP_NOP1, "OP_NOP1", 1, opcodeNop}, @@ -513,7 +514,6 @@ var opcodeArray = [256]opcode{ OP_NOP10: {OP_NOP10, "OP_NOP10", 1, opcodeNop}, // Undefined opcodes. - OP_UNKNOWN186: {OP_UNKNOWN186, "OP_UNKNOWN186", 1, opcodeInvalid}, OP_UNKNOWN187: {OP_UNKNOWN187, "OP_UNKNOWN187", 1, opcodeInvalid}, OP_UNKNOWN188: {OP_UNKNOWN188, "OP_UNKNOWN188", 1, opcodeInvalid}, OP_UNKNOWN189: {OP_UNKNOWN189, "OP_UNKNOWN189", 1, opcodeInvalid}, @@ -2106,6 +2106,84 @@ func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error { return err } +// opcodeCheckSigAdd implements the OP_CHECKSIGADD operation defined in BIP +// 342. This is a replacement for OP_CHECKMULTISIGVERIFY and OP_CHECKMULTISIG +// that lends better to batch sig validation, as well as a possible future of +// signature aggregation across inputs. +// +// The op code takes a public key, an integer (N) and a signature, and returns +// N if the signature was the empty vector, and n+1 otherwise. +// +// Stack transformation: [... pubkey n signature] -> [... n | n+1 ] -> [...] +func opcodeCheckSigAdd(op *opcode, data []byte, vm *Engine) error { + // This op code can only be used if tapsript execution is active. + // Before the soft fork, this opcode was marked as an invalid reserved + // op code. + if vm.taprootCtx == nil { + str := fmt.Sprintf("attempt to execute invalid opcode %s", op.name) + return scriptError(ErrReservedOpcode, str) + } + + // At this point, at least 3 elements need to be present on the stack. + // If not, then we'll exit early. + // + // TODO(roasbeef): actually needed? ops will fail if not enough + + // Pop the signature, integer n, and public key off the stack. + pubKeyBytes, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + accumulatorInt, err := vm.dstack.PopInt() + if err != nil { + return err + } + sigBytes, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + + // If the signature is empty, then we'll just push the value N back + // onto the stack and continue from here. + // + // TODO(roasbeef): unknown key check before this? + if len(sigBytes) == 0 { + vm.dstack.PushInt(accumulatorInt) + return nil + } + + // Otherwise, we'll attempt to validate the signature as normal. + // + // If the constructor fails immediately, then it's because the public + // key size is zero, so we'll fail all script execution. + sigVerifier, err := newBaseTapscriptSigVerifier( + pubKeyBytes, sigBytes, vm, + ) + if err != nil { + return err + } + + valid := sigVerifier.Verify() + + // If the signature is invalid, this we fail execution, as it should + // have been an empty signature. + if !valid { + str := "signature not empty on failed checksig" + return scriptError(ErrNullFail, str) + } + + // Otherwise, we increment the accumulatorInt by one, and push that + // back onto the stack. + vm.dstack.PushInt(accumulatorInt + 1) + + // Account for changes in the sig ops budget after this execution. + if err := vm.taprootCtx.tallysigOp(); err != nil { + return err + } + + return nil +} + // parsedSigInfo houses a raw signature along with its parsed form and a flag // for whether or not it has already been parsed. It is used to prevent parsing // the same signature multiple times when verifying a multisig. diff --git a/txscript/opcode_test.go b/txscript/opcode_test.go index 91263c21b1c..15c62907aa2 100644 --- a/txscript/opcode_test.go +++ b/txscript/opcode_test.go @@ -77,7 +77,7 @@ func TestOpcodeDisasm(t *testing.T) { 0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY", 0xfa: "OP_SMALLINTEGER", 0xfb: "OP_PUBKEYS", 0xfd: "OP_PUBKEYHASH", 0xfe: "OP_PUBKEY", - 0xff: "OP_INVALIDOPCODE", + 0xff: "OP_INVALIDOPCODE", 0xba: "OP_CHECKSIGADD", } for opcodeVal, expectedStr := range expectedStrings { var data []byte @@ -123,7 +123,7 @@ func TestOpcodeDisasm(t *testing.T) { } // OP_UNKNOWN#. - case opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc: + case opcodeVal >= 0xbb && opcodeVal <= 0xf9 || opcodeVal == 0xfc: expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } @@ -191,7 +191,13 @@ func TestOpcodeDisasm(t *testing.T) { // OP_UNKNOWN#. case opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc: - expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) + switch opcodeVal { + // OP_UNKNOWN186 a.k.a 0xba is now OP_CHECKSIGADD. + case 0xba: + expectedStr = "OP_CHECKSIGADD" + default: + expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) + } } var buf strings.Builder