lnwallet: add 3rd-party signature verification for taproot test vectors
What changed, and why it matters
This commit only adds a new test to the project's test suite. It does not change any production code, user-facing behavior, or network protocol. The new test cryptographically checks that example transactions in the project's test data carry valid Bitcoin signatures. It is a defensive quality improvement, not a fix for a known bug or vulnerability.
No security action required. Treat as a normal test-quality improvement. Reviewers may optionally confirm that the new test passes in CI and that the test vectors it exercises cover the intended taproot spending paths.
Security signals we found
Adds independent cryptographic signature verification for test vectors
Uses txscript.NewEngine with StandardVerifyFlags to mirror on-chain validation
Verifies both the commitment transaction and each HTLC resolution transaction
No production code or privilege boundary is changed
Evidence from the diff
The change adds a signature_verification sub-test inside verifyTaprootVectors in lnwallet/taproot_test_vectors_test.go. It deserializes stored commitment and HTLC resolution transactions from test vectors, builds a txscript.Engine for each input, and executes Bitcoin script verification against the relevant prevout’s pkScript and amount. This validates control blocks, tap leaf hashes, sighashes, and signatures exactly as a Bitcoin node would. It is purely additive test code (+99 lines, no deletions) and does not modify wallet, RPC, or consensus logic.
Changed components
lnwallet/taproot_test_vectors_test.goInspect captured patch +99 / −0
diff --git a/lnwallet/taproot_test_vectors_test.go b/lnwallet/taproot_test_vectors_test.go
index 791c36f..f2ceb6f 100644
--- a/lnwallet/taproot_test_vectors_test.go
+++ b/lnwallet/taproot_test_vectors_test.go
@@ -1282,6 +1282,23 @@ func verifyTaprootVectors(t *testing.T) {
})
}
})
+
+ // Verify signatures cryptographically as a third party would.
+ t.Run("signature_verification", func(t *testing.T) {
+ fundingPkScript, err := hex.DecodeString(
+ stored.Scripts.Funding.PkScript,
+ )
+ require.NoError(t, err)
+
+ for _, storedTx := range stored.Transactions {
+ t.Run(storedTx.Name, func(t *testing.T) {
+ verifyCommitmentTxSig(
+ t, storedTx, fundingPkScript,
+ tc.fundingAmount,
+ )
+ })
+ }
+ })
}
// extractHash160FromScript uses the script tokenizer to find and extract the
@@ -1314,3 +1331,85 @@ func extractHash160FromScript(t *testing.T, script []byte) [20]byte {
var zero [20]byte
return zero
}
+
+// verifyCommitmentTxSig performs third-party verification of the commitment
+// transaction and all HTLC resolution transactions by executing the taproot
+// script verification engine against the provided witnesses.
+func verifyCommitmentTxSig(t *testing.T, txCase TransactionTestCase,
+ fundingPkScript []byte, fundingAmt btcutil.Amount) {
+
+ // Deserialize the commitment transaction.
+ commitTxBytes, err := hex.DecodeString(
+ txCase.ExpectedCommitmentTxHex,
+ )
+ require.NoError(t, err)
+
+ commitTx := wire.NewMsgTx(2)
+ err = commitTx.Deserialize(bytes.NewReader(commitTxBytes))
+ require.NoError(t, err)
+
+ // Verify the commitment tx witness against the funding output.
+ prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
+ fundingPkScript, int64(fundingAmt),
+ )
+ sigHashes := txscript.NewTxSigHashes(commitTx, prevOutFetcher)
+
+ vm, err := txscript.NewEngine(
+ fundingPkScript, commitTx, 0,
+ txscript.StandardVerifyFlags, nil,
+ sigHashes, int64(fundingAmt), prevOutFetcher,
+ )
+ require.NoError(t, err, "failed to create script engine for "+
+ "commitment tx")
+
+ err = vm.Execute()
+ require.NoError(t, err, "commitment tx signature verification "+
+ "failed")
+
+ t.Logf("commitment tx signature verified successfully")
+
+ // Verify each HTLC resolution transaction against its commitment
+ // output.
+ for i, htlcDesc := range txCase.HtlcDescs {
+ htlcTxBytes, err := hex.DecodeString(
+ htlcDesc.ResolutionTxHex,
+ )
+ require.NoError(t, err)
+
+ htlcTx := wire.NewMsgTx(2)
+ err = htlcTx.Deserialize(bytes.NewReader(htlcTxBytes))
+ require.NoError(t, err)
+
+ // The HTLC tx spends from the commitment tx. Find the
+ // output it references.
+ prevOutIdx := htlcTx.TxIn[0].PreviousOutPoint.Index
+ require.Less(t, int(prevOutIdx), len(commitTx.TxOut),
+ "HTLC tx references invalid output index")
+
+ prevOut := commitTx.TxOut[prevOutIdx]
+ htlcPrevFetcher := txscript.NewCannedPrevOutputFetcher(
+ prevOut.PkScript, prevOut.Value,
+ )
+ htlcSigHashes := txscript.NewTxSigHashes(
+ htlcTx, htlcPrevFetcher,
+ )
+
+ htlcVM, err := txscript.NewEngine(
+ prevOut.PkScript, htlcTx, 0,
+ txscript.StandardVerifyFlags, nil,
+ htlcSigHashes, prevOut.Value,
+ htlcPrevFetcher,
+ )
+ require.NoError(t, err,
+ fmt.Sprintf("failed to create script engine for "+
+ "HTLC resolution tx %d", i))
+
+ err = htlcVM.Execute()
+ require.NoError(t, err,
+ fmt.Sprintf("HTLC resolution tx %d signature "+
+ "verification failed", i))
+
+ t.Logf("HTLC resolution tx %d signature verified "+
+ "successfully", i)
+ }
+}
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.