Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pion/datachannel v1.5.10 // indirect
Expand Down
38 changes: 38 additions & 0 deletions pkg/bitcoin/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
P2WPKHScript
P2SHScript
P2WSHScript
P2TRScript
)

func (st ScriptType) String() string {
Expand All @@ -31,6 +32,8 @@ func (st ScriptType) String() string {
return "P2SH"
case P2WSHScript:
return "P2WSH"
case P2TRScript:
return "P2TR"
default:
return "NonStandard"
}
Expand Down Expand Up @@ -147,8 +150,25 @@ func PayToScriptHash(scriptHash [20]byte) (Script, error) {
Script()
}

// PayToTaproot constructs a P2TR script for the provided 32-byte x-only
// Taproot output key. The function assumes the provided output key is valid.
//
// The argument must be the final Taproot output key committed to by the
// scriptPubKey. This helper does not derive a BIP-341/BIP-86 tweak from an
// internal key.
func PayToTaproot(outputKey [32]byte) (Script, error) {
return txscript.NewScriptBuilder().
AddOp(txscript.OP_1).
AddData(outputKey[:]).
Script()
}

// GetScriptType gets the ScriptType of the given Script.
func GetScriptType(script Script) ScriptType {
if isPayToTaproot(script) {
return P2TRScript
}

switch txscript.GetScriptClass(script) {
case txscript.PubKeyHashTy:
return P2PKHScript
Expand All @@ -163,6 +183,12 @@ func GetScriptType(script Script) ScriptType {
}
}

func isPayToTaproot(script Script) bool {
return len(script) == 34 &&
script[0] == txscript.OP_1 &&
script[1] == txscript.OP_DATA_32
}

// ExtractPublicKeyHash extracts the public key hash from a P2WPKH or P2PKH
// script.
func ExtractPublicKeyHash(script Script) ([20]byte, error) {
Expand All @@ -189,3 +215,15 @@ func ExtractPublicKeyHash(script Script) ([20]byte, error) {

return publicKeyHash, nil
}

// ExtractTaprootKey extracts the x-only output key from a P2TR script.
func ExtractTaprootKey(script Script) ([32]byte, error) {
if GetScriptType(script) != P2TRScript {
return [32]byte{}, fmt.Errorf("not a P2TR script")
}

var outputKey [32]byte
copy(outputKey[:], script[2:])

return outputKey, nil
}
83 changes: 83 additions & 0 deletions pkg/bitcoin/script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,32 @@ func TestPayToScriptHash(t *testing.T) {
testutils.AssertBytesEqual(t, expectedResult, result[:])
}

func TestPayToTaproot(t *testing.T) {
outputKeyBytes, err := hex.DecodeString(
"1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
)
if err != nil {
t.Fatal(err)
}

var outputKey [32]byte
copy(outputKey[:], outputKeyBytes)

result, err := PayToTaproot(outputKey)
if err != nil {
t.Fatal(err)
}

expectedResult, err := hex.DecodeString(
"51201b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
)
if err != nil {
t.Fatal(err)
}

testutils.AssertBytesEqual(t, expectedResult, result[:])
}

func TestGetScriptType(t *testing.T) {
fromHex := func(hexString string) []byte {
bytes, err := hex.DecodeString(hexString)
Expand Down Expand Up @@ -363,6 +389,10 @@ func TestGetScriptType(t *testing.T) {
script: fromHex("002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96"),
expectedType: P2WSHScript,
},
"p2tr script": {
script: fromHex("51201b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"),
expectedType: P2TRScript,
},
"non-standard script": {
script: fromHex(
"14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d0003" +
Expand All @@ -387,6 +417,59 @@ func TestGetScriptType(t *testing.T) {
}
}

func TestExtractTaprootKey(t *testing.T) {
fromHex := func(hexString string) []byte {
bytes, err := hex.DecodeString(hexString)
if err != nil {
t.Fatal(err)
}
return bytes
}

var outputKey [32]byte
copy(
outputKey[:],
fromHex("1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"),
)

var tests = map[string]struct {
script Script
expectedOutputKey [32]byte
expectedErr error
}{
"P2TR script": {
script: fromHex("51201b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"),
expectedOutputKey: outputKey,
},
"other script": {
script: fromHex("00148db50eb52063ea9d98b3eac91489a90f738986f6"),
expectedErr: fmt.Errorf("not a P2TR script"),
},
}

for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
actualOutputKey, err := ExtractTaprootKey(test.script)

if !reflect.DeepEqual(test.expectedErr, err) {
t.Errorf(
"unexpected error\nexpected: %+v\nactual: %+v\n",
test.expectedErr,
err,
)
}

if test.expectedOutputKey != actualOutputKey {
t.Errorf(
"unexpected taproot output key\nexpected: 0x%x\nactual: 0x%x\n",
test.expectedOutputKey,
actualOutputKey,
)
}
})
}
}

func TestExtractPublicKeyHash(t *testing.T) {
fromHex := func(hexString string) []byte {
bytes, err := hex.DecodeString(hexString)
Expand Down
Loading
Loading