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
This commit is contained in:
Olaoluwa Osuntokun 2022-01-06 18:27:00 -08:00
parent a4beed9f72
commit 3ce6130ee4
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
2 changed files with 58 additions and 7 deletions

View file

@ -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,14 +2027,60 @@ 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:
// Account for changes in the sig ops budget after this
// execution, but only for non-empty signatures.
if len(fullSigBytes) > 0 {
if err := vm.taprootCtx.tallysigOp(); err != nil {
return err
}
}
// Empty public keys immeidately cause execution to fail.
if len(pkBytes) == 0 {
return fmt.Errorf("nil pub key")
}
// If this is tapscript execution, and the signature was
// actually an empty vector, then we push on an empty vector
// and continue execution from ther, but only if the pubkey
// isn't empty.
if len(fullSigBytes) == 0 {
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
}
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
// above and empty sig vectors
valid := sigVerifier.Verify()
switch {
case !valid && vm.hasFlag(ScriptVerifyNullFail) && len(fullSigBytes[1:]) > 0:
// 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:
fallthrough
case !valid && vm.hasFlag(ScriptVerifyNullFail) && len(fullSigBytes) > 0:
str := "signature not empty on failed checksig"
return scriptError(ErrNullFail, str)
}

View file

@ -474,7 +474,7 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int {
}
switch witnessVersion {
case 0:
case BaseSegwitWitnessVersion:
switch {
case len(witnessProgram) == payToWitnessPubKeyHashDataSize:
return 1
@ -484,8 +484,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
}