What changed, and why it matters
This commit only adds a new test case to the project's test suite. It does not change any production code. The test verifies that a PSBT (Partially Signed Bitcoin Transaction) finalizer correctly orders signatures for a specific type of Taproot script (multi_a) regardless of the order in which the signatures are recorded in the PSBT. It is a defensive regression test, not a fix for a known vulnerability.
No action required. This is a test-only change. Reviewers may optionally verify that the existing production implementation already passes the new test case, confirming the regression guard is effective.
Security signals we found
Regression test for Taproot multi_a signature ordering
No production code changes
Defensive guard against incorrect finalizer implementations
Evidence from the diff
The diff modifies psbt/taproot_multi_a_test.go to add a second sub-test to TestTaprootMultiAFinalizerOrdersSignatures. The new sub-test supplies signature records in reverse script order and asserts that the final witness stack is still produced in script order. The commit message explicitly states this ‘guards against implementations that only reverse the input slice,’ which would pass for script-ordered records but fail for shuffled records. No implementation code is changed.
Changed components
psbt/taproot_multi_a_test.goInspect captured patch +36 / −13
### psbt/taproot_multi_a_test.go
@@ -21,20 +21,43 @@ func TestTaprootMultiAFinalizerOrdersSignatures(t *testing.T) {
AddInt64(2).AddOp(txscript.OP_NUMEQUAL).Script()
require.NoError(t, err)
- packet := taprootMultiATestPacket(t, script, []*TaprootScriptSpendSig{
- {XOnlyPubKey: keyA, Signature: sigA},
- {XOnlyPubKey: keyB, Signature: sigB},
- })
+ testCases := []struct {
+ name string
+ sigs []*TaprootScriptSpendSig
+ }{
+ {
+ name: "script order",
+ sigs: []*TaprootScriptSpendSig{
+ {XOnlyPubKey: keyA, Signature: sigA},
+ {XOnlyPubKey: keyB, Signature: sigB},
+ },
+ },
+ {
+ name: "reverse script order",
+ sigs: []*TaprootScriptSpendSig{
+ {XOnlyPubKey: keyB, Signature: sigB},
+ {XOnlyPubKey: keyA, Signature: sigA},
+ },
+ },
+ }
- require.NoError(t, MaybeFinalizeAll(packet))
- finalTx, err := Extract(packet)
- require.NoError(t, err)
- require.Equal(t, wire.TxWitness{
- sigB,
- sigA,
- script,
- make([]byte, 33),
- }, finalTx.TxIn[0].Witness)
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ packet := taprootMultiATestPacket(
+ t, script, testCase.sigs,
+ )
+
+ require.NoError(t, MaybeFinalizeAll(packet))
+ finalTx, err := Extract(packet)
+ require.NoError(t, err)
+ require.Equal(t, wire.TxWitness{
+ sigB,
+ sigA,
+ script,
+ make([]byte, 33),
+ }, finalTx.TxIn[0].Witness)
+ })
+ }
}
func TestTaprootMultiAFinalizerAddsPlaceholders(t *testing.T) {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.