Skip to content

Commit df971c5

Browse files
committed
staticaddr: validate server address parameters
1 parent 6ea1c79 commit df971c5

2 files changed

Lines changed: 156 additions & 11 deletions

File tree

staticaddr/address/manager.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1212
"github.com/btcsuite/btcd/btcutil"
1313
"github.com/btcsuite/btcd/chaincfg"
14+
"github.com/btcsuite/btcd/wire"
1415
"github.com/lightninglabs/lndclient"
1516
"github.com/lightninglabs/loop/staticaddr/script"
1617
"github.com/lightninglabs/loop/staticaddr/version"
@@ -21,6 +22,13 @@ import (
2122
"github.com/lightningnetwork/lnd/lnwallet"
2223
)
2324

25+
const (
26+
// maxStaticAddressCSVExpiry is the maximum CSV delay that we accept
27+
// from the server for a static address timeout path: 200 days at 144
28+
// blocks per day.
29+
maxStaticAddressCSVExpiry = uint32(200 * 144)
30+
)
31+
2432
// ManagerConfig holds the configuration for the address manager.
2533
type ManagerConfig struct {
2634
// AddressClient is the client that communicates with the loop server
@@ -158,9 +166,16 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
158166
return nil, 0, err
159167
}
160168

169+
if resp == nil {
170+
return nil, 0, fmt.Errorf("missing server new address response")
171+
}
172+
161173
serverParams := resp.GetParams()
174+
if err := validateServerAddressParams(serverParams); err != nil {
175+
return nil, 0, err
176+
}
162177

163-
serverPubKey, err := btcec.ParsePubKey(serverParams.ServerKey)
178+
serverPubKey, err := btcec.ParsePubKey(serverParams.GetServerKey())
164179
if err != nil {
165180
return nil, 0, err
166181
}
@@ -222,6 +237,36 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
222237
return address, int64(serverParams.Expiry), nil
223238
}
224239

240+
// validateServerAddressParams validates the server-controlled static address
241+
// parameters before they are committed into the address script or database.
242+
func validateServerAddressParams(
243+
params *staticaddressrpc.ServerAddressParameters) error {
244+
245+
if params == nil {
246+
return fmt.Errorf("missing server address parameters")
247+
}
248+
249+
if len(params.GetServerKey()) == 0 {
250+
return fmt.Errorf("missing server public key")
251+
}
252+
253+
expiry := params.GetExpiry()
254+
switch {
255+
case expiry == 0:
256+
return fmt.Errorf("static address CSV expiry must be non-zero")
257+
258+
case expiry&^wire.SequenceLockTimeMask != 0:
259+
return fmt.Errorf("invalid static address CSV flags: %x",
260+
expiry)
261+
262+
case expiry > maxStaticAddressCSVExpiry:
263+
return fmt.Errorf("static address CSV expiry %v exceeds "+
264+
"maximum %v", expiry, maxStaticAddressCSVExpiry)
265+
}
266+
267+
return nil
268+
}
269+
225270
// GetTaprootAddress returns a taproot address for the given client and server
226271
// public keys and expiry.
227272
func (m *Manager) GetTaprootAddress(clientPubkey, serverPubkey *btcec.PublicKey,

staticaddr/address/manager_test.go

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/btcsuite/btcd/btcec/v2"
99
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1010
"github.com/btcsuite/btcd/btcutil"
11+
"github.com/btcsuite/btcd/wire"
1112
"github.com/lightninglabs/loop/loopdb"
1213
"github.com/lightninglabs/loop/staticaddr/script"
1314
"github.com/lightninglabs/loop/swap"
@@ -93,8 +94,9 @@ func (m *mockStaticAddressClient) ServerNewAddress(ctx context.Context,
9394

9495
args := m.Called(ctx, in, opts)
9596

96-
return args.Get(0).(*swapserverrpc.ServerNewAddressResponse),
97-
args.Error(1)
97+
resp, _ := args.Get(0).(*swapserverrpc.ServerNewAddressResponse)
98+
99+
return resp, args.Error(1)
98100
}
99101

100102
// TestManager tests the static address manager generates the corerct static
@@ -128,6 +130,92 @@ func TestManager(t *testing.T) {
128130
require.EqualValues(t, defaultExpiry, expiry)
129131
}
130132

133+
// TestNewAddressValidatesServerResponse tests that the untrusted
134+
// ServerNewAddress response is validated before the address script is created.
135+
func TestNewAddressValidatesServerResponse(t *testing.T) {
136+
tests := []struct {
137+
name string
138+
resp *swapserverrpc.ServerNewAddressResponse
139+
expected string
140+
}{
141+
{
142+
name: "nil response",
143+
expected: "missing server new address response",
144+
},
145+
{
146+
name: "nil params",
147+
resp: &swapserverrpc.ServerNewAddressResponse{},
148+
expected: "missing server address parameters",
149+
},
150+
{
151+
name: "missing server key",
152+
resp: &swapserverrpc.ServerNewAddressResponse{
153+
Params: &swapserverrpc.ServerAddressParameters{
154+
Expiry: defaultExpiry,
155+
},
156+
},
157+
expected: "missing server public key",
158+
},
159+
{
160+
name: "zero expiry",
161+
resp: newServerNewAddressResponse(0),
162+
expected: "static address CSV expiry must be non-zero",
163+
},
164+
{
165+
name: "seconds flag",
166+
resp: newServerNewAddressResponse(
167+
wire.SequenceLockTimeIsSeconds | 1,
168+
),
169+
expected: "invalid static address CSV flags",
170+
},
171+
{
172+
name: "disabled flag",
173+
resp: newServerNewAddressResponse(
174+
wire.SequenceLockTimeDisabled | 1,
175+
),
176+
expected: "invalid static address CSV flags",
177+
},
178+
{
179+
name: "reserved flag",
180+
resp: newServerNewAddressResponse(
181+
wire.SequenceLockTimeMask + 1,
182+
),
183+
expected: "invalid static address CSV flags",
184+
},
185+
{
186+
name: "too large",
187+
resp: newServerNewAddressResponse(
188+
maxStaticAddressCSVExpiry + 1,
189+
),
190+
expected: "exceeds maximum",
191+
},
192+
}
193+
194+
for _, test := range tests {
195+
test := test
196+
197+
t.Run(test.name, func(t *testing.T) {
198+
testContext := NewAddressManagerTestContextWithResponse(
199+
t, test.resp,
200+
)
201+
202+
_, _, err := testContext.manager.NewAddress(t.Context())
203+
require.ErrorContains(t, err, test.expected)
204+
})
205+
}
206+
}
207+
208+
// TestNewAddressAcceptsMaxCSVExpiry tests the upper valid CSV boundary.
209+
func TestNewAddressAcceptsMaxCSVExpiry(t *testing.T) {
210+
testContext := NewAddressManagerTestContextWithResponse(
211+
t, newServerNewAddressResponse(maxStaticAddressCSVExpiry),
212+
)
213+
214+
_, expiry, err := testContext.manager.NewAddress(t.Context())
215+
require.NoError(t, err)
216+
require.EqualValues(t, maxStaticAddressCSVExpiry, expiry)
217+
}
218+
131219
// GenerateExpectedTaprootAddress generates the expected taproot address that
132220
// the predefined parameters are supposed to generate.
133221
func GenerateExpectedTaprootAddress(t *ManagerTestContext) (
@@ -170,6 +258,16 @@ type ManagerTestContext struct {
170258
// NewAddressManagerTestContext creates a new test context for the static
171259
// address manager.
172260
func NewAddressManagerTestContext(t *testing.T) *ManagerTestContext {
261+
return NewAddressManagerTestContextWithResponse(
262+
t, newServerNewAddressResponse(defaultExpiry),
263+
)
264+
}
265+
266+
// NewAddressManagerTestContextWithResponse creates a new test context with a
267+
// custom ServerNewAddress response.
268+
func NewAddressManagerTestContextWithResponse(t *testing.T,
269+
resp *swapserverrpc.ServerNewAddressResponse) *ManagerTestContext {
270+
173271
ctxb, cancel := context.WithCancel(context.Background())
174272
defer cancel()
175273

@@ -184,14 +282,7 @@ func NewAddressManagerTestContext(t *testing.T) *ManagerTestContext {
184282

185283
mockStaticAddressClient.On(
186284
"ServerNewAddress", mock.Anything, mock.Anything, mock.Anything,
187-
).Return(
188-
&swapserverrpc.ServerNewAddressResponse{
189-
Params: &swapserverrpc.ServerAddressParameters{
190-
ServerKey: defaultServerPubkeyBytes,
191-
Expiry: defaultExpiry,
192-
},
193-
}, nil,
194-
)
285+
).Return(resp, nil)
195286

196287
cfg := &ManagerConfig{
197288
Store: store,
@@ -215,3 +306,12 @@ func NewAddressManagerTestContext(t *testing.T) *ManagerTestContext {
215306
mockStaticAddressClient: mockStaticAddressClient,
216307
}
217308
}
309+
310+
func newServerNewAddressResponse(expiry uint32) *swapserverrpc.ServerNewAddressResponse {
311+
return &swapserverrpc.ServerNewAddressResponse{
312+
Params: &swapserverrpc.ServerAddressParameters{
313+
ServerKey: defaultServerPubkeyBytes,
314+
Expiry: expiry,
315+
},
316+
}
317+
}

0 commit comments

Comments
 (0)