• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14000719599

21 Mar 2025 08:54PM UTC coverage: 58.717% (-10.3%) from 68.989%
14000719599

Pull #8754

github

web-flow
Merge 29f363f18 into 5235f3b24
Pull Request #8754: Add `Outbound` Remote Signer implementation

1562 of 2088 new or added lines in 41 files covered. (74.81%)

28126 existing lines in 464 files now uncovered.

97953 of 166822 relevant lines covered (58.72%)

1.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.36
/lnrpc/walletrpc/walletkit_server.go
1
//go:build walletrpc
2
// +build walletrpc
3

4
package walletrpc
5

6
import (
7
        "bytes"
8
        "context"
9
        "encoding/base64"
10
        "encoding/binary"
11
        "errors"
12
        "fmt"
13
        "math"
14
        "os"
15
        "path/filepath"
16
        "sort"
17
        "sync"
18
        "sync/atomic"
19
        "time"
20

21
        "github.com/btcsuite/btcd/btcec/v2"
22
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
23
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
24
        "github.com/btcsuite/btcd/btcutil"
25
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
26
        "github.com/btcsuite/btcd/btcutil/psbt"
27
        "github.com/btcsuite/btcd/chaincfg/chainhash"
28
        "github.com/btcsuite/btcd/txscript"
29
        "github.com/btcsuite/btcd/wire"
30
        "github.com/btcsuite/btcwallet/waddrmgr"
31
        base "github.com/btcsuite/btcwallet/wallet"
32
        "github.com/btcsuite/btcwallet/wtxmgr"
33
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
34
        "github.com/lightningnetwork/lnd/channeldb"
35
        "github.com/lightningnetwork/lnd/contractcourt"
36
        "github.com/lightningnetwork/lnd/fn/v2"
37
        "github.com/lightningnetwork/lnd/input"
38
        "github.com/lightningnetwork/lnd/keychain"
39
        "github.com/lightningnetwork/lnd/labels"
40
        "github.com/lightningnetwork/lnd/lnrpc"
41
        "github.com/lightningnetwork/lnd/lnrpc/signrpc"
42
        "github.com/lightningnetwork/lnd/lnwallet"
43
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
44
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
45
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
46
        "github.com/lightningnetwork/lnd/macaroons"
47
        "github.com/lightningnetwork/lnd/sweep"
48
        "golang.org/x/exp/maps"
49
        "google.golang.org/grpc"
50
        "gopkg.in/macaroon-bakery.v2/bakery"
51
)
52

53
var (
54
        // macaroonOps are the set of capabilities that our minted macaroon (if
55
        // it doesn't already exist) will have.
56
        macaroonOps = []bakery.Op{
57
                {
58
                        Entity: "address",
59
                        Action: "write",
60
                },
61
                {
62
                        Entity: "address",
63
                        Action: "read",
64
                },
65
                {
66
                        Entity: "onchain",
67
                        Action: "write",
68
                },
69
                {
70
                        Entity: "onchain",
71
                        Action: "read",
72
                },
73
        }
74

75
        // macPermissions maps RPC calls to the permissions they require.
76
        macPermissions = map[string][]bakery.Op{
77
                "/walletrpc.WalletKit/DeriveNextKey": {{
78
                        Entity: "address",
79
                        Action: "read",
80
                }},
81
                "/walletrpc.WalletKit/DeriveKey": {{
82
                        Entity: "address",
83
                        Action: "read",
84
                }},
85
                "/walletrpc.WalletKit/NextAddr": {{
86
                        Entity: "address",
87
                        Action: "read",
88
                }},
89
                "/walletrpc.WalletKit/GetTransaction": {{
90
                        Entity: "onchain",
91
                        Action: "read",
92
                }},
93
                "/walletrpc.WalletKit/PublishTransaction": {{
94
                        Entity: "onchain",
95
                        Action: "write",
96
                }},
97
                "/walletrpc.WalletKit/SendOutputs": {{
98
                        Entity: "onchain",
99
                        Action: "write",
100
                }},
101
                "/walletrpc.WalletKit/EstimateFee": {{
102
                        Entity: "onchain",
103
                        Action: "read",
104
                }},
105
                "/walletrpc.WalletKit/PendingSweeps": {{
106
                        Entity: "onchain",
107
                        Action: "read",
108
                }},
109
                "/walletrpc.WalletKit/BumpFee": {{
110
                        Entity: "onchain",
111
                        Action: "write",
112
                }},
113
                "/walletrpc.WalletKit/BumpForceCloseFee": {{
114
                        Entity: "onchain",
115
                        Action: "write",
116
                }},
117
                "/walletrpc.WalletKit/ListSweeps": {{
118
                        Entity: "onchain",
119
                        Action: "read",
120
                }},
121
                "/walletrpc.WalletKit/LabelTransaction": {{
122
                        Entity: "onchain",
123
                        Action: "write",
124
                }},
125
                "/walletrpc.WalletKit/LeaseOutput": {{
126
                        Entity: "onchain",
127
                        Action: "write",
128
                }},
129
                "/walletrpc.WalletKit/ReleaseOutput": {{
130
                        Entity: "onchain",
131
                        Action: "write",
132
                }},
133
                "/walletrpc.WalletKit/ListLeases": {{
134
                        Entity: "onchain",
135
                        Action: "read",
136
                }},
137
                "/walletrpc.WalletKit/ListUnspent": {{
138
                        Entity: "onchain",
139
                        Action: "read",
140
                }},
141
                "/walletrpc.WalletKit/ListAddresses": {{
142
                        Entity: "onchain",
143
                        Action: "read",
144
                }},
145
                "/walletrpc.WalletKit/SignMessageWithAddr": {{
146
                        Entity: "onchain",
147
                        Action: "write",
148
                }},
149
                "/walletrpc.WalletKit/VerifyMessageWithAddr": {{
150
                        Entity: "onchain",
151
                        Action: "write",
152
                }},
153
                "/walletrpc.WalletKit/FundPsbt": {{
154
                        Entity: "onchain",
155
                        Action: "write",
156
                }},
157
                "/walletrpc.WalletKit/SignPsbt": {{
158
                        Entity: "onchain",
159
                        Action: "write",
160
                }},
161
                "/walletrpc.WalletKit/FinalizePsbt": {{
162
                        Entity: "onchain",
163
                        Action: "write",
164
                }},
165
                "/walletrpc.WalletKit/ListAccounts": {{
166
                        Entity: "onchain",
167
                        Action: "read",
168
                }},
169
                "/walletrpc.WalletKit/RequiredReserve": {{
170
                        Entity: "onchain",
171
                        Action: "read",
172
                }},
173
                "/walletrpc.WalletKit/ImportAccount": {{
174
                        Entity: "onchain",
175
                        Action: "write",
176
                }},
177
                "/walletrpc.WalletKit/ImportPublicKey": {{
178
                        Entity: "onchain",
179
                        Action: "write",
180
                }},
181
                "/walletrpc.WalletKit/ImportTapscript": {{
182
                        Entity: "onchain",
183
                        Action: "write",
184
                }},
185
                "/walletrpc.WalletKit/RemoveTransaction": {{
186
                        Entity: "onchain",
187
                        Action: "write",
188
                }},
189
                "/walletrpc.WalletKit/SignCoordinatorStreams": {{
190
                        Entity: "remotesigner",
191
                        Action: "generate",
192
                }},
193
        }
194

195
        // DefaultWalletKitMacFilename is the default name of the wallet kit
196
        // macaroon that we expect to find via a file handle within the main
197
        // configuration file in this package.
198
        DefaultWalletKitMacFilename = "walletkit.macaroon"
199

200
        // allWitnessTypes is a mapping between the witness types defined in the
201
        // `input` package, and the witness types in the protobuf definition.
202
        // This map is necessary because the native enum and the protobuf enum
203
        // are numbered differently. The protobuf enum cannot be renumbered
204
        // because this would break backwards compatibility with older clients,
205
        // and the native enum cannot be renumbered because it is stored in the
206
        // watchtower and BreachArbitrator databases.
207
        //
208
        //nolint:ll
209
        allWitnessTypes = map[input.WitnessType]WitnessType{
210
                input.CommitmentTimeLock:                           WitnessType_COMMITMENT_TIME_LOCK,
211
                input.CommitmentNoDelay:                            WitnessType_COMMITMENT_NO_DELAY,
212
                input.CommitmentRevoke:                             WitnessType_COMMITMENT_REVOKE,
213
                input.HtlcOfferedRevoke:                            WitnessType_HTLC_OFFERED_REVOKE,
214
                input.HtlcAcceptedRevoke:                           WitnessType_HTLC_ACCEPTED_REVOKE,
215
                input.HtlcOfferedTimeoutSecondLevel:                WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
216
                input.HtlcAcceptedSuccessSecondLevel:               WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
217
                input.HtlcOfferedRemoteTimeout:                     WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT,
218
                input.HtlcAcceptedRemoteSuccess:                    WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS,
219
                input.HtlcSecondLevelRevoke:                        WitnessType_HTLC_SECOND_LEVEL_REVOKE,
220
                input.WitnessKeyHash:                               WitnessType_WITNESS_KEY_HASH,
221
                input.NestedWitnessKeyHash:                         WitnessType_NESTED_WITNESS_KEY_HASH,
222
                input.CommitmentAnchor:                             WitnessType_COMMITMENT_ANCHOR,
223
                input.HtlcOfferedTimeoutSecondLevelInputConfirmed:  WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED,
224
                input.HtlcAcceptedSuccessSecondLevelInputConfirmed: WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED,
225
                input.CommitSpendNoDelayTweakless:                  WitnessType_COMMITMENT_NO_DELAY_TWEAKLESS,
226
                input.CommitmentToRemoteConfirmed:                  WitnessType_COMMITMENT_TO_REMOTE_CONFIRMED,
227
                input.LeaseCommitmentTimeLock:                      WitnessType_LEASE_COMMITMENT_TIME_LOCK,
228
                input.LeaseCommitmentToRemoteConfirmed:             WitnessType_LEASE_COMMITMENT_TO_REMOTE_CONFIRMED,
229
                input.LeaseHtlcOfferedTimeoutSecondLevel:           WitnessType_LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
230
                input.LeaseHtlcAcceptedSuccessSecondLevel:          WitnessType_LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
231
                input.TaprootPubKeySpend:                           WitnessType_TAPROOT_PUB_KEY_SPEND,
232
                input.TaprootLocalCommitSpend:                      WitnessType_TAPROOT_LOCAL_COMMIT_SPEND,
233
                input.TaprootRemoteCommitSpend:                     WitnessType_TAPROOT_REMOTE_COMMIT_SPEND,
234
                input.TaprootAnchorSweepSpend:                      WitnessType_TAPROOT_ANCHOR_SWEEP_SPEND,
235
                input.TaprootHtlcOfferedTimeoutSecondLevel:         WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
236
                input.TaprootHtlcAcceptedSuccessSecondLevel:        WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
237
                input.TaprootHtlcSecondLevelRevoke:                 WitnessType_TAPROOT_HTLC_SECOND_LEVEL_REVOKE,
238
                input.TaprootHtlcAcceptedRevoke:                    WitnessType_TAPROOT_HTLC_ACCEPTED_REVOKE,
239
                input.TaprootHtlcOfferedRevoke:                     WitnessType_TAPROOT_HTLC_OFFERED_REVOKE,
240
                input.TaprootHtlcOfferedRemoteTimeout:              WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT,
241
                input.TaprootHtlcLocalOfferedTimeout:               WitnessType_TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT,
242
                input.TaprootHtlcAcceptedRemoteSuccess:             WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS,
243
                input.TaprootHtlcAcceptedLocalSuccess:              WitnessType_TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS,
244
                input.TaprootCommitmentRevoke:                      WitnessType_TAPROOT_COMMITMENT_REVOKE,
245
        }
246
)
247

248
// InboundRemoteSignerConnection is an interface that mimics a subset of the
249
// rpcwallet InboundRemoteSignerConnection interface to avoid circular
250
// dependencies.
251
type InboundRemoteSignerConnection interface {
252
        // AddConnection feeds the inbound connection handler with the incoming
253
        // stream set up by an outbound remote signer and then blocks until the
254
        // stream is closed. Lnd can then send any requests to the remote signer
255
        // through the stream.
256
        AddConnection(stream WalletKit_SignCoordinatorStreamsServer) error
257
}
258

259
// ServerShell is a shell struct holding a reference to the actual sub-server.
260
// It is used to register the gRPC sub-server with the root server before we
261
// have the necessary dependencies to populate the actual sub-server.
262
type ServerShell struct {
263
        WalletKitServer
264
}
265

266
// WalletKit is a sub-RPC server that exposes a tool kit which allows clients
267
// to execute common wallet operations. This includes requesting new addresses,
268
// keys (for contracts!), and publishing transactions.
269
type WalletKit struct {
270
        injected int32 // To be used atomically.
271

272
        // Required by the grpc-gateway/v2 library for forward compatibility.
273
        UnimplementedWalletKitServer
274

275
        cfg *Config
276

277
        // As we allow rpc requests into the server before dependencies have
278
        // been finalized, the read lock should be held in functions that
279
        // accesses values from the cfg before dependencies are finalized.
280
        // The write lock should be held when setting the cfg.
281
        sync.RWMutex
282
}
283

284
// A compile time check to ensure that WalletKit fully implements the
285
// WalletKitServer gRPC service.
286
var _ WalletKitServer = (*WalletKit)(nil)
287

288
// New creates a new instance of the WalletKit sub-RPC server.
289
func New() (*WalletKit, lnrpc.MacaroonPerms, error) {
3✔
290
        return &WalletKit{cfg: &Config{}}, macPermissions, nil
3✔
291
}
3✔
292

293
// Stop signals any active goroutines for a graceful closure.
294
//
295
// NOTE: This is part of the lnrpc.SubServer interface.
296
func (w *WalletKit) Stop() error {
3✔
297
        return nil
3✔
298
}
3✔
299

300
// InjectDependencies populates the sub-server's dependencies. If the
301
// finalizeDependencies boolean is true, then the sub-server will finalize its
302
// dependencies and return an error if any required dependencies are missing.
303
//
304
// NOTE: This is part of the lnrpc.SubServer interface.
305
func (w *WalletKit) InjectDependencies(
306
        configRegistry lnrpc.SubServerConfigDispatcher,
307
        finalizeDependencies bool) error {
3✔
308

3✔
309
        if finalizeDependencies && atomic.AddInt32(&w.injected, 1) != 1 {
3✔
NEW
310
                return lnrpc.ErrDependenciesFinalized
×
NEW
311
        }
×
312

313
        w.Lock()
3✔
314
        defer w.Unlock()
3✔
315

3✔
316
        cfg, err := getConfig(configRegistry, finalizeDependencies)
3✔
317
        if err != nil {
3✔
NEW
318
                return err
×
NEW
319
        }
×
320

321
        if finalizeDependencies {
6✔
322
                w.cfg = cfg
3✔
323

3✔
324
                return nil
3✔
325
        }
3✔
326

327
        // If the path of the wallet kit macaroon wasn't specified, then we'll
328
        // assume that it's found at the default network directory.
329
        if cfg.WalletKitMacPath == "" {
6✔
330
                cfg.WalletKitMacPath = filepath.Join(
3✔
331
                        cfg.NetworkDir, DefaultWalletKitMacFilename,
3✔
332
                )
3✔
333
        }
3✔
334

335
        // Now that we know the full path of the wallet kit macaroon, we can
336
        // check to see if we need to create it or not. If stateless_init is set
337
        // then we don't write the macaroons.
338
        macFilePath := cfg.WalletKitMacPath
3✔
339
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
3✔
340
                !lnrpc.FileExists(macFilePath) {
3✔
UNCOV
341

×
UNCOV
342
                log.Infof("Baking macaroons for WalletKit RPC Server at: %v",
×
UNCOV
343
                        macFilePath)
×
UNCOV
344

×
UNCOV
345
                // At this point, we know that the wallet kit macaroon doesn't
×
UNCOV
346
                // yet, exist, so we need to create it with the help of the
×
UNCOV
347
                // main macaroon service.
×
UNCOV
348
                walletKitMac, err := cfg.MacService.NewMacaroon(
×
UNCOV
349
                        context.Background(), macaroons.DefaultRootKeyID,
×
UNCOV
350
                        macaroonOps...,
×
UNCOV
351
                )
×
UNCOV
352
                if err != nil {
×
NEW
353
                        return err
×
354
                }
×
UNCOV
355
                walletKitMacBytes, err := walletKitMac.M().MarshalBinary()
×
UNCOV
356
                if err != nil {
×
NEW
357
                        return err
×
358
                }
×
UNCOV
359
                err = os.WriteFile(macFilePath, walletKitMacBytes, 0644)
×
UNCOV
360
                if err != nil {
×
361
                        _ = os.Remove(macFilePath)
×
NEW
362
                        return err
×
363
                }
×
364
        }
365

366
        w.cfg = cfg
3✔
367

3✔
368
        return nil
3✔
369
}
370

371
// Name returns a unique string representation of the sub-server. This can be
372
// used to identify the sub-server and also de-duplicate them.
373
//
374
// NOTE: This is part of the lnrpc.SubServer interface.
375
func (w *WalletKit) Name() string {
3✔
376
        return SubServerName
3✔
377
}
3✔
378

379
// RegisterWithRootServer will be called by the root gRPC server to direct a
380
// sub RPC server to register itself with the main gRPC root server. Until this
381
// is called, each sub-server won't be able to have requests routed towards it.
382
//
383
// NOTE: This is part of the lnrpc.GrpcHandler interface.
384
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
3✔
385
        // We make sure that we register it with the main gRPC server to ensure
3✔
386
        // all our methods are routed properly.
3✔
387
        RegisterWalletKitServer(grpcServer, r)
3✔
388

3✔
389
        log.Debugf("WalletKit RPC server successfully registered with " +
3✔
390
                "root gRPC server")
3✔
391

3✔
392
        return nil
3✔
393
}
3✔
394

395
// RegisterWithRestServer will be called by the root REST mux to direct a sub
396
// RPC server to register itself with the main REST mux server. Until this is
397
// called, each sub-server won't be able to have requests routed towards it.
398
//
399
// NOTE: This is part of the lnrpc.GrpcHandler interface.
400
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
401
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
3✔
402

3✔
403
        // We make sure that we register it with the main REST server to ensure
3✔
404
        // all our methods are routed properly.
3✔
405
        err := RegisterWalletKitHandlerFromEndpoint(ctx, mux, dest, opts)
3✔
406
        if err != nil {
3✔
407
                log.Errorf("Could not register WalletKit REST server "+
×
408
                        "with root REST server: %v", err)
×
409
                return err
×
410
        }
×
411

412
        log.Debugf("WalletKit REST server successfully registered with " +
3✔
413
                "root REST server")
3✔
414
        return nil
3✔
415
}
416

417
// CreateSubServer creates an instance of the sub-server, and returns the
418
// macaroon permissions that the sub-server wishes to pass on to the root server
419
// for all methods routed towards it.
420
//
421
// NOTE: This is part of the lnrpc.GrpcHandler interface.
422
func (r *ServerShell) CreateSubServer() (
423
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
3✔
424

3✔
425
        subServer, macPermissions, err := New()
3✔
426
        if err != nil {
3✔
427
                return nil, nil, err
×
428
        }
×
429

430
        r.WalletKitServer = subServer
3✔
431
        return subServer, macPermissions, nil
3✔
432
}
433

434
// internalScope returns the internal key scope.
435
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
3✔
436
        return waddrmgr.KeyScope{
3✔
437
                Purpose: keychain.BIP0043Purpose,
3✔
438
                Coin:    w.cfg.ChainParams.HDCoinType,
3✔
439
        }
3✔
440
}
3✔
441

442
// ListUnspent returns useful information about each unspent output owned by
443
// the wallet, as reported by the underlying `ListUnspentWitness`; the
444
// information returned is: outpoint, amount in satoshis, address, address
445
// type, scriptPubKey in hex and number of confirmations. The result is
446
// filtered to contain outputs whose number of confirmations is between a
447
// minimum and maximum number of confirmations specified by the user.
448
func (w *WalletKit) ListUnspent(ctx context.Context,
449
        req *ListUnspentRequest) (*ListUnspentResponse, error) {
3✔
450

3✔
451
        // Force min_confs and max_confs to be zero if unconfirmed_only is
3✔
452
        // true.
3✔
453
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
3✔
454
                return nil, fmt.Errorf("min_confs and max_confs must be zero " +
×
455
                        "if unconfirmed_only is true")
×
456
        }
×
457

458
        // When unconfirmed_only is inactive and max_confs is zero (default
459
        // values), we will override max_confs to be a MaxInt32, in order
460
        // to return all confirmed and unconfirmed utxos as a default response.
461
        if req.MaxConfs == 0 && !req.UnconfirmedOnly {
6✔
462
                req.MaxConfs = math.MaxInt32
3✔
463
        }
3✔
464

465
        // Validate the confirmation arguments.
466
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
3✔
467
        if err != nil {
3✔
468
                return nil, err
×
469
        }
×
470

471
        // With our arguments validated, we'll query the internal wallet for
472
        // the set of UTXOs that match our query.
473
        //
474
        // We'll acquire the global coin selection lock to ensure there aren't
475
        // any other concurrent processes attempting to lock any UTXOs which may
476
        // be shown available to us.
477
        var utxos []*lnwallet.Utxo
3✔
478

3✔
479
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
6✔
480
                utxos, err = w.cfg.Wallet.ListUnspentWitness(
3✔
481
                        minConfs, maxConfs, req.Account,
3✔
482
                )
3✔
483

3✔
484
                return err
3✔
485
        })
3✔
486
        if err != nil {
3✔
487
                return nil, err
×
488
        }
×
489

490
        rpcUtxos, err := lnrpc.MarshalUtxos(utxos, w.cfg.ChainParams)
3✔
491
        if err != nil {
3✔
492
                return nil, err
×
493
        }
×
494

495
        return &ListUnspentResponse{
3✔
496
                Utxos: rpcUtxos,
3✔
497
        }, nil
3✔
498
}
499

500
// SignCoordinatorStreams opens a bi-directional streaming RPC, which is used
501
// to allow a remote signer to process sign requests on behalf of the wallet.
502
func (w *WalletKit) SignCoordinatorStreams(
503
        stream WalletKit_SignCoordinatorStreamsServer) error {
3✔
504

3✔
505
        w.RLock()
3✔
506

3✔
507
        // Check that the user actually has configured that the reverse remote
3✔
508
        // signer functionality should be enabled.
3✔
509
        if w.cfg.RemoteSignerConnection == nil {
3✔
NEW
510
                w.RUnlock()
×
NEW
511

×
NEW
512
                return fmt.Errorf("inbound connections from remote signers " +
×
NEW
513
                        "not enabled in config")
×
NEW
514
        }
×
515

516
        connectionCoordinator := w.cfg.RemoteSignerConnection
3✔
517

3✔
518
        // Release the read lock as we will acquire the write in the
3✔
519
        // InjectDependencies function while the stream is still open.
3✔
520
        w.RUnlock()
3✔
521

3✔
522
        return connectionCoordinator.AddConnection(stream)
3✔
523
}
524

525
// LeaseOutput locks an output to the given ID, preventing it from being
526
// available for any future coin selection attempts. The absolute time of the
527
// lock's expiration is returned. The expiration of the lock can be extended by
528
// successive invocations of this call. Outputs can be unlocked before their
529
// expiration through `ReleaseOutput`.
530
//
531
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
532
// output has already been locked to a different ID, then
533
// wtxmgr.ErrOutputAlreadyLocked is returned.
534
func (w *WalletKit) LeaseOutput(ctx context.Context,
535
        req *LeaseOutputRequest) (*LeaseOutputResponse, error) {
×
536

×
537
        if len(req.Id) != 32 {
×
538
                return nil, errors.New("id must be 32 random bytes")
×
539
        }
×
540
        var lockID wtxmgr.LockID
×
541
        copy(lockID[:], req.Id)
×
542

×
543
        // Don't allow ID's of 32 bytes, but all zeros.
×
544
        if lockID == (wtxmgr.LockID{}) {
×
545
                return nil, errors.New("id must be 32 random bytes")
×
546
        }
×
547

548
        // Don't allow our internal ID to be used externally for locking. Only
549
        // unlocking is allowed.
550
        if lockID == chanfunding.LndInternalLockID {
×
551
                return nil, errors.New("reserved id cannot be used")
×
552
        }
×
553

554
        op, err := UnmarshallOutPoint(req.Outpoint)
×
555
        if err != nil {
×
556
                return nil, err
×
557
        }
×
558

559
        // Use the specified lock duration or fall back to the default.
560
        duration := chanfunding.DefaultLockDuration
×
561
        if req.ExpirationSeconds != 0 {
×
562
                duration = time.Duration(req.ExpirationSeconds) * time.Second
×
563
        }
×
564

565
        // Acquire the global coin selection lock to ensure there aren't any
566
        // other concurrent processes attempting to lease the same UTXO.
567
        var expiration time.Time
×
568
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
569
                expiration, err = w.cfg.Wallet.LeaseOutput(
×
570
                        lockID, *op, duration,
×
571
                )
×
572
                return err
×
573
        })
×
574
        if err != nil {
×
575
                return nil, err
×
576
        }
×
577

578
        return &LeaseOutputResponse{
×
579
                Expiration: uint64(expiration.Unix()),
×
580
        }, nil
×
581
}
582

583
// ReleaseOutput unlocks an output, allowing it to be available for coin
584
// selection if it remains unspent. The ID should match the one used to
585
// originally lock the output.
586
func (w *WalletKit) ReleaseOutput(ctx context.Context,
587
        req *ReleaseOutputRequest) (*ReleaseOutputResponse, error) {
×
588

×
589
        if len(req.Id) != 32 {
×
590
                return nil, errors.New("id must be 32 random bytes")
×
591
        }
×
592
        var lockID wtxmgr.LockID
×
593
        copy(lockID[:], req.Id)
×
594

×
595
        op, err := UnmarshallOutPoint(req.Outpoint)
×
596
        if err != nil {
×
597
                return nil, err
×
598
        }
×
599

600
        // Acquire the global coin selection lock to maintain consistency as
601
        // it's acquired when we initially leased the output.
602
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
603
                return w.cfg.Wallet.ReleaseOutput(lockID, *op)
×
604
        })
×
605
        if err != nil {
×
606
                return nil, err
×
607
        }
×
608

609
        return &ReleaseOutputResponse{
×
610
                Status: fmt.Sprintf("output %v released", op.String()),
×
611
        }, nil
×
612
}
613

614
// ListLeases returns a list of all currently locked utxos.
615
func (w *WalletKit) ListLeases(ctx context.Context,
616
        req *ListLeasesRequest) (*ListLeasesResponse, error) {
×
617

×
618
        leases, err := w.cfg.Wallet.ListLeasedOutputs()
×
619
        if err != nil {
×
620
                return nil, err
×
621
        }
×
622

623
        return &ListLeasesResponse{
×
624
                LockedUtxos: marshallLeases(leases),
×
625
        }, nil
×
626
}
627

628
// DeriveNextKey attempts to derive the *next* key within the key family
629
// (account in BIP43) specified. This method should return the next external
630
// child within this branch.
631
func (w *WalletKit) DeriveNextKey(ctx context.Context,
632
        req *KeyReq) (*signrpc.KeyDescriptor, error) {
3✔
633

3✔
634
        nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey(
3✔
635
                keychain.KeyFamily(req.KeyFamily),
3✔
636
        )
3✔
637
        if err != nil {
3✔
638
                return nil, err
×
639
        }
×
640

641
        return &signrpc.KeyDescriptor{
3✔
642
                KeyLoc: &signrpc.KeyLocator{
3✔
643
                        KeyFamily: int32(nextKeyDesc.Family),
3✔
644
                        KeyIndex:  int32(nextKeyDesc.Index),
3✔
645
                },
3✔
646
                RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(),
3✔
647
        }, nil
3✔
648
}
649

650
// DeriveKey attempts to derive an arbitrary key specified by the passed
651
// KeyLocator.
652
func (w *WalletKit) DeriveKey(ctx context.Context,
653
        req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
3✔
654

3✔
655
        keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{
3✔
656
                Family: keychain.KeyFamily(req.KeyFamily),
3✔
657
                Index:  uint32(req.KeyIndex),
3✔
658
        })
3✔
659
        if err != nil {
3✔
660
                return nil, err
×
661
        }
×
662

663
        return &signrpc.KeyDescriptor{
3✔
664
                KeyLoc: &signrpc.KeyLocator{
3✔
665
                        KeyFamily: int32(keyDesc.Family),
3✔
666
                        KeyIndex:  int32(keyDesc.Index),
3✔
667
                },
3✔
668
                RawKeyBytes: keyDesc.PubKey.SerializeCompressed(),
3✔
669
        }, nil
3✔
670
}
671

672
// NextAddr returns the next unused address within the wallet.
673
func (w *WalletKit) NextAddr(ctx context.Context,
674
        req *AddrRequest) (*AddrResponse, error) {
3✔
675

3✔
676
        account := lnwallet.DefaultAccountName
3✔
677
        if req.Account != "" {
3✔
678
                account = req.Account
×
679
        }
×
680

681
        addrType := lnwallet.WitnessPubKey
3✔
682
        switch req.Type {
3✔
683
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
684
                addrType = lnwallet.NestedWitnessPubKey
×
685

686
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
687
                return nil, fmt.Errorf("invalid address type for next "+
×
688
                        "address: %v", req.Type)
×
689

690
        case AddressType_TAPROOT_PUBKEY:
×
691
                addrType = lnwallet.TaprootPubkey
×
692
        }
693

694
        addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account)
3✔
695
        if err != nil {
3✔
696
                return nil, err
×
697
        }
×
698

699
        return &AddrResponse{
3✔
700
                Addr: addr.String(),
3✔
701
        }, nil
3✔
702
}
703

704
// GetTransaction returns a transaction from the wallet given its hash.
705
func (w *WalletKit) GetTransaction(_ context.Context,
706
        req *GetTransactionRequest) (*lnrpc.Transaction, error) {
3✔
707

3✔
708
        // If the client doesn't specify a hash, then there's nothing to
3✔
709
        // return.
3✔
710
        if req.Txid == "" {
3✔
711
                return nil, fmt.Errorf("must provide a transaction hash")
×
712
        }
×
713

714
        txHash, err := chainhash.NewHashFromStr(req.Txid)
3✔
715
        if err != nil {
3✔
716
                return nil, err
×
717
        }
×
718

719
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
3✔
720
        if err != nil {
3✔
721
                return nil, err
×
722
        }
×
723

724
        return lnrpc.RPCTransaction(res), nil
3✔
725
}
726

727
// Attempts to publish the passed transaction to the network. Once this returns
728
// without an error, the wallet will continually attempt to re-broadcast the
729
// transaction on start up, until it enters the chain.
730
func (w *WalletKit) PublishTransaction(ctx context.Context,
731
        req *Transaction) (*PublishResponse, error) {
3✔
732

3✔
733
        switch {
3✔
734
        // If the client doesn't specify a transaction, then there's nothing to
735
        // publish.
736
        case len(req.TxHex) == 0:
×
737
                return nil, fmt.Errorf("must provide a transaction to " +
×
738
                        "publish")
×
739
        }
740

741
        tx := &wire.MsgTx{}
3✔
742
        txReader := bytes.NewReader(req.TxHex)
3✔
743
        if err := tx.Deserialize(txReader); err != nil {
3✔
744
                return nil, err
×
745
        }
×
746

747
        label, err := labels.ValidateAPI(req.Label)
3✔
748
        if err != nil {
3✔
749
                return nil, err
×
750
        }
×
751

752
        err = w.cfg.Wallet.PublishTransaction(tx, label)
3✔
753
        if err != nil {
3✔
754
                return nil, err
×
755
        }
×
756

757
        return &PublishResponse{}, nil
3✔
758
}
759

760
// RemoveTransaction attempts to remove the transaction and all of its
761
// descendants resulting from further spends of the outputs of the provided
762
// transaction id.
763
// NOTE: We do not remove the transaction from the rebroadcaster which might
764
// run in the background rebroadcasting not yet confirmed transactions. We do
765
// not have access to the rebroadcaster here nor should we. This command is not
766
// a way to remove transactions from the network. It is a way to shortcircuit
767
// wallet utxo housekeeping while transactions are still unconfirmed and we know
768
// that a transaction will never confirm because a replacement already pays
769
// higher fees.
770
func (w *WalletKit) RemoveTransaction(_ context.Context,
771
        req *GetTransactionRequest) (*RemoveTransactionResponse, error) {
3✔
772

3✔
773
        // If the client doesn't specify a hash, then there's nothing to
3✔
774
        // return.
3✔
775
        if req.Txid == "" {
3✔
776
                return nil, fmt.Errorf("must provide a transaction hash")
×
777
        }
×
778

779
        txHash, err := chainhash.NewHashFromStr(req.Txid)
3✔
780
        if err != nil {
3✔
781
                return nil, err
×
782
        }
×
783

784
        // Query the tx store of our internal wallet for the specified
785
        // transaction.
786
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
3✔
787
        if err != nil {
3✔
788
                return nil, fmt.Errorf("transaction with txid=%v not found "+
×
789
                        "in the internal wallet store", txHash)
×
790
        }
×
791

792
        // Only allow unconfirmed transactions to be removed because as soon
793
        // as a transaction is confirmed it will be evaluated by the wallet
794
        // again and the wallet state would be updated in case the user had
795
        // removed the transaction accidentally.
796
        if res.NumConfirmations > 0 {
3✔
797
                return nil, fmt.Errorf("transaction with txid=%v is already "+
×
798
                        "confirmed (numConfs=%d) cannot be removed", txHash,
×
799
                        res.NumConfirmations)
×
800
        }
×
801

802
        tx := &wire.MsgTx{}
3✔
803
        txReader := bytes.NewReader(res.RawTx)
3✔
804
        if err := tx.Deserialize(txReader); err != nil {
3✔
805
                return nil, err
×
806
        }
×
807

808
        err = w.cfg.Wallet.RemoveDescendants(tx)
3✔
809
        if err != nil {
3✔
810
                return nil, err
×
811
        }
×
812

813
        return &RemoveTransactionResponse{
3✔
814
                Status: "Successfully removed transaction",
3✔
815
        }, nil
3✔
816
}
817

818
// SendOutputs is similar to the existing sendmany call in Bitcoind, and allows
819
// the caller to create a transaction that sends to several outputs at once.
820
// This is ideal when wanting to batch create a set of transactions.
821
func (w *WalletKit) SendOutputs(ctx context.Context,
822
        req *SendOutputsRequest) (*SendOutputsResponse, error) {
3✔
823

3✔
824
        switch {
3✔
825
        // If the client didn't specify any outputs to create, then  we can't
826
        // proceed .
827
        case len(req.Outputs) == 0:
×
828
                return nil, fmt.Errorf("must specify at least one output " +
×
829
                        "to create")
×
830
        }
831

832
        // Before we can request this transaction to be created, we'll need to
833
        // amp the protos back into the format that the internal wallet will
834
        // recognize.
835
        var totalOutputValue int64
3✔
836
        outputsToCreate := make([]*wire.TxOut, 0, len(req.Outputs))
3✔
837
        for _, output := range req.Outputs {
6✔
838
                outputsToCreate = append(outputsToCreate, &wire.TxOut{
3✔
839
                        Value:    output.Value,
3✔
840
                        PkScript: output.PkScript,
3✔
841
                })
3✔
842
                totalOutputValue += output.Value
3✔
843
        }
3✔
844

845
        // Then, we'll extract the minimum number of confirmations that each
846
        // output we use to fund the transaction should satisfy.
847
        minConfs, err := lnrpc.ExtractMinConfs(
3✔
848
                req.MinConfs, req.SpendUnconfirmed,
3✔
849
        )
3✔
850
        if err != nil {
3✔
851
                return nil, err
×
852
        }
×
853

854
        // Before sending out funds we need to ensure that the remainder of our
855
        // wallet funds would cover for the anchor reserve requirement. We'll
856
        // also take unconfirmed funds into account.
857
        walletBalance, err := w.cfg.Wallet.ConfirmedBalance(
3✔
858
                0, lnwallet.DefaultAccountName,
3✔
859
        )
3✔
860
        if err != nil {
3✔
861
                return nil, err
×
862
        }
×
863

864
        // We'll get the currently required reserve amount.
865
        reserve, err := w.RequiredReserve(ctx, &RequiredReserveRequest{})
3✔
866
        if err != nil {
3✔
867
                return nil, err
×
868
        }
×
869

870
        // Then we check if our current wallet balance undershoots the required
871
        // reserve if we'd send out the outputs specified in the request.
872
        if int64(walletBalance)-totalOutputValue < reserve.RequiredReserve {
6✔
873
                return nil, ErrInsufficientReserve
3✔
874
        }
3✔
875

876
        label, err := labels.ValidateAPI(req.Label)
3✔
877
        if err != nil {
3✔
878
                return nil, err
×
879
        }
×
880

881
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
3✔
882
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
3✔
883
        )
3✔
884
        if err != nil {
3✔
885
                return nil, err
×
886
        }
×
887

888
        // Now that we have the outputs mapped and checked for the reserve
889
        // requirement, we can request that the wallet attempts to create this
890
        // transaction.
891
        tx, err := w.cfg.Wallet.SendOutputs(
3✔
892
                nil, outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw),
3✔
893
                minConfs, label, coinSelectionStrategy,
3✔
894
        )
3✔
895
        if err != nil {
3✔
896
                return nil, err
×
897
        }
×
898

899
        var b bytes.Buffer
3✔
900
        if err := tx.Serialize(&b); err != nil {
3✔
901
                return nil, err
×
902
        }
×
903

904
        return &SendOutputsResponse{
3✔
905
                RawTx: b.Bytes(),
3✔
906
        }, nil
3✔
907
}
908

909
// EstimateFee attempts to query the internal fee estimator of the wallet to
910
// determine the fee (in sat/kw) to attach to a transaction in order to achieve
911
// the confirmation target.
912
func (w *WalletKit) EstimateFee(ctx context.Context,
913
        req *EstimateFeeRequest) (*EstimateFeeResponse, error) {
×
914

×
915
        switch {
×
916
        // A confirmation target of zero doesn't make any sense. Similarly, we
917
        // reject confirmation targets of 1 as they're unreasonable.
918
        case req.ConfTarget == 0 || req.ConfTarget == 1:
×
919
                return nil, fmt.Errorf("confirmation target must be greater " +
×
920
                        "than 1")
×
921
        }
922

923
        satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW(
×
924
                uint32(req.ConfTarget),
×
925
        )
×
926
        if err != nil {
×
927
                return nil, err
×
928
        }
×
929

930
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
×
931

×
932
        return &EstimateFeeResponse{
×
933
                SatPerKw:            int64(satPerKw),
×
934
                MinRelayFeeSatPerKw: int64(relayFeePerKw),
×
935
        }, nil
×
936
}
937

938
// PendingSweeps returns lists of on-chain outputs that lnd is currently
939
// attempting to sweep within its central batching engine. Outputs with similar
940
// fee rates are batched together in order to sweep them within a single
941
// transaction. The fee rate of each sweeping transaction is determined by
942
// taking the average fee rate of all the outputs it's trying to sweep.
943
func (w *WalletKit) PendingSweeps(ctx context.Context,
944
        in *PendingSweepsRequest) (*PendingSweepsResponse, error) {
3✔
945

3✔
946
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
3✔
947
        // sweep.
3✔
948
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
3✔
949
        if err != nil {
3✔
950
                return nil, err
×
951
        }
×
952

953
        // Convert them into their respective RPC format.
954
        rpcPendingSweeps := make([]*PendingSweep, 0, len(inputsMap))
3✔
955
        for _, inp := range inputsMap {
6✔
956
                witnessType, ok := allWitnessTypes[inp.WitnessType]
3✔
957
                if !ok {
3✔
958
                        return nil, fmt.Errorf("unhandled witness type %v for "+
×
959
                                "input %v", inp.WitnessType, inp.OutPoint)
×
960
                }
×
961

962
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
3✔
963
                amountSat := uint32(inp.Amount)
3✔
964
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
3✔
965
                broadcastAttempts := uint32(inp.BroadcastAttempts)
3✔
966

3✔
967
                // Get the requested starting fee rate, if set.
3✔
968
                startingFeeRate := fn.MapOptionZ(
3✔
969
                        inp.Params.StartingFeeRate,
3✔
970
                        func(feeRate chainfee.SatPerKWeight) uint64 {
6✔
971
                                return uint64(feeRate.FeePerVByte())
3✔
972
                        })
3✔
973

974
                ps := &PendingSweep{
3✔
975
                        Outpoint:             op,
3✔
976
                        WitnessType:          witnessType,
3✔
977
                        AmountSat:            amountSat,
3✔
978
                        SatPerVbyte:          satPerVbyte,
3✔
979
                        BroadcastAttempts:    broadcastAttempts,
3✔
980
                        Immediate:            inp.Params.Immediate,
3✔
981
                        Budget:               uint64(inp.Params.Budget),
3✔
982
                        DeadlineHeight:       inp.DeadlineHeight,
3✔
983
                        RequestedSatPerVbyte: startingFeeRate,
3✔
984
                }
3✔
985
                rpcPendingSweeps = append(rpcPendingSweeps, ps)
3✔
986
        }
987

988
        return &PendingSweepsResponse{
3✔
989
                PendingSweeps: rpcPendingSweeps,
3✔
990
        }, nil
3✔
991
}
992

993
// UnmarshallOutPoint converts an outpoint from its lnrpc type to its canonical
994
// type.
995
func UnmarshallOutPoint(op *lnrpc.OutPoint) (*wire.OutPoint, error) {
3✔
996
        if op == nil {
3✔
997
                return nil, fmt.Errorf("empty outpoint provided")
×
998
        }
×
999

1000
        var hash chainhash.Hash
3✔
1001
        switch {
3✔
1002
        // Return an error if both txid fields are unpopulated.
1003
        case len(op.TxidBytes) == 0 && len(op.TxidStr) == 0:
×
1004
                return nil, fmt.Errorf("TxidBytes and TxidStr are both " +
×
1005
                        "unspecified")
×
1006

1007
        // The hash was provided as raw bytes.
1008
        case len(op.TxidBytes) != 0:
3✔
1009
                copy(hash[:], op.TxidBytes)
3✔
1010

1011
        // The hash was provided as a hex-encoded string.
1012
        case len(op.TxidStr) != 0:
×
1013
                h, err := chainhash.NewHashFromStr(op.TxidStr)
×
1014
                if err != nil {
×
1015
                        return nil, err
×
1016
                }
×
1017
                hash = *h
×
1018
        }
1019

1020
        return &wire.OutPoint{
3✔
1021
                Hash:  hash,
3✔
1022
                Index: op.OutputIndex,
3✔
1023
        }, nil
3✔
1024
}
1025

1026
// validateBumpFeeRequest makes sure the deprecated fields are not used when
1027
// the new fields are set.
1028
func validateBumpFeeRequest(in *BumpFeeRequest, estimator chainfee.Estimator) (
1029
        fn.Option[chainfee.SatPerKWeight], bool, error) {
3✔
1030

3✔
1031
        // Get the specified fee rate if set.
3✔
1032
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
3✔
1033

3✔
1034
        // We only allow using either the deprecated field or the new field.
3✔
1035
        switch {
3✔
1036
        case in.SatPerByte != 0 && in.SatPerVbyte != 0:
×
1037
                return satPerKwOpt, false, fmt.Errorf("either SatPerByte or " +
×
1038
                        "SatPerVbyte should be set, but not both")
×
1039

1040
        case in.SatPerByte != 0:
×
1041
                satPerKw := chainfee.SatPerVByte(
×
1042
                        in.SatPerByte,
×
1043
                ).FeePerKWeight()
×
1044
                satPerKwOpt = fn.Some(satPerKw)
×
1045

1046
        case in.SatPerVbyte != 0:
2✔
1047
                satPerKw := chainfee.SatPerVByte(
2✔
1048
                        in.SatPerVbyte,
2✔
1049
                ).FeePerKWeight()
2✔
1050
                satPerKwOpt = fn.Some(satPerKw)
2✔
1051
        }
1052

1053
        // We make sure either the conf target or the exact fee rate is
1054
        // specified for the starting fee of the fee function.
1055
        if in.TargetConf != 0 && !satPerKwOpt.IsNone() {
3✔
1056
                return satPerKwOpt, false,
×
1057
                        fmt.Errorf("either TargetConf or SatPerVbyte should " +
×
1058
                                "be set, to specify the starting fee rate of " +
×
1059
                                "the fee function")
×
1060
        }
×
1061

1062
        // In case the user specified a conf target, we estimate the fee rate
1063
        // for the given target using the provided estimator.
1064
        if in.TargetConf != 0 {
5✔
1065
                startingFeeRate, err := estimator.EstimateFeePerKW(
2✔
1066
                        in.TargetConf,
2✔
1067
                )
2✔
1068
                if err != nil {
2✔
1069
                        return satPerKwOpt, false, fmt.Errorf("unable to "+
×
1070
                                "estimate fee rate for target conf %d: %w",
×
1071
                                in.TargetConf, err)
×
1072
                }
×
1073

1074
                // Set the starting fee rate to the estimated fee rate.
1075
                satPerKwOpt = fn.Some(startingFeeRate)
2✔
1076
        }
1077

1078
        var immediate bool
3✔
1079
        switch {
3✔
1080
        case in.Force && in.Immediate:
×
1081
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
1082
                        "Immediate should be set, but not both")
×
1083

1084
        case in.Force:
×
1085
                immediate = in.Force
×
1086

1087
        case in.Immediate:
3✔
1088
                immediate = in.Immediate
3✔
1089
        }
1090

1091
        if in.DeadlineDelta != 0 && in.Budget == 0 {
3✔
1092
                return satPerKwOpt, immediate, fmt.Errorf("budget must be " +
×
1093
                        "set if deadline-delta is set")
×
1094
        }
×
1095

1096
        return satPerKwOpt, immediate, nil
3✔
1097
}
1098

1099
// prepareSweepParams creates the sweep params to be used for the sweeper. It
1100
// returns the new params and a bool indicating whether this is an existing
1101
// input.
1102
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1103
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
3✔
1104

3✔
1105
        // Return an error if the bump fee request is invalid.
3✔
1106
        feeRate, immediate, err := validateBumpFeeRequest(
3✔
1107
                in, w.cfg.FeeEstimator,
3✔
1108
        )
3✔
1109
        if err != nil {
3✔
1110
                return sweep.Params{}, false, err
×
1111
        }
×
1112

1113
        // Get the current pending inputs.
1114
        inputMap, err := w.cfg.Sweeper.PendingInputs()
3✔
1115
        if err != nil {
3✔
1116
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1117
                        "pending inputs: %w", err)
×
1118
        }
×
1119

1120
        // Find the pending input.
1121
        //
1122
        // TODO(yy): act differently based on the state of the input?
1123
        inp, ok := inputMap[op]
3✔
1124

3✔
1125
        if !ok {
5✔
1126
                // NOTE: if this input doesn't exist and the new budget is not
2✔
1127
                // specified, the params would have a zero budget.
2✔
1128
                params := sweep.Params{
2✔
1129
                        Immediate:       immediate,
2✔
1130
                        StartingFeeRate: feeRate,
2✔
1131
                        Budget:          btcutil.Amount(in.Budget),
2✔
1132
                }
2✔
1133

2✔
1134
                if in.DeadlineDelta != 0 {
2✔
1135
                        params.DeadlineHeight = fn.Some(
×
1136
                                int32(in.DeadlineDelta) + currentHeight,
×
1137
                        )
×
1138
                }
×
1139

1140
                return params, ok, nil
2✔
1141
        }
1142

1143
        // Find the existing budget used for this input. Note that this value
1144
        // must be greater than zero.
1145
        budget := inp.Params.Budget
3✔
1146

3✔
1147
        // Set the new budget if specified. If a new deadline delta is
3✔
1148
        // specified we also require the budget value which is checked in the
3✔
1149
        // validateBumpFeeRequest function.
3✔
1150
        if in.Budget != 0 {
6✔
1151
                budget = btcutil.Amount(in.Budget)
3✔
1152
        }
3✔
1153

1154
        // For an existing input, we assign it first, then overwrite it if
1155
        // a deadline is requested.
1156
        deadline := inp.Params.DeadlineHeight
3✔
1157

3✔
1158
        // Set the deadline if it was specified.
3✔
1159
        //
3✔
1160
        // TODO(yy): upgrade `falafel` so we can make this field optional. Atm
3✔
1161
        // we cannot distinguish between user's not setting the field and
3✔
1162
        // setting it to 0.
3✔
1163
        if in.DeadlineDelta != 0 {
6✔
1164
                deadline = fn.Some(int32(in.DeadlineDelta) + currentHeight)
3✔
1165
        }
3✔
1166

1167
        startingFeeRate := inp.Params.StartingFeeRate
3✔
1168

3✔
1169
        // We only set the starting fee rate if it was specified else we keep
3✔
1170
        // the existing one.
3✔
1171
        if feeRate.IsSome() {
5✔
1172
                startingFeeRate = feeRate
2✔
1173
        }
2✔
1174

1175
        // Prepare the new sweep params.
1176
        //
1177
        // NOTE: if this input doesn't exist and the new budget is not
1178
        // specified, the params would have a zero budget.
1179
        params := sweep.Params{
3✔
1180
                Immediate:       immediate,
3✔
1181
                DeadlineHeight:  deadline,
3✔
1182
                StartingFeeRate: startingFeeRate,
3✔
1183
                Budget:          budget,
3✔
1184
        }
3✔
1185

3✔
1186
        log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
3✔
1187
                "params=%v, new params=%v", op, inp.Params, params)
3✔
1188

3✔
1189
        return params, ok, nil
3✔
1190
}
1191

1192
// BumpFee allows bumping the fee rate of an arbitrary input. A fee preference
1193
// can be expressed either as a specific fee rate or a delta of blocks in which
1194
// the output should be swept on-chain within. If a fee preference is not
1195
// explicitly specified, then an error is returned. The status of the input
1196
// sweep can be checked through the PendingSweeps RPC.
1197
func (w *WalletKit) BumpFee(ctx context.Context,
1198
        in *BumpFeeRequest) (*BumpFeeResponse, error) {
3✔
1199

3✔
1200
        // Parse the outpoint from the request.
3✔
1201
        op, err := UnmarshallOutPoint(in.Outpoint)
3✔
1202
        if err != nil {
3✔
1203
                return nil, err
×
1204
        }
×
1205

1206
        // Get the current height so we can calculate the deadline height.
1207
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
3✔
1208
        if err != nil {
3✔
1209
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1210
                        err)
×
1211
        }
×
1212

1213
        // We now create a new sweeping params and update it in the sweeper.
1214
        // This will complicate the RBF conditions if this input has already
1215
        // been offered to sweeper before and it has already been included in a
1216
        // tx with other inputs. If this is the case, two results are possible:
1217
        // - either this input successfully RBFed the existing tx, or,
1218
        // - the budget of this input was not enough to RBF the existing tx.
1219
        params, existing, err := w.prepareSweepParams(in, *op, currentHeight)
3✔
1220
        if err != nil {
3✔
1221
                return nil, err
×
1222
        }
×
1223

1224
        // If this input exists, we will update its params.
1225
        if existing {
6✔
1226
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
3✔
1227
                if err != nil {
3✔
1228
                        return nil, err
×
1229
                }
×
1230

1231
                return &BumpFeeResponse{
3✔
1232
                        Status: "Successfully registered rbf-tx with sweeper",
3✔
1233
                }, nil
3✔
1234
        }
1235

1236
        // Otherwise, create a new sweeping request for this input.
1237
        err = w.sweepNewInput(op, uint32(currentHeight), params)
2✔
1238
        if err != nil {
2✔
1239
                return nil, err
×
1240
        }
×
1241

1242
        return &BumpFeeResponse{
2✔
1243
                Status: "Successfully registered CPFP-tx with the sweeper",
2✔
1244
        }, nil
2✔
1245
}
1246

1247
// getWaitingCloseChannel returns the waiting close channel in case it does
1248
// exist in the underlying channel state database.
1249
func (w *WalletKit) getWaitingCloseChannel(
1250
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
2✔
1251

2✔
1252
        // Fetch all channels, which still have their commitment transaction not
2✔
1253
        // confirmed (waiting close channels).
2✔
1254
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
2✔
1255
        if err != nil {
2✔
1256
                return nil, err
×
1257
        }
×
1258

1259
        channel := fn.Find(chans, func(c *channeldb.OpenChannel) bool {
4✔
1260
                return c.FundingOutpoint == chanPoint
2✔
1261
        })
2✔
1262

1263
        return channel.UnwrapOrErr(errors.New("channel not found"))
2✔
1264
}
1265

1266
// BumpForceCloseFee bumps the fee rate of an unconfirmed anchor channel. It
1267
// updates the new fee rate parameters with the sweeper subsystem. Additionally
1268
// it will try to create anchor cpfp transactions for all possible commitment
1269
// transactions (local, remote, remote-dangling) so depending on which
1270
// commitment is in the local mempool only one of them will succeed in being
1271
// broadcasted.
1272
func (w *WalletKit) BumpForceCloseFee(_ context.Context,
1273
        in *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
2✔
1274

2✔
1275
        if in.ChanPoint == nil {
2✔
1276
                return nil, fmt.Errorf("no chan_point provided")
×
1277
        }
×
1278

1279
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
2✔
1280
        if err != nil {
2✔
1281
                return nil, err
×
1282
        }
×
1283

1284
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
2✔
1285
        if err != nil {
2✔
1286
                return nil, err
×
1287
        }
×
1288

1289
        // Get the relevant channel if it is in the waiting close state.
1290
        channel, err := w.getWaitingCloseChannel(*outPoint)
2✔
1291
        if err != nil {
2✔
1292
                return nil, err
×
1293
        }
×
1294

1295
        if !channel.ChanType.HasAnchors() {
2✔
1296
                return nil, fmt.Errorf("not able to bump the fee of a " +
×
1297
                        "non-anchor channel")
×
1298
        }
×
1299

1300
        // Match pending sweeps with commitments of the channel for which a bump
1301
        // is requested. Depending on the commitment state when force closing
1302
        // the channel we might have up to 3 commitments to consider when
1303
        // bumping the fee.
1304
        commitSet := fn.NewSet[chainhash.Hash]()
2✔
1305

2✔
1306
        if channel.LocalCommitment.CommitTx != nil {
4✔
1307
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
2✔
1308
                commitSet.Add(localTxID)
2✔
1309
        }
2✔
1310

1311
        if channel.RemoteCommitment.CommitTx != nil {
4✔
1312
                remoteTxID := channel.RemoteCommitment.CommitTx.TxHash()
2✔
1313
                commitSet.Add(remoteTxID)
2✔
1314
        }
2✔
1315

1316
        // Check whether there was a dangling commitment at the time the channel
1317
        // was force closed.
1318
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
2✔
1319
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
2✔
1320
                return nil, err
×
1321
        }
×
1322

1323
        if remoteCommitDiff != nil {
2✔
1324
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1325
                commitSet.Add(hash)
×
1326
        }
×
1327

1328
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1329
        // sweep.
1330
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
2✔
1331
        if err != nil {
2✔
1332
                return nil, err
×
1333
        }
×
1334

1335
        // Get the current height so we can calculate the deadline height.
1336
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1337
        if err != nil {
2✔
1338
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1339
                        err)
×
1340
        }
×
1341

1342
        pendingSweeps := maps.Values(inputsMap)
2✔
1343

2✔
1344
        // Discard everything except for the anchor sweeps.
2✔
1345
        anchors := fn.Filter(
2✔
1346
                pendingSweeps,
2✔
1347
                func(sweep *sweep.PendingInputResponse) bool {
4✔
1348
                        // Only filter for anchor inputs because these are the
2✔
1349
                        // only inputs which can be used to bump a closed
2✔
1350
                        // unconfirmed commitment transaction.
2✔
1351
                        isCommitAnchor := sweep.WitnessType ==
2✔
1352
                                input.CommitmentAnchor
2✔
1353
                        isTaprootSweepSpend := sweep.WitnessType ==
2✔
1354
                                input.TaprootAnchorSweepSpend
2✔
1355
                        if !isCommitAnchor && !isTaprootSweepSpend {
2✔
1356
                                return false
×
1357
                        }
×
1358

1359
                        return commitSet.Contains(sweep.OutPoint.Hash)
2✔
1360
                },
1361
        )
1362

1363
        if len(anchors) == 0 {
2✔
1364
                return nil, fmt.Errorf("unable to find pending anchor outputs")
×
1365
        }
×
1366

1367
        // Filter all relevant anchor sweeps and update the sweep request.
1368
        for _, anchor := range anchors {
4✔
1369
                // Anchor cpfp bump request are predictable because they are
2✔
1370
                // swept separately hence not batched with other sweeps (they
2✔
1371
                // are marked with the exclusive group flag). Bumping the fee
2✔
1372
                // rate does not create any conflicting fee bump conditions.
2✔
1373
                // Either the rbf requirements are met or the bump is rejected
2✔
1374
                // by the mempool rules.
2✔
1375
                params, existing, err := w.prepareSweepParams(
2✔
1376
                        &BumpFeeRequest{
2✔
1377
                                Outpoint:      lnrpcOutpoint,
2✔
1378
                                TargetConf:    in.TargetConf,
2✔
1379
                                SatPerVbyte:   in.StartingFeerate,
2✔
1380
                                Immediate:     in.Immediate,
2✔
1381
                                Budget:        in.Budget,
2✔
1382
                                DeadlineDelta: in.DeadlineDelta,
2✔
1383
                        }, anchor.OutPoint, currentHeight,
2✔
1384
                )
2✔
1385
                if err != nil {
2✔
1386
                        return nil, err
×
1387
                }
×
1388

1389
                // There might be the case when an anchor sweep is confirmed
1390
                // between fetching the pending sweeps and preparing the sweep
1391
                // params. We log this case and proceed.
1392
                if !existing {
2✔
1393
                        log.Errorf("Sweep anchor input(%v) not known to the " +
×
1394
                                "sweeper subsystem")
×
1395
                        continue
×
1396
                }
1397

1398
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
2✔
1399
                if err != nil {
2✔
1400
                        return nil, err
×
1401
                }
×
1402
        }
1403

1404
        return &BumpForceCloseFeeResponse{
2✔
1405
                Status: "Successfully registered anchor-cpfp transaction to" +
2✔
1406
                        "bump channel force close transaction",
2✔
1407
        }, nil
2✔
1408
}
1409

1410
// sweepNewInput handles the case where an input is seen the first time by the
1411
// sweeper. It will fetch the output from the wallet and construct an input and
1412
// offer it to the sweeper.
1413
//
1414
// NOTE: if the budget is not set, the default budget ratio is used.
1415
func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
1416
        params sweep.Params) error {
2✔
1417

2✔
1418
        log.Debugf("Attempting to sweep outpoint %s", op)
2✔
1419

2✔
1420
        // Since the sweeper is not aware of the input, we'll assume the user
2✔
1421
        // is attempting to bump an unconfirmed transaction's fee rate by
2✔
1422
        // sweeping an output within it under control of the wallet with a
2✔
1423
        // higher fee rate. In this case, this will be a CPFP.
2✔
1424
        //
2✔
1425
        // We'll gather all of the information required by the UtxoSweeper in
2✔
1426
        // order to sweep the output.
2✔
1427
        utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
2✔
1428
        if err != nil {
2✔
1429
                return err
×
1430
        }
×
1431

1432
        // We're only able to bump the fee of unconfirmed transactions.
1433
        if utxo.Confirmations > 0 {
2✔
1434
                return errors.New("unable to bump fee of a confirmed " +
×
1435
                        "transaction")
×
1436
        }
×
1437

1438
        // TODO(ziggie): The budget value should ideally only be set for CPFP
1439
        // requests because for RBF requests we should have already registered
1440
        // the input including the budget value in the first place. However it
1441
        // might not be set and then depending on the deadline delta fee
1442
        // estimations might become too aggressive. So need to evaluate whether
1443
        // we set a default value here, make it configurable or fail request
1444
        // in that case.
1445
        if params.Budget == 0 {
4✔
1446
                params.Budget = utxo.Value.MulF64(
2✔
1447
                        contractcourt.DefaultBudgetRatio,
2✔
1448
                )
2✔
1449

2✔
1450
                log.Warnf("[BumpFee]: setting default budget value of %v for "+
2✔
1451
                        "input=%v, which will be used for the maximum fee "+
2✔
1452
                        "rate estimation (budget was not specified)",
2✔
1453
                        params.Budget, op)
2✔
1454
        }
2✔
1455

1456
        signDesc := &input.SignDescriptor{
2✔
1457
                Output: &wire.TxOut{
2✔
1458
                        PkScript: utxo.PkScript,
2✔
1459
                        Value:    int64(utxo.Value),
2✔
1460
                },
2✔
1461
                HashType: txscript.SigHashAll,
2✔
1462
        }
2✔
1463

2✔
1464
        var witnessType input.WitnessType
2✔
1465
        switch utxo.AddressType {
2✔
1466
        case lnwallet.WitnessPubKey:
×
1467
                witnessType = input.WitnessKeyHash
×
1468
        case lnwallet.NestedWitnessPubKey:
×
1469
                witnessType = input.NestedWitnessKeyHash
×
1470
        case lnwallet.TaprootPubkey:
2✔
1471
                witnessType = input.TaprootPubKeySpend
2✔
1472
                signDesc.HashType = txscript.SigHashDefault
2✔
1473
        default:
×
1474
                return fmt.Errorf("unknown input witness %v", op)
×
1475
        }
1476

1477
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
2✔
1478
                params)
2✔
1479

2✔
1480
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
2✔
1481
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
2✔
1482
                return err
×
1483
        }
×
1484

1485
        return nil
2✔
1486
}
1487

1488
// ListSweeps returns a list of the sweeps that our node has published.
1489
func (w *WalletKit) ListSweeps(ctx context.Context,
1490
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
3✔
1491

3✔
1492
        sweeps, err := w.cfg.Sweeper.ListSweeps()
3✔
1493
        if err != nil {
3✔
1494
                return nil, err
×
1495
        }
×
1496

1497
        sweepTxns := make(map[string]bool)
3✔
1498
        for _, sweep := range sweeps {
6✔
1499
                sweepTxns[sweep.String()] = true
3✔
1500
        }
3✔
1501

1502
        // Some of our sweeps could have been replaced by fee, or dropped out
1503
        // of the mempool. Here, we lookup our wallet transactions so that we
1504
        // can match our list of sweeps against the list of transactions that
1505
        // the wallet is still tracking. Sweeps are currently always swept to
1506
        // the default wallet account.
1507
        txns, firstIdx, lastIdx, err := w.cfg.Wallet.ListTransactionDetails(
3✔
1508
                in.StartHeight, btcwallet.UnconfirmedHeight,
3✔
1509
                lnwallet.DefaultAccountName, 0, 0,
3✔
1510
        )
3✔
1511
        if err != nil {
3✔
1512
                return nil, err
×
1513
        }
×
1514

1515
        var (
3✔
1516
                txids     []string
3✔
1517
                txDetails []*lnwallet.TransactionDetail
3✔
1518
        )
3✔
1519

3✔
1520
        for _, tx := range txns {
6✔
1521
                _, ok := sweepTxns[tx.Hash.String()]
3✔
1522
                if !ok {
6✔
1523
                        continue
3✔
1524
                }
1525

1526
                // Add the txid or full tx details depending on whether we want
1527
                // verbose output or not.
1528
                if in.Verbose {
6✔
1529
                        txDetails = append(txDetails, tx)
3✔
1530
                } else {
6✔
1531
                        txids = append(txids, tx.Hash.String())
3✔
1532
                }
3✔
1533
        }
1534

1535
        if in.Verbose {
6✔
1536
                return &ListSweepsResponse{
3✔
1537
                        Sweeps: &ListSweepsResponse_TransactionDetails{
3✔
1538
                                TransactionDetails: lnrpc.RPCTransactionDetails(
3✔
1539
                                        txDetails, firstIdx, lastIdx,
3✔
1540
                                ),
3✔
1541
                        },
3✔
1542
                }, nil
3✔
1543
        }
3✔
1544

1545
        return &ListSweepsResponse{
3✔
1546
                Sweeps: &ListSweepsResponse_TransactionIds{
3✔
1547
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
3✔
1548
                                TransactionIds: txids,
3✔
1549
                        },
3✔
1550
                },
3✔
1551
        }, nil
3✔
1552
}
1553

1554
// LabelTransaction adds a label to a transaction.
1555
func (w *WalletKit) LabelTransaction(ctx context.Context,
1556
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
3✔
1557

3✔
1558
        // Check that the label provided in non-zero.
3✔
1559
        if len(req.Label) == 0 {
6✔
1560
                return nil, ErrZeroLabel
3✔
1561
        }
3✔
1562

1563
        // Validate the length of the non-zero label. We do not need to use the
1564
        // label returned here, because the original is non-zero so will not
1565
        // be replaced.
1566
        if _, err := labels.ValidateAPI(req.Label); err != nil {
3✔
1567
                return nil, err
×
1568
        }
×
1569

1570
        hash, err := chainhash.NewHash(req.Txid)
3✔
1571
        if err != nil {
3✔
1572
                return nil, err
×
1573
        }
×
1574

1575
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
3✔
1576

3✔
1577
        return &LabelTransactionResponse{
3✔
1578
                Status: fmt.Sprintf("transaction label '%s' added", req.Label),
3✔
1579
        }, err
3✔
1580
}
1581

1582
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
1583
// the outputs specified in the template. There are three ways a user can
1584
// specify what we call the template (a list of inputs and outputs to use in the
1585
// PSBT): Either as a PSBT packet directly with no coin selection (using the
1586
// legacy "psbt" field), a PSBT with advanced coin selection support (using the
1587
// new "coin_select" field) or as a raw RPC message (using the "raw" field).
1588
// The legacy "psbt" and "raw" modes, the following restrictions apply:
1589
//  1. If there are no inputs specified in the template, coin selection is
1590
//     performed automatically.
1591
//  2. If the template does contain any inputs, it is assumed that full coin
1592
//     selection happened externally and no additional inputs are added. If the
1593
//     specified inputs aren't enough to fund the outputs with the given fee
1594
//     rate, an error is returned.
1595
//
1596
// The new "coin_select" mode does not have these restrictions and allows the
1597
// user to specify a PSBT with inputs and outputs and still perform coin
1598
// selection on top of that.
1599
// For all modes this RPC requires any inputs that are specified to be locked by
1600
// the user (if they belong to this node in the first place).
1601
// After either selecting or verifying the inputs, all input UTXOs are locked
1602
// with an internal app ID. A custom address type for change can be specified
1603
// for default accounts and single imported public keys (only P2TR for now).
1604
// Otherwise, P2WPKH will be used by default. No custom address type should be
1605
// provided for custom accounts as we will always generate the change address
1606
// using the coin selection key scope.
1607
//
1608
// NOTE: If this method returns without an error, it is the caller's
1609
// responsibility to either spend the locked UTXOs (by finalizing and then
1610
// publishing the transaction) or to unlock/release the locked UTXOs in case of
1611
// an error on the caller's side.
1612
func (w *WalletKit) FundPsbt(_ context.Context,
1613
        req *FundPsbtRequest) (*FundPsbtResponse, error) {
3✔
1614

3✔
1615
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
3✔
1616
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
3✔
1617
        )
3✔
1618
        if err != nil {
3✔
1619
                return nil, err
×
1620
        }
×
1621

1622
        // Determine the desired transaction fee.
1623
        var feeSatPerKW chainfee.SatPerKWeight
3✔
1624
        switch {
3✔
1625
        // Estimate the fee by the target number of blocks to confirmation.
1626
        case req.GetTargetConf() != 0:
×
1627
                targetConf := req.GetTargetConf()
×
1628
                if targetConf < 2 {
×
1629
                        return nil, fmt.Errorf("confirmation target must be " +
×
1630
                                "greater than 1")
×
1631
                }
×
1632

1633
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
×
1634
                        targetConf,
×
1635
                )
×
1636
                if err != nil {
×
1637
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1638
                                err)
×
1639
                }
×
1640

1641
        // Convert the fee to sat/kW from the specified sat/vByte.
1642
        case req.GetSatPerVbyte() != 0:
3✔
1643
                feeSatPerKW = chainfee.SatPerKVByte(
3✔
1644
                        req.GetSatPerVbyte() * 1000,
3✔
1645
                ).FeePerKWeight()
3✔
1646

1647
        case req.GetSatPerKw() != 0:
×
1648
                feeSatPerKW = chainfee.SatPerKWeight(req.GetSatPerKw())
×
1649

1650
        default:
×
1651
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1652
                        "specify either target_conf, sat_per_vbyte or " +
×
1653
                        "sat_per_kw")
×
1654
        }
1655

1656
        // Then, we'll extract the minimum number of confirmations that each
1657
        // output we use to fund the transaction should satisfy.
1658
        minConfs, err := lnrpc.ExtractMinConfs(
3✔
1659
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
3✔
1660
        )
3✔
1661
        if err != nil {
3✔
1662
                return nil, err
×
1663
        }
×
1664

1665
        // We'll assume the PSBT will be funded by the default account unless
1666
        // otherwise specified.
1667
        account := lnwallet.DefaultAccountName
3✔
1668
        if req.Account != "" {
6✔
1669
                account = req.Account
3✔
1670
        }
3✔
1671

1672
        // There are three ways a user can specify what we call the template (a
1673
        // list of inputs and outputs to use in the PSBT): Either as a PSBT
1674
        // packet directly with no coin selection, a PSBT with coin selection or
1675
        // as a special RPC message. Find out which one the user wants to use,
1676
        // they are mutually exclusive.
1677
        switch {
3✔
1678
        // The template is specified as a PSBT. All we have to do is parse it.
1679
        case req.GetPsbt() != nil:
3✔
1680
                r := bytes.NewReader(req.GetPsbt())
3✔
1681
                packet, err := psbt.NewFromRawBytes(r, false)
3✔
1682
                if err != nil {
3✔
1683
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1684
                }
×
1685

1686
                // Run the actual funding process now, using the internal
1687
                // wallet.
1688
                return w.fundPsbtInternalWallet(
3✔
1689
                        account, keyScopeFromChangeAddressType(req.ChangeType),
3✔
1690
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
3✔
1691
                )
3✔
1692

1693
        // The template is specified as a PSBT with the intention to perform
1694
        // coin selection even if inputs are already present.
1695
        case req.GetCoinSelect() != nil:
3✔
1696
                coinSelectRequest := req.GetCoinSelect()
3✔
1697
                r := bytes.NewReader(coinSelectRequest.Psbt)
3✔
1698
                packet, err := psbt.NewFromRawBytes(r, false)
3✔
1699
                if err != nil {
3✔
1700
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1701
                }
×
1702

1703
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
3✔
1704
                if numOutputs == 0 {
3✔
1705
                        return nil, fmt.Errorf("no outputs specified in " +
×
1706
                                "template")
×
1707
                }
×
1708

1709
                outputSum := int64(0)
3✔
1710
                for _, txOut := range packet.UnsignedTx.TxOut {
6✔
1711
                        outputSum += txOut.Value
3✔
1712
                }
3✔
1713
                if outputSum <= 0 {
3✔
1714
                        return nil, fmt.Errorf("output sum must be positive")
×
1715
                }
×
1716

1717
                var (
3✔
1718
                        changeIndex int32 = -1
3✔
1719
                        changeType  chanfunding.ChangeAddressType
3✔
1720
                )
3✔
1721
                switch t := coinSelectRequest.ChangeOutput.(type) {
3✔
1722
                // The user wants to use an existing output as change output.
1723
                case *PsbtCoinSelect_ExistingOutputIndex:
3✔
1724
                        if t.ExistingOutputIndex < 0 ||
3✔
1725
                                t.ExistingOutputIndex >= numOutputs {
3✔
1726

×
1727
                                return nil, fmt.Errorf("change output index "+
×
1728
                                        "out of range: %d",
×
1729
                                        t.ExistingOutputIndex)
×
1730
                        }
×
1731

1732
                        changeIndex = t.ExistingOutputIndex
3✔
1733

3✔
1734
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
3✔
1735
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
3✔
1736
                        if err != nil {
3✔
1737
                                return nil, fmt.Errorf("error parsing change "+
×
1738
                                        "script: %w", err)
×
1739
                        }
×
1740

1741
                        changeType = chanfunding.ExistingChangeAddress
3✔
1742

1743
                // The user wants to use a new output as change output.
1744
                case *PsbtCoinSelect_Add:
×
1745
                        // We already set the change index to -1 above to
×
1746
                        // indicate no change output should be used if possible
×
1747
                        // or a new one should be created if needed. So we only
×
1748
                        // need to parse the type of change output we want to
×
1749
                        // create.
×
1750
                        switch req.ChangeType {
×
1751
                        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
1752
                                changeType = chanfunding.P2TRChangeAddress
×
1753

1754
                        default:
×
1755
                                changeType = chanfunding.P2WKHChangeAddress
×
1756
                        }
1757

1758
                default:
×
1759
                        return nil, fmt.Errorf("unknown change output type")
×
1760
                }
1761

1762
                maxFeeRatio := chanfunding.DefaultMaxFeeRatio
3✔
1763

3✔
1764
                if req.MaxFeeRatio != 0 {
3✔
1765
                        maxFeeRatio = req.MaxFeeRatio
×
1766
                }
×
1767

1768
                // Run the actual funding process now, using the channel funding
1769
                // coin selection algorithm.
1770
                return w.fundPsbtCoinSelect(
3✔
1771
                        account, changeIndex, packet, minConfs, changeType,
3✔
1772
                        feeSatPerKW, coinSelectionStrategy, maxFeeRatio,
3✔
1773
                )
3✔
1774

1775
        // The template is specified as a RPC message. We need to create a new
1776
        // PSBT and copy the RPC information over.
1777
        case req.GetRaw() != nil:
3✔
1778
                tpl := req.GetRaw()
3✔
1779

3✔
1780
                txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
3✔
1781
                for addrStr, amt := range tpl.Outputs {
6✔
1782
                        addr, err := btcutil.DecodeAddress(
3✔
1783
                                addrStr, w.cfg.ChainParams,
3✔
1784
                        )
3✔
1785
                        if err != nil {
3✔
1786
                                return nil, fmt.Errorf("error parsing address "+
×
1787
                                        "%s for network %s: %v", addrStr,
×
1788
                                        w.cfg.ChainParams.Name, err)
×
1789
                        }
×
1790

1791
                        if !addr.IsForNet(w.cfg.ChainParams) {
3✔
1792
                                return nil, fmt.Errorf("address is not for %s",
×
1793
                                        w.cfg.ChainParams.Name)
×
1794
                        }
×
1795

1796
                        pkScript, err := txscript.PayToAddrScript(addr)
3✔
1797
                        if err != nil {
3✔
1798
                                return nil, fmt.Errorf("error getting pk "+
×
1799
                                        "script for address %s: %w", addrStr,
×
1800
                                        err)
×
1801
                        }
×
1802

1803
                        txOut = append(txOut, &wire.TxOut{
3✔
1804
                                Value:    int64(amt),
3✔
1805
                                PkScript: pkScript,
3✔
1806
                        })
3✔
1807
                }
1808

1809
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
3✔
1810
                for idx, in := range tpl.Inputs {
3✔
1811
                        op, err := UnmarshallOutPoint(in)
×
1812
                        if err != nil {
×
1813
                                return nil, fmt.Errorf("error parsing "+
×
1814
                                        "outpoint: %w", err)
×
1815
                        }
×
1816
                        txIn[idx] = op
×
1817
                }
1818

1819
                sequences := make([]uint32, len(txIn))
3✔
1820
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
3✔
1821
                if err != nil {
3✔
1822
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1823
                }
×
1824

1825
                // Run the actual funding process now, using the internal
1826
                // wallet.
1827
                return w.fundPsbtInternalWallet(
3✔
1828
                        account, keyScopeFromChangeAddressType(req.ChangeType),
3✔
1829
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
3✔
1830
                )
3✔
1831

1832
        default:
×
1833
                return nil, fmt.Errorf("transaction template missing, need " +
×
1834
                        "to specify either PSBT or raw TX template")
×
1835
        }
1836
}
1837

1838
// fundPsbtInternalWallet uses the "old" PSBT funding method of the internal
1839
// wallet that does not allow specifying custom inputs while selecting coins.
1840
func (w *WalletKit) fundPsbtInternalWallet(account string,
1841
        keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
1842
        feeSatPerKW chainfee.SatPerKWeight,
1843
        strategy base.CoinSelectionStrategy) (*FundPsbtResponse, error) {
3✔
1844

3✔
1845
        // The RPC parsing part is now over. Several of the following operations
3✔
1846
        // require us to hold the global coin selection lock, so we do the rest
3✔
1847
        // of the tasks while holding the lock. The result is a list of locked
3✔
1848
        // UTXOs.
3✔
1849
        var response *FundPsbtResponse
3✔
1850
        err := w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
6✔
1851
                // In case the user did specify inputs, we need to make sure
3✔
1852
                // they are known to us, still unspent and not yet locked.
3✔
1853
                if len(packet.UnsignedTx.TxIn) > 0 {
6✔
1854
                        // Get a list of all unspent witness outputs.
3✔
1855
                        utxos, err := w.cfg.Wallet.ListUnspentWitness(
3✔
1856
                                minConfs, defaultMaxConf, account,
3✔
1857
                        )
3✔
1858
                        if err != nil {
3✔
1859
                                return err
×
1860
                        }
×
1861

1862
                        // filterFn makes sure utxos which are unconfirmed and
1863
                        // still used by the sweeper are not used.
1864
                        filterFn := func(u *lnwallet.Utxo) bool {
6✔
1865
                                // Confirmed utxos are always allowed.
3✔
1866
                                if u.Confirmations > 0 {
6✔
1867
                                        return true
3✔
1868
                                }
3✔
1869

1870
                                // Unconfirmed utxos in use by the sweeper are
1871
                                // not stable to use because they can be
1872
                                // replaced.
1873
                                if w.cfg.Sweeper.IsSweeperOutpoint(u.OutPoint) {
6✔
1874
                                        log.Warnf("Cannot use unconfirmed "+
3✔
1875
                                                "utxo=%v because it is "+
3✔
1876
                                                "unstable and could be "+
3✔
1877
                                                "replaced", u.OutPoint)
3✔
1878

3✔
1879
                                        return false
3✔
1880
                                }
3✔
1881

1882
                                return true
×
1883
                        }
1884

1885
                        eligibleUtxos := fn.Filter(utxos, filterFn)
3✔
1886

3✔
1887
                        // Validate all inputs against our known list of UTXOs
3✔
1888
                        // now.
3✔
1889
                        err = verifyInputsUnspent(
3✔
1890
                                packet.UnsignedTx.TxIn, eligibleUtxos,
3✔
1891
                        )
3✔
1892
                        if err != nil {
6✔
1893
                                return err
3✔
1894
                        }
3✔
1895
                }
1896

1897
                // currentHeight is needed to determine whether the internal
1898
                // wallet utxo is still unconfirmed.
1899
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
3✔
1900
                if err != nil {
3✔
1901
                        return fmt.Errorf("unable to retrieve current "+
×
1902
                                "height: %v", err)
×
1903
                }
×
1904

1905
                // restrictUnstableUtxos is a filter function which disallows
1906
                // the usage of unconfirmed outputs published (still in use) by
1907
                // the sweeper.
1908
                restrictUnstableUtxos := func(utxo wtxmgr.Credit) bool {
6✔
1909
                        // Wallet utxos which are unmined have a height
3✔
1910
                        // of -1.
3✔
1911
                        if utxo.Height != -1 && utxo.Height <= currentHeight {
6✔
1912
                                // Confirmed utxos are always allowed.
3✔
1913
                                return true
3✔
1914
                        }
3✔
1915

1916
                        // Utxos used by the sweeper are not used for
1917
                        // channel openings.
1918
                        allowed := !w.cfg.Sweeper.IsSweeperOutpoint(
3✔
1919
                                utxo.OutPoint,
3✔
1920
                        )
3✔
1921
                        if !allowed {
6✔
1922
                                log.Warnf("Cannot use unconfirmed "+
3✔
1923
                                        "utxo=%v because it is "+
3✔
1924
                                        "unstable and could be "+
3✔
1925
                                        "replaced", utxo.OutPoint)
3✔
1926
                        }
3✔
1927

1928
                        return allowed
3✔
1929
                }
1930

1931
                // We made sure the input from the user is as sane as possible.
1932
                // We can now ask the wallet to fund the TX. This will not yet
1933
                // lock any coins but might still change the wallet DB by
1934
                // generating a new change address.
1935
                changeIndex, err := w.cfg.Wallet.FundPsbt(
3✔
1936
                        packet, minConfs, feeSatPerKW, account, keyScope,
3✔
1937
                        strategy, restrictUnstableUtxos,
3✔
1938
                )
3✔
1939
                if err != nil {
6✔
1940
                        return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
3✔
1941
                }
3✔
1942

1943
                // Now we have obtained a set of coins that can be used to fund
1944
                // the TX. Let's lock them to be sure they aren't spent by the
1945
                // time the PSBT is published. This is the action we do here
1946
                // that could cause an error. Therefore, if some of the UTXOs
1947
                // cannot be locked, the rollback of the other's locks also
1948
                // happens in this function. If we ever need to do more after
1949
                // this function, we need to extract the rollback needs to be
1950
                // extracted into a defer.
1951
                outpoints := make([]wire.OutPoint, len(packet.UnsignedTx.TxIn))
3✔
1952
                for i, txIn := range packet.UnsignedTx.TxIn {
6✔
1953
                        outpoints[i] = txIn.PreviousOutPoint
3✔
1954
                }
3✔
1955

1956
                response, err = w.lockAndCreateFundingResponse(
3✔
1957
                        packet, outpoints, changeIndex,
3✔
1958
                )
3✔
1959

3✔
1960
                return err
3✔
1961
        })
1962
        if err != nil {
6✔
1963
                return nil, err
3✔
1964
        }
3✔
1965

1966
        return response, nil
3✔
1967
}
1968

1969
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
1970
// funding coin selection algorithm that allows specifying custom inputs while
1971
// selecting coins.
1972
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
1973
        packet *psbt.Packet, minConfs int32,
1974
        changeType chanfunding.ChangeAddressType,
1975
        feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy,
1976
        maxFeeRatio float64) (*FundPsbtResponse, error) {
3✔
1977

3✔
1978
        // We want to make sure we don't select any inputs that are already
3✔
1979
        // specified in the template. To do that, we require those inputs to
3✔
1980
        // either not belong to this lnd at all or to be already locked through
3✔
1981
        // a manual lock call by the user. Either way, they should not appear in
3✔
1982
        // the list of unspent outputs.
3✔
1983
        err := w.assertNotAvailable(packet.UnsignedTx.TxIn, minConfs, account)
3✔
1984
        if err != nil {
3✔
1985
                return nil, err
×
1986
        }
×
1987

1988
        // In case the user just specified the input outpoints of UTXOs we own,
1989
        // the fee estimation below will error out because the UTXO information
1990
        // is missing. We need to fetch the UTXO information from the wallet
1991
        // and add it to the PSBT. We ignore inputs we don't actually know as
1992
        // they could belong to another wallet.
1993
        err = w.cfg.Wallet.DecorateInputs(packet, false)
3✔
1994
        if err != nil {
3✔
1995
                return nil, fmt.Errorf("error decorating inputs: %w", err)
×
1996
        }
×
1997

1998
        // Before we select anything, we need to calculate the input, output and
1999
        // current weight amounts. While doing that we also ensure the PSBT has
2000
        // all the required information we require at this step.
2001
        var (
3✔
2002
                inputSum, outputSum btcutil.Amount
3✔
2003
                estimator           input.TxWeightEstimator
3✔
2004
        )
3✔
2005
        for i := range packet.Inputs {
6✔
2006
                in := packet.Inputs[i]
3✔
2007

3✔
2008
                err := btcwallet.EstimateInputWeight(&in, &estimator)
3✔
2009
                if err != nil {
3✔
2010
                        return nil, fmt.Errorf("error estimating input "+
×
2011
                                "weight: %w", err)
×
2012
                }
×
2013

2014
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
3✔
2015
        }
2016
        for i := range packet.UnsignedTx.TxOut {
6✔
2017
                out := packet.UnsignedTx.TxOut[i]
3✔
2018

3✔
2019
                estimator.AddOutput(out.PkScript)
3✔
2020
                outputSum += btcutil.Amount(out.Value)
3✔
2021
        }
3✔
2022

2023
        // The amount we want to fund is the total output sum plus the current
2024
        // fee estimate, minus the sum of any already specified inputs. Since we
2025
        // pass the estimator of the current transaction into the coin selection
2026
        // algorithm, we don't need to subtract the fees here.
2027
        fundingAmount := outputSum - inputSum
3✔
2028

3✔
2029
        var changeDustLimit btcutil.Amount
3✔
2030
        switch changeType {
3✔
2031
        case chanfunding.P2TRChangeAddress:
×
2032
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
×
2033

2034
        case chanfunding.P2WKHChangeAddress:
×
2035
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
×
2036

2037
        case chanfunding.ExistingChangeAddress:
3✔
2038
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
3✔
2039
                changeDustLimit = lnwallet.DustLimitForSize(
3✔
2040
                        len(changeOut.PkScript),
3✔
2041
                )
3✔
2042
        }
2043

2044
        // Do we already have enough inputs specified to pay for the TX as it
2045
        // is? In that case we only need to allocate any change, if there is
2046
        // any.
2047
        packetFeeNoChange := feeRate.FeeForWeight(estimator.Weight())
3✔
2048
        if inputSum >= outputSum+packetFeeNoChange {
3✔
2049
                // Calculate the packet's fee with a change output so, so we can
×
2050
                // let the coin selection algorithm decide whether to use a
×
2051
                // change output or not.
×
2052
                switch changeType {
×
2053
                case chanfunding.P2TRChangeAddress:
×
2054
                        estimator.AddP2TROutput()
×
2055

2056
                case chanfunding.P2WKHChangeAddress:
×
2057
                        estimator.AddP2WKHOutput()
×
2058
                }
2059
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
×
2060

×
2061
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
×
2062
                        inputSum, outputSum, packetFeeNoChange,
×
2063
                        packetFeeWithChange, changeDustLimit, changeType,
×
2064
                        maxFeeRatio,
×
2065
                )
×
2066
                if err != nil {
×
2067
                        return nil, fmt.Errorf("error calculating change "+
×
2068
                                "amount: %w", err)
×
2069
                }
×
2070

2071
                // We shouldn't get into this branch if the input sum isn't
2072
                // enough to pay for the current package without a change
2073
                // output. So this should never be non-zero.
2074
                if needMore != 0 {
×
2075
                        return nil, fmt.Errorf("internal error with change " +
×
2076
                                "amount calculation")
×
2077
                }
×
2078

2079
                if changeAmt > 0 {
×
2080
                        changeIndex, err = w.handleChange(
×
2081
                                packet, changeIndex, int64(changeAmt),
×
2082
                                changeType, account,
×
2083
                        )
×
2084
                        if err != nil {
×
2085
                                return nil, fmt.Errorf("error handling change "+
×
2086
                                        "amount: %w", err)
×
2087
                        }
×
2088
                }
2089

2090
                // We're done. Let's serialize and return the updated package.
2091
                return w.lockAndCreateFundingResponse(packet, nil, changeIndex)
×
2092
        }
2093

2094
        // The RPC parsing part is now over. Several of the following operations
2095
        // require us to hold the global coin selection lock, so we do the rest
2096
        // of the tasks while holding the lock. The result is a list of locked
2097
        // UTXOs.
2098
        var response *FundPsbtResponse
3✔
2099
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
6✔
2100
                // Get a list of all unspent witness outputs.
3✔
2101
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
3✔
2102
                        minConfs, defaultMaxConf, account,
3✔
2103
                )
3✔
2104
                if err != nil {
3✔
2105
                        return err
×
2106
                }
×
2107

2108
                coins := make([]base.Coin, len(utxos))
3✔
2109
                for i, utxo := range utxos {
6✔
2110
                        coins[i] = base.Coin{
3✔
2111
                                TxOut: wire.TxOut{
3✔
2112
                                        Value:    int64(utxo.Value),
3✔
2113
                                        PkScript: utxo.PkScript,
3✔
2114
                                },
3✔
2115
                                OutPoint: utxo.OutPoint,
3✔
2116
                        }
3✔
2117
                }
3✔
2118

2119
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
3✔
2120
                        feeRate, fundingAmount, changeDustLimit, coins,
3✔
2121
                        strategy, estimator, changeType, maxFeeRatio,
3✔
2122
                )
3✔
2123
                if err != nil {
3✔
2124
                        return fmt.Errorf("error selecting coins: %w", err)
×
2125
                }
×
2126

2127
                if changeAmount > 0 {
6✔
2128
                        changeIndex, err = w.handleChange(
3✔
2129
                                packet, changeIndex, int64(changeAmount),
3✔
2130
                                changeType, account,
3✔
2131
                        )
3✔
2132
                        if err != nil {
3✔
2133
                                return fmt.Errorf("error handling change "+
×
2134
                                        "amount: %w", err)
×
2135
                        }
×
2136
                }
2137

2138
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
3✔
2139
                for i := range selectedCoins {
6✔
2140
                        coin := selectedCoins[i]
3✔
2141
                        addedOutpoints[i] = coin.OutPoint
3✔
2142

3✔
2143
                        packet.UnsignedTx.TxIn = append(
3✔
2144
                                packet.UnsignedTx.TxIn, &wire.TxIn{
3✔
2145
                                        PreviousOutPoint: coin.OutPoint,
3✔
2146
                                },
3✔
2147
                        )
3✔
2148
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
3✔
2149
                                WitnessUtxo: &coin.TxOut,
3✔
2150
                        })
3✔
2151
                }
3✔
2152

2153
                // Now that we've added the bare TX inputs, we also need to add
2154
                // the more verbose input information to the packet, so a future
2155
                // signer doesn't need to do any lookups. We skip any inputs
2156
                // that our wallet doesn't own.
2157
                err = w.cfg.Wallet.DecorateInputs(packet, false)
3✔
2158
                if err != nil {
3✔
2159
                        return fmt.Errorf("error decorating inputs: %w", err)
×
2160
                }
×
2161

2162
                response, err = w.lockAndCreateFundingResponse(
3✔
2163
                        packet, addedOutpoints, changeIndex,
3✔
2164
                )
3✔
2165

3✔
2166
                return err
3✔
2167
        })
2168
        if err != nil {
3✔
2169
                return nil, err
×
2170
        }
×
2171

2172
        return response, nil
3✔
2173
}
2174

2175
// assertNotAvailable makes sure the specified inputs either don't belong to
2176
// this node or are already locked by the user.
2177
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2178
        account string) error {
3✔
2179

3✔
2180
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
6✔
2181
                // Get a list of all unspent witness outputs.
3✔
2182
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
3✔
2183
                        minConfs, defaultMaxConf, account,
3✔
2184
                )
3✔
2185
                if err != nil {
3✔
2186
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2187
                }
×
2188

2189
                // We'll now check that none of the inputs specified in the
2190
                // template are available to us. That means they either don't
2191
                // belong to us or are already locked by the user.
2192
                for _, txIn := range inputs {
6✔
2193
                        for _, utxo := range utxos {
6✔
2194
                                if txIn.PreviousOutPoint == utxo.OutPoint {
3✔
2195
                                        return fmt.Errorf("input %v is not "+
×
2196
                                                "locked", txIn.PreviousOutPoint)
×
2197
                                }
×
2198
                        }
2199
                }
2200

2201
                return nil
3✔
2202
        })
2203
}
2204

2205
// lockAndCreateFundingResponse locks the given outpoints and creates a funding
2206
// response with the serialized PSBT, the change index and the locked UTXOs.
2207
func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet,
2208
        newOutpoints []wire.OutPoint, changeIndex int32) (*FundPsbtResponse,
2209
        error) {
3✔
2210

3✔
2211
        // Make sure we can properly serialize the packet. If this goes wrong
3✔
2212
        // then something isn't right with the inputs, and we probably shouldn't
3✔
2213
        // try to lock any of them.
3✔
2214
        var buf bytes.Buffer
3✔
2215
        err := packet.Serialize(&buf)
3✔
2216
        if err != nil {
3✔
2217
                return nil, fmt.Errorf("error serializing funded PSBT: %w", err)
×
2218
        }
×
2219

2220
        locks, err := lockInputs(w.cfg.Wallet, newOutpoints)
3✔
2221
        if err != nil {
3✔
2222
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2223
        }
×
2224

2225
        // Convert the lock leases to the RPC format.
2226
        rpcLocks := marshallLeases(locks)
3✔
2227

3✔
2228
        return &FundPsbtResponse{
3✔
2229
                FundedPsbt:        buf.Bytes(),
3✔
2230
                ChangeOutputIndex: changeIndex,
3✔
2231
                LockedUtxos:       rpcLocks,
3✔
2232
        }, nil
3✔
2233
}
2234

2235
// handleChange is a closure that either adds the non-zero change amount to an
2236
// existing output or creates a change output. The function returns the new
2237
// change output index if a new change output was added.
2238
func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
2239
        changeAmount int64, changeType chanfunding.ChangeAddressType,
2240
        changeAccount string) (int32, error) {
3✔
2241

3✔
2242
        // Does an existing output get the change?
3✔
2243
        if changeIndex >= 0 {
6✔
2244
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
3✔
2245
                changeOut.Value += changeAmount
3✔
2246

3✔
2247
                return changeIndex, nil
3✔
2248
        }
3✔
2249

2250
        // The user requested a new change output.
2251
        addrType := addrTypeFromChangeAddressType(changeType)
×
2252
        changeAddr, err := w.cfg.Wallet.NewAddress(
×
2253
                addrType, true, changeAccount,
×
2254
        )
×
2255
        if err != nil {
×
2256
                return 0, fmt.Errorf("could not derive change address: %w", err)
×
2257
        }
×
2258

2259
        changeScript, err := txscript.PayToAddrScript(changeAddr)
×
2260
        if err != nil {
×
2261
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2262
        }
×
2263

2264
        // We need to add the derivation info for the change address in case it
2265
        // is a P2TR address. This is mostly to prove it's a bare BIP-0086
2266
        // address, which is required for some protocols (such as Taproot
2267
        // Assets).
2268
        pOut := psbt.POutput{}
×
2269
        _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
×
2270
        if isTaprootChangeAddr {
×
2271
                changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
×
2272
                if err != nil {
×
2273
                        return 0, fmt.Errorf("could not get address info: %w",
×
2274
                                err)
×
2275
                }
×
2276

2277
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2278
                        changeAddrInfo,
×
2279
                )
×
2280
                if err != nil {
×
2281
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2282
                                "%w", err)
×
2283
                }
×
2284

2285
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2286
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2287
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2288
                        trDeriv,
×
2289
                }
×
2290
        }
2291

2292
        newChangeIndex := int32(len(packet.Outputs))
×
2293
        packet.UnsignedTx.TxOut = append(
×
2294
                packet.UnsignedTx.TxOut, &wire.TxOut{
×
2295
                        Value:    changeAmount,
×
2296
                        PkScript: changeScript,
×
2297
                },
×
2298
        )
×
2299
        packet.Outputs = append(packet.Outputs, pOut)
×
2300

×
2301
        return newChangeIndex, nil
×
2302
}
2303

2304
// marshallLeases converts the lock leases to the RPC format.
2305
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
3✔
2306
        rpcLocks := make([]*UtxoLease, len(locks))
3✔
2307
        for idx, lock := range locks {
6✔
2308
                lock := lock
3✔
2309

3✔
2310
                rpcLocks[idx] = &UtxoLease{
3✔
2311
                        Id:         lock.LockID[:],
3✔
2312
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
3✔
2313
                        Expiration: uint64(lock.Expiration.Unix()),
3✔
2314
                        PkScript:   lock.PkScript,
3✔
2315
                        Value:      uint64(lock.Value),
3✔
2316
                }
3✔
2317
        }
3✔
2318

2319
        return rpcLocks
3✔
2320
}
2321

2322
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2323
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2324
// it returns nil.
2325
func keyScopeFromChangeAddressType(
2326
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
3✔
2327

3✔
2328
        switch changeAddressType {
3✔
2329
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
3✔
2330
                return &waddrmgr.KeyScopeBIP0086
3✔
2331

2332
        default:
3✔
2333
                return nil
3✔
2334
        }
2335
}
2336

2337
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2338
// lnwallet.AddressType.
2339
func addrTypeFromChangeAddressType(
2340
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
×
2341

×
2342
        switch changeAddressType {
×
2343
        case chanfunding.P2TRChangeAddress:
×
2344
                return lnwallet.TaprootPubkey
×
2345

2346
        default:
×
2347
                return lnwallet.WitnessPubKey
×
2348
        }
2349
}
2350

2351
// SignPsbt expects a partial transaction with all inputs and outputs fully
2352
// declared and tries to sign all unsigned inputs that have all required fields
2353
// (UTXO information, BIP32 derivation information, witness or sig scripts)
2354
// set.
2355
// If no error is returned, the PSBT is ready to be given to the next signer or
2356
// to be finalized if lnd was the last signer.
2357
//
2358
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
2359
// perform any other tasks (such as coin selection, UTXO locking or
2360
// input/output/fee value validation, PSBT finalization). Any input that is
2361
// incomplete will be skipped.
2362
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
2363
        *SignPsbtResponse, error) {
3✔
2364

3✔
2365
        packet, err := psbt.NewFromRawBytes(
3✔
2366
                bytes.NewReader(req.FundedPsbt), false,
3✔
2367
        )
3✔
2368
        if err != nil {
3✔
2369
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2370
                        req.FundedPsbt)
×
2371
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2372
        }
×
2373

2374
        // Before we attempt to sign the packet, ensure that every input either
2375
        // has a witness UTXO, or a non witness UTXO.
2376
        for idx := range packet.UnsignedTx.TxIn {
6✔
2377
                in := packet.Inputs[idx]
3✔
2378

3✔
2379
                // Doesn't have either a witness or non witness UTXO so we need
3✔
2380
                // to exit here as otherwise signing will fail.
3✔
2381
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
6✔
2382
                        return nil, fmt.Errorf("input (index=%v) doesn't "+
3✔
2383
                                "specify any UTXO info", idx)
3✔
2384
                }
3✔
2385
        }
2386

2387
        // Let the wallet do the heavy lifting. This will sign all inputs that
2388
        // we have the UTXO for. If some inputs can't be signed and don't have
2389
        // witness data attached, they will just be skipped.
2390
        signedInputs, err := w.cfg.Wallet.SignPsbt(packet)
3✔
2391
        if err != nil {
3✔
2392
                return nil, fmt.Errorf("error signing PSBT: %w", err)
×
2393
        }
×
2394

2395
        // Serialize the signed PSBT in both the packet and wire format.
2396
        var signedPsbtBytes bytes.Buffer
3✔
2397
        err = packet.Serialize(&signedPsbtBytes)
3✔
2398
        if err != nil {
3✔
2399
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2400
        }
×
2401

2402
        return &SignPsbtResponse{
3✔
2403
                SignedPsbt:   signedPsbtBytes.Bytes(),
3✔
2404
                SignedInputs: signedInputs,
3✔
2405
        }, nil
3✔
2406
}
2407

2408
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
2409
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
2410
// the last signer of the transaction. That means, if there are any unsigned
2411
// non-witness inputs or inputs without UTXO information attached or inputs
2412
// without witness data that do not belong to lnd's wallet, this method will
2413
// fail. If no error is returned, the PSBT is ready to be extracted and the
2414
// final TX within to be broadcast.
2415
//
2416
// NOTE: This method does NOT publish the transaction once finalized. It is the
2417
// caller's responsibility to either publish the transaction on success or
2418
// unlock/release any locked UTXOs in case of an error in this method.
2419
func (w *WalletKit) FinalizePsbt(_ context.Context,
2420
        req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
3✔
2421

3✔
2422
        // We'll assume the PSBT was funded by the default account unless
3✔
2423
        // otherwise specified.
3✔
2424
        account := lnwallet.DefaultAccountName
3✔
2425
        if req.Account != "" {
3✔
2426
                account = req.Account
×
2427
        }
×
2428

2429
        // Parse the funded PSBT.
2430
        packet, err := psbt.NewFromRawBytes(
3✔
2431
                bytes.NewReader(req.FundedPsbt), false,
3✔
2432
        )
3✔
2433
        if err != nil {
3✔
2434
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2435
        }
×
2436

2437
        // The only check done at this level is to validate that the PSBT is
2438
        // not complete. The wallet performs all other checks.
2439
        if packet.IsComplete() {
3✔
2440
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2441
        }
×
2442

2443
        // Let the wallet do the heavy lifting. This will sign all inputs that
2444
        // we have the UTXO for. If some inputs can't be signed and don't have
2445
        // witness data attached, this will fail.
2446
        err = w.cfg.Wallet.FinalizePsbt(packet, account)
3✔
2447
        if err != nil {
3✔
2448
                return nil, fmt.Errorf("error finalizing PSBT: %w", err)
×
2449
        }
×
2450

2451
        var (
3✔
2452
                finalPsbtBytes bytes.Buffer
3✔
2453
                finalTxBytes   bytes.Buffer
3✔
2454
        )
3✔
2455

3✔
2456
        // Serialize the finalized PSBT in both the packet and wire format.
3✔
2457
        err = packet.Serialize(&finalPsbtBytes)
3✔
2458
        if err != nil {
3✔
2459
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2460
        }
×
2461
        finalTx, err := psbt.Extract(packet)
3✔
2462
        if err != nil {
3✔
2463
                return nil, fmt.Errorf("unable to extract final TX: %w", err)
×
2464
        }
×
2465
        err = finalTx.Serialize(&finalTxBytes)
3✔
2466
        if err != nil {
3✔
2467
                return nil, fmt.Errorf("error serializing final TX: %w", err)
×
2468
        }
×
2469

2470
        return &FinalizePsbtResponse{
3✔
2471
                SignedPsbt: finalPsbtBytes.Bytes(),
3✔
2472
                RawFinalTx: finalTxBytes.Bytes(),
3✔
2473
        }, nil
3✔
2474
}
2475

2476
// marshalWalletAccount converts the properties of an account into its RPC
2477
// representation.
2478
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2479
        account *waddrmgr.AccountProperties) (*Account, error) {
3✔
2480

3✔
2481
        var addrType AddressType
3✔
2482
        switch account.KeyScope {
3✔
2483
        case waddrmgr.KeyScopeBIP0049Plus:
3✔
2484
                // No address schema present represents the traditional BIP-0049
3✔
2485
                // address derivation scheme.
3✔
2486
                if account.AddrSchema == nil {
6✔
2487
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
3✔
2488
                        break
3✔
2489
                }
2490

2491
                switch *account.AddrSchema {
3✔
2492
                case waddrmgr.KeyScopeBIP0049AddrSchema:
3✔
2493
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
3✔
2494

2495
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
3✔
2496
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
3✔
2497

2498
                default:
×
2499
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2500
                                *account.AddrSchema)
×
2501
                }
2502

2503
        case waddrmgr.KeyScopeBIP0084:
3✔
2504
                addrType = AddressType_WITNESS_PUBKEY_HASH
3✔
2505

2506
        case waddrmgr.KeyScopeBIP0086:
3✔
2507
                addrType = AddressType_TAPROOT_PUBKEY
3✔
2508

2509
        case internalScope:
3✔
2510
                addrType = AddressType_WITNESS_PUBKEY_HASH
3✔
2511

2512
        default:
×
2513
                return nil, fmt.Errorf("account %v has unsupported "+
×
2514
                        "key scope %v", account.AccountName, account.KeyScope)
×
2515
        }
2516

2517
        rpcAccount := &Account{
3✔
2518
                Name:             account.AccountName,
3✔
2519
                AddressType:      addrType,
3✔
2520
                ExternalKeyCount: account.ExternalKeyCount,
3✔
2521
                InternalKeyCount: account.InternalKeyCount,
3✔
2522
                WatchOnly:        account.IsWatchOnly,
3✔
2523
        }
3✔
2524

3✔
2525
        // The remaining fields can only be done on accounts other than the
3✔
2526
        // default imported one existing within each key scope.
3✔
2527
        if account.AccountName != waddrmgr.ImportedAddrAccountName {
6✔
2528
                nonHardenedIndex := account.AccountPubKey.ChildIndex() -
3✔
2529
                        hdkeychain.HardenedKeyStart
3✔
2530
                rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
3✔
2531
                if account.MasterKeyFingerprint != 0 {
3✔
2532
                        var mkfp [4]byte
×
2533
                        binary.BigEndian.PutUint32(
×
2534
                                mkfp[:], account.MasterKeyFingerprint,
×
2535
                        )
×
2536
                        rpcAccount.MasterKeyFingerprint = mkfp[:]
×
2537
                }
×
2538
                rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
3✔
2539
                        account.KeyScope, nonHardenedIndex)
3✔
2540
        }
2541

2542
        return rpcAccount, nil
3✔
2543
}
2544

2545
// marshalWalletAddressList converts the list of address into its RPC
2546
// representation.
2547
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2548
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
3✔
2549

3✔
2550
        // Get the RPC representation of account.
3✔
2551
        rpcAccount, err := marshalWalletAccount(
3✔
2552
                w.internalScope(), account,
3✔
2553
        )
3✔
2554
        if err != nil {
3✔
2555
                return nil, err
×
2556
        }
×
2557

2558
        addresses := make([]*AddressProperty, len(addressList))
3✔
2559
        for idx, addr := range addressList {
6✔
2560
                var pubKeyBytes []byte
3✔
2561
                if addr.PublicKey != nil {
6✔
2562
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
3✔
2563
                }
3✔
2564
                addresses[idx] = &AddressProperty{
3✔
2565
                        Address:        addr.Address,
3✔
2566
                        IsInternal:     addr.Internal,
3✔
2567
                        Balance:        int64(addr.Balance),
3✔
2568
                        DerivationPath: addr.DerivationPath,
3✔
2569
                        PublicKey:      pubKeyBytes,
3✔
2570
                }
3✔
2571
        }
2572

2573
        rpcAddressList := &AccountWithAddresses{
3✔
2574
                Name:           rpcAccount.Name,
3✔
2575
                AddressType:    rpcAccount.AddressType,
3✔
2576
                DerivationPath: rpcAccount.DerivationPath,
3✔
2577
                Addresses:      addresses,
3✔
2578
        }
3✔
2579

3✔
2580
        return rpcAddressList, nil
3✔
2581
}
2582

2583
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2584
// name and key scope filter can be provided to filter through all of the wallet
2585
// accounts and return only those matching.
2586
func (w *WalletKit) ListAccounts(ctx context.Context,
2587
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
3✔
2588

3✔
2589
        // Map the supported address types into their corresponding key scope.
3✔
2590
        var keyScopeFilter *waddrmgr.KeyScope
3✔
2591
        switch req.AddressType {
3✔
2592
        case AddressType_UNKNOWN:
3✔
2593
                break
3✔
2594

2595
        case AddressType_WITNESS_PUBKEY_HASH:
3✔
2596
                keyScope := waddrmgr.KeyScopeBIP0084
3✔
2597
                keyScopeFilter = &keyScope
3✔
2598

2599
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2600
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
3✔
2601

3✔
2602
                keyScope := waddrmgr.KeyScopeBIP0049Plus
3✔
2603
                keyScopeFilter = &keyScope
3✔
2604

2605
        case AddressType_TAPROOT_PUBKEY:
3✔
2606
                keyScope := waddrmgr.KeyScopeBIP0086
3✔
2607
                keyScopeFilter = &keyScope
3✔
2608

2609
        default:
×
2610
                return nil, fmt.Errorf("unhandled address type %v",
×
2611
                        req.AddressType)
×
2612
        }
2613

2614
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
3✔
2615
        if err != nil {
3✔
2616
                return nil, err
×
2617
        }
×
2618

2619
        rpcAccounts := make([]*Account, 0, len(accounts))
3✔
2620
        for _, account := range accounts {
6✔
2621
                // Don't include the default imported accounts created by the
3✔
2622
                // wallet in the response if they don't have any keys imported.
3✔
2623
                if account.AccountName == waddrmgr.ImportedAddrAccountName &&
3✔
2624
                        account.ImportedKeyCount == 0 {
6✔
2625

3✔
2626
                        continue
3✔
2627
                }
2628

2629
                rpcAccount, err := marshalWalletAccount(
3✔
2630
                        w.internalScope(), account,
3✔
2631
                )
3✔
2632
                if err != nil {
3✔
2633
                        return nil, err
×
2634
                }
×
2635
                rpcAccounts = append(rpcAccounts, rpcAccount)
3✔
2636
        }
2637

2638
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
3✔
2639
}
2640

2641
// RequiredReserve returns the minimum amount of satoshis that should be
2642
// kept in the wallet in order to fee bump anchor channels if necessary.
2643
// The value scales with the number of public anchor channels but is
2644
// capped at a maximum.
2645
func (w *WalletKit) RequiredReserve(ctx context.Context,
2646
        req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
3✔
2647

3✔
2648
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
3✔
2649
        if err != nil {
3✔
2650
                return nil, err
×
2651
        }
×
2652

2653
        additionalChans := req.AdditionalPublicChannels
3✔
2654
        totalChans := uint32(numAnchorChans) + additionalChans
3✔
2655
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
3✔
2656

3✔
2657
        return &RequiredReserveResponse{
3✔
2658
                RequiredReserve: int64(reserved),
3✔
2659
        }, nil
3✔
2660
}
2661

2662
// ListAddresses retrieves all the addresses along with their balance. An
2663
// account name filter can be provided to filter through all of the
2664
// wallet accounts and return the addresses of only those matching.
2665
func (w *WalletKit) ListAddresses(ctx context.Context,
2666
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
3✔
2667

3✔
2668
        addressLists, err := w.cfg.Wallet.ListAddresses(
3✔
2669
                req.AccountName,
3✔
2670
                req.ShowCustomAccounts,
3✔
2671
        )
3✔
2672
        if err != nil {
3✔
2673
                return nil, err
×
2674
        }
×
2675

2676
        // Create a slice of accounts from addressLists map.
2677
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
3✔
2678
        for account := range addressLists {
6✔
2679
                accounts = append(accounts, account)
3✔
2680
        }
3✔
2681

2682
        // Sort the accounts by derivation path.
2683
        sort.Slice(accounts, func(i, j int) bool {
6✔
2684
                scopeI := accounts[i].KeyScope
3✔
2685
                scopeJ := accounts[j].KeyScope
3✔
2686
                if scopeI.Purpose == scopeJ.Purpose {
3✔
2687
                        if scopeI.Coin == scopeJ.Coin {
×
2688
                                acntNumI := accounts[i].AccountNumber
×
2689
                                acntNumJ := accounts[j].AccountNumber
×
2690
                                return acntNumI < acntNumJ
×
2691
                        }
×
2692

2693
                        return scopeI.Coin < scopeJ.Coin
×
2694
                }
2695

2696
                return scopeI.Purpose < scopeJ.Purpose
3✔
2697
        })
2698

2699
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
3✔
2700
        for _, account := range accounts {
6✔
2701
                addressList := addressLists[account]
3✔
2702
                rpcAddressList, err := marshalWalletAddressList(
3✔
2703
                        w, account, addressList,
3✔
2704
                )
3✔
2705
                if err != nil {
3✔
2706
                        return nil, err
×
2707
                }
×
2708

2709
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
3✔
2710
        }
2711

2712
        return &ListAddressesResponse{
3✔
2713
                AccountWithAddresses: rpcAddressLists,
3✔
2714
        }, nil
3✔
2715
}
2716

2717
// parseAddrType parses an address type from its RPC representation to a
2718
// *waddrmgr.AddressType.
2719
func parseAddrType(addrType AddressType,
2720
        required bool) (*waddrmgr.AddressType, error) {
3✔
2721

3✔
2722
        switch addrType {
3✔
2723
        case AddressType_UNKNOWN:
×
2724
                if required {
×
2725
                        return nil, fmt.Errorf("an address type must be " +
×
2726
                                "specified")
×
2727
                }
×
2728
                return nil, nil
×
2729

2730
        case AddressType_WITNESS_PUBKEY_HASH:
3✔
2731
                addrTyp := waddrmgr.WitnessPubKey
3✔
2732
                return &addrTyp, nil
3✔
2733

2734
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
3✔
2735
                addrTyp := waddrmgr.NestedWitnessPubKey
3✔
2736
                return &addrTyp, nil
3✔
2737

2738
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
3✔
2739
                addrTyp := waddrmgr.WitnessPubKey
3✔
2740
                return &addrTyp, nil
3✔
2741

2742
        case AddressType_TAPROOT_PUBKEY:
3✔
2743
                addrTyp := waddrmgr.TaprootPubKey
3✔
2744
                return &addrTyp, nil
3✔
2745

2746
        default:
×
2747
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2748
        }
2749
}
2750

2751
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2752
// transaction or a signature. It is prepended in front of the message and
2753
// follows the same standard as bitcoin core and btcd.
2754
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2755

2756
// SignMessageWithAddr signs a message with the private key of the provided
2757
// address. The address needs to belong to the lnd wallet.
2758
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2759
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
3✔
2760

3✔
2761
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
3✔
2762
        if err != nil {
3✔
2763
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2764
        }
×
2765

2766
        if !addr.IsForNet(w.cfg.ChainParams) {
3✔
2767
                return nil, fmt.Errorf("encoded address is for "+
×
2768
                        "the wrong network %s", req.Addr)
×
2769
        }
×
2770

2771
        // Fetch address infos from own wallet and check whether it belongs
2772
        // to the lnd wallet.
2773
        managedAddr, err := w.cfg.Wallet.AddressInfo(addr)
3✔
2774
        if err != nil {
3✔
2775
                return nil, fmt.Errorf("address could not be found in the "+
×
2776
                        "wallet database: %w", err)
×
2777
        }
×
2778

2779
        // Verifying by checking the interface type that the wallet knows about
2780
        // the public and private keys so it can sign the message with the
2781
        // private key of this address.
2782
        pubKey, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
3✔
2783
        if !ok {
3✔
2784
                return nil, fmt.Errorf("private key to address is unknown")
×
2785
        }
×
2786

2787
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
3✔
2788
        if err != nil {
3✔
2789
                return nil, err
×
2790
        }
×
2791

2792
        // For all address types (P2WKH, NP2WKH,P2TR) the ECDSA compact signing
2793
        // algorithm is used. For P2TR addresses this represents a special case.
2794
        // ECDSA is used to create a compact signature which makes the public
2795
        // key of the signature recoverable. For Schnorr no known compact
2796
        // signing algorithm exists yet.
2797
        privKey, err := pubKey.PrivKey()
3✔
2798
        if err != nil {
3✔
2799
                return nil, fmt.Errorf("no private key could be "+
×
2800
                        "fetched from wallet database: %w", err)
×
2801
        }
×
2802

2803
        sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
3✔
2804

3✔
2805
        // Bitcoin signatures are base64 encoded (being compatible with
3✔
2806
        // bitcoin-core and btcd).
3✔
2807
        sig := base64.StdEncoding.EncodeToString(sigBytes)
3✔
2808

3✔
2809
        return &SignMessageWithAddrResponse{
3✔
2810
                Signature: sig,
3✔
2811
        }, nil
3✔
2812
}
2813

2814
// VerifyMessageWithAddr verifies a signature on a message with a provided
2815
// address, it checks both the validity of the signature itself and then
2816
// verifies whether the signature corresponds to the public key of the
2817
// provided address. There is no dependence on the private key of the address
2818
// therefore also external addresses are allowed to verify signatures.
2819
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
2820
func (w *WalletKit) VerifyMessageWithAddr(_ context.Context,
2821
        req *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse,
2822
        error) {
3✔
2823

3✔
2824
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
3✔
2825
        if err != nil {
3✔
2826
                return nil, fmt.Errorf("malformed base64 encoding of "+
×
2827
                        "the signature: %w", err)
×
2828
        }
×
2829

2830
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
3✔
2831
        if err != nil {
3✔
2832
                return nil, err
×
2833
        }
×
2834

2835
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
3✔
2836
        if err != nil {
3✔
2837
                return nil, fmt.Errorf("unable to recover public key "+
×
2838
                        "from compact signature: %w", err)
×
2839
        }
×
2840

2841
        var serializedPubkey []byte
3✔
2842
        if wasCompressed {
6✔
2843
                serializedPubkey = pk.SerializeCompressed()
3✔
2844
        } else {
3✔
2845
                serializedPubkey = pk.SerializeUncompressed()
×
2846
        }
×
2847

2848
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
3✔
2849
        if err != nil {
3✔
2850
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2851
        }
×
2852

2853
        if !addr.IsForNet(w.cfg.ChainParams) {
3✔
2854
                return nil, fmt.Errorf("encoded address is for"+
×
2855
                        "the wrong network %s", req.Addr)
×
2856
        }
×
2857

2858
        var (
3✔
2859
                address    btcutil.Address
3✔
2860
                pubKeyHash = btcutil.Hash160(serializedPubkey)
3✔
2861
        )
3✔
2862

3✔
2863
        // Ensure the address is one of the supported types.
3✔
2864
        switch addr.(type) {
3✔
2865
        case *btcutil.AddressPubKeyHash:
3✔
2866
                address, err = btcutil.NewAddressPubKeyHash(
3✔
2867
                        pubKeyHash, w.cfg.ChainParams,
3✔
2868
                )
3✔
2869
                if err != nil {
3✔
2870
                        return nil, err
×
2871
                }
×
2872

2873
        case *btcutil.AddressWitnessPubKeyHash:
3✔
2874
                address, err = btcutil.NewAddressWitnessPubKeyHash(
3✔
2875
                        pubKeyHash, w.cfg.ChainParams,
3✔
2876
                )
3✔
2877
                if err != nil {
3✔
2878
                        return nil, err
×
2879
                }
×
2880

2881
        case *btcutil.AddressScriptHash:
3✔
2882
                // Check if address is a Nested P2WKH (NP2WKH).
3✔
2883
                address, err = btcutil.NewAddressWitnessPubKeyHash(
3✔
2884
                        pubKeyHash, w.cfg.ChainParams,
3✔
2885
                )
3✔
2886
                if err != nil {
3✔
2887
                        return nil, err
×
2888
                }
×
2889

2890
                witnessScript, err := txscript.PayToAddrScript(address)
3✔
2891
                if err != nil {
3✔
2892
                        return nil, err
×
2893
                }
×
2894

2895
                address, err = btcutil.NewAddressScriptHashFromHash(
3✔
2896
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
3✔
2897
                )
3✔
2898
                if err != nil {
3✔
2899
                        return nil, err
×
2900
                }
×
2901

2902
        case *btcutil.AddressTaproot:
3✔
2903
                // Only addresses without a tapscript are allowed because
3✔
2904
                // the verification is using the internal key.
3✔
2905
                tapKey := txscript.ComputeTaprootKeyNoScript(pk)
3✔
2906
                address, err = btcutil.NewAddressTaproot(
3✔
2907
                        schnorr.SerializePubKey(tapKey),
3✔
2908
                        w.cfg.ChainParams,
3✔
2909
                )
3✔
2910
                if err != nil {
3✔
2911
                        return nil, err
×
2912
                }
×
2913

2914
        default:
×
2915
                return nil, fmt.Errorf("unsupported address type")
×
2916
        }
2917

2918
        return &VerifyMessageWithAddrResponse{
3✔
2919
                Valid:  req.Addr == address.EncodeAddress(),
3✔
2920
                Pubkey: serializedPubkey,
3✔
2921
        }, nil
3✔
2922
}
2923

2924
// ImportAccount imports an account backed by an account extended public key.
2925
// The master key fingerprint denotes the fingerprint of the root key
2926
// corresponding to the account public key (also known as the key with
2927
// derivation path m/). This may be required by some hardware wallets for proper
2928
// identification and signing.
2929
//
2930
// The address type can usually be inferred from the key's version, but may be
2931
// required for certain keys to map them into the proper scope.
2932
//
2933
// For BIP-0044 keys, an address type must be specified as we intend to not
2934
// support importing BIP-0044 keys into the wallet using the legacy
2935
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
2936
// the standard BIP-0049 derivation scheme, while a witness address type will
2937
// force the standard BIP-0084 derivation scheme.
2938
//
2939
// For BIP-0049 keys, an address type must also be specified to make a
2940
// distinction between the standard BIP-0049 address schema (nested witness
2941
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
2942
// externally, witness pubkeys internally).
2943
func (w *WalletKit) ImportAccount(_ context.Context,
2944
        req *ImportAccountRequest) (*ImportAccountResponse, error) {
3✔
2945

3✔
2946
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
3✔
2947
        if err != nil {
3✔
2948
                return nil, err
×
2949
        }
×
2950

2951
        var mkfp uint32
3✔
2952
        switch len(req.MasterKeyFingerprint) {
3✔
2953
        // No master key fingerprint provided, which is fine as it's not
2954
        // required.
2955
        case 0:
3✔
2956
        // Expected length.
2957
        case 4:
×
2958
                mkfp = binary.BigEndian.Uint32(req.MasterKeyFingerprint)
×
2959
        default:
×
2960
                return nil, errors.New("invalid length for master key " +
×
2961
                        "fingerprint, expected 4 bytes in big-endian")
×
2962
        }
2963

2964
        addrType, err := parseAddrType(req.AddressType, false)
3✔
2965
        if err != nil {
3✔
2966
                return nil, err
×
2967
        }
×
2968

2969
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
3✔
2970
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
3✔
2971
        )
3✔
2972
        if err != nil {
6✔
2973
                return nil, err
3✔
2974
        }
3✔
2975

2976
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
3✔
2977
        if err != nil {
3✔
2978
                return nil, err
×
2979
        }
×
2980

2981
        resp := &ImportAccountResponse{Account: rpcAccount}
3✔
2982
        if !req.DryRun {
6✔
2983
                return resp, nil
3✔
2984
        }
3✔
2985

2986
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
2987
        for i := 0; i < len(extAddrs); i++ {
×
2988
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
2989
        }
×
2990
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
2991
        for i := 0; i < len(intAddrs); i++ {
×
2992
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
2993
        }
×
2994

2995
        return resp, nil
×
2996
}
2997

2998
// ImportPublicKey imports a single derived public key into the wallet. The
2999
// address type can usually be inferred from the key's version, but in the case
3000
// of legacy versions (xpub, tpub), an address type must be specified as we
3001
// intend to not support importing BIP-44 keys into the wallet using the legacy
3002
// pay-to-pubkey-hash (P2PKH) scheme. For Taproot keys, this will only watch
3003
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
3004
// spend or script spend outputs.
3005
func (w *WalletKit) ImportPublicKey(_ context.Context,
3006
        req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
3✔
3007

3✔
3008
        var (
3✔
3009
                pubKey *btcec.PublicKey
3✔
3010
                err    error
3✔
3011
        )
3✔
3012
        switch req.AddressType {
3✔
3013
        case AddressType_TAPROOT_PUBKEY:
3✔
3014
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
3✔
3015

3016
        default:
3✔
3017
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
3✔
3018
        }
3019
        if err != nil {
3✔
3020
                return nil, err
×
3021
        }
×
3022

3023
        addrType, err := parseAddrType(req.AddressType, true)
3✔
3024
        if err != nil {
3✔
3025
                return nil, err
×
3026
        }
×
3027

3028
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
3✔
3029
                return nil, err
×
3030
        }
×
3031

3032
        return &ImportPublicKeyResponse{
3✔
3033
                Status: fmt.Sprintf("public key %x imported",
3✔
3034
                        pubKey.SerializeCompressed()),
3✔
3035
        }, nil
3✔
3036
}
3037

3038
// ImportTapscript imports a Taproot script and internal key and adds the
3039
// resulting Taproot output key as a watch-only output script into the wallet.
3040
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
3041
// path) use ImportPublicKey.
3042
//
3043
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
3044
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
3045
func (w *WalletKit) ImportTapscript(_ context.Context,
3046
        req *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
3✔
3047

3✔
3048
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
3✔
3049
        if err != nil {
3✔
3050
                return nil, fmt.Errorf("error parsing internal key: %w", err)
×
3051
        }
×
3052

3053
        var tapscript *waddrmgr.Tapscript
3✔
3054
        switch {
3✔
3055
        case req.GetFullTree() != nil:
3✔
3056
                tree := req.GetFullTree()
3✔
3057
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
3✔
3058
                for idx, leaf := range tree.AllLeaves {
6✔
3059
                        leaves[idx] = txscript.TapLeaf{
3✔
3060
                                LeafVersion: txscript.TapscriptLeafVersion(
3✔
3061
                                        leaf.LeafVersion,
3✔
3062
                                ),
3✔
3063
                                Script: leaf.Script,
3✔
3064
                        }
3✔
3065
                }
3✔
3066

3067
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
3✔
3068

3069
        case req.GetPartialReveal() != nil:
3✔
3070
                partialReveal := req.GetPartialReveal()
3✔
3071
                if partialReveal.RevealedLeaf == nil {
3✔
3072
                        return nil, fmt.Errorf("missing revealed leaf")
×
3073
                }
×
3074

3075
                revealedLeaf := txscript.TapLeaf{
3✔
3076
                        LeafVersion: txscript.TapscriptLeafVersion(
3✔
3077
                                partialReveal.RevealedLeaf.LeafVersion,
3✔
3078
                        ),
3✔
3079
                        Script: partialReveal.RevealedLeaf.Script,
3✔
3080
                }
3✔
3081
                if len(partialReveal.FullInclusionProof)%32 != 0 {
3✔
3082
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
3083
                                "length, expected multiple of 32, got %d",
×
3084
                                len(partialReveal.FullInclusionProof)%32)
×
3085
                }
×
3086

3087
                tapscript = input.TapscriptPartialReveal(
3✔
3088
                        internalKey, revealedLeaf,
3✔
3089
                        partialReveal.FullInclusionProof,
3✔
3090
                )
3✔
3091

3092
        case req.GetRootHashOnly() != nil:
3✔
3093
                rootHash := req.GetRootHashOnly()
3✔
3094
                if len(rootHash) == 0 {
3✔
3095
                        return nil, fmt.Errorf("missing root hash")
×
3096
                }
×
3097

3098
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
3✔
3099

3100
        case req.GetFullKeyOnly():
3✔
3101
                tapscript = input.TapscriptFullKeyOnly(internalKey)
3✔
3102

3103
        default:
×
3104
                return nil, fmt.Errorf("invalid script")
×
3105
        }
3106

3107
        taprootScope := waddrmgr.KeyScopeBIP0086
3✔
3108
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
3✔
3109
        if err != nil {
3✔
3110
                return nil, fmt.Errorf("error importing script into wallet: %w",
×
3111
                        err)
×
3112
        }
×
3113

3114
        return &ImportTapscriptResponse{
3✔
3115
                P2TrAddress: addr.Address().String(),
3✔
3116
        }, nil
3✔
3117
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc