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"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +48,9 @@ const (
|
||||||
// witnessV0ScriptHashLen is the length of a P2WSH script.
|
// witnessV0ScriptHashLen is the length of a P2WSH script.
|
||||||
witnessV0ScriptHashLen = 34
|
witnessV0ScriptHashLen = 34
|
||||||
|
|
||||||
|
// witnessV1TaprootLen is the length of a P2TR script.
|
||||||
|
witnessV1TaprootLen = 34
|
||||||
|
|
||||||
// maxLen is the maximum script length supported by ParsePkScript.
|
// maxLen is the maximum script length supported by ParsePkScript.
|
||||||
maxLen = witnessV0ScriptHashLen
|
maxLen = witnessV0ScriptHashLen
|
||||||
)
|
)
|
||||||
|
@ -100,7 +103,7 @@ func ParsePkScript(pkScript []byte) (PkScript, error) {
|
||||||
func isSupportedScriptType(class ScriptClass) bool {
|
func isSupportedScriptType(class ScriptClass) bool {
|
||||||
switch class {
|
switch class {
|
||||||
case PubKeyHashTy, WitnessV0PubKeyHashTy, ScriptHashTy,
|
case PubKeyHashTy, WitnessV0PubKeyHashTy, ScriptHashTy,
|
||||||
WitnessV0ScriptHashTy:
|
WitnessV0ScriptHashTy, WitnessV1TaprootTy:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
@ -133,6 +136,10 @@ func (s PkScript) Script() []byte {
|
||||||
script = make([]byte, witnessV0ScriptHashLen)
|
script = make([]byte, witnessV0ScriptHashLen)
|
||||||
copy(script, s.script[:witnessV0ScriptHashLen])
|
copy(script, s.script[:witnessV0ScriptHashLen])
|
||||||
|
|
||||||
|
case WitnessV1TaprootTy:
|
||||||
|
script = make([]byte, witnessV1TaprootLen)
|
||||||
|
copy(script, s.script[:witnessV1TaprootLen])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Unsupported script type.
|
// Unsupported script type.
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -429,12 +429,12 @@ func TestComputePkScript(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkScript.Class() != test.class {
|
if pkScript.Class() != test.class {
|
||||||
t.Fatalf("expected pkScript of type %v, got %v",
|
t.Fatalf("%s: expected pkScript of type %v, got %v",
|
||||||
test.class, pkScript.Class())
|
test.name, test.class, pkScript.Class())
|
||||||
}
|
}
|
||||||
if !bytes.Equal(pkScript.Script(), test.pkScript) {
|
if !bytes.Equal(pkScript.Script(), test.pkScript) {
|
||||||
t.Fatalf("expected pkScript=%x, got pkScript=%x",
|
t.Fatalf("%s: expected pkScript=%x, got pkScript=%x",
|
||||||
test.pkScript, pkScript.Script())
|
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
|
// 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
|
// more than that value (e.g. 17, 18 19) as the maximum number of
|
||||||
// allowed pubkeys. This is, unfortunately, now part 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
|
// reasons. This could be made more correct with a new
|
||||||
// script version, however, ideally all multisignaure
|
// script version, however, ideally all multisignaure
|
||||||
// operations in new script versions should move to
|
// 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]
|
witnessScript := witness[len(witness)-1]
|
||||||
return countSigOpsV0(witnessScript, true)
|
return countSigOpsV0(witnessScript, true)
|
||||||
}
|
}
|
||||||
|
case 1:
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/368831371d97a642beb54b5c4eb6eb0fedaa16b4/src/script/interpreter.cpp#L2090
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -173,6 +173,7 @@ func TestGetPreciseSigOps(t *testing.T) {
|
||||||
// nested p2sh, and invalid variants are counted properly.
|
// nested p2sh, and invalid variants are counted properly.
|
||||||
func TestGetWitnessSigOpCount(t *testing.T) {
|
func TestGetWitnessSigOpCount(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
||||||
|
@ -182,7 +183,7 @@ func TestGetWitnessSigOpCount(t *testing.T) {
|
||||||
|
|
||||||
numSigOps int
|
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.
|
// should only have a single sig-op counted.
|
||||||
{
|
{
|
||||||
name: "p2wkh",
|
name: "p2wkh",
|
||||||
|
|
|
@ -7,9 +7,9 @@ package txscript
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -58,6 +58,7 @@ const (
|
||||||
WitnessV0ScriptHashTy // Pay to witness script hash.
|
WitnessV0ScriptHashTy // Pay to witness script hash.
|
||||||
MultiSigTy // Multi signature.
|
MultiSigTy // Multi signature.
|
||||||
NullDataTy // Empty data-only (provably prunable).
|
NullDataTy // Empty data-only (provably prunable).
|
||||||
|
WitnessV1TaprootTy // Taproot output
|
||||||
WitnessUnknownTy // Witness unknown
|
WitnessUnknownTy // Witness unknown
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -72,6 +73,7 @@ var scriptClassToName = []string{
|
||||||
WitnessV0ScriptHashTy: "witness_v0_scripthash",
|
WitnessV0ScriptHashTy: "witness_v0_scripthash",
|
||||||
MultiSigTy: "multisig",
|
MultiSigTy: "multisig",
|
||||||
NullDataTy: "nulldata",
|
NullDataTy: "nulldata",
|
||||||
|
WitnessV1TaprootTy: "witness_v1_taproot",
|
||||||
WitnessUnknownTy: "witness_unknown",
|
WitnessUnknownTy: "witness_unknown",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,11 +351,11 @@ func IsMultisigSigScript(script []byte) bool {
|
||||||
func extractWitnessPubKeyHash(script []byte) []byte {
|
func extractWitnessPubKeyHash(script []byte) []byte {
|
||||||
// A pay-to-witness-pubkey-hash script is of the form:
|
// A pay-to-witness-pubkey-hash script is of the form:
|
||||||
// OP_0 OP_DATA_20 <20-byte-hash>
|
// OP_0 OP_DATA_20 <20-byte-hash>
|
||||||
if len(script) == 22 &&
|
if len(script) == witnessV0PubKeyHashLen &&
|
||||||
script[0] == OP_0 &&
|
script[0] == OP_0 &&
|
||||||
script[1] == OP_DATA_20 {
|
script[1] == OP_DATA_20 {
|
||||||
|
|
||||||
return script[2:22]
|
return script[2:witnessV0PubKeyHashLen]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -365,13 +367,13 @@ func isWitnessPubKeyHashScript(script []byte) bool {
|
||||||
return extractWitnessPubKeyHash(script) != nil
|
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
|
// script if it is standard pay-to-witness-script-hash script. It will return
|
||||||
// nil otherwise.
|
// nil otherwise.
|
||||||
func extractWitnessScriptHash(script []byte) []byte {
|
func extractWitnessV0ScriptHash(script []byte) []byte {
|
||||||
// A pay-to-witness-script-hash script is of the form:
|
// A pay-to-witness-script-hash script is of the form:
|
||||||
// OP_0 OP_DATA_32 <32-byte-hash>
|
// OP_0 OP_DATA_32 <32-byte-hash>
|
||||||
if len(script) == 34 &&
|
if len(script) == witnessV0ScriptHashLen &&
|
||||||
script[0] == OP_0 &&
|
script[0] == OP_0 &&
|
||||||
script[1] == OP_DATA_32 {
|
script[1] == OP_DATA_32 {
|
||||||
|
|
||||||
|
@ -381,10 +383,26 @@ func extractWitnessScriptHash(script []byte) []byte {
|
||||||
return nil
|
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
|
// isWitnessScriptHashScript returns whether or not the passed script is a
|
||||||
// standard pay-to-witness-script-hash script.
|
// standard pay-to-witness-script-hash script.
|
||||||
func isWitnessScriptHashScript(script []byte) bool {
|
func isWitnessScriptHashScript(script []byte) bool {
|
||||||
return extractWitnessScriptHash(script) != nil
|
return extractWitnessV0ScriptHash(script) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractWitnessProgramInfo returns the version and program if the passed
|
// 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
|
// smallest program is the witness version, followed by a data push of
|
||||||
// 2 bytes. The largest allowed witness program has a data push of
|
// 2 bytes. The largest allowed witness program has a data push of
|
||||||
// 40-bytes.
|
// 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 {
|
func isWitnessProgramScript(script []byte) bool {
|
||||||
_, _, valid := extractWitnessProgramInfo(script)
|
_, _, valid := extractWitnessProgramInfo(script)
|
||||||
return valid
|
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
|
// isNullDataScript returns whether or not the passed script is a standard
|
||||||
// null data script.
|
// null data script.
|
||||||
//
|
//
|
||||||
|
@ -480,41 +511,49 @@ func isNullDataScript(scriptVersion uint16, script []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// scriptType returns the type of the script being inspected from the known
|
// scriptType returns the type of the script being inspected from the known
|
||||||
// standard types.
|
// standard types. The version version should be 0 if the script is segwit v0
|
||||||
//
|
// or prior, and 1 for segwit v1 (taproot) scripts.
|
||||||
// NOTE: All scripts that are not version 0 are currently considered non
|
|
||||||
// standard.
|
|
||||||
func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
|
func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
|
||||||
if scriptVersion != 0 {
|
switch scriptVersion {
|
||||||
return NonStandardTy
|
case 0:
|
||||||
}
|
switch {
|
||||||
|
case isPubKeyScript(script):
|
||||||
switch {
|
return PubKeyTy
|
||||||
case isPubKeyScript(script):
|
case isPubKeyHashScript(script):
|
||||||
return PubKeyTy
|
return PubKeyHashTy
|
||||||
case isPubKeyHashScript(script):
|
case isScriptHashScript(script):
|
||||||
return PubKeyHashTy
|
return ScriptHashTy
|
||||||
case isScriptHashScript(script):
|
case isWitnessPubKeyHashScript(script):
|
||||||
return ScriptHashTy
|
return WitnessV0PubKeyHashTy
|
||||||
case isWitnessPubKeyHashScript(script):
|
case isWitnessScriptHashScript(script):
|
||||||
return WitnessV0PubKeyHashTy
|
return WitnessV0ScriptHashTy
|
||||||
case isWitnessScriptHashScript(script):
|
case isMultisigScript(scriptVersion, script):
|
||||||
return WitnessV0ScriptHashTy
|
return MultiSigTy
|
||||||
case isMultisigScript(scriptVersion, script):
|
case isNullDataScript(scriptVersion, script):
|
||||||
return MultiSigTy
|
return NullDataTy
|
||||||
case isNullDataScript(scriptVersion, script):
|
}
|
||||||
return NullDataTy
|
case 1:
|
||||||
default:
|
switch {
|
||||||
return NonStandardTy
|
case isWitnessTaprootScript(script):
|
||||||
|
return WitnessV1TaprootTy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return NonStandardTy
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScriptClass returns the class of the script passed.
|
// GetScriptClass returns the class of the script passed.
|
||||||
//
|
//
|
||||||
// NonStandardTy will be returned when the script does not parse.
|
// NonStandardTy will be returned when the script does not parse.
|
||||||
func GetScriptClass(script []byte) ScriptClass {
|
func GetScriptClass(script []byte) ScriptClass {
|
||||||
const scriptVersion = 0
|
const scriptVersionSegWit = 0
|
||||||
return typeOfScript(scriptVersion, script)
|
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
|
// 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.
|
// Not including script. That is handled by the caller.
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
case WitnessV1TaprootTy:
|
||||||
|
// Not including script. That is handled by the caller.
|
||||||
|
return 1
|
||||||
|
|
||||||
case MultiSigTy:
|
case MultiSigTy:
|
||||||
// Standard multisig has a push a small number for the number
|
// Standard multisig has a push a small number for the number
|
||||||
// of sigs and number of keys. Check the first push instruction
|
// 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
|
// 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.
|
// 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) {
|
func PushedData(script []byte) ([][]byte, error) {
|
||||||
const scriptVersion = 0
|
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
|
// signatures associated with the passed PkScript. Note that it only works for
|
||||||
// 'standard' transaction script types. Any data such as public keys which are
|
// 'standard' transaction script types. Any data such as public keys which are
|
||||||
// invalid are omitted from the results.
|
// invalid are omitted from the results.
|
||||||
//
|
func ExtractPkScriptAddrs(pkScript []byte,
|
||||||
// NOTE: This function only attempts to identify version 0 scripts. The return
|
chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) {
|
||||||
// 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) {
|
|
||||||
|
|
||||||
// Check for pay-to-pubkey-hash script.
|
// Check for pay-to-pubkey-hash script.
|
||||||
if hash := extractPubKeyHash(pkScript); hash != nil {
|
if hash := extractPubKeyHash(pkScript); hash != nil {
|
||||||
|
@ -956,7 +992,7 @@ func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (Script
|
||||||
return WitnessV0PubKeyHashTy, addrs, 1, nil
|
return WitnessV0PubKeyHashTy, addrs, 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if hash := extractWitnessScriptHash(pkScript); hash != nil {
|
if hash := extractWitnessV0ScriptHash(pkScript); hash != nil {
|
||||||
var addrs []btcutil.Address
|
var addrs []btcutil.Address
|
||||||
addr, err := btcutil.NewAddressWitnessScriptHash(hash, chainParams)
|
addr, err := btcutil.NewAddressWitnessScriptHash(hash, chainParams)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue