txscript: add taproot script type

Add the WitnessV1TaprootTy script class and return it from
GetScriptClass / typeOfScript.

Bump the btcutil dep to leverage new taproot address type.
This commit is contained in:
Brian Stafford 2021-11-01 13:42:22 -05:00 committed by Olaoluwa Osuntokun
parent 74e9690d0e
commit bfd0f4a492
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
5 changed files with 103 additions and 56 deletions

View file

@ -7,9 +7,9 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcd/btcutil"
"golang.org/x/crypto/ripemd160"
)
@ -48,6 +48,9 @@ const (
// witnessV0ScriptHashLen is the length of a P2WSH script.
witnessV0ScriptHashLen = 34
// witnessV1TaprootLen is the length of a P2TR script.
witnessV1TaprootLen = 34
// maxLen is the maximum script length supported by ParsePkScript.
maxLen = witnessV0ScriptHashLen
)
@ -100,7 +103,7 @@ func ParsePkScript(pkScript []byte) (PkScript, error) {
func isSupportedScriptType(class ScriptClass) bool {
switch class {
case PubKeyHashTy, WitnessV0PubKeyHashTy, ScriptHashTy,
WitnessV0ScriptHashTy:
WitnessV0ScriptHashTy, WitnessV1TaprootTy:
return true
default:
return false
@ -133,6 +136,10 @@ func (s PkScript) Script() []byte {
script = make([]byte, witnessV0ScriptHashLen)
copy(script, s.script[:witnessV0ScriptHashLen])
case WitnessV1TaprootTy:
script = make([]byte, witnessV1TaprootLen)
copy(script, s.script[:witnessV1TaprootLen])
default:
// Unsupported script type.
return nil

View file

@ -429,12 +429,12 @@ func TestComputePkScript(t *testing.T) {
}
if pkScript.Class() != test.class {
t.Fatalf("expected pkScript of type %v, got %v",
test.class, pkScript.Class())
t.Fatalf("%s: expected pkScript of type %v, got %v",
test.name, test.class, pkScript.Class())
}
if !bytes.Equal(pkScript.Script(), test.pkScript) {
t.Fatalf("expected pkScript=%x, got pkScript=%x",
test.pkScript, pkScript.Script())
t.Fatalf("%s: expected pkScript=%x, got pkScript=%x",
test.name, test.pkScript, pkScript.Script())
}
})
}

View file

@ -651,7 +651,7 @@ func countSigOpsV0(script []byte, precise bool) int {
// covering 1 through 16 pubkeys, which means this will count any
// more than that value (e.g. 17, 18 19) as the maximum number of
// allowed pubkeys. This is, unfortunately, now part of
// the Bitcion consensus rules, due to historical
// the Bitcoin consensus rules, due to historical
// reasons. This could be made more correct with a new
// script version, however, ideally all multisignaure
// operations in new script versions should move to
@ -799,6 +799,9 @@ 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
return 0
}
return 0

View file

@ -173,6 +173,7 @@ func TestGetPreciseSigOps(t *testing.T) {
// nested p2sh, and invalid variants are counted properly.
func TestGetWitnessSigOpCount(t *testing.T) {
t.Parallel()
tests := []struct {
name string
@ -182,7 +183,7 @@ func TestGetWitnessSigOpCount(t *testing.T) {
numSigOps int
}{
// A regualr p2wkh witness program. The output being spent
// A regular p2wkh witness program. The output being spent
// should only have a single sig-op counted.
{
name: "p2wkh",

View file

@ -7,9 +7,9 @@ package txscript
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcd/btcutil"
)
const (
@ -58,6 +58,7 @@ const (
WitnessV0ScriptHashTy // Pay to witness script hash.
MultiSigTy // Multi signature.
NullDataTy // Empty data-only (provably prunable).
WitnessV1TaprootTy // Taproot output
WitnessUnknownTy // Witness unknown
)
@ -72,6 +73,7 @@ var scriptClassToName = []string{
WitnessV0ScriptHashTy: "witness_v0_scripthash",
MultiSigTy: "multisig",
NullDataTy: "nulldata",
WitnessV1TaprootTy: "witness_v1_taproot",
WitnessUnknownTy: "witness_unknown",
}
@ -349,11 +351,11 @@ func IsMultisigSigScript(script []byte) bool {
func extractWitnessPubKeyHash(script []byte) []byte {
// A pay-to-witness-pubkey-hash script is of the form:
// OP_0 OP_DATA_20 <20-byte-hash>
if len(script) == 22 &&
if len(script) == witnessV0PubKeyHashLen &&
script[0] == OP_0 &&
script[1] == OP_DATA_20 {
return script[2:22]
return script[2:witnessV0PubKeyHashLen]
}
return nil
@ -365,13 +367,13 @@ func isWitnessPubKeyHashScript(script []byte) bool {
return extractWitnessPubKeyHash(script) != nil
}
// extractWitnessScriptHash extracts the witness script hash from the passed
// extractWitnessV0ScriptHash extracts the witness script hash from the passed
// script if it is standard pay-to-witness-script-hash script. It will return
// nil otherwise.
func extractWitnessScriptHash(script []byte) []byte {
func extractWitnessV0ScriptHash(script []byte) []byte {
// A pay-to-witness-script-hash script is of the form:
// OP_0 OP_DATA_32 <32-byte-hash>
if len(script) == 34 &&
if len(script) == witnessV0ScriptHashLen &&
script[0] == OP_0 &&
script[1] == OP_DATA_32 {
@ -381,10 +383,26 @@ func extractWitnessScriptHash(script []byte) []byte {
return nil
}
// extractWitnessV1ScriptHash extracts the witness script hash from the passed
// script if it is standard pay-to-witness-script-hash script. It will return
// nil otherwise.
func extractWitnessV1ScriptHash(script []byte) []byte {
// A pay-to-witness-script-hash script is of the form:
// OP_1 OP_DATA_32 <32-byte-hash>
if len(script) == witnessV1TaprootLen &&
script[0] == OP_1 &&
script[1] == OP_DATA_32 {
return script[2:34]
}
return nil
}
// isWitnessScriptHashScript returns whether or not the passed script is a
// standard pay-to-witness-script-hash script.
func isWitnessScriptHashScript(script []byte) bool {
return extractWitnessScriptHash(script) != nil
return extractWitnessV0ScriptHash(script) != nil
}
// extractWitnessProgramInfo returns the version and program if the passed
@ -435,15 +453,28 @@ func extractWitnessProgramInfo(script []byte) (int, []byte, bool) {
// smallest program is the witness version, followed by a data push of
// 2 bytes. The largest allowed witness program has a data push of
// 40-bytes.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func isWitnessProgramScript(script []byte) bool {
_, _, valid := extractWitnessProgramInfo(script)
return valid
}
// isWitnessTaprootScript returns true if the passed script is for a
// pay-to-witness-taproot output, false otherwise.
func isWitnessTaprootScript(script []byte) bool {
return extractWitnessV1ScriptHash(script) != nil
}
// isAnnexedWitness returns true if the passed witness has a final push
// that is a witness annex.
func isAnnexedWitness(witness [][]byte) bool {
const OP_ANNEX = 0x50
if len(witness) < 2 {
return false
}
lastElement := witness[len(witness)-1]
return len(lastElement) > 0 && lastElement[0] == OP_ANNEX
}
// isNullDataScript returns whether or not the passed script is a standard
// null data script.
//
@ -480,15 +511,11 @@ func isNullDataScript(scriptVersion uint16, script []byte) bool {
}
// scriptType returns the type of the script being inspected from the known
// standard types.
//
// NOTE: All scripts that are not version 0 are currently considered non
// standard.
// standard types. The version version should be 0 if the script is segwit v0
// or prior, and 1 for segwit v1 (taproot) scripts.
func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
if scriptVersion != 0 {
return NonStandardTy
}
switch scriptVersion {
case 0:
switch {
case isPubKeyScript(script):
return PubKeyTy
@ -504,17 +531,29 @@ func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
return MultiSigTy
case isNullDataScript(scriptVersion, script):
return NullDataTy
default:
return NonStandardTy
}
case 1:
switch {
case isWitnessTaprootScript(script):
return WitnessV1TaprootTy
}
}
return NonStandardTy
}
// GetScriptClass returns the class of the script passed.
//
// NonStandardTy will be returned when the script does not parse.
func GetScriptClass(script []byte) ScriptClass {
const scriptVersion = 0
return typeOfScript(scriptVersion, script)
const scriptVersionSegWit = 0
classSegWit := typeOfScript(scriptVersionSegWit, script)
if classSegWit != NonStandardTy {
return classSegWit
}
const scriptVersionTaproot = 1
return typeOfScript(scriptVersionTaproot, script)
}
// NewScriptClass returns the ScriptClass corresponding to the string name
@ -561,6 +600,10 @@ func expectedInputs(script []byte, class ScriptClass) int {
// Not including script. That is handled by the caller.
return 1
case WitnessV1TaprootTy:
// Not including script. That is handled by the caller.
return 1
case MultiSigTy:
// Standard multisig has a push a small number for the number
// of sigs and number of keys. Check the first push instruction
@ -848,10 +891,6 @@ func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, er
// PushedData returns an array of byte slices containing any pushed data found
// in the passed script. This includes OP_0, but not OP_1 - OP_16.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func PushedData(script []byte) ([][]byte, error) {
const scriptVersion = 0
@ -900,11 +939,8 @@ func scriptHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address {
// signatures associated with the passed PkScript. Note that it only works for
// 'standard' transaction script types. Any data such as public keys which are
// invalid are omitted from the results.
//
// NOTE: This function only attempts to identify version 0 scripts. The return
// value will indicate a nonstandard script type for other script versions along
// with an invalid script version error.
func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) {
func ExtractPkScriptAddrs(pkScript []byte,
chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) {
// Check for pay-to-pubkey-hash script.
if hash := extractPubKeyHash(pkScript); hash != nil {
@ -956,7 +992,7 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script
return WitnessV0PubKeyHashTy, addrs, 1, nil
}
if hash := extractWitnessScriptHash(pkScript); hash != nil {
if hash := extractWitnessV0ScriptHash(pkScript); hash != nil {
var addrs []btcutil.Address
addr, err := btcutil.NewAddressWitnessScriptHash(hash, chainParams)
if err == nil {