Skip to content

Commit cc0392a

Browse files
committed
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.
1 parent 605e72a commit cc0392a

4 files changed

Lines changed: 80 additions & 16 deletions

File tree

server_mock_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
910
"github.com/btcsuite/btcd/btcutil"
1011
"github.com/btcsuite/btcd/chaincfg"
1112
"github.com/btcsuite/btcd/wire"
1213
"github.com/lightninglabs/lndclient"
1314
"github.com/lightninglabs/loop/loopdb"
1415
"github.com/lightninglabs/loop/test"
16+
"github.com/lightningnetwork/lnd/input"
1517
invpkg "github.com/lightningnetwork/lnd/invoices"
1618
"github.com/lightningnetwork/lnd/lntypes"
1719
"github.com/lightningnetwork/lnd/lnwire"
@@ -32,6 +34,13 @@ var (
3234
testMaxSwapAmount = btcutil.Amount(1000000)
3335
)
3436

37+
// mockMuSig2SigningData returns size-correct placeholder data. Client tests
38+
// only assert response handling, not MuSig2 cryptographic validity.
39+
func mockMuSig2SigningData() ([]byte, []byte, error) {
40+
return make([]byte, musig2.PubNonceSize),
41+
make([]byte, input.MuSig2PartialSigSize), nil
42+
}
43+
3544
// serverMock is used in client unit tests to simulate swap server behaviour.
3645
type serverMock struct {
3746
expectedSwapAmt btcutil.Amount
@@ -276,7 +285,7 @@ func (s *serverMock) MuSig2SignSweep(_ context.Context, _ loopdb.ProtocolVersion
276285
_ lntypes.Hash, _ [32]byte, _ []byte, _ []byte) ([]byte,
277286
[]byte, error) {
278287

279-
return nil, nil, nil
288+
return mockMuSig2SigningData()
280289
}
281290

282291
func (s *serverMock) MultiMuSig2SignSweep(ctx context.Context,
@@ -285,7 +294,7 @@ func (s *serverMock) MultiMuSig2SignSweep(ctx context.Context,
285294
prevoutMap map[wire.OutPoint]*wire.TxOut) (
286295
[]byte, []byte, error) {
287296

288-
return nil, nil, nil
297+
return mockMuSig2SigningData()
289298
}
290299

291300
func (s *serverMock) PushKey(_ context.Context, _ loopdb.ProtocolVersion,

swap_server_client.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,9 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
405405
return nil, err
406406
}
407407

408-
var senderKey [33]byte
409-
copy(senderKey[:], swapResp.SenderKey)
410-
411-
// Validate sender key.
412-
_, err = btcec.ParsePubKey(senderKey[:])
408+
senderKey, err := parseServerPubKey("sender key", swapResp.SenderKey)
413409
if err != nil {
414-
return nil, fmt.Errorf("invalid sender key: %v", err)
410+
return nil, err
415411
}
416412

417413
return &newLoopOutResponse{
@@ -470,14 +466,22 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
470466
return nil, err
471467
}
472468

473-
var receiverKey, receiverInternalKey [33]byte
474-
copy(receiverKey[:], swapResp.ReceiverKey)
475-
copy(receiverInternalKey[:], swapResp.ReceiverInternalPubkey)
476-
477-
// Validate receiver key.
478-
_, err = btcec.ParsePubKey(receiverKey[:])
469+
receiverKey, err := parseServerPubKey(
470+
"receiver key", swapResp.ReceiverKey,
471+
)
479472
if err != nil {
480-
return nil, fmt.Errorf("invalid sender key: %v", err)
473+
return nil, err
474+
}
475+
476+
var receiverInternalKey [btcec.PubKeyBytesLenCompressed]byte
477+
if loopdb.CurrentProtocolVersion() >= loopdb.ProtocolVersionMuSig2 {
478+
receiverInternalKey, err = parseServerPubKey(
479+
"receiver internal key",
480+
swapResp.ReceiverInternalPubkey,
481+
)
482+
if err != nil {
483+
return nil, err
484+
}
481485
}
482486

483487
return &newLoopInResponse{
@@ -488,6 +492,29 @@ func (s *grpcSwapServerClient) NewLoopInSwap(ctx context.Context,
488492
}, nil
489493
}
490494

495+
// parseServerPubKey validates that keyBytes is a well-formed compressed public
496+
// key received from the server and returns it as a fixed-size array. The name
497+
// argument is used to produce a descriptive error if validation fails.
498+
func parseServerPubKey(name string,
499+
keyBytes []byte) ([btcec.PubKeyBytesLenCompressed]byte, error) {
500+
501+
var key [btcec.PubKeyBytesLenCompressed]byte
502+
503+
if len(keyBytes) != btcec.PubKeyBytesLenCompressed {
504+
return key, fmt.Errorf("invalid %s length: got %d, want %d",
505+
name, len(keyBytes), btcec.PubKeyBytesLenCompressed)
506+
}
507+
508+
_, err := btcec.ParsePubKey(keyBytes)
509+
if err != nil {
510+
return key, fmt.Errorf("invalid %s: %v", name, err)
511+
}
512+
513+
copy(key[:], keyBytes)
514+
515+
return key, nil
516+
}
517+
491518
// ServerUpdate summarizes an update from the swap server.
492519
type ServerUpdate struct {
493520
// State is the state that the server has sent us.

swap_server_client_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package loop
2+
3+
import (
4+
"testing"
5+
6+
looptest "github.com/lightninglabs/loop/test"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
// TestParseServerPubKey ensures that parseServerPubKey accepts a valid
11+
// compressed public key and rejects keys with an invalid length or contents.
12+
func TestParseServerPubKey(t *testing.T) {
13+
t.Parallel()
14+
15+
_, pubKey := looptest.CreateKey(1)
16+
pubKeyBytes := pubKey.SerializeCompressed()
17+
18+
parsedKey, err := parseServerPubKey("test key", pubKeyBytes)
19+
require.NoError(t, err)
20+
require.Equal(t, pubKeyBytes, parsedKey[:])
21+
22+
_, err = parseServerPubKey("test key", pubKeyBytes[:32])
23+
require.ErrorContains(t, err, "invalid test key length")
24+
25+
invalidKey := make([]byte, 33)
26+
_, err = parseServerPubKey("test key", invalidKey)
27+
require.ErrorContains(t, err, "invalid test key")
28+
}

testcontext_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func mockMuSig2SignSweep(ctx context.Context,
6767
prevoutMap map[wire.OutPoint]*wire.TxOut) (
6868
[]byte, []byte, error) {
6969

70-
return nil, nil, nil
70+
return mockMuSig2SigningData()
7171
}
7272

7373
func newSwapClient(t *testing.T, config *clientConfig) *Client {

0 commit comments

Comments
 (0)