mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
sweepbatcher: reject malformed MuSig2 cosign data
The cooperative batch sweep path receives a server nonce and partial signature before constructing a keyspend witness. Validate both byte slice lengths before registering the nonce or combining signatures, so malformed server responses fail explicitly instead of being zero-padded into fixed-size MuSig2 buffers. Update batcher test helpers to return size-correct placeholder signing data under the stricter validation.
This commit is contained in:
parent
db9bd06629
commit
605e72a261
2 changed files with 88 additions and 6 deletions
|
|
@ -1882,6 +1882,12 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateServerMuSig2SigningData(
|
||||
serverNonce, serverSig,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serverPublicNonce [musig2.PubNonceSize]byte
|
||||
copy(serverPublicNonce[:], serverNonce)
|
||||
|
||||
|
|
@ -1934,6 +1940,26 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
|||
return finalSig, nil
|
||||
}
|
||||
|
||||
// validateServerMuSig2SigningData rejects malformed MuSig2 cosigning data
|
||||
// received from the server by checking that the nonce and partial signature
|
||||
// have the expected lengths before they are passed to the signer.
|
||||
func validateServerMuSig2SigningData(serverNonce,
|
||||
serverSig []byte) error {
|
||||
|
||||
if len(serverNonce) != musig2.PubNonceSize {
|
||||
return fmt.Errorf("invalid server nonce length: got %d, "+
|
||||
"want %d", len(serverNonce), musig2.PubNonceSize)
|
||||
}
|
||||
|
||||
if len(serverSig) != input.MuSig2PartialSigSize {
|
||||
return fmt.Errorf("invalid server partial signature "+
|
||||
"length: got %d, want %d", len(serverSig),
|
||||
input.MuSig2PartialSigSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateRbfRate updates the fee rate we should use for the new batch
|
||||
// transaction. This fee rate does not guarantee RBF success, but the continuous
|
||||
// increase leads to an eventual successful RBF replacement.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
|
@ -87,7 +88,62 @@ func testMuSig2SignSweep(ctx context.Context,
|
|||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
}
|
||||
|
||||
// testMuSig2SigningData returns size-correct placeholder data. These tests
|
||||
// only exercise control flow around the signing response, not cryptographic
|
||||
// validity.
|
||||
func testMuSig2SigningData() ([]byte, []byte, error) {
|
||||
return make([]byte, musig2.PubNonceSize),
|
||||
make([]byte, input.MuSig2PartialSigSize), nil
|
||||
}
|
||||
|
||||
// TestValidateServerMuSig2SigningData ensures that MuSig2 cosigning data from
|
||||
// the server is accepted when well-formed and rejected when the nonce or
|
||||
// partial signature has an unexpected length.
|
||||
func TestValidateServerMuSig2SigningData(t *testing.T) {
|
||||
validNonce, validSig, err := testMuSig2SigningData()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
serverNonce []byte
|
||||
serverSig []byte
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid signing data",
|
||||
serverNonce: validNonce,
|
||||
serverSig: validSig,
|
||||
},
|
||||
{
|
||||
name: "invalid nonce length",
|
||||
serverNonce: validNonce[:musig2.PubNonceSize-1],
|
||||
serverSig: validSig,
|
||||
errContains: "invalid server nonce length",
|
||||
},
|
||||
{
|
||||
name: "invalid partial signature length",
|
||||
serverNonce: validNonce,
|
||||
serverSig: validSig[:input.MuSig2PartialSigSize-1],
|
||||
errContains: "invalid server partial signature length",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := validateServerMuSig2SigningData(
|
||||
tc.serverNonce, tc.serverSig,
|
||||
)
|
||||
if tc.errContains == "" {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.ErrorContains(t, err, tc.errContains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var customSignature = func() []byte {
|
||||
|
|
@ -5024,7 +5080,7 @@ func testWithMixedBatch(t *testing.T, store testStore,
|
|||
[]byte, []byte, error) {
|
||||
|
||||
if swapHash == swapHashes[2] {
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("test error")
|
||||
}
|
||||
|
|
@ -5377,14 +5433,14 @@ func testWithMixedBatchLarge(t *testing.T, store testStore,
|
|||
} else {
|
||||
swapHash2Used = true
|
||||
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
}
|
||||
|
||||
case swapHash == preimages[5].Hash():
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
|
||||
case swapHash == preimages[8].Hash():
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("test error")
|
||||
|
|
@ -5431,7 +5487,7 @@ func testWithMixedBatchCoopOnly(t *testing.T, store testStore,
|
|||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
return testMuSig2SigningData()
|
||||
}
|
||||
|
||||
// All the sweeps are cooperative.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue