From 568ec1bb89de5c7dbe32b0667a4518d7dd3ee955 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 6 Jan 2022 18:27:00 -0800 Subject: [PATCH] txscript: implement OP_CHECKSIG semantics for tapscript validation In this commit, we implement the new checksig semantics as part of tapscript validation. Namely: * OP_CHECKSIGVERIFY no longer pops the OP_TRUE off the stack (TODO(roasbeef): verify)) * the new sig ops semantics are added where each sig deducts 50 from a starting budget of 50+the weight of the witness * NULLFAIL is always enforced, meaning invalid sigs MUST be an empty sig array --- txscript/opcode.go | 55 +++++++++++++++++++++++++++++++++++++++++++++- txscript/script.go | 8 ++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/txscript/opcode.go b/txscript/opcode.go index 1a4a5335d4d..867a415caf3 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -1986,7 +1986,10 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error { // The signature actually needs needs to be longer than this, but at // least 1 byte is needed for the hash type below. The full length is // checked depending on the script flags and upon parsing the signature. - if len(fullSigBytes) < 1 { + // + // This only applies if tapscript verification isn't active, as this + // check is done within the sighash itself. + if vm.taprootCtx == nil && len(fullSigBytes) < 1 { vm.dstack.PushBool(false) return nil } @@ -2024,6 +2027,43 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error { vm.dstack.PushBool(false) return nil } + + // Otherwise, this is routine tapscript execution. + case vm.taprootCtx == nil: + // If this is tapscript execution, and the signature was + // actually an empty vector, then we just on an empty vector + // and continue execution from there + if len(fullSigBytes) == 0 { + // TODO(roasbeef): re-verify nil vs empty vector here + vm.dstack.PushByteArray([]byte{}) + return nil + } + + // 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( + pkBytes, fullSigBytes, vm, + ) + if err != nil { + return err + } + + // Account for changes in the sig ops budget after this + // execution. + if err := vm.taprootCtx.tallysigOp(); err != nil { + return err + } + + default: + // We skip segwit v1 in isolation here, as the v1 rules aren't + // used in script execution (for sig verification) and are only + // part of the top-level key-spend verification which we + // already skipped. + // + // In other words, this path shouldn't ever be reached + // + // TODO(roasbeef): return an error? } // TODO(roasbeef): verify NULLFAIL semantics as relates to constructors @@ -2031,6 +2071,11 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error { valid := sigVerifier.Verify() switch { + // For tapscript, and prior execution with null fail active, if the + // signature is invalid, then this MUST be an empty signature. + case !valid && vm.taprootCtx != nil && + (len(fullSigBytes) != 0 || len(fullSigBytes) != 1): + fallthrough case !valid && vm.hasFlag(ScriptVerifyNullFail) && len(fullSigBytes[1:]) > 0: str := "signature not empty on failed checksig" return scriptError(ErrNullFail, str) @@ -2047,6 +2092,14 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error { // Stack transformation: [... signature pubkey] -> [... bool] -> [...] func opcodeCheckSigVerify(op *opcode, data []byte, vm *Engine) error { err := opcodeCheckSig(op, data, vm) + + // During tapscript execution this op code doesn't modify the stack in + // the case of a nil error. Execution simply picks up from where we + // left off. + if vm.taprootCtx != nil && err == nil { + return nil + } + if err == nil { err = abstractVerify(op, vm, ErrCheckSigVerify) } diff --git a/txscript/script.go b/txscript/script.go index 2ac2d31a124..2f44f23d25b 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -1060,7 +1060,7 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int { } switch witnessVersion { - case 0: + case BaseSegwitWitnessVersion: switch { case len(witnessProgram) == payToWitnessPubKeyHashDataSize: return 1 @@ -1070,8 +1070,10 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int { witnessScript := witness[len(witness)-1] return countSigOpsV0(witnessScript, true) } - case 1: - // https://github.com/bitcoin/bitcoin/blob/368831371d97a642beb54b5c4eb6eb0fedaa16b4/src/script/interpreter.cpp#L2090 + + // Taproot signature operations don't count towards the block-wide sig + // op limit, instead a distinct weight-based accounting method is used. + case TaprootWitnessVersion: return 0 }