Skip to content

Commit

Permalink
Merge pull request #1941 from Roasbeef/sighash-taproot-keyspend-bug-fix
Browse files Browse the repository at this point in the history
Sighash taproot keyspend bug fix
  • Loading branch information
Roasbeef authored Jan 25, 2023
2 parents 016b3ad + d6efaa7 commit be056b0
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 1 deletion.
2 changes: 1 addition & 1 deletion txscript/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func RawTxInTaprootSignature(tx *wire.MsgTx, sigHashes *TxSigHashes, idx int,

// If this is sighash default, then we can just return the signature
// directly.
if hashType&SigHashDefault == SigHashDefault {
if hashType == SigHashDefault {
return sig, nil
}

Expand Down
204 changes: 204 additions & 0 deletions txscript/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import (
"testing"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/require"
)

type addressToKey struct {
Expand Down Expand Up @@ -1692,3 +1694,205 @@ nexttest:
}
}
}

// TestRawTxInTaprootSignature tests that the RawTxInTaprootSignature function
// generates valid signatures for all relevant sighash types.
func TestRawTxInTaprootSignature(t *testing.T) {
t.Parallel()

privKey, err := btcec.NewPrivateKey()
require.NoError(t, err)

pubKey := ComputeTaprootKeyNoScript(privKey.PubKey())

pkScript, err := PayToTaprootScript(pubKey)
require.NoError(t, err)

// We'll reuse this simple transaction for the tests below. It ends up
// spending from a bip86 P2TR output.
testTx := wire.NewMsgTx(2)
testTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Index: 1,
},
})
txOut := &wire.TxOut{
Value: 1e8, PkScript: pkScript,
}
testTx.AddTxOut(txOut)

tests := []struct {
sigHashType SigHashType
}{
{
sigHashType: SigHashDefault,
},
{
sigHashType: SigHashAll,
},
{
sigHashType: SigHashNone,
},
{
sigHashType: SigHashSingle,
},
{
sigHashType: SigHashSingle | SigHashAnyOneCanPay,
},
{
sigHashType: SigHashNone | SigHashAnyOneCanPay,
},
{
sigHashType: SigHashAll | SigHashAnyOneCanPay,
},
}
for _, test := range tests {
name := fmt.Sprintf("sighash=%v", test.sigHashType)
t.Run(name, func(t *testing.T) {
prevFetcher := NewCannedPrevOutputFetcher(
txOut.PkScript, txOut.Value,
)
sigHashes := NewTxSigHashes(testTx, prevFetcher)

sig, err := RawTxInTaprootSignature(
testTx, sigHashes, 0, txOut.Value, txOut.PkScript,
nil, test.sigHashType, privKey,
)
require.NoError(t, err)

// If this isn't sighash default, then a sighash should be
// applied. Otherwise, it should be a normal sig.
expectedLen := schnorr.SignatureSize
if test.sigHashType != SigHashDefault {
expectedLen += 1
}
require.Len(t, sig, expectedLen)

// Finally, ensure that the signature produced is valid.
txCopy := testTx.Copy()
txCopy.TxIn[0].Witness = wire.TxWitness{sig}
vm, err := NewEngine(
txOut.PkScript, txCopy, 0, StandardVerifyFlags,
nil, sigHashes, txOut.Value, prevFetcher,
)
require.NoError(t, err)

require.NoError(t, vm.Execute())
})
}
}

// TestRawTxInTapscriptSignature thats that we're able to produce valid schnorr
// signatures for a simple tapscript spend, for various sighash types.
func TestRawTxInTapscriptSignature(t *testing.T) {
t.Parallel()

privKey, err := btcec.NewPrivateKey()
require.NoError(t, err)

internalKey := privKey.PubKey()

// Our script will be a simple OP_CHECKSIG as the sole leaf of a
// tapscript tree. We'll also re-use the internal key as the key in the
// leaf.
builder := NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(internalKey))
builder.AddOp(OP_CHECKSIG)
pkScript, err := builder.Script()
require.NoError(t, err)

tapLeaf := NewBaseTapLeaf(pkScript)
tapScriptTree := AssembleTaprootScriptTree(tapLeaf)

ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(
internalKey,
)

tapScriptRootHash := tapScriptTree.RootNode.TapHash()
outputKey := ComputeTaprootOutputKey(
internalKey, tapScriptRootHash[:],
)
p2trScript, err := PayToTaprootScript(outputKey)
require.NoError(t, err)

// We'll reuse this simple transaction for the tests below. It ends up
// spending from a bip86 P2TR output.
testTx := wire.NewMsgTx(2)
testTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Index: 1,
},
})
txOut := &wire.TxOut{
Value: 1e8, PkScript: p2trScript,
}
testTx.AddTxOut(txOut)

tests := []struct {
sigHashType SigHashType
}{
{
sigHashType: SigHashDefault,
},
{
sigHashType: SigHashAll,
},
{
sigHashType: SigHashNone,
},
{
sigHashType: SigHashSingle,
},
{
sigHashType: SigHashSingle | SigHashAnyOneCanPay,
},
{
sigHashType: SigHashNone | SigHashAnyOneCanPay,
},
{
sigHashType: SigHashAll | SigHashAnyOneCanPay,
},
}
for _, test := range tests {
name := fmt.Sprintf("sighash=%v", test.sigHashType)
t.Run(name, func(t *testing.T) {
prevFetcher := NewCannedPrevOutputFetcher(
txOut.PkScript, txOut.Value,
)
sigHashes := NewTxSigHashes(testTx, prevFetcher)

sig, err := RawTxInTapscriptSignature(
testTx, sigHashes, 0, txOut.Value,
txOut.PkScript, tapLeaf, test.sigHashType,
privKey,
)
require.NoError(t, err)

// If this isn't sighash default, then a sighash should
// be applied. Otherwise, it should be a normal sig.
expectedLen := schnorr.SignatureSize
if test.sigHashType != SigHashDefault {
expectedLen += 1
}
require.Len(t, sig, expectedLen)

// Now that we have the sig, we'll make a valid witness
// including the control block.
ctrlBlockBytes, err := ctrlBlock.ToBytes()
require.NoError(t, err)
txCopy := testTx.Copy()
txCopy.TxIn[0].Witness = wire.TxWitness{
sig, pkScript, ctrlBlockBytes,
}

// Finally, ensure that the signature produced is valid.
vm, err := NewEngine(
txOut.PkScript, txCopy, 0, StandardVerifyFlags,
nil, sigHashes, txOut.Value, prevFetcher,
)
require.NoError(t, err)

require.NoError(t, vm.Execute())
})
}
}
8 changes: 8 additions & 0 deletions txscript/taproot.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,3 +774,11 @@ func AssembleTaprootScriptTree(leaves ...TapLeaf) *IndexedTapScriptTree {

return scriptTree
}

// PayToTaprootScript creates a pk script for a pay-to-taproot output key.
func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
return NewScriptBuilder().
AddOp(OP_1).
AddData(schnorr.SerializePubKey(taprootKey)).
Script()
}

0 comments on commit be056b0

Please sign in to comment.