mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-13 11:35:52 +01:00
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:
parent
74e9690d0e
commit
bfd0f4a492
5 changed files with 103 additions and 56 deletions
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue