diff --git a/txscript/sign.go b/txscript/sign.go index f3001d89..0a11e1b1 100644 --- a/txscript/sign.go +++ b/txscript/sign.go @@ -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 } diff --git a/txscript/sign_test.go b/txscript/sign_test.go index ae10ba17..b3cf5119 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,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()) + }) + } +} diff --git a/txscript/taproot.go b/txscript/taproot.go index cd73aff4..003eb19a 100644 --- a/txscript/taproot.go +++ b/txscript/taproot.go @@ -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() +}