Skip to content

Commit

Permalink
add taproot script type
Browse files Browse the repository at this point in the history
Add the WitnessV1TaprootTy script class and return it from GetScriptClass
/ typeOfScript.

Fix ComputePkScript, which was returning incorrect results for taproot
(an probably other) scripts. ComputePkScript is now essentially useless
for both v0 and v1 output witnesses.

Bump the btcutil dep to leverage new taproot address type.
  • Loading branch information
buck54321 committed Nov 21, 2021
1 parent 7070d53 commit 890bab9
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 82 deletions.
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
48 changes: 22 additions & 26 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,35 +239,24 @@ 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 no longer say for certain
// what kind of script the witness is in most cases. The only case in which we
// can say for sure is when the witness data has an annex as the last push. In
// that case, we can identify the script type, but we lack the ability to
// reconstruct the script itself.
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.
// If the last push starts with the annex flag, this is a taproot spend.
// We can set the script class, but we can't say what the pubkey script
// looks like with just the witness data.
case isAnnexedWitness(witness):
pkScript.class = WitnessV1TaprootTy

// For any other witnesses, we can't say for certain what type it is or what
// the pubkey script will be.
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
}

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

0 comments on commit 890bab9

Please sign in to comment.