Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

txscript: add taproot script type #1768

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/btcsuite/btcd

require (
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
github.com/btcsuite/btcutil v1.0.3-0.20210929233259-9cdf59f60c51
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd
github.com/btcsuite/goleveldb v1.0.0
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ=
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
github.com/btcsuite/btcutil v1.0.3-0.20210929233259-9cdf59f60c51 h1:6XGSs4BMDRlNR9k+tpr1g2S6ZfQhKQl/Xr276yAEfP0=
github.com/btcsuite/btcutil v1.0.3-0.20210929233259-9cdf59f60c51/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
Expand Down
41 changes: 11 additions & 30 deletions txscript/pkscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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
)
Expand Down Expand Up @@ -99,7 +102,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
Expand Down Expand Up @@ -132,6 +135,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
Expand Down Expand Up @@ -232,37 +239,11 @@ func computeNonWitnessPkScript(sigScript []byte) (PkScript, error) {

// computeWitnessPkScript computes the script of an output by looking at the
// spending input's witness.
// IMPORTANT: With the addition of taproot, we can not say for certain
// what kind of script the witness is.
func computeWitnessPkScript(witness wire.TxWitness) (PkScript, error) {
// We'll use the last item of the witness stack to determine the proper
// witness type.
lastWitnessItem := witness[len(witness)-1]

var pkScript PkScript
switch {
// If the witness stack has a size of 2 and its last item is a
// compressed public key, then this is a P2WPKH witness.
case len(witness) == 2 && len(lastWitnessItem) == compressedPubKeyLen:
pubKeyHash := hash160(lastWitnessItem)
script, err := payToWitnessPubKeyHashScript(pubKeyHash)
if err != nil {
return pkScript, err
}

pkScript.class = WitnessV0PubKeyHashTy
copy(pkScript.script[:], script)

// For any other witnesses, we'll assume it's a P2WSH witness.
default:
scriptHash := sha256.Sum256(lastWitnessItem)
script, err := payToWitnessScriptHashScript(scriptHash[:])
if err != nil {
return pkScript, err
}

pkScript.class = WitnessV0ScriptHashTy
copy(pkScript.script[:], script)
}

pkScript.class = WitnessUnknownTy
return pkScript, nil
}

Expand Down
40 changes: 12 additions & 28 deletions txscript/pkscript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func TestComputePkScript(t *testing.T) {
pkScript: nil,
},
{
name: "P2WSH witness",
name: "witness unknown",
sigScript: nil,
witness: [][]byte{
{},
Expand All @@ -348,20 +348,14 @@ func TestComputePkScript(t *testing.T) {
0x42, 0x59, 0x90, 0xac, 0xac,
},
},
class: WitnessV0ScriptHashTy,
pkScript: []byte{
// OP_0
0x00,
// OP_DATA_32
0x20,
// <32-byte script hash>
0x01, 0xd5, 0xd9, 0x2e, 0xff, 0xa6, 0xff, 0xba,
0x3e, 0xfa, 0x37, 0x9f, 0x98, 0x30, 0xd0, 0xf7,
0x56, 0x18, 0xb1, 0x33, 0x93, 0x82, 0x71, 0x52,
0xd2, 0x6e, 0x43, 0x09, 0x00, 0x0e, 0x88, 0xb1,
},
// We can't say for sure what kind of pubkey script this is. Before
// taproot, it would have been p2sh
class: WitnessUnknownTy,
},
{
// Before taproot, we could tell this script type based on its
// structure. But now this particular structure matches rules for
// both witness_v0_keyhash and witness_v1_taproot.
name: "P2WPKH witness",
sigScript: nil,
witness: [][]byte{
Expand All @@ -378,17 +372,7 @@ func TestComputePkScript(t *testing.T) {
0x59, 0x90, 0xac,
},
},
class: WitnessV0PubKeyHashTy,
pkScript: []byte{
// OP_0
0x00,
// OP_DATA_20
0x14,
// <20-byte pubkey hash>
0x1d, 0x7c, 0xd6, 0xc7, 0x5c, 0x2e, 0x86, 0xf4,
0xcb, 0xf9, 0x8e, 0xae, 0xd2, 0x21, 0xb3, 0x0b,
0xd9, 0xa0, 0xb9, 0x28,
},
class: WitnessUnknownTy,
},
// Invalid v0 P2WPKH - same as above but missing a byte on the
// public key.
Expand Down Expand Up @@ -429,12 +413,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())
}
})
}
Expand Down
5 changes: 4 additions & 1 deletion txscript/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Comment on lines +802 to 805
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain about this.


return 0
Expand Down
33 changes: 32 additions & 1 deletion txscript/script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ func TestGetPreciseSigOps(t *testing.T) {
// nested p2sh, and invalid variants are counted properly.
func TestGetWitnessSigOpCount(t *testing.T) {
t.Parallel()
const OP_ANNEX = 0x50
tests := []struct {
name string

Expand All @@ -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",
Expand Down Expand Up @@ -244,6 +245,36 @@ func TestGetWitnessSigOpCount(t *testing.T) {
" EQUALVERIFY CHECKSIG DATA_20 0x91"),
},
},

{
name: "taproot key-path spend",
numSigOps: 0, // DRAFT NOTE: verify these should really be zero.
pkScript: hexToBytes("512058ce9d16c3384731a1727e512530620d031ee7b12f42aade70c9f976a905a74b"),
witness: wire.TxWitness{
hexToBytes("DCBA"),
},
},
{
name: "taproot script-path spend without annex",
numSigOps: 0,
pkScript: hexToBytes("512058ce9d16c3384731a1727e512530620d031ee7b12f42aade70c9f976a905a74b"),
witness: wire.TxWitness{
[]byte("signature"),
mustParseShortForm("CHECKSIG CHECKSIG"),
[]byte("control block"),
},
},
{
name: "taproot script-path spend with annex",
numSigOps: 0,
pkScript: hexToBytes("512058ce9d16c3384731a1727e512530620d031ee7b12f42aade70c9f976a905a74b"),
witness: wire.TxWitness{
[]byte("stuff"),
mustParseShortForm("CHECKSIG"),
[]byte("control block"),
[]byte{OP_ANNEX},
},
},
}

for _, test := range tests {
Expand Down
Loading