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

lightningnetwork / lnd / 11219354629

07 Oct 2024 03:56PM UTC coverage: 58.585% (-0.2%) from 58.814%
11219354629

Pull #9147

github

ziggie1984
fixup! sqlc: migration up script for payments.
Pull Request #9147: [Part 1|3] Introduce SQL Payment schema into LND

130227 of 222287 relevant lines covered (58.59%)

29106.19 hits per line

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

66.22
/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
        "time"
18

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

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

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

189
        // DefaultWalletKitMacFilename is the default name of the wallet kit
190
        // macaroon that we expect to find via a file handle within the main
191
        // configuration file in this package.
192
        DefaultWalletKitMacFilename = "walletkit.macaroon"
193

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

242
// ServerShell is a shell struct holding a reference to the actual sub-server.
243
// It is used to register the gRPC sub-server with the root server before we
244
// have the necessary dependencies to populate the actual sub-server.
245
type ServerShell struct {
246
        WalletKitServer
247
}
248

249
// WalletKit is a sub-RPC server that exposes a tool kit which allows clients
250
// to execute common wallet operations. This includes requesting new addresses,
251
// keys (for contracts!), and publishing transactions.
252
type WalletKit struct {
253
        // Required by the grpc-gateway/v2 library for forward compatibility.
254
        UnimplementedWalletKitServer
255

256
        cfg *Config
257
}
258

259
// A compile time check to ensure that WalletKit fully implements the
260
// WalletKitServer gRPC service.
261
var _ WalletKitServer = (*WalletKit)(nil)
262

263
// New creates a new instance of the WalletKit sub-RPC server.
264
func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) {
2✔
265
        // If the path of the wallet kit macaroon wasn't specified, then we'll
2✔
266
        // assume that it's found at the default network directory.
2✔
267
        if cfg.WalletKitMacPath == "" {
4✔
268
                cfg.WalletKitMacPath = filepath.Join(
2✔
269
                        cfg.NetworkDir, DefaultWalletKitMacFilename,
2✔
270
                )
2✔
271
        }
2✔
272

273
        // Now that we know the full path of the wallet kit macaroon, we can
274
        // check to see if we need to create it or not. If stateless_init is set
275
        // then we don't write the macaroons.
276
        macFilePath := cfg.WalletKitMacPath
2✔
277
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
2✔
278
                !lnrpc.FileExists(macFilePath) {
4✔
279

2✔
280
                log.Infof("Baking macaroons for WalletKit RPC Server at: %v",
2✔
281
                        macFilePath)
2✔
282

2✔
283
                // At this point, we know that the wallet kit macaroon doesn't
2✔
284
                // yet, exist, so we need to create it with the help of the
2✔
285
                // main macaroon service.
2✔
286
                walletKitMac, err := cfg.MacService.NewMacaroon(
2✔
287
                        context.Background(), macaroons.DefaultRootKeyID,
2✔
288
                        macaroonOps...,
2✔
289
                )
2✔
290
                if err != nil {
2✔
291
                        return nil, nil, err
×
292
                }
×
293
                walletKitMacBytes, err := walletKitMac.M().MarshalBinary()
2✔
294
                if err != nil {
2✔
295
                        return nil, nil, err
×
296
                }
×
297
                err = os.WriteFile(macFilePath, walletKitMacBytes, 0644)
2✔
298
                if err != nil {
2✔
299
                        _ = os.Remove(macFilePath)
×
300
                        return nil, nil, err
×
301
                }
×
302
        }
303

304
        walletKit := &WalletKit{
2✔
305
                cfg: cfg,
2✔
306
        }
2✔
307

2✔
308
        return walletKit, macPermissions, nil
2✔
309
}
310

311
// Start launches any helper goroutines required for the sub-server to function.
312
//
313
// NOTE: This is part of the lnrpc.SubServer interface.
314
func (w *WalletKit) Start() error {
2✔
315
        return nil
2✔
316
}
2✔
317

318
// Stop signals any active goroutines for a graceful closure.
319
//
320
// NOTE: This is part of the lnrpc.SubServer interface.
321
func (w *WalletKit) Stop() error {
2✔
322
        return nil
2✔
323
}
2✔
324

325
// Name returns a unique string representation of the sub-server. This can be
326
// used to identify the sub-server and also de-duplicate them.
327
//
328
// NOTE: This is part of the lnrpc.SubServer interface.
329
func (w *WalletKit) Name() string {
2✔
330
        return SubServerName
2✔
331
}
2✔
332

333
// RegisterWithRootServer will be called by the root gRPC server to direct a
334
// sub RPC server to register itself with the main gRPC root server. Until this
335
// is called, each sub-server won't be able to have requests routed towards it.
336
//
337
// NOTE: This is part of the lnrpc.GrpcHandler interface.
338
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
2✔
339
        // We make sure that we register it with the main gRPC server to ensure
2✔
340
        // all our methods are routed properly.
2✔
341
        RegisterWalletKitServer(grpcServer, r)
2✔
342

2✔
343
        log.Debugf("WalletKit RPC server successfully registered with " +
2✔
344
                "root gRPC server")
2✔
345

2✔
346
        return nil
2✔
347
}
2✔
348

349
// RegisterWithRestServer will be called by the root REST mux to direct a sub
350
// RPC server to register itself with the main REST mux server. Until this is
351
// called, each sub-server won't be able to have requests routed towards it.
352
//
353
// NOTE: This is part of the lnrpc.GrpcHandler interface.
354
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
355
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
2✔
356

2✔
357
        // We make sure that we register it with the main REST server to ensure
2✔
358
        // all our methods are routed properly.
2✔
359
        err := RegisterWalletKitHandlerFromEndpoint(ctx, mux, dest, opts)
2✔
360
        if err != nil {
2✔
361
                log.Errorf("Could not register WalletKit REST server "+
×
362
                        "with root REST server: %v", err)
×
363
                return err
×
364
        }
×
365

366
        log.Debugf("WalletKit REST server successfully registered with " +
2✔
367
                "root REST server")
2✔
368
        return nil
2✔
369
}
370

371
// CreateSubServer populates the subserver's dependencies using the passed
372
// SubServerConfigDispatcher. This method should fully initialize the
373
// sub-server instance, making it ready for action. It returns the macaroon
374
// permissions that the sub-server wishes to pass on to the root server for all
375
// methods routed towards it.
376
//
377
// NOTE: This is part of the lnrpc.GrpcHandler interface.
378
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
379
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
2✔
380

2✔
381
        subServer, macPermissions, err := createNewSubServer(configRegistry)
2✔
382
        if err != nil {
2✔
383
                return nil, nil, err
×
384
        }
×
385

386
        r.WalletKitServer = subServer
2✔
387
        return subServer, macPermissions, nil
2✔
388
}
389

390
// internalScope returns the internal key scope.
391
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
2✔
392
        return waddrmgr.KeyScope{
2✔
393
                Purpose: keychain.BIP0043Purpose,
2✔
394
                Coin:    w.cfg.ChainParams.HDCoinType,
2✔
395
        }
2✔
396
}
2✔
397

398
// ListUnspent returns useful information about each unspent output owned by
399
// the wallet, as reported by the underlying `ListUnspentWitness`; the
400
// information returned is: outpoint, amount in satoshis, address, address
401
// type, scriptPubKey in hex and number of confirmations. The result is
402
// filtered to contain outputs whose number of confirmations is between a
403
// minimum and maximum number of confirmations specified by the user.
404
func (w *WalletKit) ListUnspent(ctx context.Context,
405
        req *ListUnspentRequest) (*ListUnspentResponse, error) {
2✔
406

2✔
407
        // Force min_confs and max_confs to be zero if unconfirmed_only is
2✔
408
        // true.
2✔
409
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
2✔
410
                return nil, fmt.Errorf("min_confs and max_confs must be zero " +
×
411
                        "if unconfirmed_only is true")
×
412
        }
×
413

414
        // When unconfirmed_only is inactive and max_confs is zero (default
415
        // values), we will override max_confs to be a MaxInt32, in order
416
        // to return all confirmed and unconfirmed utxos as a default response.
417
        if req.MaxConfs == 0 && !req.UnconfirmedOnly {
4✔
418
                req.MaxConfs = math.MaxInt32
2✔
419
        }
2✔
420

421
        // Validate the confirmation arguments.
422
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
2✔
423
        if err != nil {
2✔
424
                return nil, err
×
425
        }
×
426

427
        // With our arguments validated, we'll query the internal wallet for
428
        // the set of UTXOs that match our query.
429
        //
430
        // We'll acquire the global coin selection lock to ensure there aren't
431
        // any other concurrent processes attempting to lock any UTXOs which may
432
        // be shown available to us.
433
        var utxos []*lnwallet.Utxo
2✔
434
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
435
                utxos, err = w.cfg.Wallet.ListUnspentWitness(
2✔
436
                        minConfs, maxConfs, req.Account,
2✔
437
                )
2✔
438

2✔
439
                return err
2✔
440
        })
2✔
441
        if err != nil {
2✔
442
                return nil, err
×
443
        }
×
444

445
        rpcUtxos, err := lnrpc.MarshalUtxos(utxos, w.cfg.ChainParams)
2✔
446
        if err != nil {
2✔
447
                return nil, err
×
448
        }
×
449

450
        return &ListUnspentResponse{
2✔
451
                Utxos: rpcUtxos,
2✔
452
        }, nil
2✔
453
}
454

455
// LeaseOutput locks an output to the given ID, preventing it from being
456
// available for any future coin selection attempts. The absolute time of the
457
// lock's expiration is returned. The expiration of the lock can be extended by
458
// successive invocations of this call. Outputs can be unlocked before their
459
// expiration through `ReleaseOutput`.
460
//
461
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
462
// output has already been locked to a different ID, then
463
// wtxmgr.ErrOutputAlreadyLocked is returned.
464
func (w *WalletKit) LeaseOutput(ctx context.Context,
465
        req *LeaseOutputRequest) (*LeaseOutputResponse, error) {
×
466

×
467
        if len(req.Id) != 32 {
×
468
                return nil, errors.New("id must be 32 random bytes")
×
469
        }
×
470
        var lockID wtxmgr.LockID
×
471
        copy(lockID[:], req.Id)
×
472

×
473
        // Don't allow ID's of 32 bytes, but all zeros.
×
474
        if lockID == (wtxmgr.LockID{}) {
×
475
                return nil, errors.New("id must be 32 random bytes")
×
476
        }
×
477

478
        // Don't allow our internal ID to be used externally for locking. Only
479
        // unlocking is allowed.
480
        if lockID == chanfunding.LndInternalLockID {
×
481
                return nil, errors.New("reserved id cannot be used")
×
482
        }
×
483

484
        op, err := UnmarshallOutPoint(req.Outpoint)
×
485
        if err != nil {
×
486
                return nil, err
×
487
        }
×
488

489
        // Use the specified lock duration or fall back to the default.
490
        duration := chanfunding.DefaultLockDuration
×
491
        if req.ExpirationSeconds != 0 {
×
492
                duration = time.Duration(req.ExpirationSeconds) * time.Second
×
493
        }
×
494

495
        // Acquire the global coin selection lock to ensure there aren't any
496
        // other concurrent processes attempting to lease the same UTXO.
497
        var expiration time.Time
×
498
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
499
                expiration, err = w.cfg.Wallet.LeaseOutput(
×
500
                        lockID, *op, duration,
×
501
                )
×
502
                return err
×
503
        })
×
504
        if err != nil {
×
505
                return nil, err
×
506
        }
×
507

508
        return &LeaseOutputResponse{
×
509
                Expiration: uint64(expiration.Unix()),
×
510
        }, nil
×
511
}
512

513
// ReleaseOutput unlocks an output, allowing it to be available for coin
514
// selection if it remains unspent. The ID should match the one used to
515
// originally lock the output.
516
func (w *WalletKit) ReleaseOutput(ctx context.Context,
517
        req *ReleaseOutputRequest) (*ReleaseOutputResponse, error) {
×
518

×
519
        if len(req.Id) != 32 {
×
520
                return nil, errors.New("id must be 32 random bytes")
×
521
        }
×
522
        var lockID wtxmgr.LockID
×
523
        copy(lockID[:], req.Id)
×
524

×
525
        op, err := UnmarshallOutPoint(req.Outpoint)
×
526
        if err != nil {
×
527
                return nil, err
×
528
        }
×
529

530
        // Acquire the global coin selection lock to maintain consistency as
531
        // it's acquired when we initially leased the output.
532
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
533
                return w.cfg.Wallet.ReleaseOutput(lockID, *op)
×
534
        })
×
535
        if err != nil {
×
536
                return nil, err
×
537
        }
×
538

539
        return &ReleaseOutputResponse{}, nil
×
540
}
541

542
// ListLeases returns a list of all currently locked utxos.
543
func (w *WalletKit) ListLeases(ctx context.Context,
544
        req *ListLeasesRequest) (*ListLeasesResponse, error) {
×
545

×
546
        leases, err := w.cfg.Wallet.ListLeasedOutputs()
×
547
        if err != nil {
×
548
                return nil, err
×
549
        }
×
550

551
        return &ListLeasesResponse{
×
552
                LockedUtxos: marshallLeases(leases),
×
553
        }, nil
×
554
}
555

556
// DeriveNextKey attempts to derive the *next* key within the key family
557
// (account in BIP43) specified. This method should return the next external
558
// child within this branch.
559
func (w *WalletKit) DeriveNextKey(ctx context.Context,
560
        req *KeyReq) (*signrpc.KeyDescriptor, error) {
2✔
561

2✔
562
        nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey(
2✔
563
                keychain.KeyFamily(req.KeyFamily),
2✔
564
        )
2✔
565
        if err != nil {
2✔
566
                return nil, err
×
567
        }
×
568

569
        return &signrpc.KeyDescriptor{
2✔
570
                KeyLoc: &signrpc.KeyLocator{
2✔
571
                        KeyFamily: int32(nextKeyDesc.Family),
2✔
572
                        KeyIndex:  int32(nextKeyDesc.Index),
2✔
573
                },
2✔
574
                RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(),
2✔
575
        }, nil
2✔
576
}
577

578
// DeriveKey attempts to derive an arbitrary key specified by the passed
579
// KeyLocator.
580
func (w *WalletKit) DeriveKey(ctx context.Context,
581
        req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
2✔
582

2✔
583
        keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{
2✔
584
                Family: keychain.KeyFamily(req.KeyFamily),
2✔
585
                Index:  uint32(req.KeyIndex),
2✔
586
        })
2✔
587
        if err != nil {
2✔
588
                return nil, err
×
589
        }
×
590

591
        return &signrpc.KeyDescriptor{
2✔
592
                KeyLoc: &signrpc.KeyLocator{
2✔
593
                        KeyFamily: int32(keyDesc.Family),
2✔
594
                        KeyIndex:  int32(keyDesc.Index),
2✔
595
                },
2✔
596
                RawKeyBytes: keyDesc.PubKey.SerializeCompressed(),
2✔
597
        }, nil
2✔
598
}
599

600
// NextAddr returns the next unused address within the wallet.
601
func (w *WalletKit) NextAddr(ctx context.Context,
602
        req *AddrRequest) (*AddrResponse, error) {
2✔
603

2✔
604
        account := lnwallet.DefaultAccountName
2✔
605
        if req.Account != "" {
2✔
606
                account = req.Account
×
607
        }
×
608

609
        addrType := lnwallet.WitnessPubKey
2✔
610
        switch req.Type {
2✔
611
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
612
                addrType = lnwallet.NestedWitnessPubKey
×
613

614
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
615
                return nil, fmt.Errorf("invalid address type for next "+
×
616
                        "address: %v", req.Type)
×
617

618
        case AddressType_TAPROOT_PUBKEY:
×
619
                addrType = lnwallet.TaprootPubkey
×
620
        }
621

622
        addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account)
2✔
623
        if err != nil {
2✔
624
                return nil, err
×
625
        }
×
626

627
        return &AddrResponse{
2✔
628
                Addr: addr.String(),
2✔
629
        }, nil
2✔
630
}
631

632
// GetTransaction returns a transaction from the wallet given its hash.
633
func (w *WalletKit) GetTransaction(_ context.Context,
634
        req *GetTransactionRequest) (*lnrpc.Transaction, error) {
2✔
635

2✔
636
        // If the client doesn't specify a hash, then there's nothing to
2✔
637
        // return.
2✔
638
        if req.Txid == "" {
2✔
639
                return nil, fmt.Errorf("must provide a transaction hash")
×
640
        }
×
641

642
        txHash, err := chainhash.NewHashFromStr(req.Txid)
2✔
643
        if err != nil {
2✔
644
                return nil, err
×
645
        }
×
646

647
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
2✔
648
        if err != nil {
2✔
649
                return nil, err
×
650
        }
×
651

652
        return lnrpc.RPCTransaction(res), nil
2✔
653
}
654

655
// Attempts to publish the passed transaction to the network. Once this returns
656
// without an error, the wallet will continually attempt to re-broadcast the
657
// transaction on start up, until it enters the chain.
658
func (w *WalletKit) PublishTransaction(ctx context.Context,
659
        req *Transaction) (*PublishResponse, error) {
2✔
660

2✔
661
        switch {
2✔
662
        // If the client doesn't specify a transaction, then there's nothing to
663
        // publish.
664
        case len(req.TxHex) == 0:
×
665
                return nil, fmt.Errorf("must provide a transaction to " +
×
666
                        "publish")
×
667
        }
668

669
        tx := &wire.MsgTx{}
2✔
670
        txReader := bytes.NewReader(req.TxHex)
2✔
671
        if err := tx.Deserialize(txReader); err != nil {
2✔
672
                return nil, err
×
673
        }
×
674

675
        label, err := labels.ValidateAPI(req.Label)
2✔
676
        if err != nil {
2✔
677
                return nil, err
×
678
        }
×
679

680
        err = w.cfg.Wallet.PublishTransaction(tx, label)
2✔
681
        if err != nil {
2✔
682
                return nil, err
×
683
        }
×
684

685
        return &PublishResponse{}, nil
2✔
686
}
687

688
// RemoveTransaction attempts to remove the transaction and all of its
689
// descendants resulting from further spends of the outputs of the provided
690
// transaction id.
691
// NOTE: We do not remove the transaction from the rebroadcaster which might
692
// run in the background rebroadcasting not yet confirmed transactions. We do
693
// not have access to the rebroadcaster here nor should we. This command is not
694
// a way to remove transactions from the network. It is a way to shortcircuit
695
// wallet utxo housekeeping while transactions are still unconfirmed and we know
696
// that a transaction will never confirm because a replacement already pays
697
// higher fees.
698
func (w *WalletKit) RemoveTransaction(_ context.Context,
699
        req *GetTransactionRequest) (*RemoveTransactionResponse, error) {
2✔
700

2✔
701
        // If the client doesn't specify a hash, then there's nothing to
2✔
702
        // return.
2✔
703
        if req.Txid == "" {
2✔
704
                return nil, fmt.Errorf("must provide a transaction hash")
×
705
        }
×
706

707
        txHash, err := chainhash.NewHashFromStr(req.Txid)
2✔
708
        if err != nil {
2✔
709
                return nil, err
×
710
        }
×
711

712
        // Query the tx store of our internal wallet for the specified
713
        // transaction.
714
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
2✔
715
        if err != nil {
2✔
716
                return nil, fmt.Errorf("transaction with txid=%v not found "+
×
717
                        "in the internal wallet store", txHash)
×
718
        }
×
719

720
        // Only allow unconfirmed transactions to be removed because as soon
721
        // as a transaction is confirmed it will be evaluated by the wallet
722
        // again and the wallet state would be updated in case the user had
723
        // removed the transaction accidentally.
724
        if res.NumConfirmations > 0 {
2✔
725
                return nil, fmt.Errorf("transaction with txid=%v is already "+
×
726
                        "confirmed (numConfs=%d) cannot be removed", txHash,
×
727
                        res.NumConfirmations)
×
728
        }
×
729

730
        tx := &wire.MsgTx{}
2✔
731
        txReader := bytes.NewReader(res.RawTx)
2✔
732
        if err := tx.Deserialize(txReader); err != nil {
2✔
733
                return nil, err
×
734
        }
×
735

736
        err = w.cfg.Wallet.RemoveDescendants(tx)
2✔
737
        if err != nil {
2✔
738
                return nil, err
×
739
        }
×
740

741
        return &RemoveTransactionResponse{
2✔
742
                Status: "Successfully removed transaction",
2✔
743
        }, nil
2✔
744
}
745

746
// SendOutputs is similar to the existing sendmany call in Bitcoind, and allows
747
// the caller to create a transaction that sends to several outputs at once.
748
// This is ideal when wanting to batch create a set of transactions.
749
func (w *WalletKit) SendOutputs(ctx context.Context,
750
        req *SendOutputsRequest) (*SendOutputsResponse, error) {
2✔
751

2✔
752
        switch {
2✔
753
        // If the client didn't specify any outputs to create, then  we can't
754
        // proceed .
755
        case len(req.Outputs) == 0:
×
756
                return nil, fmt.Errorf("must specify at least one output " +
×
757
                        "to create")
×
758
        }
759

760
        // Before we can request this transaction to be created, we'll need to
761
        // amp the protos back into the format that the internal wallet will
762
        // recognize.
763
        var totalOutputValue int64
2✔
764
        outputsToCreate := make([]*wire.TxOut, 0, len(req.Outputs))
2✔
765
        for _, output := range req.Outputs {
4✔
766
                outputsToCreate = append(outputsToCreate, &wire.TxOut{
2✔
767
                        Value:    output.Value,
2✔
768
                        PkScript: output.PkScript,
2✔
769
                })
2✔
770
                totalOutputValue += output.Value
2✔
771
        }
2✔
772

773
        // Then, we'll extract the minimum number of confirmations that each
774
        // output we use to fund the transaction should satisfy.
775
        minConfs, err := lnrpc.ExtractMinConfs(
2✔
776
                req.MinConfs, req.SpendUnconfirmed,
2✔
777
        )
2✔
778
        if err != nil {
2✔
779
                return nil, err
×
780
        }
×
781

782
        // Before sending out funds we need to ensure that the remainder of our
783
        // wallet funds would cover for the anchor reserve requirement. We'll
784
        // also take unconfirmed funds into account.
785
        walletBalance, err := w.cfg.Wallet.ConfirmedBalance(
2✔
786
                0, lnwallet.DefaultAccountName,
2✔
787
        )
2✔
788
        if err != nil {
2✔
789
                return nil, err
×
790
        }
×
791

792
        // We'll get the currently required reserve amount.
793
        reserve, err := w.RequiredReserve(ctx, &RequiredReserveRequest{})
2✔
794
        if err != nil {
2✔
795
                return nil, err
×
796
        }
×
797

798
        // Then we check if our current wallet balance undershoots the required
799
        // reserve if we'd send out the outputs specified in the request.
800
        if int64(walletBalance)-totalOutputValue < reserve.RequiredReserve {
4✔
801
                return nil, ErrInsufficientReserve
2✔
802
        }
2✔
803

804
        label, err := labels.ValidateAPI(req.Label)
2✔
805
        if err != nil {
2✔
806
                return nil, err
×
807
        }
×
808

809
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
2✔
810
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
2✔
811
        )
2✔
812
        if err != nil {
2✔
813
                return nil, err
×
814
        }
×
815

816
        // Now that we have the outputs mapped and checked for the reserve
817
        // requirement, we can request that the wallet attempts to create this
818
        // transaction.
819
        tx, err := w.cfg.Wallet.SendOutputs(
2✔
820
                nil, outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw),
2✔
821
                minConfs, label, coinSelectionStrategy,
2✔
822
        )
2✔
823
        if err != nil {
2✔
824
                return nil, err
×
825
        }
×
826

827
        var b bytes.Buffer
2✔
828
        if err := tx.Serialize(&b); err != nil {
2✔
829
                return nil, err
×
830
        }
×
831

832
        return &SendOutputsResponse{
2✔
833
                RawTx: b.Bytes(),
2✔
834
        }, nil
2✔
835
}
836

837
// EstimateFee attempts to query the internal fee estimator of the wallet to
838
// determine the fee (in sat/kw) to attach to a transaction in order to achieve
839
// the confirmation target.
840
func (w *WalletKit) EstimateFee(ctx context.Context,
841
        req *EstimateFeeRequest) (*EstimateFeeResponse, error) {
×
842

×
843
        switch {
×
844
        // A confirmation target of zero doesn't make any sense. Similarly, we
845
        // reject confirmation targets of 1 as they're unreasonable.
846
        case req.ConfTarget == 0 || req.ConfTarget == 1:
×
847
                return nil, fmt.Errorf("confirmation target must be greater " +
×
848
                        "than 1")
×
849
        }
850

851
        satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW(
×
852
                uint32(req.ConfTarget),
×
853
        )
×
854
        if err != nil {
×
855
                return nil, err
×
856
        }
×
857

858
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
×
859

×
860
        return &EstimateFeeResponse{
×
861
                SatPerKw:            int64(satPerKw),
×
862
                MinRelayFeeSatPerKw: int64(relayFeePerKw),
×
863
        }, nil
×
864
}
865

866
// PendingSweeps returns lists of on-chain outputs that lnd is currently
867
// attempting to sweep within its central batching engine. Outputs with similar
868
// fee rates are batched together in order to sweep them within a single
869
// transaction. The fee rate of each sweeping transaction is determined by
870
// taking the average fee rate of all the outputs it's trying to sweep.
871
func (w *WalletKit) PendingSweeps(ctx context.Context,
872
        in *PendingSweepsRequest) (*PendingSweepsResponse, error) {
2✔
873

2✔
874
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
2✔
875
        // sweep.
2✔
876
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
2✔
877
        if err != nil {
2✔
878
                return nil, err
×
879
        }
×
880

881
        // Convert them into their respective RPC format.
882
        rpcPendingSweeps := make([]*PendingSweep, 0, len(inputsMap))
2✔
883
        for _, inp := range inputsMap {
4✔
884
                witnessType, ok := allWitnessTypes[inp.WitnessType]
2✔
885
                if !ok {
2✔
886
                        return nil, fmt.Errorf("unhandled witness type %v for "+
×
887
                                "input %v", inp.WitnessType, inp.OutPoint)
×
888
                }
×
889

890
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
2✔
891
                amountSat := uint32(inp.Amount)
2✔
892
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
2✔
893
                broadcastAttempts := uint32(inp.BroadcastAttempts)
2✔
894

2✔
895
                // Get the requested starting fee rate, if set.
2✔
896
                startingFeeRate := fn.MapOptionZ(
2✔
897
                        inp.Params.StartingFeeRate,
2✔
898
                        func(feeRate chainfee.SatPerKWeight) uint64 {
4✔
899
                                return uint64(feeRate.FeePerVByte())
2✔
900
                        })
2✔
901

902
                ps := &PendingSweep{
2✔
903
                        Outpoint:             op,
2✔
904
                        WitnessType:          witnessType,
2✔
905
                        AmountSat:            amountSat,
2✔
906
                        SatPerVbyte:          satPerVbyte,
2✔
907
                        BroadcastAttempts:    broadcastAttempts,
2✔
908
                        Immediate:            inp.Params.Immediate,
2✔
909
                        Budget:               uint64(inp.Params.Budget),
2✔
910
                        DeadlineHeight:       inp.DeadlineHeight,
2✔
911
                        RequestedSatPerVbyte: startingFeeRate,
2✔
912
                }
2✔
913
                rpcPendingSweeps = append(rpcPendingSweeps, ps)
2✔
914
        }
915

916
        return &PendingSweepsResponse{
2✔
917
                PendingSweeps: rpcPendingSweeps,
2✔
918
        }, nil
2✔
919
}
920

921
// UnmarshallOutPoint converts an outpoint from its lnrpc type to its canonical
922
// type.
923
func UnmarshallOutPoint(op *lnrpc.OutPoint) (*wire.OutPoint, error) {
2✔
924
        if op == nil {
2✔
925
                return nil, fmt.Errorf("empty outpoint provided")
×
926
        }
×
927

928
        var hash chainhash.Hash
2✔
929
        switch {
2✔
930
        // Return an error if both txid fields are unpopulated.
931
        case len(op.TxidBytes) == 0 && len(op.TxidStr) == 0:
×
932
                return nil, fmt.Errorf("TxidBytes and TxidStr are both " +
×
933
                        "unspecified")
×
934

935
        // The hash was provided as raw bytes.
936
        case len(op.TxidBytes) != 0:
2✔
937
                copy(hash[:], op.TxidBytes)
2✔
938

939
        // The hash was provided as a hex-encoded string.
940
        case len(op.TxidStr) != 0:
×
941
                h, err := chainhash.NewHashFromStr(op.TxidStr)
×
942
                if err != nil {
×
943
                        return nil, err
×
944
                }
×
945
                hash = *h
×
946
        }
947

948
        return &wire.OutPoint{
2✔
949
                Hash:  hash,
2✔
950
                Index: op.OutputIndex,
2✔
951
        }, nil
2✔
952
}
953

954
// validateBumpFeeRequest makes sure the deprecated fields are not used when
955
// the new fields are set.
956
func validateBumpFeeRequest(in *BumpFeeRequest) (
957
        fn.Option[chainfee.SatPerKWeight], bool, error) {
2✔
958

2✔
959
        // Get the specified fee rate if set.
2✔
960
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
2✔
961

2✔
962
        // We only allow using either the deprecated field or the new field.
2✔
963
        switch {
2✔
964
        case in.SatPerByte != 0 && in.SatPerVbyte != 0:
×
965
                return satPerKwOpt, false, fmt.Errorf("either SatPerByte or " +
×
966
                        "SatPerVbyte should be set, but not both")
×
967

968
        case in.SatPerByte != 0:
×
969
                satPerKw := chainfee.SatPerVByte(
×
970
                        in.SatPerByte,
×
971
                ).FeePerKWeight()
×
972
                satPerKwOpt = fn.Some(satPerKw)
×
973

974
        case in.SatPerVbyte != 0:
2✔
975
                satPerKw := chainfee.SatPerVByte(
2✔
976
                        in.SatPerVbyte,
2✔
977
                ).FeePerKWeight()
2✔
978
                satPerKwOpt = fn.Some(satPerKw)
2✔
979
        }
980

981
        var immediate bool
2✔
982
        switch {
2✔
983
        case in.Force && in.Immediate:
×
984
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
985
                        "Immediate should be set, but not both")
×
986

987
        case in.Force:
×
988
                immediate = in.Force
×
989

990
        case in.Immediate:
2✔
991
                immediate = in.Immediate
2✔
992
        }
993

994
        return satPerKwOpt, immediate, nil
2✔
995
}
996

997
// prepareSweepParams creates the sweep params to be used for the sweeper. It
998
// returns the new params and a bool indicating whether this is an existing
999
// input.
1000
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1001
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
2✔
1002

2✔
1003
        // Return an error if both deprecated and new fields are used.
2✔
1004
        feerate, immediate, err := validateBumpFeeRequest(in)
2✔
1005
        if err != nil {
2✔
1006
                return sweep.Params{}, false, err
×
1007
        }
×
1008

1009
        // Get the current pending inputs.
1010
        inputMap, err := w.cfg.Sweeper.PendingInputs()
2✔
1011
        if err != nil {
2✔
1012
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1013
                        "pending inputs: %w", err)
×
1014
        }
×
1015

1016
        // Find the pending input.
1017
        //
1018
        // TODO(yy): act differently based on the state of the input?
1019
        inp, ok := inputMap[op]
2✔
1020

2✔
1021
        if !ok {
4✔
1022
                // NOTE: if this input doesn't exist and the new budget is not
2✔
1023
                // specified, the params would have a zero budget.
2✔
1024
                params := sweep.Params{
2✔
1025
                        Immediate:       immediate,
2✔
1026
                        StartingFeeRate: feerate,
2✔
1027
                        Budget:          btcutil.Amount(in.Budget),
2✔
1028
                }
2✔
1029
                if in.TargetConf != 0 {
2✔
1030
                        params.DeadlineHeight = fn.Some(
×
1031
                                int32(in.TargetConf) + currentHeight,
×
1032
                        )
×
1033
                }
×
1034

1035
                return params, ok, nil
2✔
1036
        }
1037

1038
        // Find the existing budget used for this input. Note that this value
1039
        // must be greater than zero.
1040
        budget := inp.Params.Budget
2✔
1041

2✔
1042
        // Set the new budget if specified.
2✔
1043
        if in.Budget != 0 {
4✔
1044
                budget = btcutil.Amount(in.Budget)
2✔
1045
        }
2✔
1046

1047
        // For an existing input, we assign it first, then overwrite it if
1048
        // a deadline is requested.
1049
        deadline := inp.Params.DeadlineHeight
2✔
1050

2✔
1051
        // Set the deadline if target conf is specified.
2✔
1052
        //
2✔
1053
        // TODO(yy): upgrade `falafel` so we can make this field optional. Atm
2✔
1054
        // we cannot distinguish between user's not setting the field and
2✔
1055
        // setting it to 0.
2✔
1056
        if in.TargetConf != 0 {
4✔
1057
                deadline = fn.Some(int32(in.TargetConf) + currentHeight)
2✔
1058
        }
2✔
1059

1060
        // Prepare the new sweep params.
1061
        //
1062
        // NOTE: if this input doesn't exist and the new budget is not
1063
        // specified, the params would have a zero budget.
1064
        params := sweep.Params{
2✔
1065
                Immediate:       immediate,
2✔
1066
                StartingFeeRate: feerate,
2✔
1067
                DeadlineHeight:  deadline,
2✔
1068
                Budget:          budget,
2✔
1069
        }
2✔
1070

2✔
1071
        if ok {
4✔
1072
                log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
2✔
1073
                        "params=%v, new params=%v", op, inp.Params, params)
2✔
1074
        }
2✔
1075

1076
        return params, ok, nil
2✔
1077
}
1078

1079
// BumpFee allows bumping the fee rate of an arbitrary input. A fee preference
1080
// can be expressed either as a specific fee rate or a delta of blocks in which
1081
// the output should be swept on-chain within. If a fee preference is not
1082
// explicitly specified, then an error is returned. The status of the input
1083
// sweep can be checked through the PendingSweeps RPC.
1084
func (w *WalletKit) BumpFee(ctx context.Context,
1085
        in *BumpFeeRequest) (*BumpFeeResponse, error) {
2✔
1086

2✔
1087
        // Parse the outpoint from the request.
2✔
1088
        op, err := UnmarshallOutPoint(in.Outpoint)
2✔
1089
        if err != nil {
2✔
1090
                return nil, err
×
1091
        }
×
1092

1093
        // Get the current height so we can calculate the deadline height.
1094
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1095
        if err != nil {
2✔
1096
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1097
                        err)
×
1098
        }
×
1099

1100
        // We now create a new sweeping params and update it in the sweeper.
1101
        // This will complicate the RBF conditions if this input has already
1102
        // been offered to sweeper before and it has already been included in a
1103
        // tx with other inputs. If this is the case, two results are possible:
1104
        // - either this input successfully RBFed the existing tx, or,
1105
        // - the budget of this input was not enough to RBF the existing tx.
1106
        params, existing, err := w.prepareSweepParams(in, *op, currentHeight)
2✔
1107
        if err != nil {
2✔
1108
                return nil, err
×
1109
        }
×
1110

1111
        // If this input exists, we will update its params.
1112
        if existing {
4✔
1113
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
2✔
1114
                if err != nil {
2✔
1115
                        return nil, err
×
1116
                }
×
1117

1118
                return &BumpFeeResponse{
2✔
1119
                        Status: "Successfully registered rbf-tx with sweeper",
2✔
1120
                }, nil
2✔
1121
        }
1122

1123
        // Otherwise, create a new sweeping request for this input.
1124
        err = w.sweepNewInput(op, uint32(currentHeight), params)
2✔
1125
        if err != nil {
2✔
1126
                return nil, err
×
1127
        }
×
1128

1129
        return &BumpFeeResponse{
2✔
1130
                Status: "Successfully registered CPFP-tx with the sweeper",
2✔
1131
        }, nil
2✔
1132
}
1133

1134
// getWaitingCloseChannel returns the waiting close channel in case it does
1135
// exist in the underlying channel state database.
1136
func (w *WalletKit) getWaitingCloseChannel(
1137
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
2✔
1138

2✔
1139
        // Fetch all channels, which still have their commitment transaction not
2✔
1140
        // confirmed (waiting close channels).
2✔
1141
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
2✔
1142
        if err != nil {
2✔
1143
                return nil, err
×
1144
        }
×
1145

1146
        channel := fn.Find(func(c *channeldb.OpenChannel) bool {
4✔
1147
                return c.FundingOutpoint == chanPoint
2✔
1148
        }, chans)
2✔
1149

1150
        return channel.UnwrapOrErr(errors.New("channel not found"))
2✔
1151
}
1152

1153
// BumpForceCloseFee bumps the fee rate of an unconfirmed anchor channel. It
1154
// updates the new fee rate parameters with the sweeper subsystem. Additionally
1155
// it will try to create anchor cpfp transactions for all possible commitment
1156
// transactions (local, remote, remote-dangling) so depending on which
1157
// commitment is in the local mempool only one of them will succeed in being
1158
// broadcasted.
1159
func (w *WalletKit) BumpForceCloseFee(_ context.Context,
1160
        in *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
2✔
1161

2✔
1162
        if in.ChanPoint == nil {
2✔
1163
                return nil, fmt.Errorf("no chan_point provided")
×
1164
        }
×
1165

1166
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
2✔
1167
        if err != nil {
2✔
1168
                return nil, err
×
1169
        }
×
1170

1171
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
2✔
1172
        if err != nil {
2✔
1173
                return nil, err
×
1174
        }
×
1175

1176
        // Get the relevant channel if it is in the waiting close state.
1177
        channel, err := w.getWaitingCloseChannel(*outPoint)
2✔
1178
        if err != nil {
2✔
1179
                return nil, err
×
1180
        }
×
1181

1182
        // Match pending sweeps with commitments of the channel for which a bump
1183
        // is requested. Depending on the commitment state when force closing
1184
        // the channel we might have up to 3 commitments to consider when
1185
        // bumping the fee.
1186
        commitSet := fn.NewSet[chainhash.Hash]()
2✔
1187

2✔
1188
        if channel.LocalCommitment.CommitTx != nil {
4✔
1189
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
2✔
1190
                commitSet.Add(localTxID)
2✔
1191
        }
2✔
1192

1193
        if channel.RemoteCommitment.CommitTx != nil {
4✔
1194
                remoteTxID := channel.RemoteCommitment.CommitTx.TxHash()
2✔
1195
                commitSet.Add(remoteTxID)
2✔
1196
        }
2✔
1197

1198
        // Check whether there was a dangling commitment at the time the channel
1199
        // was force closed.
1200
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
2✔
1201
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
2✔
1202
                return nil, err
×
1203
        }
×
1204

1205
        if remoteCommitDiff != nil {
2✔
1206
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1207
                commitSet.Add(hash)
×
1208
        }
×
1209

1210
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1211
        // sweep.
1212
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
2✔
1213
        if err != nil {
2✔
1214
                return nil, err
×
1215
        }
×
1216

1217
        // Get the current height so we can calculate the deadline height.
1218
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1219
        if err != nil {
2✔
1220
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1221
                        err)
×
1222
        }
×
1223

1224
        pendingSweeps := maps.Values(inputsMap)
2✔
1225

2✔
1226
        // Discard everything except for the anchor sweeps.
2✔
1227
        anchors := fn.Filter(func(sweep *sweep.PendingInputResponse) bool {
4✔
1228
                // Only filter for anchor inputs because these are the only
2✔
1229
                // inputs which can be used to bump a closed unconfirmed
2✔
1230
                // commitment transaction.
2✔
1231
                if sweep.WitnessType != input.CommitmentAnchor &&
2✔
1232
                        sweep.WitnessType != input.TaprootAnchorSweepSpend {
2✔
1233

×
1234
                        return false
×
1235
                }
×
1236

1237
                return commitSet.Contains(sweep.OutPoint.Hash)
2✔
1238
        }, pendingSweeps)
1239

1240
        // Filter all relevant anchor sweeps and update the sweep request.
1241
        for _, anchor := range anchors {
4✔
1242
                // Anchor cpfp bump request are predictable because they are
2✔
1243
                // swept separately hence not batched with other sweeps (they
2✔
1244
                // are marked with the exclusive group flag). Bumping the fee
2✔
1245
                // rate does not create any conflicting fee bump conditions.
2✔
1246
                // Either the rbf requirements are met or the bump is rejected
2✔
1247
                // by the mempool rules.
2✔
1248
                params, existing, err := w.prepareSweepParams(
2✔
1249
                        &BumpFeeRequest{
2✔
1250
                                Outpoint:    lnrpcOutpoint,
2✔
1251
                                TargetConf:  in.DeadlineDelta,
2✔
1252
                                SatPerVbyte: in.StartingFeerate,
2✔
1253
                                Immediate:   in.Immediate,
2✔
1254
                                Budget:      in.Budget,
2✔
1255
                        }, anchor.OutPoint, currentHeight,
2✔
1256
                )
2✔
1257
                if err != nil {
2✔
1258
                        return nil, err
×
1259
                }
×
1260

1261
                // There might be the case when an anchor sweep is confirmed
1262
                // between fetching the pending sweeps and preparing the sweep
1263
                // params. We log this case and proceed.
1264
                if !existing {
2✔
1265
                        log.Errorf("Sweep anchor input(%v) not known to the " +
×
1266
                                "sweeper subsystem")
×
1267
                        continue
×
1268
                }
1269

1270
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
2✔
1271
                if err != nil {
2✔
1272
                        return nil, err
×
1273
                }
×
1274
        }
1275

1276
        return &BumpForceCloseFeeResponse{
2✔
1277
                Status: "Successfully registered anchor-cpfp transaction to" +
2✔
1278
                        "bump channel force close transaction",
2✔
1279
        }, nil
2✔
1280
}
1281

1282
// sweepNewInput handles the case where an input is seen the first time by the
1283
// sweeper. It will fetch the output from the wallet and construct an input and
1284
// offer it to the sweeper.
1285
//
1286
// NOTE: if the budget is not set, the default budget ratio is used.
1287
func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
1288
        params sweep.Params) error {
2✔
1289

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

2✔
1292
        // Since the sweeper is not aware of the input, we'll assume the user
2✔
1293
        // is attempting to bump an unconfirmed transaction's fee rate by
2✔
1294
        // sweeping an output within it under control of the wallet with a
2✔
1295
        // higher fee rate. In this case, this will be a CPFP.
2✔
1296
        //
2✔
1297
        // We'll gather all of the information required by the UtxoSweeper in
2✔
1298
        // order to sweep the output.
2✔
1299
        utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
2✔
1300
        if err != nil {
2✔
1301
                return err
×
1302
        }
×
1303

1304
        // We're only able to bump the fee of unconfirmed transactions.
1305
        if utxo.Confirmations > 0 {
2✔
1306
                return errors.New("unable to bump fee of a confirmed " +
×
1307
                        "transaction")
×
1308
        }
×
1309

1310
        // If there's no budget set, use the default value.
1311
        if params.Budget == 0 {
4✔
1312
                params.Budget = utxo.Value.MulF64(
2✔
1313
                        contractcourt.DefaultBudgetRatio,
2✔
1314
                )
2✔
1315
        }
2✔
1316

1317
        signDesc := &input.SignDescriptor{
2✔
1318
                Output: &wire.TxOut{
2✔
1319
                        PkScript: utxo.PkScript,
2✔
1320
                        Value:    int64(utxo.Value),
2✔
1321
                },
2✔
1322
                HashType: txscript.SigHashAll,
2✔
1323
        }
2✔
1324

2✔
1325
        var witnessType input.WitnessType
2✔
1326
        switch utxo.AddressType {
2✔
1327
        case lnwallet.WitnessPubKey:
×
1328
                witnessType = input.WitnessKeyHash
×
1329
        case lnwallet.NestedWitnessPubKey:
×
1330
                witnessType = input.NestedWitnessKeyHash
×
1331
        case lnwallet.TaprootPubkey:
2✔
1332
                witnessType = input.TaprootPubKeySpend
2✔
1333
                signDesc.HashType = txscript.SigHashDefault
2✔
1334
        default:
×
1335
                return fmt.Errorf("unknown input witness %v", op)
×
1336
        }
1337

1338
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
2✔
1339
                params)
2✔
1340

2✔
1341
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
2✔
1342
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
2✔
1343
                return err
×
1344
        }
×
1345

1346
        return nil
2✔
1347
}
1348

1349
// ListSweeps returns a list of the sweeps that our node has published.
1350
func (w *WalletKit) ListSweeps(ctx context.Context,
1351
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
2✔
1352

2✔
1353
        sweeps, err := w.cfg.Sweeper.ListSweeps()
2✔
1354
        if err != nil {
2✔
1355
                return nil, err
×
1356
        }
×
1357

1358
        sweepTxns := make(map[string]bool)
2✔
1359
        for _, sweep := range sweeps {
4✔
1360
                sweepTxns[sweep.String()] = true
2✔
1361
        }
2✔
1362

1363
        // Some of our sweeps could have been replaced by fee, or dropped out
1364
        // of the mempool. Here, we lookup our wallet transactions so that we
1365
        // can match our list of sweeps against the list of transactions that
1366
        // the wallet is still tracking. Sweeps are currently always swept to
1367
        // the default wallet account.
1368
        transactions, err := w.cfg.Wallet.ListTransactionDetails(
2✔
1369
                in.StartHeight, btcwallet.UnconfirmedHeight,
2✔
1370
                lnwallet.DefaultAccountName,
2✔
1371
        )
2✔
1372
        if err != nil {
2✔
1373
                return nil, err
×
1374
        }
×
1375

1376
        var (
2✔
1377
                txids     []string
2✔
1378
                txDetails []*lnwallet.TransactionDetail
2✔
1379
        )
2✔
1380

2✔
1381
        for _, tx := range transactions {
4✔
1382
                _, ok := sweepTxns[tx.Hash.String()]
2✔
1383
                if !ok {
4✔
1384
                        continue
2✔
1385
                }
1386

1387
                // Add the txid or full tx details depending on whether we want
1388
                // verbose output or not.
1389
                if in.Verbose {
4✔
1390
                        txDetails = append(txDetails, tx)
2✔
1391
                } else {
4✔
1392
                        txids = append(txids, tx.Hash.String())
2✔
1393
                }
2✔
1394
        }
1395

1396
        if in.Verbose {
4✔
1397
                return &ListSweepsResponse{
2✔
1398
                        Sweeps: &ListSweepsResponse_TransactionDetails{
2✔
1399
                                TransactionDetails: lnrpc.RPCTransactionDetails(
2✔
1400
                                        txDetails,
2✔
1401
                                ),
2✔
1402
                        },
2✔
1403
                }, nil
2✔
1404
        }
2✔
1405

1406
        return &ListSweepsResponse{
2✔
1407
                Sweeps: &ListSweepsResponse_TransactionIds{
2✔
1408
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
2✔
1409
                                TransactionIds: txids,
2✔
1410
                        },
2✔
1411
                },
2✔
1412
        }, nil
2✔
1413
}
1414

1415
// LabelTransaction adds a label to a transaction.
1416
func (w *WalletKit) LabelTransaction(ctx context.Context,
1417
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
2✔
1418

2✔
1419
        // Check that the label provided in non-zero.
2✔
1420
        if len(req.Label) == 0 {
4✔
1421
                return nil, ErrZeroLabel
2✔
1422
        }
2✔
1423

1424
        // Validate the length of the non-zero label. We do not need to use the
1425
        // label returned here, because the original is non-zero so will not
1426
        // be replaced.
1427
        if _, err := labels.ValidateAPI(req.Label); err != nil {
2✔
1428
                return nil, err
×
1429
        }
×
1430

1431
        hash, err := chainhash.NewHash(req.Txid)
2✔
1432
        if err != nil {
2✔
1433
                return nil, err
×
1434
        }
×
1435

1436
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
2✔
1437
        return &LabelTransactionResponse{}, err
2✔
1438
}
1439

1440
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
1441
// the outputs specified in the template. There are three ways a user can
1442
// specify what we call the template (a list of inputs and outputs to use in the
1443
// PSBT): Either as a PSBT packet directly with no coin selection (using the
1444
// legacy "psbt" field), a PSBT with advanced coin selection support (using the
1445
// new "coin_select" field) or as a raw RPC message (using the "raw" field).
1446
// The legacy "psbt" and "raw" modes, the following restrictions apply:
1447
//  1. If there are no inputs specified in the template, coin selection is
1448
//     performed automatically.
1449
//  2. If the template does contain any inputs, it is assumed that full coin
1450
//     selection happened externally and no additional inputs are added. If the
1451
//     specified inputs aren't enough to fund the outputs with the given fee
1452
//     rate, an error is returned.
1453
//
1454
// The new "coin_select" mode does not have these restrictions and allows the
1455
// user to specify a PSBT with inputs and outputs and still perform coin
1456
// selection on top of that.
1457
// For all modes this RPC requires any inputs that are specified to be locked by
1458
// the user (if they belong to this node in the first place).
1459
// After either selecting or verifying the inputs, all input UTXOs are locked
1460
// with an internal app ID. A custom address type for change can be specified
1461
// for default accounts and single imported public keys (only P2TR for now).
1462
// Otherwise, P2WPKH will be used by default. No custom address type should be
1463
// provided for custom accounts as we will always generate the change address
1464
// using the coin selection key scope.
1465
//
1466
// NOTE: If this method returns without an error, it is the caller's
1467
// responsibility to either spend the locked UTXOs (by finalizing and then
1468
// publishing the transaction) or to unlock/release the locked UTXOs in case of
1469
// an error on the caller's side.
1470
func (w *WalletKit) FundPsbt(_ context.Context,
1471
        req *FundPsbtRequest) (*FundPsbtResponse, error) {
2✔
1472

2✔
1473
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
2✔
1474
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
2✔
1475
        )
2✔
1476
        if err != nil {
2✔
1477
                return nil, err
×
1478
        }
×
1479

1480
        // Determine the desired transaction fee.
1481
        var feeSatPerKW chainfee.SatPerKWeight
2✔
1482
        switch {
2✔
1483
        // Estimate the fee by the target number of blocks to confirmation.
1484
        case req.GetTargetConf() != 0:
×
1485
                targetConf := req.GetTargetConf()
×
1486
                if targetConf < 2 {
×
1487
                        return nil, fmt.Errorf("confirmation target must be " +
×
1488
                                "greater than 1")
×
1489
                }
×
1490

1491
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
×
1492
                        targetConf,
×
1493
                )
×
1494
                if err != nil {
×
1495
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1496
                                err)
×
1497
                }
×
1498

1499
        // Convert the fee to sat/kW from the specified sat/vByte.
1500
        case req.GetSatPerVbyte() != 0:
2✔
1501
                feeSatPerKW = chainfee.SatPerKVByte(
2✔
1502
                        req.GetSatPerVbyte() * 1000,
2✔
1503
                ).FeePerKWeight()
2✔
1504

1505
        default:
×
1506
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1507
                        "specify either target_conf or sat_per_vbyte")
×
1508
        }
1509

1510
        // Then, we'll extract the minimum number of confirmations that each
1511
        // output we use to fund the transaction should satisfy.
1512
        minConfs, err := lnrpc.ExtractMinConfs(
2✔
1513
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
2✔
1514
        )
2✔
1515
        if err != nil {
2✔
1516
                return nil, err
×
1517
        }
×
1518

1519
        // We'll assume the PSBT will be funded by the default account unless
1520
        // otherwise specified.
1521
        account := lnwallet.DefaultAccountName
2✔
1522
        if req.Account != "" {
4✔
1523
                account = req.Account
2✔
1524
        }
2✔
1525

1526
        // There are three ways a user can specify what we call the template (a
1527
        // list of inputs and outputs to use in the PSBT): Either as a PSBT
1528
        // packet directly with no coin selection, a PSBT with coin selection or
1529
        // as a special RPC message. Find out which one the user wants to use,
1530
        // they are mutually exclusive.
1531
        switch {
2✔
1532
        // The template is specified as a PSBT. All we have to do is parse it.
1533
        case req.GetPsbt() != nil:
2✔
1534
                r := bytes.NewReader(req.GetPsbt())
2✔
1535
                packet, err := psbt.NewFromRawBytes(r, false)
2✔
1536
                if err != nil {
2✔
1537
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1538
                }
×
1539

1540
                // Run the actual funding process now, using the internal
1541
                // wallet.
1542
                return w.fundPsbtInternalWallet(
2✔
1543
                        account, keyScopeFromChangeAddressType(req.ChangeType),
2✔
1544
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
2✔
1545
                )
2✔
1546

1547
        // The template is specified as a PSBT with the intention to perform
1548
        // coin selection even if inputs are already present.
1549
        case req.GetCoinSelect() != nil:
2✔
1550
                coinSelectRequest := req.GetCoinSelect()
2✔
1551
                r := bytes.NewReader(coinSelectRequest.Psbt)
2✔
1552
                packet, err := psbt.NewFromRawBytes(r, false)
2✔
1553
                if err != nil {
2✔
1554
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1555
                }
×
1556

1557
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
2✔
1558
                if numOutputs == 0 {
2✔
1559
                        return nil, fmt.Errorf("no outputs specified in " +
×
1560
                                "template")
×
1561
                }
×
1562

1563
                outputSum := int64(0)
2✔
1564
                for _, txOut := range packet.UnsignedTx.TxOut {
4✔
1565
                        outputSum += txOut.Value
2✔
1566
                }
2✔
1567
                if outputSum <= 0 {
2✔
1568
                        return nil, fmt.Errorf("output sum must be positive")
×
1569
                }
×
1570

1571
                var (
2✔
1572
                        changeIndex int32 = -1
2✔
1573
                        changeType  chanfunding.ChangeAddressType
2✔
1574
                )
2✔
1575
                switch t := coinSelectRequest.ChangeOutput.(type) {
2✔
1576
                // The user wants to use an existing output as change output.
1577
                case *PsbtCoinSelect_ExistingOutputIndex:
2✔
1578
                        if t.ExistingOutputIndex < 0 ||
2✔
1579
                                t.ExistingOutputIndex >= numOutputs {
2✔
1580

×
1581
                                return nil, fmt.Errorf("change output index "+
×
1582
                                        "out of range: %d",
×
1583
                                        t.ExistingOutputIndex)
×
1584
                        }
×
1585

1586
                        changeIndex = t.ExistingOutputIndex
2✔
1587

2✔
1588
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
1589
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
2✔
1590
                        if err != nil {
2✔
1591
                                return nil, fmt.Errorf("error parsing change "+
×
1592
                                        "script: %w", err)
×
1593
                        }
×
1594

1595
                        changeType = chanfunding.ExistingChangeAddress
2✔
1596

1597
                // The user wants to use a new output as change output.
1598
                case *PsbtCoinSelect_Add:
×
1599
                        // We already set the change index to -1 above to
×
1600
                        // indicate no change output should be used if possible
×
1601
                        // or a new one should be created if needed. So we only
×
1602
                        // need to parse the type of change output we want to
×
1603
                        // create.
×
1604
                        switch req.ChangeType {
×
1605
                        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
1606
                                changeType = chanfunding.P2TRChangeAddress
×
1607

1608
                        default:
×
1609
                                changeType = chanfunding.P2WKHChangeAddress
×
1610
                        }
1611

1612
                default:
×
1613
                        return nil, fmt.Errorf("unknown change output type")
×
1614
                }
1615

1616
                // Run the actual funding process now, using the channel funding
1617
                // coin selection algorithm.
1618
                return w.fundPsbtCoinSelect(
2✔
1619
                        account, changeIndex, packet, minConfs, changeType,
2✔
1620
                        feeSatPerKW, coinSelectionStrategy,
2✔
1621
                )
2✔
1622

1623
        // The template is specified as a RPC message. We need to create a new
1624
        // PSBT and copy the RPC information over.
1625
        case req.GetRaw() != nil:
2✔
1626
                tpl := req.GetRaw()
2✔
1627

2✔
1628
                txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
2✔
1629
                for addrStr, amt := range tpl.Outputs {
4✔
1630
                        addr, err := btcutil.DecodeAddress(
2✔
1631
                                addrStr, w.cfg.ChainParams,
2✔
1632
                        )
2✔
1633
                        if err != nil {
2✔
1634
                                return nil, fmt.Errorf("error parsing address "+
×
1635
                                        "%s for network %s: %v", addrStr,
×
1636
                                        w.cfg.ChainParams.Name, err)
×
1637
                        }
×
1638

1639
                        if !addr.IsForNet(w.cfg.ChainParams) {
2✔
1640
                                return nil, fmt.Errorf("address is not for %s",
×
1641
                                        w.cfg.ChainParams.Name)
×
1642
                        }
×
1643

1644
                        pkScript, err := txscript.PayToAddrScript(addr)
2✔
1645
                        if err != nil {
2✔
1646
                                return nil, fmt.Errorf("error getting pk "+
×
1647
                                        "script for address %s: %w", addrStr,
×
1648
                                        err)
×
1649
                        }
×
1650

1651
                        txOut = append(txOut, &wire.TxOut{
2✔
1652
                                Value:    int64(amt),
2✔
1653
                                PkScript: pkScript,
2✔
1654
                        })
2✔
1655
                }
1656

1657
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
2✔
1658
                for idx, in := range tpl.Inputs {
2✔
1659
                        op, err := UnmarshallOutPoint(in)
×
1660
                        if err != nil {
×
1661
                                return nil, fmt.Errorf("error parsing "+
×
1662
                                        "outpoint: %w", err)
×
1663
                        }
×
1664
                        txIn[idx] = op
×
1665
                }
1666

1667
                sequences := make([]uint32, len(txIn))
2✔
1668
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
2✔
1669
                if err != nil {
2✔
1670
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1671
                }
×
1672

1673
                // Run the actual funding process now, using the internal
1674
                // wallet.
1675
                return w.fundPsbtInternalWallet(
2✔
1676
                        account, keyScopeFromChangeAddressType(req.ChangeType),
2✔
1677
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
2✔
1678
                )
2✔
1679

1680
        default:
×
1681
                return nil, fmt.Errorf("transaction template missing, need " +
×
1682
                        "to specify either PSBT or raw TX template")
×
1683
        }
1684
}
1685

1686
// fundPsbtInternalWallet uses the "old" PSBT funding method of the internal
1687
// wallet that does not allow specifying custom inputs while selecting coins.
1688
func (w *WalletKit) fundPsbtInternalWallet(account string,
1689
        keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
1690
        feeSatPerKW chainfee.SatPerKWeight,
1691
        strategy base.CoinSelectionStrategy) (*FundPsbtResponse, error) {
2✔
1692

2✔
1693
        // The RPC parsing part is now over. Several of the following operations
2✔
1694
        // require us to hold the global coin selection lock, so we do the rest
2✔
1695
        // of the tasks while holding the lock. The result is a list of locked
2✔
1696
        // UTXOs.
2✔
1697
        var response *FundPsbtResponse
2✔
1698
        err := w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
1699
                // In case the user did specify inputs, we need to make sure
2✔
1700
                // they are known to us, still unspent and not yet locked.
2✔
1701
                if len(packet.UnsignedTx.TxIn) > 0 {
4✔
1702
                        // Get a list of all unspent witness outputs.
2✔
1703
                        utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
1704
                                minConfs, defaultMaxConf, account,
2✔
1705
                        )
2✔
1706
                        if err != nil {
2✔
1707
                                return err
×
1708
                        }
×
1709

1710
                        // filterFn makes sure utxos which are unconfirmed and
1711
                        // still used by the sweeper are not used.
1712
                        filterFn := func(u *lnwallet.Utxo) bool {
4✔
1713
                                // Confirmed utxos are always allowed.
2✔
1714
                                if u.Confirmations > 0 {
4✔
1715
                                        return true
2✔
1716
                                }
2✔
1717

1718
                                // Unconfirmed utxos in use by the sweeper are
1719
                                // not stable to use because they can be
1720
                                // replaced.
1721
                                if w.cfg.Sweeper.IsSweeperOutpoint(u.OutPoint) {
4✔
1722
                                        log.Warnf("Cannot use unconfirmed "+
2✔
1723
                                                "utxo=%v because it is "+
2✔
1724
                                                "unstable and could be "+
2✔
1725
                                                "replaced", u.OutPoint)
2✔
1726

2✔
1727
                                        return false
2✔
1728
                                }
2✔
1729

1730
                                return true
×
1731
                        }
1732

1733
                        eligibleUtxos := fn.Filter(filterFn, utxos)
2✔
1734

2✔
1735
                        // Validate all inputs against our known list of UTXOs
2✔
1736
                        // now.
2✔
1737
                        err = verifyInputsUnspent(
2✔
1738
                                packet.UnsignedTx.TxIn, eligibleUtxos,
2✔
1739
                        )
2✔
1740
                        if err != nil {
4✔
1741
                                return err
2✔
1742
                        }
2✔
1743
                }
1744

1745
                // currentHeight is needed to determine whether the internal
1746
                // wallet utxo is still unconfirmed.
1747
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1748
                if err != nil {
2✔
1749
                        return fmt.Errorf("unable to retrieve current "+
×
1750
                                "height: %v", err)
×
1751
                }
×
1752

1753
                // restrictUnstableUtxos is a filter function which disallows
1754
                // the usage of unconfirmed outputs published (still in use) by
1755
                // the sweeper.
1756
                restrictUnstableUtxos := func(utxo wtxmgr.Credit) bool {
4✔
1757
                        // Wallet utxos which are unmined have a height
2✔
1758
                        // of -1.
2✔
1759
                        if utxo.Height != -1 && utxo.Height <= currentHeight {
4✔
1760
                                // Confirmed utxos are always allowed.
2✔
1761
                                return true
2✔
1762
                        }
2✔
1763

1764
                        // Utxos used by the sweeper are not used for
1765
                        // channel openings.
1766
                        allowed := !w.cfg.Sweeper.IsSweeperOutpoint(
2✔
1767
                                utxo.OutPoint,
2✔
1768
                        )
2✔
1769
                        if !allowed {
4✔
1770
                                log.Warnf("Cannot use unconfirmed "+
2✔
1771
                                        "utxo=%v because it is "+
2✔
1772
                                        "unstable and could be "+
2✔
1773
                                        "replaced", utxo.OutPoint)
2✔
1774
                        }
2✔
1775

1776
                        return allowed
2✔
1777
                }
1778

1779
                // We made sure the input from the user is as sane as possible.
1780
                // We can now ask the wallet to fund the TX. This will not yet
1781
                // lock any coins but might still change the wallet DB by
1782
                // generating a new change address.
1783
                changeIndex, err := w.cfg.Wallet.FundPsbt(
2✔
1784
                        packet, minConfs, feeSatPerKW, account, keyScope,
2✔
1785
                        strategy, restrictUnstableUtxos,
2✔
1786
                )
2✔
1787
                if err != nil {
4✔
1788
                        return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
2✔
1789
                }
2✔
1790

1791
                // Now we have obtained a set of coins that can be used to fund
1792
                // the TX. Let's lock them to be sure they aren't spent by the
1793
                // time the PSBT is published. This is the action we do here
1794
                // that could cause an error. Therefore, if some of the UTXOs
1795
                // cannot be locked, the rollback of the other's locks also
1796
                // happens in this function. If we ever need to do more after
1797
                // this function, we need to extract the rollback needs to be
1798
                // extracted into a defer.
1799
                outpoints := make([]wire.OutPoint, len(packet.UnsignedTx.TxIn))
2✔
1800
                for i, txIn := range packet.UnsignedTx.TxIn {
4✔
1801
                        outpoints[i] = txIn.PreviousOutPoint
2✔
1802
                }
2✔
1803

1804
                response, err = w.lockAndCreateFundingResponse(
2✔
1805
                        packet, outpoints, changeIndex,
2✔
1806
                )
2✔
1807

2✔
1808
                return err
2✔
1809
        })
1810
        if err != nil {
4✔
1811
                return nil, err
2✔
1812
        }
2✔
1813

1814
        return response, nil
2✔
1815
}
1816

1817
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
1818
// funding coin selection algorithm that allows specifying custom inputs while
1819
// selecting coins.
1820
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
1821
        packet *psbt.Packet, minConfs int32,
1822
        changeType chanfunding.ChangeAddressType,
1823
        feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy) (
1824
        *FundPsbtResponse, error) {
2✔
1825

2✔
1826
        // We want to make sure we don't select any inputs that are already
2✔
1827
        // specified in the template. To do that, we require those inputs to
2✔
1828
        // either not belong to this lnd at all or to be already locked through
2✔
1829
        // a manual lock call by the user. Either way, they should not appear in
2✔
1830
        // the list of unspent outputs.
2✔
1831
        err := w.assertNotAvailable(packet.UnsignedTx.TxIn, minConfs, account)
2✔
1832
        if err != nil {
2✔
1833
                return nil, err
×
1834
        }
×
1835

1836
        // In case the user just specified the input outpoints of UTXOs we own,
1837
        // the fee estimation below will error out because the UTXO information
1838
        // is missing. We need to fetch the UTXO information from the wallet
1839
        // and add it to the PSBT. We ignore inputs we don't actually know as
1840
        // they could belong to another wallet.
1841
        err = w.cfg.Wallet.DecorateInputs(packet, false)
2✔
1842
        if err != nil {
2✔
1843
                return nil, fmt.Errorf("error decorating inputs: %w", err)
×
1844
        }
×
1845

1846
        // Before we select anything, we need to calculate the input, output and
1847
        // current weight amounts. While doing that we also ensure the PSBT has
1848
        // all the required information we require at this step.
1849
        var (
2✔
1850
                inputSum, outputSum btcutil.Amount
2✔
1851
                estimator           input.TxWeightEstimator
2✔
1852
        )
2✔
1853
        for i := range packet.Inputs {
4✔
1854
                in := packet.Inputs[i]
2✔
1855

2✔
1856
                err := btcwallet.EstimateInputWeight(&in, &estimator)
2✔
1857
                if err != nil {
2✔
1858
                        return nil, fmt.Errorf("error estimating input "+
×
1859
                                "weight: %w", err)
×
1860
                }
×
1861

1862
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
2✔
1863
        }
1864
        for i := range packet.UnsignedTx.TxOut {
4✔
1865
                out := packet.UnsignedTx.TxOut[i]
2✔
1866

2✔
1867
                estimator.AddOutput(out.PkScript)
2✔
1868
                outputSum += btcutil.Amount(out.Value)
2✔
1869
        }
2✔
1870

1871
        // The amount we want to fund is the total output sum plus the current
1872
        // fee estimate, minus the sum of any already specified inputs. Since we
1873
        // pass the estimator of the current transaction into the coin selection
1874
        // algorithm, we don't need to subtract the fees here.
1875
        fundingAmount := outputSum - inputSum
2✔
1876

2✔
1877
        var changeDustLimit btcutil.Amount
2✔
1878
        switch changeType {
2✔
1879
        case chanfunding.P2TRChangeAddress:
×
1880
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
×
1881

1882
        case chanfunding.P2WKHChangeAddress:
×
1883
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
×
1884

1885
        case chanfunding.ExistingChangeAddress:
2✔
1886
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
1887
                changeDustLimit = lnwallet.DustLimitForSize(
2✔
1888
                        len(changeOut.PkScript),
2✔
1889
                )
2✔
1890
        }
1891

1892
        // Do we already have enough inputs specified to pay for the TX as it
1893
        // is? In that case we only need to allocate any change, if there is
1894
        // any.
1895
        packetFeeNoChange := feeRate.FeeForWeight(estimator.Weight())
2✔
1896
        if inputSum >= outputSum+packetFeeNoChange {
2✔
1897
                // Calculate the packet's fee with a change output so, so we can
×
1898
                // let the coin selection algorithm decide whether to use a
×
1899
                // change output or not.
×
1900
                switch changeType {
×
1901
                case chanfunding.P2TRChangeAddress:
×
1902
                        estimator.AddP2TROutput()
×
1903

1904
                case chanfunding.P2WKHChangeAddress:
×
1905
                        estimator.AddP2WKHOutput()
×
1906
                }
1907
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
×
1908

×
1909
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
×
1910
                        inputSum, outputSum, packetFeeNoChange,
×
1911
                        packetFeeWithChange, changeDustLimit, changeType,
×
1912
                )
×
1913
                if err != nil {
×
1914
                        return nil, fmt.Errorf("error calculating change "+
×
1915
                                "amount: %w", err)
×
1916
                }
×
1917

1918
                // We shouldn't get into this branch if the input sum isn't
1919
                // enough to pay for the current package without a change
1920
                // output. So this should never be non-zero.
1921
                if needMore != 0 {
×
1922
                        return nil, fmt.Errorf("internal error with change " +
×
1923
                                "amount calculation")
×
1924
                }
×
1925

1926
                if changeAmt > 0 {
×
1927
                        changeIndex, err = w.handleChange(
×
1928
                                packet, changeIndex, int64(changeAmt),
×
1929
                                changeType, account,
×
1930
                        )
×
1931
                        if err != nil {
×
1932
                                return nil, fmt.Errorf("error handling change "+
×
1933
                                        "amount: %w", err)
×
1934
                        }
×
1935
                }
1936

1937
                // We're done. Let's serialize and return the updated package.
1938
                return w.lockAndCreateFundingResponse(packet, nil, changeIndex)
×
1939
        }
1940

1941
        // The RPC parsing part is now over. Several of the following operations
1942
        // require us to hold the global coin selection lock, so we do the rest
1943
        // of the tasks while holding the lock. The result is a list of locked
1944
        // UTXOs.
1945
        var response *FundPsbtResponse
2✔
1946
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
1947
                // Get a list of all unspent witness outputs.
2✔
1948
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
1949
                        minConfs, defaultMaxConf, account,
2✔
1950
                )
2✔
1951
                if err != nil {
2✔
1952
                        return err
×
1953
                }
×
1954

1955
                coins := make([]base.Coin, len(utxos))
2✔
1956
                for i, utxo := range utxos {
4✔
1957
                        coins[i] = base.Coin{
2✔
1958
                                TxOut: wire.TxOut{
2✔
1959
                                        Value:    int64(utxo.Value),
2✔
1960
                                        PkScript: utxo.PkScript,
2✔
1961
                                },
2✔
1962
                                OutPoint: utxo.OutPoint,
2✔
1963
                        }
2✔
1964
                }
2✔
1965

1966
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
2✔
1967
                        feeRate, fundingAmount, changeDustLimit, coins,
2✔
1968
                        strategy, estimator, changeType,
2✔
1969
                )
2✔
1970
                if err != nil {
2✔
1971
                        return fmt.Errorf("error selecting coins: %w", err)
×
1972
                }
×
1973

1974
                if changeAmount > 0 {
4✔
1975
                        changeIndex, err = w.handleChange(
2✔
1976
                                packet, changeIndex, int64(changeAmount),
2✔
1977
                                changeType, account,
2✔
1978
                        )
2✔
1979
                        if err != nil {
2✔
1980
                                return fmt.Errorf("error handling change "+
×
1981
                                        "amount: %w", err)
×
1982
                        }
×
1983
                }
1984

1985
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
2✔
1986
                for i := range selectedCoins {
4✔
1987
                        coin := selectedCoins[i]
2✔
1988
                        addedOutpoints[i] = coin.OutPoint
2✔
1989

2✔
1990
                        packet.UnsignedTx.TxIn = append(
2✔
1991
                                packet.UnsignedTx.TxIn, &wire.TxIn{
2✔
1992
                                        PreviousOutPoint: coin.OutPoint,
2✔
1993
                                },
2✔
1994
                        )
2✔
1995
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
2✔
1996
                                WitnessUtxo: &coin.TxOut,
2✔
1997
                        })
2✔
1998
                }
2✔
1999

2000
                // Now that we've added the bare TX inputs, we also need to add
2001
                // the more verbose input information to the packet, so a future
2002
                // signer doesn't need to do any lookups. We skip any inputs
2003
                // that our wallet doesn't own.
2004
                err = w.cfg.Wallet.DecorateInputs(packet, false)
2✔
2005
                if err != nil {
2✔
2006
                        return fmt.Errorf("error decorating inputs: %w", err)
×
2007
                }
×
2008

2009
                response, err = w.lockAndCreateFundingResponse(
2✔
2010
                        packet, addedOutpoints, changeIndex,
2✔
2011
                )
2✔
2012

2✔
2013
                return err
2✔
2014
        })
2015
        if err != nil {
2✔
2016
                return nil, err
×
2017
        }
×
2018

2019
        return response, nil
2✔
2020
}
2021

2022
// assertNotAvailable makes sure the specified inputs either don't belong to
2023
// this node or are already locked by the user.
2024
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2025
        account string) error {
2✔
2026

2✔
2027
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
2028
                // Get a list of all unspent witness outputs.
2✔
2029
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
2030
                        minConfs, defaultMaxConf, account,
2✔
2031
                )
2✔
2032
                if err != nil {
2✔
2033
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2034
                }
×
2035

2036
                // We'll now check that none of the inputs specified in the
2037
                // template are available to us. That means they either don't
2038
                // belong to us or are already locked by the user.
2039
                for _, txIn := range inputs {
4✔
2040
                        for _, utxo := range utxos {
4✔
2041
                                if txIn.PreviousOutPoint == utxo.OutPoint {
2✔
2042
                                        return fmt.Errorf("input %v is not "+
×
2043
                                                "locked", txIn.PreviousOutPoint)
×
2044
                                }
×
2045
                        }
2046
                }
2047

2048
                return nil
2✔
2049
        })
2050
}
2051

2052
// lockAndCreateFundingResponse locks the given outpoints and creates a funding
2053
// response with the serialized PSBT, the change index and the locked UTXOs.
2054
func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet,
2055
        newOutpoints []wire.OutPoint, changeIndex int32) (*FundPsbtResponse,
2056
        error) {
2✔
2057

2✔
2058
        // Make sure we can properly serialize the packet. If this goes wrong
2✔
2059
        // then something isn't right with the inputs, and we probably shouldn't
2✔
2060
        // try to lock any of them.
2✔
2061
        var buf bytes.Buffer
2✔
2062
        err := packet.Serialize(&buf)
2✔
2063
        if err != nil {
2✔
2064
                return nil, fmt.Errorf("error serializing funded PSBT: %w", err)
×
2065
        }
×
2066

2067
        locks, err := lockInputs(w.cfg.Wallet, newOutpoints)
2✔
2068
        if err != nil {
2✔
2069
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2070
        }
×
2071

2072
        // Convert the lock leases to the RPC format.
2073
        rpcLocks := marshallLeases(locks)
2✔
2074

2✔
2075
        return &FundPsbtResponse{
2✔
2076
                FundedPsbt:        buf.Bytes(),
2✔
2077
                ChangeOutputIndex: changeIndex,
2✔
2078
                LockedUtxos:       rpcLocks,
2✔
2079
        }, nil
2✔
2080
}
2081

2082
// handleChange is a closure that either adds the non-zero change amount to an
2083
// existing output or creates a change output. The function returns the new
2084
// change output index if a new change output was added.
2085
func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
2086
        changeAmount int64, changeType chanfunding.ChangeAddressType,
2087
        changeAccount string) (int32, error) {
2✔
2088

2✔
2089
        // Does an existing output get the change?
2✔
2090
        if changeIndex >= 0 {
4✔
2091
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
2092
                changeOut.Value += changeAmount
2✔
2093

2✔
2094
                return changeIndex, nil
2✔
2095
        }
2✔
2096

2097
        // The user requested a new change output.
2098
        addrType := addrTypeFromChangeAddressType(changeType)
×
2099
        changeAddr, err := w.cfg.Wallet.NewAddress(
×
2100
                addrType, true, changeAccount,
×
2101
        )
×
2102
        if err != nil {
×
2103
                return 0, fmt.Errorf("could not derive change address: %w", err)
×
2104
        }
×
2105

2106
        changeScript, err := txscript.PayToAddrScript(changeAddr)
×
2107
        if err != nil {
×
2108
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2109
        }
×
2110

2111
        // We need to add the derivation info for the change address in case it
2112
        // is a P2TR address. This is mostly to prove it's a bare BIP-0086
2113
        // address, which is required for some protocols (such as Taproot
2114
        // Assets).
2115
        pOut := psbt.POutput{}
×
2116
        _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
×
2117
        if isTaprootChangeAddr {
×
2118
                changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
×
2119
                if err != nil {
×
2120
                        return 0, fmt.Errorf("could not get address info: %w",
×
2121
                                err)
×
2122
                }
×
2123

2124
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2125
                        changeAddrInfo,
×
2126
                )
×
2127
                if err != nil {
×
2128
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2129
                                "%w", err)
×
2130
                }
×
2131

2132
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2133
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2134
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2135
                        trDeriv,
×
2136
                }
×
2137
        }
2138

2139
        newChangeIndex := int32(len(packet.Outputs))
×
2140
        packet.UnsignedTx.TxOut = append(
×
2141
                packet.UnsignedTx.TxOut, &wire.TxOut{
×
2142
                        Value:    changeAmount,
×
2143
                        PkScript: changeScript,
×
2144
                },
×
2145
        )
×
2146
        packet.Outputs = append(packet.Outputs, pOut)
×
2147

×
2148
        return newChangeIndex, nil
×
2149
}
2150

2151
// marshallLeases converts the lock leases to the RPC format.
2152
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
2✔
2153
        rpcLocks := make([]*UtxoLease, len(locks))
2✔
2154
        for idx, lock := range locks {
4✔
2155
                lock := lock
2✔
2156

2✔
2157
                rpcLocks[idx] = &UtxoLease{
2✔
2158
                        Id:         lock.LockID[:],
2✔
2159
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
2✔
2160
                        Expiration: uint64(lock.Expiration.Unix()),
2✔
2161
                        PkScript:   lock.PkScript,
2✔
2162
                        Value:      uint64(lock.Value),
2✔
2163
                }
2✔
2164
        }
2✔
2165

2166
        return rpcLocks
2✔
2167
}
2168

2169
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2170
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2171
// it returns nil.
2172
func keyScopeFromChangeAddressType(
2173
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
2✔
2174

2✔
2175
        switch changeAddressType {
2✔
2176
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
2✔
2177
                return &waddrmgr.KeyScopeBIP0086
2✔
2178

2179
        default:
2✔
2180
                return nil
2✔
2181
        }
2182
}
2183

2184
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2185
// lnwallet.AddressType.
2186
func addrTypeFromChangeAddressType(
2187
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
×
2188

×
2189
        switch changeAddressType {
×
2190
        case chanfunding.P2TRChangeAddress:
×
2191
                return lnwallet.TaprootPubkey
×
2192

2193
        default:
×
2194
                return lnwallet.WitnessPubKey
×
2195
        }
2196
}
2197

2198
// SignPsbt expects a partial transaction with all inputs and outputs fully
2199
// declared and tries to sign all unsigned inputs that have all required fields
2200
// (UTXO information, BIP32 derivation information, witness or sig scripts)
2201
// set.
2202
// If no error is returned, the PSBT is ready to be given to the next signer or
2203
// to be finalized if lnd was the last signer.
2204
//
2205
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
2206
// perform any other tasks (such as coin selection, UTXO locking or
2207
// input/output/fee value validation, PSBT finalization). Any input that is
2208
// incomplete will be skipped.
2209
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
2210
        *SignPsbtResponse, error) {
2✔
2211

2✔
2212
        packet, err := psbt.NewFromRawBytes(
2✔
2213
                bytes.NewReader(req.FundedPsbt), false,
2✔
2214
        )
2✔
2215
        if err != nil {
2✔
2216
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2217
                        req.FundedPsbt)
×
2218
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2219
        }
×
2220

2221
        // Before we attempt to sign the packet, ensure that every input either
2222
        // has a witness UTXO, or a non witness UTXO.
2223
        for idx := range packet.UnsignedTx.TxIn {
4✔
2224
                in := packet.Inputs[idx]
2✔
2225

2✔
2226
                // Doesn't have either a witness or non witness UTXO so we need
2✔
2227
                // to exit here as otherwise signing will fail.
2✔
2228
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
4✔
2229
                        return nil, fmt.Errorf("input (index=%v) doesn't "+
2✔
2230
                                "specify any UTXO info", idx)
2✔
2231
                }
2✔
2232
        }
2233

2234
        // Let the wallet do the heavy lifting. This will sign all inputs that
2235
        // we have the UTXO for. If some inputs can't be signed and don't have
2236
        // witness data attached, they will just be skipped.
2237
        signedInputs, err := w.cfg.Wallet.SignPsbt(packet)
2✔
2238
        if err != nil {
2✔
2239
                return nil, fmt.Errorf("error signing PSBT: %w", err)
×
2240
        }
×
2241

2242
        // Serialize the signed PSBT in both the packet and wire format.
2243
        var signedPsbtBytes bytes.Buffer
2✔
2244
        err = packet.Serialize(&signedPsbtBytes)
2✔
2245
        if err != nil {
2✔
2246
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2247
        }
×
2248

2249
        return &SignPsbtResponse{
2✔
2250
                SignedPsbt:   signedPsbtBytes.Bytes(),
2✔
2251
                SignedInputs: signedInputs,
2✔
2252
        }, nil
2✔
2253
}
2254

2255
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
2256
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
2257
// the last signer of the transaction. That means, if there are any unsigned
2258
// non-witness inputs or inputs without UTXO information attached or inputs
2259
// without witness data that do not belong to lnd's wallet, this method will
2260
// fail. If no error is returned, the PSBT is ready to be extracted and the
2261
// final TX within to be broadcast.
2262
//
2263
// NOTE: This method does NOT publish the transaction once finalized. It is the
2264
// caller's responsibility to either publish the transaction on success or
2265
// unlock/release any locked UTXOs in case of an error in this method.
2266
func (w *WalletKit) FinalizePsbt(_ context.Context,
2267
        req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
2✔
2268

2✔
2269
        // We'll assume the PSBT was funded by the default account unless
2✔
2270
        // otherwise specified.
2✔
2271
        account := lnwallet.DefaultAccountName
2✔
2272
        if req.Account != "" {
2✔
2273
                account = req.Account
×
2274
        }
×
2275

2276
        // Parse the funded PSBT.
2277
        packet, err := psbt.NewFromRawBytes(
2✔
2278
                bytes.NewReader(req.FundedPsbt), false,
2✔
2279
        )
2✔
2280
        if err != nil {
2✔
2281
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2282
        }
×
2283

2284
        // The only check done at this level is to validate that the PSBT is
2285
        // not complete. The wallet performs all other checks.
2286
        if packet.IsComplete() {
2✔
2287
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2288
        }
×
2289

2290
        // Let the wallet do the heavy lifting. This will sign all inputs that
2291
        // we have the UTXO for. If some inputs can't be signed and don't have
2292
        // witness data attached, this will fail.
2293
        err = w.cfg.Wallet.FinalizePsbt(packet, account)
2✔
2294
        if err != nil {
2✔
2295
                return nil, fmt.Errorf("error finalizing PSBT: %w", err)
×
2296
        }
×
2297

2298
        var (
2✔
2299
                finalPsbtBytes bytes.Buffer
2✔
2300
                finalTxBytes   bytes.Buffer
2✔
2301
        )
2✔
2302

2✔
2303
        // Serialize the finalized PSBT in both the packet and wire format.
2✔
2304
        err = packet.Serialize(&finalPsbtBytes)
2✔
2305
        if err != nil {
2✔
2306
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2307
        }
×
2308
        finalTx, err := psbt.Extract(packet)
2✔
2309
        if err != nil {
2✔
2310
                return nil, fmt.Errorf("unable to extract final TX: %w", err)
×
2311
        }
×
2312
        err = finalTx.Serialize(&finalTxBytes)
2✔
2313
        if err != nil {
2✔
2314
                return nil, fmt.Errorf("error serializing final TX: %w", err)
×
2315
        }
×
2316

2317
        return &FinalizePsbtResponse{
2✔
2318
                SignedPsbt: finalPsbtBytes.Bytes(),
2✔
2319
                RawFinalTx: finalTxBytes.Bytes(),
2✔
2320
        }, nil
2✔
2321
}
2322

2323
// marshalWalletAccount converts the properties of an account into its RPC
2324
// representation.
2325
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2326
        account *waddrmgr.AccountProperties) (*Account, error) {
2✔
2327

2✔
2328
        var addrType AddressType
2✔
2329
        switch account.KeyScope {
2✔
2330
        case waddrmgr.KeyScopeBIP0049Plus:
2✔
2331
                // No address schema present represents the traditional BIP-0049
2✔
2332
                // address derivation scheme.
2✔
2333
                if account.AddrSchema == nil {
4✔
2334
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
2✔
2335
                        break
2✔
2336
                }
2337

2338
                switch *account.AddrSchema {
2✔
2339
                case waddrmgr.KeyScopeBIP0049AddrSchema:
2✔
2340
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
2✔
2341

2342
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
2✔
2343
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
2✔
2344

2345
                default:
×
2346
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2347
                                *account.AddrSchema)
×
2348
                }
2349

2350
        case waddrmgr.KeyScopeBIP0084:
2✔
2351
                addrType = AddressType_WITNESS_PUBKEY_HASH
2✔
2352

2353
        case waddrmgr.KeyScopeBIP0086:
2✔
2354
                addrType = AddressType_TAPROOT_PUBKEY
2✔
2355

2356
        case internalScope:
2✔
2357
                addrType = AddressType_WITNESS_PUBKEY_HASH
2✔
2358

2359
        default:
×
2360
                return nil, fmt.Errorf("account %v has unsupported "+
×
2361
                        "key scope %v", account.AccountName, account.KeyScope)
×
2362
        }
2363

2364
        rpcAccount := &Account{
2✔
2365
                Name:             account.AccountName,
2✔
2366
                AddressType:      addrType,
2✔
2367
                ExternalKeyCount: account.ExternalKeyCount,
2✔
2368
                InternalKeyCount: account.InternalKeyCount,
2✔
2369
                WatchOnly:        account.IsWatchOnly,
2✔
2370
        }
2✔
2371

2✔
2372
        // The remaining fields can only be done on accounts other than the
2✔
2373
        // default imported one existing within each key scope.
2✔
2374
        if account.AccountName != waddrmgr.ImportedAddrAccountName {
4✔
2375
                nonHardenedIndex := account.AccountPubKey.ChildIndex() -
2✔
2376
                        hdkeychain.HardenedKeyStart
2✔
2377
                rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
2✔
2378
                if account.MasterKeyFingerprint != 0 {
2✔
2379
                        var mkfp [4]byte
×
2380
                        binary.BigEndian.PutUint32(
×
2381
                                mkfp[:], account.MasterKeyFingerprint,
×
2382
                        )
×
2383
                        rpcAccount.MasterKeyFingerprint = mkfp[:]
×
2384
                }
×
2385
                rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
2✔
2386
                        account.KeyScope, nonHardenedIndex)
2✔
2387
        }
2388

2389
        return rpcAccount, nil
2✔
2390
}
2391

2392
// marshalWalletAddressList converts the list of address into its RPC
2393
// representation.
2394
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2395
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
2✔
2396

2✔
2397
        // Get the RPC representation of account.
2✔
2398
        rpcAccount, err := marshalWalletAccount(
2✔
2399
                w.internalScope(), account,
2✔
2400
        )
2✔
2401
        if err != nil {
2✔
2402
                return nil, err
×
2403
        }
×
2404

2405
        addresses := make([]*AddressProperty, len(addressList))
2✔
2406
        for idx, addr := range addressList {
4✔
2407
                var pubKeyBytes []byte
2✔
2408
                if addr.PublicKey != nil {
4✔
2409
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
2✔
2410
                }
2✔
2411
                addresses[idx] = &AddressProperty{
2✔
2412
                        Address:        addr.Address,
2✔
2413
                        IsInternal:     addr.Internal,
2✔
2414
                        Balance:        int64(addr.Balance),
2✔
2415
                        DerivationPath: addr.DerivationPath,
2✔
2416
                        PublicKey:      pubKeyBytes,
2✔
2417
                }
2✔
2418
        }
2419

2420
        rpcAddressList := &AccountWithAddresses{
2✔
2421
                Name:           rpcAccount.Name,
2✔
2422
                AddressType:    rpcAccount.AddressType,
2✔
2423
                DerivationPath: rpcAccount.DerivationPath,
2✔
2424
                Addresses:      addresses,
2✔
2425
        }
2✔
2426

2✔
2427
        return rpcAddressList, nil
2✔
2428
}
2429

2430
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2431
// name and key scope filter can be provided to filter through all of the wallet
2432
// accounts and return only those matching.
2433
func (w *WalletKit) ListAccounts(ctx context.Context,
2434
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
2✔
2435

2✔
2436
        // Map the supported address types into their corresponding key scope.
2✔
2437
        var keyScopeFilter *waddrmgr.KeyScope
2✔
2438
        switch req.AddressType {
2✔
2439
        case AddressType_UNKNOWN:
2✔
2440
                break
2✔
2441

2442
        case AddressType_WITNESS_PUBKEY_HASH:
2✔
2443
                keyScope := waddrmgr.KeyScopeBIP0084
2✔
2444
                keyScopeFilter = &keyScope
2✔
2445

2446
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2447
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
2✔
2448

2✔
2449
                keyScope := waddrmgr.KeyScopeBIP0049Plus
2✔
2450
                keyScopeFilter = &keyScope
2✔
2451

2452
        case AddressType_TAPROOT_PUBKEY:
2✔
2453
                keyScope := waddrmgr.KeyScopeBIP0086
2✔
2454
                keyScopeFilter = &keyScope
2✔
2455

2456
        default:
×
2457
                return nil, fmt.Errorf("unhandled address type %v",
×
2458
                        req.AddressType)
×
2459
        }
2460

2461
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
2✔
2462
        if err != nil {
2✔
2463
                return nil, err
×
2464
        }
×
2465

2466
        rpcAccounts := make([]*Account, 0, len(accounts))
2✔
2467
        for _, account := range accounts {
4✔
2468
                // Don't include the default imported accounts created by the
2✔
2469
                // wallet in the response if they don't have any keys imported.
2✔
2470
                if account.AccountName == waddrmgr.ImportedAddrAccountName &&
2✔
2471
                        account.ImportedKeyCount == 0 {
4✔
2472

2✔
2473
                        continue
2✔
2474
                }
2475

2476
                rpcAccount, err := marshalWalletAccount(
2✔
2477
                        w.internalScope(), account,
2✔
2478
                )
2✔
2479
                if err != nil {
2✔
2480
                        return nil, err
×
2481
                }
×
2482
                rpcAccounts = append(rpcAccounts, rpcAccount)
2✔
2483
        }
2484

2485
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
2✔
2486
}
2487

2488
// RequiredReserve returns the minimum amount of satoshis that should be
2489
// kept in the wallet in order to fee bump anchor channels if necessary.
2490
// The value scales with the number of public anchor channels but is
2491
// capped at a maximum.
2492
func (w *WalletKit) RequiredReserve(ctx context.Context,
2493
        req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
2✔
2494

2✔
2495
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
2✔
2496
        if err != nil {
2✔
2497
                return nil, err
×
2498
        }
×
2499

2500
        additionalChans := req.AdditionalPublicChannels
2✔
2501
        totalChans := uint32(numAnchorChans) + additionalChans
2✔
2502
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
2✔
2503

2✔
2504
        return &RequiredReserveResponse{
2✔
2505
                RequiredReserve: int64(reserved),
2✔
2506
        }, nil
2✔
2507
}
2508

2509
// ListAddresses retrieves all the addresses along with their balance. An
2510
// account name filter can be provided to filter through all of the
2511
// wallet accounts and return the addresses of only those matching.
2512
func (w *WalletKit) ListAddresses(ctx context.Context,
2513
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
2✔
2514

2✔
2515
        addressLists, err := w.cfg.Wallet.ListAddresses(
2✔
2516
                req.AccountName,
2✔
2517
                req.ShowCustomAccounts,
2✔
2518
        )
2✔
2519
        if err != nil {
2✔
2520
                return nil, err
×
2521
        }
×
2522

2523
        // Create a slice of accounts from addressLists map.
2524
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
2✔
2525
        for account := range addressLists {
4✔
2526
                accounts = append(accounts, account)
2✔
2527
        }
2✔
2528

2529
        // Sort the accounts by derivation path.
2530
        sort.Slice(accounts, func(i, j int) bool {
4✔
2531
                scopeI := accounts[i].KeyScope
2✔
2532
                scopeJ := accounts[j].KeyScope
2✔
2533
                if scopeI.Purpose == scopeJ.Purpose {
2✔
2534
                        if scopeI.Coin == scopeJ.Coin {
×
2535
                                acntNumI := accounts[i].AccountNumber
×
2536
                                acntNumJ := accounts[j].AccountNumber
×
2537
                                return acntNumI < acntNumJ
×
2538
                        }
×
2539

2540
                        return scopeI.Coin < scopeJ.Coin
×
2541
                }
2542

2543
                return scopeI.Purpose < scopeJ.Purpose
2✔
2544
        })
2545

2546
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
2✔
2547
        for _, account := range accounts {
4✔
2548
                addressList := addressLists[account]
2✔
2549
                rpcAddressList, err := marshalWalletAddressList(
2✔
2550
                        w, account, addressList,
2✔
2551
                )
2✔
2552
                if err != nil {
2✔
2553
                        return nil, err
×
2554
                }
×
2555

2556
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
2✔
2557
        }
2558

2559
        return &ListAddressesResponse{
2✔
2560
                AccountWithAddresses: rpcAddressLists,
2✔
2561
        }, nil
2✔
2562
}
2563

2564
// parseAddrType parses an address type from its RPC representation to a
2565
// *waddrmgr.AddressType.
2566
func parseAddrType(addrType AddressType,
2567
        required bool) (*waddrmgr.AddressType, error) {
2✔
2568

2✔
2569
        switch addrType {
2✔
2570
        case AddressType_UNKNOWN:
×
2571
                if required {
×
2572
                        return nil, fmt.Errorf("an address type must be " +
×
2573
                                "specified")
×
2574
                }
×
2575
                return nil, nil
×
2576

2577
        case AddressType_WITNESS_PUBKEY_HASH:
2✔
2578
                addrTyp := waddrmgr.WitnessPubKey
2✔
2579
                return &addrTyp, nil
2✔
2580

2581
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
2✔
2582
                addrTyp := waddrmgr.NestedWitnessPubKey
2✔
2583
                return &addrTyp, nil
2✔
2584

2585
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
2✔
2586
                addrTyp := waddrmgr.WitnessPubKey
2✔
2587
                return &addrTyp, nil
2✔
2588

2589
        case AddressType_TAPROOT_PUBKEY:
2✔
2590
                addrTyp := waddrmgr.TaprootPubKey
2✔
2591
                return &addrTyp, nil
2✔
2592

2593
        default:
×
2594
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2595
        }
2596
}
2597

2598
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2599
// transaction or a signature. It is prepended in front of the message and
2600
// follows the same standard as bitcoin core and btcd.
2601
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2602

2603
// SignMessageWithAddr signs a message with the private key of the provided
2604
// address. The address needs to belong to the lnd wallet.
2605
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2606
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
2✔
2607

2✔
2608
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
2✔
2609
        if err != nil {
2✔
2610
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2611
        }
×
2612

2613
        if !addr.IsForNet(w.cfg.ChainParams) {
2✔
2614
                return nil, fmt.Errorf("encoded address is for "+
×
2615
                        "the wrong network %s", req.Addr)
×
2616
        }
×
2617

2618
        // Fetch address infos from own wallet and check whether it belongs
2619
        // to the lnd wallet.
2620
        managedAddr, err := w.cfg.Wallet.AddressInfo(addr)
2✔
2621
        if err != nil {
2✔
2622
                return nil, fmt.Errorf("address could not be found in the "+
×
2623
                        "wallet database: %w", err)
×
2624
        }
×
2625

2626
        // Verifying by checking the interface type that the wallet knows about
2627
        // the public and private keys so it can sign the message with the
2628
        // private key of this address.
2629
        pubKey, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
2✔
2630
        if !ok {
2✔
2631
                return nil, fmt.Errorf("private key to address is unknown")
×
2632
        }
×
2633

2634
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
2✔
2635
        if err != nil {
2✔
2636
                return nil, err
×
2637
        }
×
2638

2639
        // For all address types (P2WKH, NP2WKH,P2TR) the ECDSA compact signing
2640
        // algorithm is used. For P2TR addresses this represents a special case.
2641
        // ECDSA is used to create a compact signature which makes the public
2642
        // key of the signature recoverable. For Schnorr no known compact
2643
        // signing algorithm exists yet.
2644
        privKey, err := pubKey.PrivKey()
2✔
2645
        if err != nil {
2✔
2646
                return nil, fmt.Errorf("no private key could be "+
×
2647
                        "fetched from wallet database: %w", err)
×
2648
        }
×
2649

2650
        sigBytes, err := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
2✔
2651
        if err != nil {
2✔
2652
                return nil, fmt.Errorf("failed to create signature: %w", err)
2✔
2653
        }
2✔
2654

2✔
2655
        // Bitcoin signatures are base64 encoded (being compatible with
2✔
2656
        // bitcoin-core and btcd).
2✔
2657
        sig := base64.StdEncoding.EncodeToString(sigBytes)
2✔
2658

2✔
2659
        return &SignMessageWithAddrResponse{
2660
                Signature: sig,
2661
        }, nil
2662
}
2663

2664
// VerifyMessageWithAddr verifies a signature on a message with a provided
2665
// address, it checks both the validity of the signature itself and then
2666
// verifies whether the signature corresponds to the public key of the
2667
// provided address. There is no dependence on the private key of the address
2668
// therefore also external addresses are allowed to verify signatures.
2669
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
2✔
2670
func (w *WalletKit) VerifyMessageWithAddr(_ context.Context,
2✔
2671
        req *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse,
2✔
2672
        error) {
2✔
2673

×
2674
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
×
2675
        if err != nil {
×
2676
                return nil, fmt.Errorf("malformed base64 encoding of "+
2677
                        "the signature: %w", err)
2✔
2678
        }
2✔
2679

×
2680
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
×
2681
        if err != nil {
2682
                return nil, err
2✔
2683
        }
2✔
2684

×
2685
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
×
2686
        if err != nil {
×
2687
                return nil, fmt.Errorf("unable to recover public key "+
2688
                        "from compact signature: %w", err)
2✔
2689
        }
4✔
2690

2✔
2691
        var serializedPubkey []byte
2✔
2692
        if wasCompressed {
×
2693
                serializedPubkey = pk.SerializeCompressed()
×
2694
        } else {
2695
                serializedPubkey = pk.SerializeUncompressed()
2✔
2696
        }
2✔
2697

×
2698
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
×
2699
        if err != nil {
2700
                return nil, fmt.Errorf("unable to decode address: %w", err)
2✔
2701
        }
×
2702

×
2703
        if !addr.IsForNet(w.cfg.ChainParams) {
×
2704
                return nil, fmt.Errorf("encoded address is for"+
2705
                        "the wrong network %s", req.Addr)
2✔
2706
        }
2✔
2707

2✔
2708
        var (
2✔
2709
                address    btcutil.Address
2✔
2710
                pubKeyHash = btcutil.Hash160(serializedPubkey)
2✔
2711
        )
2✔
2712

2✔
2713
        // Ensure the address is one of the supported types.
2✔
2714
        switch addr.(type) {
2✔
2715
        case *btcutil.AddressPubKeyHash:
2✔
2716
                address, err = btcutil.NewAddressPubKeyHash(
2✔
2717
                        pubKeyHash, w.cfg.ChainParams,
×
2718
                )
×
2719
                if err != nil {
2720
                        return nil, err
2✔
2721
                }
2✔
2722

2✔
2723
        case *btcutil.AddressWitnessPubKeyHash:
2✔
2724
                address, err = btcutil.NewAddressWitnessPubKeyHash(
2✔
2725
                        pubKeyHash, w.cfg.ChainParams,
×
2726
                )
×
2727
                if err != nil {
2728
                        return nil, err
2✔
2729
                }
2✔
2730

2✔
2731
        case *btcutil.AddressScriptHash:
2✔
2732
                // Check if address is a Nested P2WKH (NP2WKH).
2✔
2733
                address, err = btcutil.NewAddressWitnessPubKeyHash(
2✔
2734
                        pubKeyHash, w.cfg.ChainParams,
×
2735
                )
×
2736
                if err != nil {
2737
                        return nil, err
2✔
2738
                }
2✔
2739

×
2740
                witnessScript, err := txscript.PayToAddrScript(address)
×
2741
                if err != nil {
2742
                        return nil, err
2✔
2743
                }
2✔
2744

2✔
2745
                address, err = btcutil.NewAddressScriptHashFromHash(
2✔
2746
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
×
2747
                )
×
2748
                if err != nil {
2749
                        return nil, err
2✔
2750
                }
2✔
2751

2✔
2752
        case *btcutil.AddressTaproot:
2✔
2753
                // Only addresses without a tapscript are allowed because
2✔
2754
                // the verification is using the internal key.
2✔
2755
                tapKey := txscript.ComputeTaprootKeyNoScript(pk)
2✔
2756
                address, err = btcutil.NewAddressTaproot(
2✔
2757
                        schnorr.SerializePubKey(tapKey),
2✔
2758
                        w.cfg.ChainParams,
×
2759
                )
×
2760
                if err != nil {
2761
                        return nil, err
×
2762
                }
×
2763

2764
        default:
2765
                return nil, fmt.Errorf("unsupported address type")
2✔
2766
        }
2✔
2767

2✔
2768
        return &VerifyMessageWithAddrResponse{
2✔
2769
                Valid:  req.Addr == address.EncodeAddress(),
2770
                Pubkey: serializedPubkey,
2771
        }, nil
2772
}
2773

2774
// ImportAccount imports an account backed by an account extended public key.
2775
// The master key fingerprint denotes the fingerprint of the root key
2776
// corresponding to the account public key (also known as the key with
2777
// derivation path m/). This may be required by some hardware wallets for proper
2778
// identification and signing.
2779
//
2780
// The address type can usually be inferred from the key's version, but may be
2781
// required for certain keys to map them into the proper scope.
2782
//
2783
// For BIP-0044 keys, an address type must be specified as we intend to not
2784
// support importing BIP-0044 keys into the wallet using the legacy
2785
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
2786
// the standard BIP-0049 derivation scheme, while a witness address type will
2787
// force the standard BIP-0084 derivation scheme.
2788
//
2789
// For BIP-0049 keys, an address type must also be specified to make a
2790
// distinction between the standard BIP-0049 address schema (nested witness
2791
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
2✔
2792
// externally, witness pubkeys internally).
2✔
2793
func (w *WalletKit) ImportAccount(_ context.Context,
2✔
2794
        req *ImportAccountRequest) (*ImportAccountResponse, error) {
2✔
2795

×
2796
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
×
2797
        if err != nil {
2798
                return nil, err
2✔
2799
        }
2✔
2800

2801
        var mkfp uint32
2802
        switch len(req.MasterKeyFingerprint) {
2✔
2803
        // No master key fingerprint provided, which is fine as it's not
2804
        // required.
×
2805
        case 0:
×
2806
        // Expected length.
×
2807
        case 4:
×
2808
                mkfp = binary.BigEndian.Uint32(req.MasterKeyFingerprint)
×
2809
        default:
2810
                return nil, errors.New("invalid length for master key " +
2811
                        "fingerprint, expected 4 bytes in big-endian")
2✔
2812
        }
2✔
2813

×
2814
        addrType, err := parseAddrType(req.AddressType, false)
×
2815
        if err != nil {
2816
                return nil, err
2✔
2817
        }
2✔
2818

2✔
2819
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
4✔
2820
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
2✔
2821
        )
2✔
2822
        if err != nil {
2823
                return nil, err
2✔
2824
        }
2✔
2825

×
2826
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
×
2827
        if err != nil {
2828
                return nil, err
2✔
2829
        }
4✔
2830

2✔
2831
        resp := &ImportAccountResponse{Account: rpcAccount}
2✔
2832
        if !req.DryRun {
2833
                return resp, nil
×
2834
        }
×
2835

×
2836
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
2837
        for i := 0; i < len(extAddrs); i++ {
×
2838
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
2839
        }
×
2840
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
2841
        for i := 0; i < len(intAddrs); i++ {
2842
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
2843
        }
2844

2845
        return resp, nil
2846
}
2847

2848
// ImportPublicKey imports a single derived public key into the wallet. The
2849
// address type can usually be inferred from the key's version, but in the case
2850
// of legacy versions (xpub, tpub), an address type must be specified as we
2851
// intend to not support importing BIP-44 keys into the wallet using the legacy
2852
// pay-to-pubkey-hash (P2PKH) scheme. For Taproot keys, this will only watch
2853
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
2✔
2854
// spend or script spend outputs.
2✔
2855
func (w *WalletKit) ImportPublicKey(_ context.Context,
2✔
2856
        req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
2✔
2857

2✔
2858
        var (
2✔
2859
                pubKey *btcec.PublicKey
2✔
2860
                err    error
2✔
2861
        )
2✔
2862
        switch req.AddressType {
2863
        case AddressType_TAPROOT_PUBKEY:
2✔
2864
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
2✔
2865

2866
        default:
2✔
2867
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
×
2868
        }
×
2869
        if err != nil {
2870
                return nil, err
2✔
2871
        }
2✔
2872

×
2873
        addrType, err := parseAddrType(req.AddressType, true)
×
2874
        if err != nil {
2875
                return nil, err
2✔
2876
        }
×
2877

×
2878
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
2879
                return nil, err
2✔
2880
        }
2881

2882
        return &ImportPublicKeyResponse{}, nil
2883
}
2884

2885
// ImportTapscript imports a Taproot script and internal key and adds the
2886
// resulting Taproot output key as a watch-only output script into the wallet.
2887
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
2888
// path) use ImportPublicKey.
2889
//
2890
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
2✔
2891
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
2✔
2892
func (w *WalletKit) ImportTapscript(_ context.Context,
2✔
2893
        req *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
2✔
2894

×
2895
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
×
2896
        if err != nil {
2897
                return nil, fmt.Errorf("error parsing internal key: %w", err)
2✔
2898
        }
2✔
2899

2✔
2900
        var tapscript *waddrmgr.Tapscript
2✔
2901
        switch {
2✔
2902
        case req.GetFullTree() != nil:
4✔
2903
                tree := req.GetFullTree()
2✔
2904
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
2✔
2905
                for idx, leaf := range tree.AllLeaves {
2✔
2906
                        leaves[idx] = txscript.TapLeaf{
2✔
2907
                                LeafVersion: txscript.TapscriptLeafVersion(
2✔
2908
                                        leaf.LeafVersion,
2✔
2909
                                ),
2✔
2910
                                Script: leaf.Script,
2911
                        }
2✔
2912
                }
2913

2✔
2914
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
2✔
2915

2✔
2916
        case req.GetPartialReveal() != nil:
×
2917
                partialReveal := req.GetPartialReveal()
×
2918
                if partialReveal.RevealedLeaf == nil {
2919
                        return nil, fmt.Errorf("missing revealed leaf")
2✔
2920
                }
2✔
2921

2✔
2922
                revealedLeaf := txscript.TapLeaf{
2✔
2923
                        LeafVersion: txscript.TapscriptLeafVersion(
2✔
2924
                                partialReveal.RevealedLeaf.LeafVersion,
2✔
2925
                        ),
2✔
2926
                        Script: partialReveal.RevealedLeaf.Script,
×
2927
                }
×
2928
                if len(partialReveal.FullInclusionProof)%32 != 0 {
×
2929
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
2930
                                "length, expected multiple of 32, got %d",
2931
                                len(partialReveal.FullInclusionProof)%32)
2✔
2932
                }
2✔
2933

2✔
2934
                tapscript = input.TapscriptPartialReveal(
2✔
2935
                        internalKey, revealedLeaf,
2936
                        partialReveal.FullInclusionProof,
2✔
2937
                )
2✔
2938

2✔
2939
        case req.GetRootHashOnly() != nil:
×
2940
                rootHash := req.GetRootHashOnly()
×
2941
                if len(rootHash) == 0 {
2942
                        return nil, fmt.Errorf("missing root hash")
2✔
2943
                }
2944

2✔
2945
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
2✔
2946

2947
        case req.GetFullKeyOnly():
×
2948
                tapscript = input.TapscriptFullKeyOnly(internalKey)
×
2949

2950
        default:
2951
                return nil, fmt.Errorf("invalid script")
2✔
2952
        }
2✔
2953

2✔
2954
        taprootScope := waddrmgr.KeyScopeBIP0086
×
2955
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
×
2956
        if err != nil {
×
2957
                return nil, fmt.Errorf("error importing script into wallet: %w",
2958
                        err)
2✔
2959
        }
2✔
2960

2✔
2961
        return &ImportTapscriptResponse{
2962
                P2TrAddress: addr.Address().String(),
2963
        }, nil
2964
}
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