mirror of
https://github.com/btcsuite/btcd.git
synced 2025-01-19 05:33:36 +01:00
Merge pull request #1941 from Roasbeef/sighash-taproot-keyspend-bug-fix
Sighash taproot keyspend bug fix
This commit is contained in:
commit
be056b0a0b
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user