From ec7b2af7f6edef7401b53ea37614c5280beb2b06 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 23 Jan 2023 20:14:53 -0800 Subject: [PATCH] txscript: add tests for RawTxInTaprootSignature and RawTxInTapscriptSignature 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. --- txscript/sign_test.go | 195 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/txscript/sign_test.go b/txscript/sign_test.go index ae10ba17d8a..1fa73c8f578 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -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,196 @@ 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 := ComputeTaprootOutputKey(privKey.PubKey(), nil) + + builder := NewScriptBuilder() + builder.AddOp(OP_1) + builder.AddData(schnorr.SerializePubKey(pubKey)) + pkScript, err := builder.Script() + 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, + }, + } + 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 := []byte{OP_1, OP_DATA_32} + p2trScript = append(p2trScript, schnorr.SerializePubKey(outputKey)...) + + // 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, + }, + } + 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()) + }) + } +}