From abeaf4e334311ff3687af358448370eb74ec235d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 6 Jan 2022 17:19:53 -0800 Subject: [PATCH] txscript: introduce new signatureVerifier interface to abstract over schnorr/ecdsa In this commit, we add a new signatureVerifier interface that will allow us to consolidate a lot of code as we'll now have 4 distinct sig+sighash types to verify: 1. pre-segwit 2. segwit v0 3. segwit v1 (taproot key spend) 4. tapscript spends We'll need to be able to handle 3 of the cases for the modified OP_CHECKSIG operator. This new abstraction allows us to keep the implementation of the function somewhat succinct. In this commit we implement a verifier for #3 which is needed to verify the top-level taproot keyspend. We expose the verifier using a new VerifyTaprootKeySpend function. --- go.mod | 1 + txscript/engine.go | 42 ++++ txscript/sigvalidate.go | 480 ++++++++++++++++++++++++++++++++++++++++ txscript/taproot.go | 50 +++++ 4 files changed, 573 insertions(+) create mode 100644 txscript/sigvalidate.go create mode 100644 txscript/taproot.go diff --git a/go.mod b/go.mod index a006d957dc..6962080350 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( require ( github.com/aead/siphash v1.0.1 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect diff --git a/txscript/engine.go b/txscript/engine.go index b576165ee0..d9df0d99ab 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" ) @@ -117,6 +118,46 @@ const ( // halforder is used to tame ECDSA malleability (see BIP0062). var halfOrder = new(big.Int).Rsh(btcec.S256().N, 1) +// taprootExecutionCtx houses the special context-specific information we need +// to validate a taproot script spend. This includes the annex, the running sig +// op count tally, and other relevant information. +type taprootExecutionCtx struct { + annex []byte + + codeSepPos uint32 + + tapLeafHash chainhash.Hash + + sigOpsBudget uint32 +} + +// sigOpsDelta is both the starting budget for sig ops for tapscript +// verification, as well as the decrease in the total budget when we encounter +// a signature. +const sigOpsDelta = 50 + +// tallysigOp attempts to decrease the current sig ops budget by sigOpsDelta. +// An error is returned if after subtracting the delta, the budget is below +// zero. +func (t *taprootExecutionCtx) tallysigOp() error { + t.sigOpsBudget -= sigOpsDelta + + if t.sigOpsBudget == 0 { + return fmt.Errorf("max sig ops exceeded") + } + + return nil +} + +// newTaprootExecutionCtx returns a fresh instance of the taproot execution +// context. +func newTaprootExecutionCtx(inputWitnessSize uint32) *taprootExecutionCtx { + return &taprootExecutionCtx{ + codeSepPos: blankCodeSepValue, + sigOpsBudget: sigOpsDelta + inputWitnessSize, + } +} + // Engine is the virtual machine that executes scripts. type Engine struct { // The following fields are set when the engine is created and must not be @@ -201,6 +242,7 @@ type Engine struct { witnessVersion int witnessProgram []byte inputAmount int64 + taprootCtx *taprootExecutionCtx } // hasFlag returns whether the script engine instance has the passed flag set. diff --git a/txscript/sigvalidate.go b/txscript/sigvalidate.go new file mode 100644 index 0000000000..7f4b6f83a7 --- /dev/null +++ b/txscript/sigvalidate.go @@ -0,0 +1,480 @@ +// Copyright (c) 2013-2022 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// signatureVerifier is an abstract interface that allows the op code execution +// to abstract over the _type_ of signature validation being executed. At this +// point in Bitcoin's history, there're four possible sig validation contexts: +// pre-segwit, segwit v0, segwit v1 (taproot key spend validation), and the +// base tapscript verification. +type signatureVerifier interface { + // Verify returns true if the signature verifier context deems the + // signature to be valid for the given context. + Verify() bool +} + +// baseSigVerifier is used to verify signatures for the _base_ system, meaning +// ECDSA signatures encoded in DER or BER encoding. +type baseSigVerifier struct { + vm *Engine + + pubKey *btcec.PublicKey + + sig *ecdsa.Signature + + fullSigBytes []byte + + sigBytes []byte + pkBytes []byte + + subScript []byte + + hashType SigHashType +} + +// parseBaseSigAndPubkey attempts to parse a signature and public key according +// to the base consensus rules, which expect an 33-byte public key and DER or +// BER encoded signature. +func parseBaseSigAndPubkey(pkBytes, fullSigBytes []byte, + vm *Engine) (*btcec.PublicKey, *ecdsa.Signature, SigHashType, error) { + + strictEncoding := vm.hasFlag(ScriptVerifyStrictEncoding) || + vm.hasFlag(ScriptVerifyDERSignatures) + + // Trim off hashtype from the signature string and check if the + // signature and pubkey conform to the strict encoding requirements + // depending on the flags. + // + // NOTE: When the strict encoding flags are set, any errors in the + // signature or public encoding here result in an immediate script error + // (and thus no result bool is pushed to the data stack). This differs + // from the logic below where any errors in parsing the signature is + // treated as the signature failure resulting in false being pushed to + // the data stack. This is required because the more general script + // validation consensus rules do not have the new strict encoding + // requirements enabled by the flags. + hashType := SigHashType(fullSigBytes[len(fullSigBytes)-1]) + sigBytes := fullSigBytes[:len(fullSigBytes)-1] + if err := vm.checkHashTypeEncoding(hashType); err != nil { + return nil, nil, 0, err + } + if err := vm.checkSignatureEncoding(sigBytes); err != nil { + return nil, nil, 0, err + } + if err := vm.checkPubKeyEncoding(pkBytes); err != nil { + return nil, nil, 0, err + } + + // First, parse the public key, which we expect to be in the proper + // encoding. + pubKey, err := btcec.ParsePubKey(pkBytes) + if err != nil { + return nil, nil, 0, err + } + + // Next, parse the signature which should be in DER or BER depending on + // the active script flags. + var signature *ecdsa.Signature + if strictEncoding { + signature, err = ecdsa.ParseDERSignature(sigBytes) + } else { + signature, err = ecdsa.ParseSignature(sigBytes) + } + if err != nil { + return nil, nil, 0, err + } + + return pubKey, signature, hashType, nil +} + +// newBaseSigVerifier returns a new instance of the base signature verifier. An +// error is returned if the signature, sighash, or public key aren't correctly +// encoded. +func newBaseSigVerifier(pkBytes, fullSigBytes []byte, + vm *Engine) (*baseSigVerifier, error) { + + pubKey, sig, hashType, err := parseBaseSigAndPubkey( + pkBytes, fullSigBytes, vm, + ) + if err != nil { + return nil, err + } + + // Get script starting from the most recent OP_CODESEPARATOR. + subScript := vm.subScript() + + return &baseSigVerifier{ + vm: vm, + pubKey: pubKey, + pkBytes: pkBytes, + sig: sig, + sigBytes: fullSigBytes[:len(fullSigBytes)-1], + subScript: subScript, + hashType: hashType, + fullSigBytes: fullSigBytes, + }, nil +} + +// verifySig attempts to verify the signature given the computed sighash. A nil +// error is returned if the signature is valid. +func (b *baseSigVerifier) verifySig(sigHash []byte) bool { + var valid bool + if b.vm.sigCache != nil { + var sigHashBytes chainhash.Hash + copy(sigHashBytes[:], sigHash[:]) + + valid = b.vm.sigCache.Exists(sigHashBytes, b.sigBytes, b.pkBytes) + if !valid && b.sig.Verify(sigHash, b.pubKey) { + b.vm.sigCache.Add(sigHashBytes, b.sigBytes, b.pkBytes) + valid = true + } + } else { + valid = b.sig.Verify(sigHash, b.pubKey) + } + + return valid +} + +// Verify returns true if the signature verifier context deems the signature to +// be valid for the given context. +// +// NOTE: This is part of the baseSigVerifier interface. +func (b *baseSigVerifier) Verify() bool { + // Remove the signature since there is no way for a signature + // to sign itself. + subScript := removeOpcodeByData(b.subScript, b.fullSigBytes) + + sigHash := calcSignatureHash( + subScript, b.hashType, &b.vm.tx, b.vm.txIdx, + ) + + return b.verifySig(sigHash) +} + +// A compile-time assertion to ensure baseSigVerifier implements the +// signatureVerifier interface. +var _ signatureVerifier = (*baseSigVerifier)(nil) + +// baseSegwitSigVerifier implements signature verification for segwit v0. The +// only difference between this and the baseSigVerifier is how the sighash is +// computed. +type baseSegwitSigVerifier struct { + *baseSigVerifier +} + +// newBaseSegwitSigVerifier returns a new instance of the base segwit verifier. +func newBaseSegwitSigVerifier(pkBytes, fullSigBytes []byte, + vm *Engine) (*baseSegwitSigVerifier, error) { + + sigVerifier, err := newBaseSigVerifier(pkBytes, fullSigBytes, vm) + if err != nil { + return nil, err + } + + return &baseSegwitSigVerifier{ + baseSigVerifier: sigVerifier, + }, nil +} + +// Verify returns true if the signature verifier context deems the signature to +// be valid for the given context. +// +// NOTE: This is part of the baseSigVerifier interface. +func (s *baseSegwitSigVerifier) Verify() bool { + var sigHashes *TxSigHashes + if s.vm.hashCache != nil { + sigHashes = s.vm.hashCache + } else { + sigHashes = NewTxSigHashes(&s.vm.tx, s.vm.prevOutFetcher) + } + + sigHash, err := calcWitnessSignatureHashRaw( + s.subScript, sigHashes, s.hashType, &s.vm.tx, s.vm.txIdx, + s.vm.inputAmount, + ) + if err != nil { + // TODO(roasbeef): this doesn't need to return an error, should + // instead be further up the stack? this only returns an error + // if the input index is greater than the number of inputs + return false + } + + return s.verifySig(sigHash) +} + +// A compile-time assertion to ensure baseSegwitSigVerifier implements the +// signatureVerifier interface. +var _ signatureVerifier = (*baseSegwitSigVerifier)(nil) + +// taprootSigVerifier verifies signatures according to the segwit v1 rules, +// which are described in BIP 341. +type taprootSigVerifier struct { + pubKey *btcec.PublicKey + pkBytes []byte + + fullSigBytes []byte + sig *schnorr.Signature + + hashType SigHashType + + sigCache *SigCache + hashCache *TxSigHashes + + tx *wire.MsgTx + + inputIndex int + + annex []byte + + prevOuts PrevOutputFetcher +} + +// parseTaprootSigAndPubKey attempts to parse the public key and signature for +// a taproot spend that may be a keyspend or script path spend. This function +// returns an error if the pubkey is invalid, or the sig is. +func parseTaprootSigAndPubKey(pkBytes, rawSig []byte, +) (*btcec.PublicKey, *schnorr.Signature, SigHashType, error) { + + // Now that we have the raw key, we'll parse it into a schnorr public + // key we can work with. + pubKey, err := schnorr.ParsePubKey(pkBytes) + if err != nil { + return nil, nil, 0, err + } + + // Next, we'll parse the signature, which may or may not be appended + // with the desired sighash flag. + var ( + sig *schnorr.Signature + sigHashType SigHashType + ) + switch { + // If the signature is exactly 64 bytes, then we know we're using the + // implicit SIGHASH_DEFAULT sighash type. + case len(rawSig) == schnorr.SignatureSize: + // First, parse out the signature which is just the raw sig itself. + sig, err = schnorr.ParseSignature(rawSig) + if err != nil { + return nil, nil, 0, err + } + + // If the sig is 64 bytes, then we'll assume that it's the + // default sighash type, which is actually an alias for + // SIGHASH_ALL. + sigHashType = SigHashDefault + + // Otherwise, if this is a signature, with a sighash looking byte + // appended that isn't all zero, then we'll extract the sighash from + // the end of the signature. + case len(rawSig) == schnorr.SignatureSize+1 && rawSig[64] != 0: + // Extract the sighash type, then snip off the last byte so we can + // parse the signature. + sigHashType = SigHashType(rawSig[schnorr.SignatureSize]) + + rawSig = rawSig[:schnorr.SignatureSize] + sig, err = schnorr.ParseSignature(rawSig) + if err != nil { + return nil, nil, 0, err + } + + // Otherwise, this is an invalid signature, so we need to bail out. + default: + // TODO(roasbeef): do proper error here + return nil, nil, 0, fmt.Errorf("invalid sig len: %v", len(rawSig)) + } + + return pubKey, sig, sigHashType, nil +} + +// newTaprootSigVerifier returns a new instance of a taproot sig verifier given +// the necessary contextual information. +func newTaprootSigVerifier(pkBytes []byte, fullSigBytes []byte, + tx *wire.MsgTx, inputIndex int, prevOuts PrevOutputFetcher, + sigCache *SigCache, hashCache *TxSigHashes, + annex []byte) (*taprootSigVerifier, error) { + + pubKey, sig, sigHashType, err := parseTaprootSigAndPubKey( + pkBytes, fullSigBytes, + ) + if err != nil { + return nil, err + } + + return &taprootSigVerifier{ + pubKey: pubKey, + pkBytes: pkBytes, + sig: sig, + fullSigBytes: fullSigBytes, + hashType: sigHashType, + tx: tx, + inputIndex: inputIndex, + prevOuts: prevOuts, + sigCache: sigCache, + hashCache: hashCache, + annex: annex, + }, nil +} + +// verifySig attempts to verify a BIP 340 signature using the internal public +// key and signature, and the passed sigHash as the message digest. +func (t *taprootSigVerifier) verifySig(sigHash []byte) bool { + // At this point, we can check to see if this signature is already + // included in the sigCcahe and is valid or not (if one was passed in). + cacheKey, _ := chainhash.NewHash(sigHash) + if t.sigCache != nil { + if t.sigCache.Exists(*cacheKey, t.fullSigBytes, t.pkBytes) { + return true + } + } + + // If we didn't find the entry in the cache, then we'll perform full + // verification as normal, adding the entry to the cache if it's found + // to be valid. + sigValid := t.sig.Verify(sigHash, t.pubKey) + if sigValid { + if t.sigCache != nil { + // The sig is valid, so we'll add it to the cache. + t.sigCache.Add(*cacheKey, t.fullSigBytes, t.pkBytes) + } + + return true + } + + // Otherwise the sig is invalid if we get to this point. + return false +} + +// Verify returns true if the signature verifier context deems the signature to +// be valid for the given context. +// +// NOTE: This is part of the baseSigVerifier interface. +func (t *taprootSigVerifier) Verify() bool { + var opts []TaprootSigHashOption + if t.annex != nil { + opts = append(opts, WithAnnex(t.annex)) + } + + // Before we attempt to verify the signature, we'll need to first + // compute the sighash based on the input and tx information. + sigHash, err := calcTaprootSignatureHashRaw( + t.hashCache, t.hashType, t.tx, t.inputIndex, t.prevOuts, + opts..., + ) + if err != nil { + // TODO(roasbeef): propagate the error here? + return false + } + + return t.verifySig(sigHash) +} + +// A compile-time assertion to ensure taprootSigVerifier implements the +// signatureVerifier interface. +var _ signatureVerifier = (*taprootSigVerifier)(nil) + +// baseTapscriptSigVerifier verifies a signature for an input spending a +// tapscript leaf from the prevoous output. +type baseTapscriptSigVerifier struct { + *taprootSigVerifier + + vm *Engine +} + +// newBaseTapscriptSigVerifier returns a new sig verifier for tapscript input +// spends. If the public key or signature aren't correctly formatted, an error +// is returned. +func newBaseTapscriptSigVerifier(pkBytes, rawSig []byte, + vm *Engine) (*baseTapscriptSigVerifier, error) { + + switch len(pkBytes) { + // If the public key is zero bytes, then this is invalid, and will fail + // immediately. + case 0: + // TODO(roasbeef): better erro + return nil, fmt.Errorf("pubkey is zero bytes") + + // If the public key is 32 byte as we expect, then we'll parse things + // as normal. + case 32: + baseTaprootVerifier, err := newTaprootSigVerifier( + pkBytes, rawSig, &vm.tx, vm.txIdx, vm.prevOutFetcher, + vm.sigCache, vm.hashCache, vm.taprootCtx.annex, + ) + if err != nil { + return nil, err + } + + return &baseTapscriptSigVerifier{ + taprootSigVerifier: baseTaprootVerifier, + vm: vm, + }, nil + + // Otherwise, we consider this to be an unknown public key, which means + // that we'll just assume the sig to be valid. + default: + // However, if the flag preventing usage of unknown key types + // is active, then we'll return that error. + if vm.hasFlag(ScriptVerifyDiscourageUpgradeablePubkeyType) { + str := fmt.Sprintf("puybkey of length %v was used", + len(pkBytes)) + return nil, scriptError( + ErrDiscourageUpgradeablePubKeyType, str, + ) + } + + return &baseTapscriptSigVerifier{ + taprootSigVerifier: &taprootSigVerifier{}, + }, nil + } +} + +// Verify returns true if the signature verifier context deems the signature to +// be valid for the given context. +// +// NOTE: This is part of the baseSigVerifier interface. +func (b *baseTapscriptSigVerifier) Verify() bool { + // If the public key is blank, then that means it wasn't 0 or 32 bytes, + // so we'll treat this as an unknown public key version and return + // true. + if b.pubKey == nil { + return true + } + + var opts []TaprootSigHashOption + opts = append(opts, WithBaseTapscriptVersion( + b.vm.taprootCtx.codeSepPos, b.vm.taprootCtx.tapLeafHash[:], + )) + + if b.vm.taprootCtx.annex != nil { + opts = append(opts, WithAnnex(b.vm.taprootCtx.annex)) + } + + // Otherwise, we'll compute the sighash using the tapscript message + // extensions and return the outcome. + sigHash, err := calcTaprootSignatureHashRaw( + b.hashCache, b.hashType, b.tx, b.inputIndex, b.prevOuts, + opts..., + ) + if err != nil { + // TODO(roasbeef): propagate the error here? + return false + } + + return b.verifySig(sigHash) +} + +// A compile-time assertion to ensure baseTapscriptSigVerifier implements the +// signatureVerifier interface. +var _ signatureVerifier = (*baseTapscriptSigVerifier)(nil) diff --git a/txscript/taproot.go b/txscript/taproot.go new file mode 100644 index 0000000000..c086cfbc54 --- /dev/null +++ b/txscript/taproot.go @@ -0,0 +1,50 @@ +package txscript + +import ( + "fmt" + + "github.com/btcsuite/btcd/wire" +) + +// VerifyTaprootKeySpend attempts to verify a top-level taproot key spend, +// returning a non-nil error if the passed signature is invalid. If a sigCache +// is passed in, then the sig cache will be consulted to skip full verification +// of a signature that has already been seen. Witness program here should be +// the 32-byte x-only schnorr output public key. +// +// NOTE: The TxSigHashes MUST be passed in and fully populated. +func VerifyTaprootKeySpend(witnessProgram []byte, rawSig []byte, tx *wire.MsgTx, + inputIndex int, prevOuts PrevOutputFetcher, hashCache *TxSigHashes, + sigCache *SigCache) error { + + // First, we'll need to extract the public key from the witness + // program. + rawKey := witnessProgram + + // Extract the annex if it exists, so we can compute the proper proper + // sighash below. + var annex []byte + witness := tx.TxIn[inputIndex].Witness + if isAnnexedWitness(witness) { + annex, _ = extractAnnex(witness) + } + + // Now that we have the public key, we can create a new top-level + // keyspend verifier that'll handle all the sighash and schnorr + // specifics for us. + keySpendVerifier, err := newTaprootSigVerifier( + rawKey, rawSig, tx, inputIndex, prevOuts, sigCache, + hashCache, annex, + ) + if err != nil { + return err + } + + valid := keySpendVerifier.Verify() + if valid { + return nil + } + + // TODO(roasbeef): add proper error + return fmt.Errorf("invalid sig") +}