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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateServerMuSig2SigningData(
|
||||||
|
serverNonce, serverSig,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var serverPublicNonce [musig2.PubNonceSize]byte
|
var serverPublicNonce [musig2.PubNonceSize]byte
|
||||||
copy(serverPublicNonce[:], serverNonce)
|
copy(serverPublicNonce[:], serverNonce)
|
||||||
|
|
||||||
|
|
@ -1934,6 +1940,26 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
||||||
return finalSig, nil
|
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
|
// updateRbfRate updates the fee rate we should use for the new batch
|
||||||
// transaction. This fee rate does not guarantee RBF success, but the continuous
|
// transaction. This fee rate does not guarantee RBF success, but the continuous
|
||||||
// increase leads to an eventual successful RBF replacement.
|
// increase leads to an eventual successful RBF replacement.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
|
@ -87,7 +88,62 @@ func testMuSig2SignSweep(ctx context.Context,
|
||||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||||
[]byte, []byte, error) {
|
[]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 {
|
var customSignature = func() []byte {
|
||||||
|
|
@ -5024,7 +5080,7 @@ func testWithMixedBatch(t *testing.T, store testStore,
|
||||||
[]byte, []byte, error) {
|
[]byte, []byte, error) {
|
||||||
|
|
||||||
if swapHash == swapHashes[2] {
|
if swapHash == swapHashes[2] {
|
||||||
return nil, nil, nil
|
return testMuSig2SigningData()
|
||||||
} else {
|
} else {
|
||||||
return nil, nil, fmt.Errorf("test error")
|
return nil, nil, fmt.Errorf("test error")
|
||||||
}
|
}
|
||||||
|
|
@ -5377,14 +5433,14 @@ func testWithMixedBatchLarge(t *testing.T, store testStore,
|
||||||
} else {
|
} else {
|
||||||
swapHash2Used = true
|
swapHash2Used = true
|
||||||
|
|
||||||
return nil, nil, nil
|
return testMuSig2SigningData()
|
||||||
}
|
}
|
||||||
|
|
||||||
case swapHash == preimages[5].Hash():
|
case swapHash == preimages[5].Hash():
|
||||||
return nil, nil, nil
|
return testMuSig2SigningData()
|
||||||
|
|
||||||
case swapHash == preimages[8].Hash():
|
case swapHash == preimages[8].Hash():
|
||||||
return nil, nil, nil
|
return testMuSig2SigningData()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("test error")
|
return nil, nil, fmt.Errorf("test error")
|
||||||
|
|
@ -5431,7 +5487,7 @@ func testWithMixedBatchCoopOnly(t *testing.T, store testStore,
|
||||||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||||
[]byte, []byte, error) {
|
[]byte, []byte, error) {
|
||||||
|
|
||||||
return nil, nil, nil
|
return testMuSig2SigningData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// All the sweeps are cooperative.
|
// All the sweeps are cooperative.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue