mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
client: reject malformed server public keys
Loop-in and loop-out responses carry compressed server public keys that are copied into fixed-size fields and later used for HTLC construction. Validate the length and parse each compressed key before storing it, and validate the MuSig2 loop-in receiver internal key as well. This turns short or unparsable server keys into explicit errors instead of silently zero-padding short responses or accepting an invalid internal key. Update root test mocks to return size-correct MuSig2 signing data under the stricter checks.
This commit is contained in:
parent
605e72a261
commit
cc0392af3f
4 changed files with 80 additions and 16 deletions
|
|
@ -6,12 +6,14 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/loop/loopdb"
|
||||
"github.com/lightninglabs/loop/test"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
|
|
@ -32,6 +34,13 @@ var (
|
|||
testMaxSwapAmount = btcutil.Amount(1000000)
|
||||
)
|
||||
|
||||
// mockMuSig2SigningData returns size-correct placeholder data. Client tests
|
||||
// only assert response handling, not MuSig2 cryptographic validity.
|
||||
func mockMuSig2SigningData() ([]byte, []byte, error) {
|
||||
return make([]byte, musig2.PubNonceSize),
|
||||
make([]byte, input.MuSig2PartialSigSize), nil
|
||||
}
|
||||
|
||||
// serverMock is used in client unit tests to simulate swap server behaviour.
|
||||
type serverMock struct {
|
||||
expectedSwapAmt btcutil.Amount
|
||||
|
|
@ -276,7 +285,7 @@ func (s *serverMock) MuSig2SignSweep(_ context.Context, _ loopdb.ProtocolVersion
|
|||
_ lntypes.Hash, _ [32]byte, _ []byte, _ []byte) ([]byte,
|
||||
[]byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
return mockMuSig2SigningData()
|
||||
}
|
||||
|
||||
func (s *serverMock) MultiMuSig2SignSweep(ctx context.Context,
|
||||
|
|
@ -285,7 +294,7 @@ func (s *serverMock) MultiMuSig2SignSweep(ctx context.Context,
|
|||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
return mockMuSig2SigningData()
|
||||
}
|
||||
|
||||
func (s *serverMock) PushKey(_ context.Context, _ loopdb.ProtocolVersion,
|
||||
|
|
|
|||
|
|
@ -405,13 +405,9 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var senderKey [33]byte
|
||||
copy(senderKey[:], swapResp.SenderKey)
|
||||
|
||||
// Validate sender key.
|
||||
_, err = btcec.ParsePubKey(senderKey[:])
|
||||
senderKey, err := parseServerPubKey("sender key", swapResp.SenderKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sender key: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &newLoopOutResponse{
|
||||
|
|
@ -470,14 +466,22 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
var receiverKey, receiverInternalKey [33]byte
|
||||
copy(receiverKey[:], swapResp.ReceiverKey)
|
||||
copy(receiverInternalKey[:], swapResp.ReceiverInternalPubkey)
|
||||
|
||||
// Validate receiver key.
|
||||
_, err = btcec.ParsePubKey(receiverKey[:])
|
||||
receiverKey, err := parseServerPubKey(
|
||||
"receiver key", swapResp.ReceiverKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sender key: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var receiverInternalKey [btcec.PubKeyBytesLenCompressed]byte
|
||||
if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 {
|
||||
receiverInternalKey, err = parseServerPubKey(
|
||||
"receiver internal key",
|
||||
swapResp.ReceiverInternalPubkey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &newLoopInResponse{
|
||||
|
|
@ -488,6 +492,29 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// parseServerPubKey validates that keyBytes is a well-formed compressed public
|
||||
// key received from the server and returns it as a fixed-size array. The name
|
||||
// argument is used to produce a descriptive error if validation fails.
|
||||
func parseServerPubKey(name string,
|
||||
keyBytes []byte) ([btcec.PubKeyBytesLenCompressed]byte, error) {
|
||||
|
||||
var key [btcec.PubKeyBytesLenCompressed]byte
|
||||
|
||||
if len(keyBytes) != btcec.PubKeyBytesLenCompressed {
|
||||
return key, fmt.Errorf("invalid %s length: got %d, want %d",
|
||||
name, len(keyBytes), btcec.PubKeyBytesLenCompressed)
|
||||
}
|
||||
|
||||
_, err := btcec.ParsePubKey(keyBytes)
|
||||
if err != nil {
|
||||
return key, fmt.Errorf("invalid %s: %v", name, err)
|
||||
}
|
||||
|
||||
copy(key[:], keyBytes)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ServerUpdate summarizes an update from the swap server.
|
||||
type ServerUpdate struct {
|
||||
// State is the state that the server has sent us.
|
||||
|
|
|
|||
28
swap_server_client_test.go
Normal file
28
swap_server_client_test.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package loop
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
looptest "github.com/lightninglabs/loop/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestParseServerPubKey ensures that parseServerPubKey accepts a valid
|
||||
// compressed public key and rejects keys with an invalid length or contents.
|
||||
func TestParseServerPubKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, pubKey := looptest.CreateKey(1)
|
||||
pubKeyBytes := pubKey.SerializeCompressed()
|
||||
|
||||
parsedKey, err := parseServerPubKey("test key", pubKeyBytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pubKeyBytes, parsedKey[:])
|
||||
|
||||
_, err = parseServerPubKey("test key", pubKeyBytes[:32])
|
||||
require.ErrorContains(t, err, "invalid test key length")
|
||||
|
||||
invalidKey := make([]byte, 33)
|
||||
_, err = parseServerPubKey("test key", invalidKey)
|
||||
require.ErrorContains(t, err, "invalid test key")
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ func mockMuSig2SignSweep(ctx context.Context,
|
|||
prevoutMap map[wire.OutPoint]*wire.TxOut) (
|
||||
[]byte, []byte, error) {
|
||||
|
||||
return nil, nil, nil
|
||||
return mockMuSig2SigningData()
|
||||
}
|
||||
|
||||
func newSwapClient(t *testing.T, config *clientConfig) *Client {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue