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

Sighash taproot keyspend bug fix #1941

Merged
merged 3 commits into from
Jan 25, 2023
Merged
Changes from 1 commit
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
Prev Previous commit
txscript: add tests for RawTxInTaprootSignature and RawTxInTapscriptS…
…ignature

In this commit, we add tests for the public functions used to generate
keyspend and tapscript signatures. Without the prior commit, these tests
will fail as the keyspend function won't properly add the sighash bytes
for things that aren't sighash default.
  • Loading branch information
Roasbeef committed Jan 25, 2023
commit d6efaa720823e534bb6583773c8cf30a378f09a2
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,
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
},
{
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())
})
}
}