Skip to content

Commit

Permalink
txscript: add tests for RawTxInTaprootSignature and RawTxInTapscriptS…
Browse files Browse the repository at this point in the history
…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
1 parent 5e0de1c commit d6efaa7
Showing 1 changed file with 204 additions and 0 deletions.
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())
})
}
}

0 comments on commit d6efaa7

Please sign in to comment.