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

lightningnetwork / lnd / 13412100018

19 Feb 2025 12:05PM UTC coverage: 44.914% (-13.9%) from 58.794%
13412100018

Pull #9521

github

web-flow
Merge 861d6970e into 0e8786348
Pull Request #9521: unit: remove GOACC, use Go 1.20 native coverage functionality

101023 of 224923 relevant lines covered (44.91%)

19479.4 hits per line

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

11.68
/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/v2"
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:ll
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) {
17✔
265
        // If the path of the wallet kit macaroon wasn't specified, then we'll
17✔
266
        // assume that it's found at the default network directory.
17✔
267
        if cfg.WalletKitMacPath == "" {
34✔
268
                cfg.WalletKitMacPath = filepath.Join(
17✔
269
                        cfg.NetworkDir, DefaultWalletKitMacFilename,
17✔
270
                )
17✔
271
        }
17✔
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
17✔
277
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
17✔
278
                !lnrpc.FileExists(macFilePath) {
17✔
279

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

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

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

17✔
308
        return walletKit, macPermissions, nil
17✔
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 {
×
315
        return nil
×
316
}
×
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 {
×
322
        return nil
×
323
}
×
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 {
×
330
        return SubServerName
×
331
}
×
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 {
×
339
        // We make sure that we register it with the main gRPC server to ensure
×
340
        // all our methods are routed properly.
×
341
        RegisterWalletKitServer(grpcServer, r)
×
342

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

×
346
        return nil
×
347
}
×
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 {
×
356

×
357
        // We make sure that we register it with the main REST server to ensure
×
358
        // all our methods are routed properly.
×
359
        err := RegisterWalletKitHandlerFromEndpoint(ctx, mux, dest, opts)
×
360
        if err != nil {
×
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 " +
×
367
                "root REST server")
×
368
        return nil
×
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) {
×
380

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

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

390
// internalScope returns the internal key scope.
391
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
×
392
        return waddrmgr.KeyScope{
×
393
                Purpose: keychain.BIP0043Purpose,
×
394
                Coin:    w.cfg.ChainParams.HDCoinType,
×
395
        }
×
396
}
×
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) {
×
406

×
407
        // Force min_confs and max_confs to be zero if unconfirmed_only is
×
408
        // true.
×
409
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
×
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 {
×
418
                req.MaxConfs = math.MaxInt32
×
419
        }
×
420

421
        // Validate the confirmation arguments.
422
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
×
423
        if err != nil {
×
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
×
434
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
435
                utxos, err = w.cfg.Wallet.ListUnspentWitness(
×
436
                        minConfs, maxConfs, req.Account,
×
437
                )
×
438

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

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

450
        return &ListUnspentResponse{
×
451
                Utxos: rpcUtxos,
×
452
        }, nil
×
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{
×
540
                Status: fmt.Sprintf("output %v released", op.String()),
×
541
        }, nil
×
542
}
543

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

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

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

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

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

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

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

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

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

602
// NextAddr returns the next unused address within the wallet.
603
func (w *WalletKit) NextAddr(ctx context.Context,
604
        req *AddrRequest) (*AddrResponse, error) {
×
605

×
606
        account := lnwallet.DefaultAccountName
×
607
        if req.Account != "" {
×
608
                account = req.Account
×
609
        }
×
610

611
        addrType := lnwallet.WitnessPubKey
×
612
        switch req.Type {
×
613
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
614
                addrType = lnwallet.NestedWitnessPubKey
×
615

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

620
        case AddressType_TAPROOT_PUBKEY:
×
621
                addrType = lnwallet.TaprootPubkey
×
622
        }
623

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

629
        return &AddrResponse{
×
630
                Addr: addr.String(),
×
631
        }, nil
×
632
}
633

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

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

644
        txHash, err := chainhash.NewHashFromStr(req.Txid)
×
645
        if err != nil {
×
646
                return nil, err
×
647
        }
×
648

649
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
×
650
        if err != nil {
×
651
                return nil, err
×
652
        }
×
653

654
        return lnrpc.RPCTransaction(res), nil
×
655
}
656

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

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

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

677
        label, err := labels.ValidateAPI(req.Label)
×
678
        if err != nil {
×
679
                return nil, err
×
680
        }
×
681

682
        err = w.cfg.Wallet.PublishTransaction(tx, label)
×
683
        if err != nil {
×
684
                return nil, err
×
685
        }
×
686

687
        return &PublishResponse{}, nil
×
688
}
689

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

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

709
        txHash, err := chainhash.NewHashFromStr(req.Txid)
×
710
        if err != nil {
×
711
                return nil, err
×
712
        }
×
713

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

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

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

738
        err = w.cfg.Wallet.RemoveDescendants(tx)
×
739
        if err != nil {
×
740
                return nil, err
×
741
        }
×
742

743
        return &RemoveTransactionResponse{
×
744
                Status: "Successfully removed transaction",
×
745
        }, nil
×
746
}
747

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

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

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

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

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

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

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

806
        label, err := labels.ValidateAPI(req.Label)
×
807
        if err != nil {
×
808
                return nil, err
×
809
        }
×
810

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

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

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

834
        return &SendOutputsResponse{
×
835
                RawTx: b.Bytes(),
×
836
        }, nil
×
837
}
838

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

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

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

860
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
×
861

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

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

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

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

892
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
×
893
                amountSat := uint32(inp.Amount)
×
894
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
×
895
                broadcastAttempts := uint32(inp.BroadcastAttempts)
×
896

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

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

918
        return &PendingSweepsResponse{
×
919
                PendingSweeps: rpcPendingSweeps,
×
920
        }, nil
×
921
}
922

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

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

937
        // The hash was provided as raw bytes.
938
        case len(op.TxidBytes) != 0:
×
939
                copy(hash[:], op.TxidBytes)
×
940

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

950
        return &wire.OutPoint{
×
951
                Hash:  hash,
×
952
                Index: op.OutputIndex,
×
953
        }, nil
×
954
}
955

956
// validateBumpFeeRequest makes sure the deprecated fields are not used when
957
// the new fields are set.
958
func validateBumpFeeRequest(in *BumpFeeRequest, estimator chainfee.Estimator) (
959
        fn.Option[chainfee.SatPerKWeight], bool, error) {
×
960

×
961
        // Get the specified fee rate if set.
×
962
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
×
963

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

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

976
        case in.SatPerVbyte != 0:
×
977
                satPerKw := chainfee.SatPerVByte(
×
978
                        in.SatPerVbyte,
×
979
                ).FeePerKWeight()
×
980
                satPerKwOpt = fn.Some(satPerKw)
×
981
        }
982

983
        // We make sure either the conf target or the exact fee rate is
984
        // specified for the starting fee of the fee function.
985
        if in.TargetConf != 0 && !satPerKwOpt.IsNone() {
×
986
                return satPerKwOpt, false,
×
987
                        fmt.Errorf("either TargetConf or SatPerVbyte should " +
×
988
                                "be set, to specify the starting fee rate of " +
×
989
                                "the fee function")
×
990
        }
×
991

992
        // In case the user specified a conf target, we estimate the fee rate
993
        // for the given target using the provided estimator.
994
        if in.TargetConf != 0 {
×
995
                startingFeeRate, err := estimator.EstimateFeePerKW(
×
996
                        in.TargetConf,
×
997
                )
×
998
                if err != nil {
×
999
                        return satPerKwOpt, false, fmt.Errorf("unable to "+
×
1000
                                "estimate fee rate for target conf %d: %w",
×
1001
                                in.TargetConf, err)
×
1002
                }
×
1003

1004
                // Set the starting fee rate to the estimated fee rate.
1005
                satPerKwOpt = fn.Some(startingFeeRate)
×
1006
        }
1007

1008
        var immediate bool
×
1009
        switch {
×
1010
        case in.Force && in.Immediate:
×
1011
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
1012
                        "Immediate should be set, but not both")
×
1013

1014
        case in.Force:
×
1015
                immediate = in.Force
×
1016

1017
        case in.Immediate:
×
1018
                immediate = in.Immediate
×
1019
        }
1020

1021
        if in.DeadlineDelta != 0 && in.Budget == 0 {
×
1022
                return satPerKwOpt, immediate, fmt.Errorf("budget must be " +
×
1023
                        "set if deadline-delta is set")
×
1024
        }
×
1025

1026
        return satPerKwOpt, immediate, nil
×
1027
}
1028

1029
// prepareSweepParams creates the sweep params to be used for the sweeper. It
1030
// returns the new params and a bool indicating whether this is an existing
1031
// input.
1032
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1033
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
×
1034

×
1035
        // Return an error if the bump fee request is invalid.
×
1036
        feeRate, immediate, err := validateBumpFeeRequest(
×
1037
                in, w.cfg.FeeEstimator,
×
1038
        )
×
1039
        if err != nil {
×
1040
                return sweep.Params{}, false, err
×
1041
        }
×
1042

1043
        // Get the current pending inputs.
1044
        inputMap, err := w.cfg.Sweeper.PendingInputs()
×
1045
        if err != nil {
×
1046
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1047
                        "pending inputs: %w", err)
×
1048
        }
×
1049

1050
        // Find the pending input.
1051
        //
1052
        // TODO(yy): act differently based on the state of the input?
1053
        inp, ok := inputMap[op]
×
1054

×
1055
        if !ok {
×
1056
                // NOTE: if this input doesn't exist and the new budget is not
×
1057
                // specified, the params would have a zero budget.
×
1058
                params := sweep.Params{
×
1059
                        Immediate:       immediate,
×
1060
                        StartingFeeRate: feeRate,
×
1061
                        Budget:          btcutil.Amount(in.Budget),
×
1062
                }
×
1063

×
1064
                if in.DeadlineDelta != 0 {
×
1065
                        params.DeadlineHeight = fn.Some(
×
1066
                                int32(in.DeadlineDelta) + currentHeight,
×
1067
                        )
×
1068
                }
×
1069

1070
                return params, ok, nil
×
1071
        }
1072

1073
        // Find the existing budget used for this input. Note that this value
1074
        // must be greater than zero.
1075
        budget := inp.Params.Budget
×
1076

×
1077
        // Set the new budget if specified. If a new deadline delta is
×
1078
        // specified we also require the budget value which is checked in the
×
1079
        // validateBumpFeeRequest function.
×
1080
        if in.Budget != 0 {
×
1081
                budget = btcutil.Amount(in.Budget)
×
1082
        }
×
1083

1084
        // For an existing input, we assign it first, then overwrite it if
1085
        // a deadline is requested.
1086
        deadline := inp.Params.DeadlineHeight
×
1087

×
1088
        // Set the deadline if it was specified.
×
1089
        //
×
1090
        // TODO(yy): upgrade `falafel` so we can make this field optional. Atm
×
1091
        // we cannot distinguish between user's not setting the field and
×
1092
        // setting it to 0.
×
1093
        if in.DeadlineDelta != 0 {
×
1094
                deadline = fn.Some(int32(in.DeadlineDelta) + currentHeight)
×
1095
        }
×
1096

1097
        startingFeeRate := inp.Params.StartingFeeRate
×
1098

×
1099
        // We only set the starting fee rate if it was specified else we keep
×
1100
        // the existing one.
×
1101
        if feeRate.IsSome() {
×
1102
                startingFeeRate = feeRate
×
1103
        }
×
1104

1105
        // Prepare the new sweep params.
1106
        //
1107
        // NOTE: if this input doesn't exist and the new budget is not
1108
        // specified, the params would have a zero budget.
1109
        params := sweep.Params{
×
1110
                Immediate:       immediate,
×
1111
                DeadlineHeight:  deadline,
×
1112
                StartingFeeRate: startingFeeRate,
×
1113
                Budget:          budget,
×
1114
        }
×
1115

×
1116
        log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
×
1117
                "params=%v, new params=%v", op, inp.Params, params)
×
1118

×
1119
        return params, ok, nil
×
1120
}
1121

1122
// BumpFee allows bumping the fee rate of an arbitrary input. A fee preference
1123
// can be expressed either as a specific fee rate or a delta of blocks in which
1124
// the output should be swept on-chain within. If a fee preference is not
1125
// explicitly specified, then an error is returned. The status of the input
1126
// sweep can be checked through the PendingSweeps RPC.
1127
func (w *WalletKit) BumpFee(ctx context.Context,
1128
        in *BumpFeeRequest) (*BumpFeeResponse, error) {
×
1129

×
1130
        // Parse the outpoint from the request.
×
1131
        op, err := UnmarshallOutPoint(in.Outpoint)
×
1132
        if err != nil {
×
1133
                return nil, err
×
1134
        }
×
1135

1136
        // Get the current height so we can calculate the deadline height.
1137
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
×
1138
        if err != nil {
×
1139
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1140
                        err)
×
1141
        }
×
1142

1143
        // We now create a new sweeping params and update it in the sweeper.
1144
        // This will complicate the RBF conditions if this input has already
1145
        // been offered to sweeper before and it has already been included in a
1146
        // tx with other inputs. If this is the case, two results are possible:
1147
        // - either this input successfully RBFed the existing tx, or,
1148
        // - the budget of this input was not enough to RBF the existing tx.
1149
        params, existing, err := w.prepareSweepParams(in, *op, currentHeight)
×
1150
        if err != nil {
×
1151
                return nil, err
×
1152
        }
×
1153

1154
        // If this input exists, we will update its params.
1155
        if existing {
×
1156
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
×
1157
                if err != nil {
×
1158
                        return nil, err
×
1159
                }
×
1160

1161
                return &BumpFeeResponse{
×
1162
                        Status: "Successfully registered rbf-tx with sweeper",
×
1163
                }, nil
×
1164
        }
1165

1166
        // Otherwise, create a new sweeping request for this input.
1167
        err = w.sweepNewInput(op, uint32(currentHeight), params)
×
1168
        if err != nil {
×
1169
                return nil, err
×
1170
        }
×
1171

1172
        return &BumpFeeResponse{
×
1173
                Status: "Successfully registered CPFP-tx with the sweeper",
×
1174
        }, nil
×
1175
}
1176

1177
// getWaitingCloseChannel returns the waiting close channel in case it does
1178
// exist in the underlying channel state database.
1179
func (w *WalletKit) getWaitingCloseChannel(
1180
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
×
1181

×
1182
        // Fetch all channels, which still have their commitment transaction not
×
1183
        // confirmed (waiting close channels).
×
1184
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
×
1185
        if err != nil {
×
1186
                return nil, err
×
1187
        }
×
1188

1189
        channel := fn.Find(chans, func(c *channeldb.OpenChannel) bool {
×
1190
                return c.FundingOutpoint == chanPoint
×
1191
        })
×
1192

1193
        return channel.UnwrapOrErr(errors.New("channel not found"))
×
1194
}
1195

1196
// BumpForceCloseFee bumps the fee rate of an unconfirmed anchor channel. It
1197
// updates the new fee rate parameters with the sweeper subsystem. Additionally
1198
// it will try to create anchor cpfp transactions for all possible commitment
1199
// transactions (local, remote, remote-dangling) so depending on which
1200
// commitment is in the local mempool only one of them will succeed in being
1201
// broadcasted.
1202
func (w *WalletKit) BumpForceCloseFee(_ context.Context,
1203
        in *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
×
1204

×
1205
        if in.ChanPoint == nil {
×
1206
                return nil, fmt.Errorf("no chan_point provided")
×
1207
        }
×
1208

1209
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
×
1210
        if err != nil {
×
1211
                return nil, err
×
1212
        }
×
1213

1214
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
×
1215
        if err != nil {
×
1216
                return nil, err
×
1217
        }
×
1218

1219
        // Get the relevant channel if it is in the waiting close state.
1220
        channel, err := w.getWaitingCloseChannel(*outPoint)
×
1221
        if err != nil {
×
1222
                return nil, err
×
1223
        }
×
1224

1225
        if !channel.ChanType.HasAnchors() {
×
1226
                return nil, fmt.Errorf("not able to bump the fee of a " +
×
1227
                        "non-anchor channel")
×
1228
        }
×
1229

1230
        // Match pending sweeps with commitments of the channel for which a bump
1231
        // is requested. Depending on the commitment state when force closing
1232
        // the channel we might have up to 3 commitments to consider when
1233
        // bumping the fee.
1234
        commitSet := fn.NewSet[chainhash.Hash]()
×
1235

×
1236
        if channel.LocalCommitment.CommitTx != nil {
×
1237
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
×
1238
                commitSet.Add(localTxID)
×
1239
        }
×
1240

1241
        if channel.RemoteCommitment.CommitTx != nil {
×
1242
                remoteTxID := channel.RemoteCommitment.CommitTx.TxHash()
×
1243
                commitSet.Add(remoteTxID)
×
1244
        }
×
1245

1246
        // Check whether there was a dangling commitment at the time the channel
1247
        // was force closed.
1248
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
×
1249
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
×
1250
                return nil, err
×
1251
        }
×
1252

1253
        if remoteCommitDiff != nil {
×
1254
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1255
                commitSet.Add(hash)
×
1256
        }
×
1257

1258
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1259
        // sweep.
1260
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
×
1261
        if err != nil {
×
1262
                return nil, err
×
1263
        }
×
1264

1265
        // Get the current height so we can calculate the deadline height.
1266
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
×
1267
        if err != nil {
×
1268
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1269
                        err)
×
1270
        }
×
1271

1272
        pendingSweeps := maps.Values(inputsMap)
×
1273

×
1274
        // Discard everything except for the anchor sweeps.
×
1275
        anchors := fn.Filter(
×
1276
                pendingSweeps,
×
1277
                func(sweep *sweep.PendingInputResponse) bool {
×
1278
                        // Only filter for anchor inputs because these are the
×
1279
                        // only inputs which can be used to bump a closed
×
1280
                        // unconfirmed commitment transaction.
×
1281
                        isCommitAnchor := sweep.WitnessType ==
×
1282
                                input.CommitmentAnchor
×
1283
                        isTaprootSweepSpend := sweep.WitnessType ==
×
1284
                                input.TaprootAnchorSweepSpend
×
1285
                        if !isCommitAnchor && !isTaprootSweepSpend {
×
1286
                                return false
×
1287
                        }
×
1288

1289
                        return commitSet.Contains(sweep.OutPoint.Hash)
×
1290
                },
1291
        )
1292

1293
        if len(anchors) == 0 {
×
1294
                return nil, fmt.Errorf("unable to find pending anchor outputs")
×
1295
        }
×
1296

1297
        // Filter all relevant anchor sweeps and update the sweep request.
1298
        for _, anchor := range anchors {
×
1299
                // Anchor cpfp bump request are predictable because they are
×
1300
                // swept separately hence not batched with other sweeps (they
×
1301
                // are marked with the exclusive group flag). Bumping the fee
×
1302
                // rate does not create any conflicting fee bump conditions.
×
1303
                // Either the rbf requirements are met or the bump is rejected
×
1304
                // by the mempool rules.
×
1305
                params, existing, err := w.prepareSweepParams(
×
1306
                        &BumpFeeRequest{
×
1307
                                Outpoint:      lnrpcOutpoint,
×
1308
                                TargetConf:    in.TargetConf,
×
1309
                                SatPerVbyte:   in.StartingFeerate,
×
1310
                                Immediate:     in.Immediate,
×
1311
                                Budget:        in.Budget,
×
1312
                                DeadlineDelta: in.DeadlineDelta,
×
1313
                        }, anchor.OutPoint, currentHeight,
×
1314
                )
×
1315
                if err != nil {
×
1316
                        return nil, err
×
1317
                }
×
1318

1319
                // There might be the case when an anchor sweep is confirmed
1320
                // between fetching the pending sweeps and preparing the sweep
1321
                // params. We log this case and proceed.
1322
                if !existing {
×
1323
                        log.Errorf("Sweep anchor input(%v) not known to the " +
×
1324
                                "sweeper subsystem")
×
1325
                        continue
×
1326
                }
1327

1328
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
×
1329
                if err != nil {
×
1330
                        return nil, err
×
1331
                }
×
1332
        }
1333

1334
        return &BumpForceCloseFeeResponse{
×
1335
                Status: "Successfully registered anchor-cpfp transaction to" +
×
1336
                        "bump channel force close transaction",
×
1337
        }, nil
×
1338
}
1339

1340
// sweepNewInput handles the case where an input is seen the first time by the
1341
// sweeper. It will fetch the output from the wallet and construct an input and
1342
// offer it to the sweeper.
1343
//
1344
// NOTE: if the budget is not set, the default budget ratio is used.
1345
func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
1346
        params sweep.Params) error {
×
1347

×
1348
        log.Debugf("Attempting to sweep outpoint %s", op)
×
1349

×
1350
        // Since the sweeper is not aware of the input, we'll assume the user
×
1351
        // is attempting to bump an unconfirmed transaction's fee rate by
×
1352
        // sweeping an output within it under control of the wallet with a
×
1353
        // higher fee rate. In this case, this will be a CPFP.
×
1354
        //
×
1355
        // We'll gather all of the information required by the UtxoSweeper in
×
1356
        // order to sweep the output.
×
1357
        utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
×
1358
        if err != nil {
×
1359
                return err
×
1360
        }
×
1361

1362
        // We're only able to bump the fee of unconfirmed transactions.
1363
        if utxo.Confirmations > 0 {
×
1364
                return errors.New("unable to bump fee of a confirmed " +
×
1365
                        "transaction")
×
1366
        }
×
1367

1368
        // TODO(ziggie): The budget value should ideally only be set for CPFP
1369
        // requests because for RBF requests we should have already registered
1370
        // the input including the budget value in the first place. However it
1371
        // might not be set and then depending on the deadline delta fee
1372
        // estimations might become too aggressive. So need to evaluate whether
1373
        // we set a default value here, make it configurable or fail request
1374
        // in that case.
1375
        if params.Budget == 0 {
×
1376
                params.Budget = utxo.Value.MulF64(
×
1377
                        contractcourt.DefaultBudgetRatio,
×
1378
                )
×
1379

×
1380
                log.Warnf("[BumpFee]: setting default budget value of %v for "+
×
1381
                        "input=%v, which will be used for the maximum fee "+
×
1382
                        "rate estimation (budget was not specified)",
×
1383
                        params.Budget, op)
×
1384
        }
×
1385

1386
        signDesc := &input.SignDescriptor{
×
1387
                Output: &wire.TxOut{
×
1388
                        PkScript: utxo.PkScript,
×
1389
                        Value:    int64(utxo.Value),
×
1390
                },
×
1391
                HashType: txscript.SigHashAll,
×
1392
        }
×
1393

×
1394
        var witnessType input.WitnessType
×
1395
        switch utxo.AddressType {
×
1396
        case lnwallet.WitnessPubKey:
×
1397
                witnessType = input.WitnessKeyHash
×
1398
        case lnwallet.NestedWitnessPubKey:
×
1399
                witnessType = input.NestedWitnessKeyHash
×
1400
        case lnwallet.TaprootPubkey:
×
1401
                witnessType = input.TaprootPubKeySpend
×
1402
                signDesc.HashType = txscript.SigHashDefault
×
1403
        default:
×
1404
                return fmt.Errorf("unknown input witness %v", op)
×
1405
        }
1406

1407
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
×
1408
                params)
×
1409

×
1410
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
×
1411
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
×
1412
                return err
×
1413
        }
×
1414

1415
        return nil
×
1416
}
1417

1418
// ListSweeps returns a list of the sweeps that our node has published.
1419
func (w *WalletKit) ListSweeps(ctx context.Context,
1420
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
×
1421

×
1422
        sweeps, err := w.cfg.Sweeper.ListSweeps()
×
1423
        if err != nil {
×
1424
                return nil, err
×
1425
        }
×
1426

1427
        sweepTxns := make(map[string]bool)
×
1428
        for _, sweep := range sweeps {
×
1429
                sweepTxns[sweep.String()] = true
×
1430
        }
×
1431

1432
        // Some of our sweeps could have been replaced by fee, or dropped out
1433
        // of the mempool. Here, we lookup our wallet transactions so that we
1434
        // can match our list of sweeps against the list of transactions that
1435
        // the wallet is still tracking. Sweeps are currently always swept to
1436
        // the default wallet account.
1437
        txns, firstIdx, lastIdx, err := w.cfg.Wallet.ListTransactionDetails(
×
1438
                in.StartHeight, btcwallet.UnconfirmedHeight,
×
1439
                lnwallet.DefaultAccountName, 0, 0,
×
1440
        )
×
1441
        if err != nil {
×
1442
                return nil, err
×
1443
        }
×
1444

1445
        var (
×
1446
                txids     []string
×
1447
                txDetails []*lnwallet.TransactionDetail
×
1448
        )
×
1449

×
1450
        for _, tx := range txns {
×
1451
                _, ok := sweepTxns[tx.Hash.String()]
×
1452
                if !ok {
×
1453
                        continue
×
1454
                }
1455

1456
                // Add the txid or full tx details depending on whether we want
1457
                // verbose output or not.
1458
                if in.Verbose {
×
1459
                        txDetails = append(txDetails, tx)
×
1460
                } else {
×
1461
                        txids = append(txids, tx.Hash.String())
×
1462
                }
×
1463
        }
1464

1465
        if in.Verbose {
×
1466
                return &ListSweepsResponse{
×
1467
                        Sweeps: &ListSweepsResponse_TransactionDetails{
×
1468
                                TransactionDetails: lnrpc.RPCTransactionDetails(
×
1469
                                        txDetails, firstIdx, lastIdx,
×
1470
                                ),
×
1471
                        },
×
1472
                }, nil
×
1473
        }
×
1474

1475
        return &ListSweepsResponse{
×
1476
                Sweeps: &ListSweepsResponse_TransactionIds{
×
1477
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
×
1478
                                TransactionIds: txids,
×
1479
                        },
×
1480
                },
×
1481
        }, nil
×
1482
}
1483

1484
// LabelTransaction adds a label to a transaction.
1485
func (w *WalletKit) LabelTransaction(ctx context.Context,
1486
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
×
1487

×
1488
        // Check that the label provided in non-zero.
×
1489
        if len(req.Label) == 0 {
×
1490
                return nil, ErrZeroLabel
×
1491
        }
×
1492

1493
        // Validate the length of the non-zero label. We do not need to use the
1494
        // label returned here, because the original is non-zero so will not
1495
        // be replaced.
1496
        if _, err := labels.ValidateAPI(req.Label); err != nil {
×
1497
                return nil, err
×
1498
        }
×
1499

1500
        hash, err := chainhash.NewHash(req.Txid)
×
1501
        if err != nil {
×
1502
                return nil, err
×
1503
        }
×
1504

1505
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
×
1506

×
1507
        return &LabelTransactionResponse{
×
1508
                Status: fmt.Sprintf("transaction label '%s' added", req.Label),
×
1509
        }, err
×
1510
}
1511

1512
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
1513
// the outputs specified in the template. There are three ways a user can
1514
// specify what we call the template (a list of inputs and outputs to use in the
1515
// PSBT): Either as a PSBT packet directly with no coin selection (using the
1516
// legacy "psbt" field), a PSBT with advanced coin selection support (using the
1517
// new "coin_select" field) or as a raw RPC message (using the "raw" field).
1518
// The legacy "psbt" and "raw" modes, the following restrictions apply:
1519
//  1. If there are no inputs specified in the template, coin selection is
1520
//     performed automatically.
1521
//  2. If the template does contain any inputs, it is assumed that full coin
1522
//     selection happened externally and no additional inputs are added. If the
1523
//     specified inputs aren't enough to fund the outputs with the given fee
1524
//     rate, an error is returned.
1525
//
1526
// The new "coin_select" mode does not have these restrictions and allows the
1527
// user to specify a PSBT with inputs and outputs and still perform coin
1528
// selection on top of that.
1529
// For all modes this RPC requires any inputs that are specified to be locked by
1530
// the user (if they belong to this node in the first place).
1531
// After either selecting or verifying the inputs, all input UTXOs are locked
1532
// with an internal app ID. A custom address type for change can be specified
1533
// for default accounts and single imported public keys (only P2TR for now).
1534
// Otherwise, P2WPKH will be used by default. No custom address type should be
1535
// provided for custom accounts as we will always generate the change address
1536
// using the coin selection key scope.
1537
//
1538
// NOTE: If this method returns without an error, it is the caller's
1539
// responsibility to either spend the locked UTXOs (by finalizing and then
1540
// publishing the transaction) or to unlock/release the locked UTXOs in case of
1541
// an error on the caller's side.
1542
func (w *WalletKit) FundPsbt(_ context.Context,
1543
        req *FundPsbtRequest) (*FundPsbtResponse, error) {
×
1544

×
1545
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
×
1546
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
×
1547
        )
×
1548
        if err != nil {
×
1549
                return nil, err
×
1550
        }
×
1551

1552
        // Determine the desired transaction fee.
1553
        var feeSatPerKW chainfee.SatPerKWeight
×
1554
        switch {
×
1555
        // Estimate the fee by the target number of blocks to confirmation.
1556
        case req.GetTargetConf() != 0:
×
1557
                targetConf := req.GetTargetConf()
×
1558
                if targetConf < 2 {
×
1559
                        return nil, fmt.Errorf("confirmation target must be " +
×
1560
                                "greater than 1")
×
1561
                }
×
1562

1563
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
×
1564
                        targetConf,
×
1565
                )
×
1566
                if err != nil {
×
1567
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1568
                                err)
×
1569
                }
×
1570

1571
        // Convert the fee to sat/kW from the specified sat/vByte.
1572
        case req.GetSatPerVbyte() != 0:
×
1573
                feeSatPerKW = chainfee.SatPerKVByte(
×
1574
                        req.GetSatPerVbyte() * 1000,
×
1575
                ).FeePerKWeight()
×
1576

1577
        case req.GetSatPerKw() != 0:
×
1578
                feeSatPerKW = chainfee.SatPerKWeight(req.GetSatPerKw())
×
1579

1580
        default:
×
1581
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1582
                        "specify either target_conf, sat_per_vbyte or " +
×
1583
                        "sat_per_kw")
×
1584
        }
1585

1586
        // Then, we'll extract the minimum number of confirmations that each
1587
        // output we use to fund the transaction should satisfy.
1588
        minConfs, err := lnrpc.ExtractMinConfs(
×
1589
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
×
1590
        )
×
1591
        if err != nil {
×
1592
                return nil, err
×
1593
        }
×
1594

1595
        // We'll assume the PSBT will be funded by the default account unless
1596
        // otherwise specified.
1597
        account := lnwallet.DefaultAccountName
×
1598
        if req.Account != "" {
×
1599
                account = req.Account
×
1600
        }
×
1601

1602
        // There are three ways a user can specify what we call the template (a
1603
        // list of inputs and outputs to use in the PSBT): Either as a PSBT
1604
        // packet directly with no coin selection, a PSBT with coin selection or
1605
        // as a special RPC message. Find out which one the user wants to use,
1606
        // they are mutually exclusive.
1607
        switch {
×
1608
        // The template is specified as a PSBT. All we have to do is parse it.
1609
        case req.GetPsbt() != nil:
×
1610
                r := bytes.NewReader(req.GetPsbt())
×
1611
                packet, err := psbt.NewFromRawBytes(r, false)
×
1612
                if err != nil {
×
1613
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1614
                }
×
1615

1616
                // Run the actual funding process now, using the internal
1617
                // wallet.
1618
                return w.fundPsbtInternalWallet(
×
1619
                        account, keyScopeFromChangeAddressType(req.ChangeType),
×
1620
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
×
1621
                )
×
1622

1623
        // The template is specified as a PSBT with the intention to perform
1624
        // coin selection even if inputs are already present.
1625
        case req.GetCoinSelect() != nil:
×
1626
                coinSelectRequest := req.GetCoinSelect()
×
1627
                r := bytes.NewReader(coinSelectRequest.Psbt)
×
1628
                packet, err := psbt.NewFromRawBytes(r, false)
×
1629
                if err != nil {
×
1630
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1631
                }
×
1632

1633
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
×
1634
                if numOutputs == 0 {
×
1635
                        return nil, fmt.Errorf("no outputs specified in " +
×
1636
                                "template")
×
1637
                }
×
1638

1639
                outputSum := int64(0)
×
1640
                for _, txOut := range packet.UnsignedTx.TxOut {
×
1641
                        outputSum += txOut.Value
×
1642
                }
×
1643
                if outputSum <= 0 {
×
1644
                        return nil, fmt.Errorf("output sum must be positive")
×
1645
                }
×
1646

1647
                var (
×
1648
                        changeIndex int32 = -1
×
1649
                        changeType  chanfunding.ChangeAddressType
×
1650
                )
×
1651
                switch t := coinSelectRequest.ChangeOutput.(type) {
×
1652
                // The user wants to use an existing output as change output.
1653
                case *PsbtCoinSelect_ExistingOutputIndex:
×
1654
                        if t.ExistingOutputIndex < 0 ||
×
1655
                                t.ExistingOutputIndex >= numOutputs {
×
1656

×
1657
                                return nil, fmt.Errorf("change output index "+
×
1658
                                        "out of range: %d",
×
1659
                                        t.ExistingOutputIndex)
×
1660
                        }
×
1661

1662
                        changeIndex = t.ExistingOutputIndex
×
1663

×
1664
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
×
1665
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
×
1666
                        if err != nil {
×
1667
                                return nil, fmt.Errorf("error parsing change "+
×
1668
                                        "script: %w", err)
×
1669
                        }
×
1670

1671
                        changeType = chanfunding.ExistingChangeAddress
×
1672

1673
                // The user wants to use a new output as change output.
1674
                case *PsbtCoinSelect_Add:
×
1675
                        // We already set the change index to -1 above to
×
1676
                        // indicate no change output should be used if possible
×
1677
                        // or a new one should be created if needed. So we only
×
1678
                        // need to parse the type of change output we want to
×
1679
                        // create.
×
1680
                        switch req.ChangeType {
×
1681
                        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
1682
                                changeType = chanfunding.P2TRChangeAddress
×
1683

1684
                        default:
×
1685
                                changeType = chanfunding.P2WKHChangeAddress
×
1686
                        }
1687

1688
                default:
×
1689
                        return nil, fmt.Errorf("unknown change output type")
×
1690
                }
1691

1692
                maxFeeRatio := chanfunding.DefaultMaxFeeRatio
×
1693

×
1694
                if req.MaxFeeRatio != 0 {
×
1695
                        maxFeeRatio = req.MaxFeeRatio
×
1696
                }
×
1697

1698
                // Run the actual funding process now, using the channel funding
1699
                // coin selection algorithm.
1700
                return w.fundPsbtCoinSelect(
×
1701
                        account, changeIndex, packet, minConfs, changeType,
×
1702
                        feeSatPerKW, coinSelectionStrategy, maxFeeRatio,
×
1703
                )
×
1704

1705
        // The template is specified as a RPC message. We need to create a new
1706
        // PSBT and copy the RPC information over.
1707
        case req.GetRaw() != nil:
×
1708
                tpl := req.GetRaw()
×
1709

×
1710
                txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
×
1711
                for addrStr, amt := range tpl.Outputs {
×
1712
                        addr, err := btcutil.DecodeAddress(
×
1713
                                addrStr, w.cfg.ChainParams,
×
1714
                        )
×
1715
                        if err != nil {
×
1716
                                return nil, fmt.Errorf("error parsing address "+
×
1717
                                        "%s for network %s: %v", addrStr,
×
1718
                                        w.cfg.ChainParams.Name, err)
×
1719
                        }
×
1720

1721
                        if !addr.IsForNet(w.cfg.ChainParams) {
×
1722
                                return nil, fmt.Errorf("address is not for %s",
×
1723
                                        w.cfg.ChainParams.Name)
×
1724
                        }
×
1725

1726
                        pkScript, err := txscript.PayToAddrScript(addr)
×
1727
                        if err != nil {
×
1728
                                return nil, fmt.Errorf("error getting pk "+
×
1729
                                        "script for address %s: %w", addrStr,
×
1730
                                        err)
×
1731
                        }
×
1732

1733
                        txOut = append(txOut, &wire.TxOut{
×
1734
                                Value:    int64(amt),
×
1735
                                PkScript: pkScript,
×
1736
                        })
×
1737
                }
1738

1739
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
×
1740
                for idx, in := range tpl.Inputs {
×
1741
                        op, err := UnmarshallOutPoint(in)
×
1742
                        if err != nil {
×
1743
                                return nil, fmt.Errorf("error parsing "+
×
1744
                                        "outpoint: %w", err)
×
1745
                        }
×
1746
                        txIn[idx] = op
×
1747
                }
1748

1749
                sequences := make([]uint32, len(txIn))
×
1750
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
×
1751
                if err != nil {
×
1752
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1753
                }
×
1754

1755
                // Run the actual funding process now, using the internal
1756
                // wallet.
1757
                return w.fundPsbtInternalWallet(
×
1758
                        account, keyScopeFromChangeAddressType(req.ChangeType),
×
1759
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
×
1760
                )
×
1761

1762
        default:
×
1763
                return nil, fmt.Errorf("transaction template missing, need " +
×
1764
                        "to specify either PSBT or raw TX template")
×
1765
        }
1766
}
1767

1768
// fundPsbtInternalWallet uses the "old" PSBT funding method of the internal
1769
// wallet that does not allow specifying custom inputs while selecting coins.
1770
func (w *WalletKit) fundPsbtInternalWallet(account string,
1771
        keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
1772
        feeSatPerKW chainfee.SatPerKWeight,
1773
        strategy base.CoinSelectionStrategy) (*FundPsbtResponse, error) {
×
1774

×
1775
        // The RPC parsing part is now over. Several of the following operations
×
1776
        // require us to hold the global coin selection lock, so we do the rest
×
1777
        // of the tasks while holding the lock. The result is a list of locked
×
1778
        // UTXOs.
×
1779
        var response *FundPsbtResponse
×
1780
        err := w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
1781
                // In case the user did specify inputs, we need to make sure
×
1782
                // they are known to us, still unspent and not yet locked.
×
1783
                if len(packet.UnsignedTx.TxIn) > 0 {
×
1784
                        // Get a list of all unspent witness outputs.
×
1785
                        utxos, err := w.cfg.Wallet.ListUnspentWitness(
×
1786
                                minConfs, defaultMaxConf, account,
×
1787
                        )
×
1788
                        if err != nil {
×
1789
                                return err
×
1790
                        }
×
1791

1792
                        // filterFn makes sure utxos which are unconfirmed and
1793
                        // still used by the sweeper are not used.
1794
                        filterFn := func(u *lnwallet.Utxo) bool {
×
1795
                                // Confirmed utxos are always allowed.
×
1796
                                if u.Confirmations > 0 {
×
1797
                                        return true
×
1798
                                }
×
1799

1800
                                // Unconfirmed utxos in use by the sweeper are
1801
                                // not stable to use because they can be
1802
                                // replaced.
1803
                                if w.cfg.Sweeper.IsSweeperOutpoint(u.OutPoint) {
×
1804
                                        log.Warnf("Cannot use unconfirmed "+
×
1805
                                                "utxo=%v because it is "+
×
1806
                                                "unstable and could be "+
×
1807
                                                "replaced", u.OutPoint)
×
1808

×
1809
                                        return false
×
1810
                                }
×
1811

1812
                                return true
×
1813
                        }
1814

1815
                        eligibleUtxos := fn.Filter(utxos, filterFn)
×
1816

×
1817
                        // Validate all inputs against our known list of UTXOs
×
1818
                        // now.
×
1819
                        err = verifyInputsUnspent(
×
1820
                                packet.UnsignedTx.TxIn, eligibleUtxos,
×
1821
                        )
×
1822
                        if err != nil {
×
1823
                                return err
×
1824
                        }
×
1825
                }
1826

1827
                // currentHeight is needed to determine whether the internal
1828
                // wallet utxo is still unconfirmed.
1829
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
×
1830
                if err != nil {
×
1831
                        return fmt.Errorf("unable to retrieve current "+
×
1832
                                "height: %v", err)
×
1833
                }
×
1834

1835
                // restrictUnstableUtxos is a filter function which disallows
1836
                // the usage of unconfirmed outputs published (still in use) by
1837
                // the sweeper.
1838
                restrictUnstableUtxos := func(utxo wtxmgr.Credit) bool {
×
1839
                        // Wallet utxos which are unmined have a height
×
1840
                        // of -1.
×
1841
                        if utxo.Height != -1 && utxo.Height <= currentHeight {
×
1842
                                // Confirmed utxos are always allowed.
×
1843
                                return true
×
1844
                        }
×
1845

1846
                        // Utxos used by the sweeper are not used for
1847
                        // channel openings.
1848
                        allowed := !w.cfg.Sweeper.IsSweeperOutpoint(
×
1849
                                utxo.OutPoint,
×
1850
                        )
×
1851
                        if !allowed {
×
1852
                                log.Warnf("Cannot use unconfirmed "+
×
1853
                                        "utxo=%v because it is "+
×
1854
                                        "unstable and could be "+
×
1855
                                        "replaced", utxo.OutPoint)
×
1856
                        }
×
1857

1858
                        return allowed
×
1859
                }
1860

1861
                // We made sure the input from the user is as sane as possible.
1862
                // We can now ask the wallet to fund the TX. This will not yet
1863
                // lock any coins but might still change the wallet DB by
1864
                // generating a new change address.
1865
                changeIndex, err := w.cfg.Wallet.FundPsbt(
×
1866
                        packet, minConfs, feeSatPerKW, account, keyScope,
×
1867
                        strategy, restrictUnstableUtxos,
×
1868
                )
×
1869
                if err != nil {
×
1870
                        return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
×
1871
                }
×
1872

1873
                // Now we have obtained a set of coins that can be used to fund
1874
                // the TX. Let's lock them to be sure they aren't spent by the
1875
                // time the PSBT is published. This is the action we do here
1876
                // that could cause an error. Therefore, if some of the UTXOs
1877
                // cannot be locked, the rollback of the other's locks also
1878
                // happens in this function. If we ever need to do more after
1879
                // this function, we need to extract the rollback needs to be
1880
                // extracted into a defer.
1881
                outpoints := make([]wire.OutPoint, len(packet.UnsignedTx.TxIn))
×
1882
                for i, txIn := range packet.UnsignedTx.TxIn {
×
1883
                        outpoints[i] = txIn.PreviousOutPoint
×
1884
                }
×
1885

1886
                response, err = w.lockAndCreateFundingResponse(
×
1887
                        packet, outpoints, changeIndex,
×
1888
                )
×
1889

×
1890
                return err
×
1891
        })
1892
        if err != nil {
×
1893
                return nil, err
×
1894
        }
×
1895

1896
        return response, nil
×
1897
}
1898

1899
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
1900
// funding coin selection algorithm that allows specifying custom inputs while
1901
// selecting coins.
1902
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
1903
        packet *psbt.Packet, minConfs int32,
1904
        changeType chanfunding.ChangeAddressType,
1905
        feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy,
1906
        maxFeeRatio float64) (*FundPsbtResponse, error) {
17✔
1907

17✔
1908
        // We want to make sure we don't select any inputs that are already
17✔
1909
        // specified in the template. To do that, we require those inputs to
17✔
1910
        // either not belong to this lnd at all or to be already locked through
17✔
1911
        // a manual lock call by the user. Either way, they should not appear in
17✔
1912
        // the list of unspent outputs.
17✔
1913
        err := w.assertNotAvailable(packet.UnsignedTx.TxIn, minConfs, account)
17✔
1914
        if err != nil {
17✔
1915
                return nil, err
×
1916
        }
×
1917

1918
        // In case the user just specified the input outpoints of UTXOs we own,
1919
        // the fee estimation below will error out because the UTXO information
1920
        // is missing. We need to fetch the UTXO information from the wallet
1921
        // and add it to the PSBT. We ignore inputs we don't actually know as
1922
        // they could belong to another wallet.
1923
        err = w.cfg.Wallet.DecorateInputs(packet, false)
17✔
1924
        if err != nil {
17✔
1925
                return nil, fmt.Errorf("error decorating inputs: %w", err)
×
1926
        }
×
1927

1928
        // Before we select anything, we need to calculate the input, output and
1929
        // current weight amounts. While doing that we also ensure the PSBT has
1930
        // all the required information we require at this step.
1931
        var (
17✔
1932
                inputSum, outputSum btcutil.Amount
17✔
1933
                estimator           input.TxWeightEstimator
17✔
1934
        )
17✔
1935
        for i := range packet.Inputs {
22✔
1936
                in := packet.Inputs[i]
5✔
1937

5✔
1938
                err := btcwallet.EstimateInputWeight(&in, &estimator)
5✔
1939
                if err != nil {
5✔
1940
                        return nil, fmt.Errorf("error estimating input "+
×
1941
                                "weight: %w", err)
×
1942
                }
×
1943

1944
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
5✔
1945
        }
1946
        for i := range packet.UnsignedTx.TxOut {
34✔
1947
                out := packet.UnsignedTx.TxOut[i]
17✔
1948

17✔
1949
                estimator.AddOutput(out.PkScript)
17✔
1950
                outputSum += btcutil.Amount(out.Value)
17✔
1951
        }
17✔
1952

1953
        // The amount we want to fund is the total output sum plus the current
1954
        // fee estimate, minus the sum of any already specified inputs. Since we
1955
        // pass the estimator of the current transaction into the coin selection
1956
        // algorithm, we don't need to subtract the fees here.
1957
        fundingAmount := outputSum - inputSum
17✔
1958

17✔
1959
        var changeDustLimit btcutil.Amount
17✔
1960
        switch changeType {
17✔
1961
        case chanfunding.P2TRChangeAddress:
4✔
1962
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
4✔
1963

1964
        case chanfunding.P2WKHChangeAddress:
5✔
1965
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
5✔
1966

1967
        case chanfunding.ExistingChangeAddress:
8✔
1968
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
8✔
1969
                changeDustLimit = lnwallet.DustLimitForSize(
8✔
1970
                        len(changeOut.PkScript),
8✔
1971
                )
8✔
1972
        }
1973

1974
        // Do we already have enough inputs specified to pay for the TX as it
1975
        // is? In that case we only need to allocate any change, if there is
1976
        // any.
1977
        packetFeeNoChange := feeRate.FeeForWeight(estimator.Weight())
17✔
1978
        if inputSum >= outputSum+packetFeeNoChange {
20✔
1979
                // Calculate the packet's fee with a change output so, so we can
3✔
1980
                // let the coin selection algorithm decide whether to use a
3✔
1981
                // change output or not.
3✔
1982
                switch changeType {
3✔
1983
                case chanfunding.P2TRChangeAddress:
1✔
1984
                        estimator.AddP2TROutput()
1✔
1985

1986
                case chanfunding.P2WKHChangeAddress:
1✔
1987
                        estimator.AddP2WKHOutput()
1✔
1988
                }
1989
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
3✔
1990

3✔
1991
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
3✔
1992
                        inputSum, outputSum, packetFeeNoChange,
3✔
1993
                        packetFeeWithChange, changeDustLimit, changeType,
3✔
1994
                        maxFeeRatio,
3✔
1995
                )
3✔
1996
                if err != nil {
3✔
1997
                        return nil, fmt.Errorf("error calculating change "+
×
1998
                                "amount: %w", err)
×
1999
                }
×
2000

2001
                // We shouldn't get into this branch if the input sum isn't
2002
                // enough to pay for the current package without a change
2003
                // output. So this should never be non-zero.
2004
                if needMore != 0 {
3✔
2005
                        return nil, fmt.Errorf("internal error with change " +
×
2006
                                "amount calculation")
×
2007
                }
×
2008

2009
                if changeAmt > 0 {
5✔
2010
                        changeIndex, err = w.handleChange(
2✔
2011
                                packet, changeIndex, int64(changeAmt),
2✔
2012
                                changeType, account,
2✔
2013
                        )
2✔
2014
                        if err != nil {
2✔
2015
                                return nil, fmt.Errorf("error handling change "+
×
2016
                                        "amount: %w", err)
×
2017
                        }
×
2018
                }
2019

2020
                // We're done. Let's serialize and return the updated package.
2021
                return w.lockAndCreateFundingResponse(packet, nil, changeIndex)
3✔
2022
        }
2023

2024
        // The RPC parsing part is now over. Several of the following operations
2025
        // require us to hold the global coin selection lock, so we do the rest
2026
        // of the tasks while holding the lock. The result is a list of locked
2027
        // UTXOs.
2028
        var response *FundPsbtResponse
14✔
2029
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
28✔
2030
                // Get a list of all unspent witness outputs.
14✔
2031
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
14✔
2032
                        minConfs, defaultMaxConf, account,
14✔
2033
                )
14✔
2034
                if err != nil {
14✔
2035
                        return err
×
2036
                }
×
2037

2038
                coins := make([]base.Coin, len(utxos))
14✔
2039
                for i, utxo := range utxos {
29✔
2040
                        coins[i] = base.Coin{
15✔
2041
                                TxOut: wire.TxOut{
15✔
2042
                                        Value:    int64(utxo.Value),
15✔
2043
                                        PkScript: utxo.PkScript,
15✔
2044
                                },
15✔
2045
                                OutPoint: utxo.OutPoint,
15✔
2046
                        }
15✔
2047
                }
15✔
2048

2049
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
14✔
2050
                        feeRate, fundingAmount, changeDustLimit, coins,
14✔
2051
                        strategy, estimator, changeType, maxFeeRatio,
14✔
2052
                )
14✔
2053
                if err != nil {
17✔
2054
                        return fmt.Errorf("error selecting coins: %w", err)
3✔
2055
                }
3✔
2056

2057
                if changeAmount > 0 {
19✔
2058
                        changeIndex, err = w.handleChange(
8✔
2059
                                packet, changeIndex, int64(changeAmount),
8✔
2060
                                changeType, account,
8✔
2061
                        )
8✔
2062
                        if err != nil {
8✔
2063
                                return fmt.Errorf("error handling change "+
×
2064
                                        "amount: %w", err)
×
2065
                        }
×
2066
                }
2067

2068
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
11✔
2069
                for i := range selectedCoins {
24✔
2070
                        coin := selectedCoins[i]
13✔
2071
                        addedOutpoints[i] = coin.OutPoint
13✔
2072

13✔
2073
                        packet.UnsignedTx.TxIn = append(
13✔
2074
                                packet.UnsignedTx.TxIn, &wire.TxIn{
13✔
2075
                                        PreviousOutPoint: coin.OutPoint,
13✔
2076
                                },
13✔
2077
                        )
13✔
2078
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
13✔
2079
                                WitnessUtxo: &coin.TxOut,
13✔
2080
                        })
13✔
2081
                }
13✔
2082

2083
                // Now that we've added the bare TX inputs, we also need to add
2084
                // the more verbose input information to the packet, so a future
2085
                // signer doesn't need to do any lookups. We skip any inputs
2086
                // that our wallet doesn't own.
2087
                err = w.cfg.Wallet.DecorateInputs(packet, false)
11✔
2088
                if err != nil {
11✔
2089
                        return fmt.Errorf("error decorating inputs: %w", err)
×
2090
                }
×
2091

2092
                response, err = w.lockAndCreateFundingResponse(
11✔
2093
                        packet, addedOutpoints, changeIndex,
11✔
2094
                )
11✔
2095

11✔
2096
                return err
11✔
2097
        })
2098
        if err != nil {
17✔
2099
                return nil, err
3✔
2100
        }
3✔
2101

2102
        return response, nil
11✔
2103
}
2104

2105
// assertNotAvailable makes sure the specified inputs either don't belong to
2106
// this node or are already locked by the user.
2107
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2108
        account string) error {
17✔
2109

17✔
2110
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
34✔
2111
                // Get a list of all unspent witness outputs.
17✔
2112
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
17✔
2113
                        minConfs, defaultMaxConf, account,
17✔
2114
                )
17✔
2115
                if err != nil {
17✔
2116
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2117
                }
×
2118

2119
                // We'll now check that none of the inputs specified in the
2120
                // template are available to us. That means they either don't
2121
                // belong to us or are already locked by the user.
2122
                for _, txIn := range inputs {
22✔
2123
                        for _, utxo := range utxos {
9✔
2124
                                if txIn.PreviousOutPoint == utxo.OutPoint {
4✔
2125
                                        return fmt.Errorf("input %v is not "+
×
2126
                                                "locked", txIn.PreviousOutPoint)
×
2127
                                }
×
2128
                        }
2129
                }
2130

2131
                return nil
17✔
2132
        })
2133
}
2134

2135
// lockAndCreateFundingResponse locks the given outpoints and creates a funding
2136
// response with the serialized PSBT, the change index and the locked UTXOs.
2137
func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet,
2138
        newOutpoints []wire.OutPoint, changeIndex int32) (*FundPsbtResponse,
2139
        error) {
14✔
2140

14✔
2141
        // Make sure we can properly serialize the packet. If this goes wrong
14✔
2142
        // then something isn't right with the inputs, and we probably shouldn't
14✔
2143
        // try to lock any of them.
14✔
2144
        var buf bytes.Buffer
14✔
2145
        err := packet.Serialize(&buf)
14✔
2146
        if err != nil {
14✔
2147
                return nil, fmt.Errorf("error serializing funded PSBT: %w", err)
×
2148
        }
×
2149

2150
        locks, err := lockInputs(w.cfg.Wallet, newOutpoints)
14✔
2151
        if err != nil {
14✔
2152
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2153
        }
×
2154

2155
        // Convert the lock leases to the RPC format.
2156
        rpcLocks := marshallLeases(locks)
14✔
2157

14✔
2158
        return &FundPsbtResponse{
14✔
2159
                FundedPsbt:        buf.Bytes(),
14✔
2160
                ChangeOutputIndex: changeIndex,
14✔
2161
                LockedUtxos:       rpcLocks,
14✔
2162
        }, nil
14✔
2163
}
2164

2165
// handleChange is a closure that either adds the non-zero change amount to an
2166
// existing output or creates a change output. The function returns the new
2167
// change output index if a new change output was added.
2168
func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
2169
        changeAmount int64, changeType chanfunding.ChangeAddressType,
2170
        changeAccount string) (int32, error) {
10✔
2171

10✔
2172
        // Does an existing output get the change?
10✔
2173
        if changeIndex >= 0 {
16✔
2174
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
6✔
2175
                changeOut.Value += changeAmount
6✔
2176

6✔
2177
                return changeIndex, nil
6✔
2178
        }
6✔
2179

2180
        // The user requested a new change output.
2181
        addrType := addrTypeFromChangeAddressType(changeType)
4✔
2182
        changeAddr, err := w.cfg.Wallet.NewAddress(
4✔
2183
                addrType, true, changeAccount,
4✔
2184
        )
4✔
2185
        if err != nil {
4✔
2186
                return 0, fmt.Errorf("could not derive change address: %w", err)
×
2187
        }
×
2188

2189
        changeScript, err := txscript.PayToAddrScript(changeAddr)
4✔
2190
        if err != nil {
4✔
2191
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2192
        }
×
2193

2194
        // We need to add the derivation info for the change address in case it
2195
        // is a P2TR address. This is mostly to prove it's a bare BIP-0086
2196
        // address, which is required for some protocols (such as Taproot
2197
        // Assets).
2198
        pOut := psbt.POutput{}
4✔
2199
        _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
4✔
2200
        if isTaprootChangeAddr {
4✔
2201
                changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
×
2202
                if err != nil {
×
2203
                        return 0, fmt.Errorf("could not get address info: %w",
×
2204
                                err)
×
2205
                }
×
2206

2207
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2208
                        changeAddrInfo,
×
2209
                )
×
2210
                if err != nil {
×
2211
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2212
                                "%w", err)
×
2213
                }
×
2214

2215
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2216
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2217
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2218
                        trDeriv,
×
2219
                }
×
2220
        }
2221

2222
        newChangeIndex := int32(len(packet.Outputs))
4✔
2223
        packet.UnsignedTx.TxOut = append(
4✔
2224
                packet.UnsignedTx.TxOut, &wire.TxOut{
4✔
2225
                        Value:    changeAmount,
4✔
2226
                        PkScript: changeScript,
4✔
2227
                },
4✔
2228
        )
4✔
2229
        packet.Outputs = append(packet.Outputs, pOut)
4✔
2230

4✔
2231
        return newChangeIndex, nil
4✔
2232
}
2233

2234
// marshallLeases converts the lock leases to the RPC format.
2235
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
14✔
2236
        rpcLocks := make([]*UtxoLease, len(locks))
14✔
2237
        for idx, lock := range locks {
27✔
2238
                lock := lock
13✔
2239

13✔
2240
                rpcLocks[idx] = &UtxoLease{
13✔
2241
                        Id:         lock.LockID[:],
13✔
2242
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
13✔
2243
                        Expiration: uint64(lock.Expiration.Unix()),
13✔
2244
                        PkScript:   lock.PkScript,
13✔
2245
                        Value:      uint64(lock.Value),
13✔
2246
                }
13✔
2247
        }
13✔
2248

2249
        return rpcLocks
14✔
2250
}
2251

2252
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2253
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2254
// it returns nil.
2255
func keyScopeFromChangeAddressType(
2256
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
×
2257

×
2258
        switch changeAddressType {
×
2259
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
2260
                return &waddrmgr.KeyScopeBIP0086
×
2261

2262
        default:
×
2263
                return nil
×
2264
        }
2265
}
2266

2267
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2268
// lnwallet.AddressType.
2269
func addrTypeFromChangeAddressType(
2270
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
4✔
2271

4✔
2272
        switch changeAddressType {
4✔
2273
        case chanfunding.P2TRChangeAddress:
2✔
2274
                return lnwallet.TaprootPubkey
2✔
2275

2276
        default:
2✔
2277
                return lnwallet.WitnessPubKey
2✔
2278
        }
2279
}
2280

2281
// SignPsbt expects a partial transaction with all inputs and outputs fully
2282
// declared and tries to sign all unsigned inputs that have all required fields
2283
// (UTXO information, BIP32 derivation information, witness or sig scripts)
2284
// set.
2285
// If no error is returned, the PSBT is ready to be given to the next signer or
2286
// to be finalized if lnd was the last signer.
2287
//
2288
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
2289
// perform any other tasks (such as coin selection, UTXO locking or
2290
// input/output/fee value validation, PSBT finalization). Any input that is
2291
// incomplete will be skipped.
2292
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
2293
        *SignPsbtResponse, error) {
×
2294

×
2295
        packet, err := psbt.NewFromRawBytes(
×
2296
                bytes.NewReader(req.FundedPsbt), false,
×
2297
        )
×
2298
        if err != nil {
×
2299
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2300
                        req.FundedPsbt)
×
2301
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2302
        }
×
2303

2304
        // Before we attempt to sign the packet, ensure that every input either
2305
        // has a witness UTXO, or a non witness UTXO.
2306
        for idx := range packet.UnsignedTx.TxIn {
×
2307
                in := packet.Inputs[idx]
×
2308

×
2309
                // Doesn't have either a witness or non witness UTXO so we need
×
2310
                // to exit here as otherwise signing will fail.
×
2311
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
×
2312
                        return nil, fmt.Errorf("input (index=%v) doesn't "+
×
2313
                                "specify any UTXO info", idx)
×
2314
                }
×
2315
        }
2316

2317
        // Let the wallet do the heavy lifting. This will sign all inputs that
2318
        // we have the UTXO for. If some inputs can't be signed and don't have
2319
        // witness data attached, they will just be skipped.
2320
        signedInputs, err := w.cfg.Wallet.SignPsbt(packet)
×
2321
        if err != nil {
×
2322
                return nil, fmt.Errorf("error signing PSBT: %w", err)
×
2323
        }
×
2324

2325
        // Serialize the signed PSBT in both the packet and wire format.
2326
        var signedPsbtBytes bytes.Buffer
×
2327
        err = packet.Serialize(&signedPsbtBytes)
×
2328
        if err != nil {
×
2329
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2330
        }
×
2331

2332
        return &SignPsbtResponse{
×
2333
                SignedPsbt:   signedPsbtBytes.Bytes(),
×
2334
                SignedInputs: signedInputs,
×
2335
        }, nil
×
2336
}
2337

2338
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
2339
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
2340
// the last signer of the transaction. That means, if there are any unsigned
2341
// non-witness inputs or inputs without UTXO information attached or inputs
2342
// without witness data that do not belong to lnd's wallet, this method will
2343
// fail. If no error is returned, the PSBT is ready to be extracted and the
2344
// final TX within to be broadcast.
2345
//
2346
// NOTE: This method does NOT publish the transaction once finalized. It is the
2347
// caller's responsibility to either publish the transaction on success or
2348
// unlock/release any locked UTXOs in case of an error in this method.
2349
func (w *WalletKit) FinalizePsbt(_ context.Context,
2350
        req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
×
2351

×
2352
        // We'll assume the PSBT was funded by the default account unless
×
2353
        // otherwise specified.
×
2354
        account := lnwallet.DefaultAccountName
×
2355
        if req.Account != "" {
×
2356
                account = req.Account
×
2357
        }
×
2358

2359
        // Parse the funded PSBT.
2360
        packet, err := psbt.NewFromRawBytes(
×
2361
                bytes.NewReader(req.FundedPsbt), false,
×
2362
        )
×
2363
        if err != nil {
×
2364
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2365
        }
×
2366

2367
        // The only check done at this level is to validate that the PSBT is
2368
        // not complete. The wallet performs all other checks.
2369
        if packet.IsComplete() {
×
2370
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2371
        }
×
2372

2373
        // Let the wallet do the heavy lifting. This will sign all inputs that
2374
        // we have the UTXO for. If some inputs can't be signed and don't have
2375
        // witness data attached, this will fail.
2376
        err = w.cfg.Wallet.FinalizePsbt(packet, account)
×
2377
        if err != nil {
×
2378
                return nil, fmt.Errorf("error finalizing PSBT: %w", err)
×
2379
        }
×
2380

2381
        var (
×
2382
                finalPsbtBytes bytes.Buffer
×
2383
                finalTxBytes   bytes.Buffer
×
2384
        )
×
2385

×
2386
        // Serialize the finalized PSBT in both the packet and wire format.
×
2387
        err = packet.Serialize(&finalPsbtBytes)
×
2388
        if err != nil {
×
2389
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2390
        }
×
2391
        finalTx, err := psbt.Extract(packet)
×
2392
        if err != nil {
×
2393
                return nil, fmt.Errorf("unable to extract final TX: %w", err)
×
2394
        }
×
2395
        err = finalTx.Serialize(&finalTxBytes)
×
2396
        if err != nil {
×
2397
                return nil, fmt.Errorf("error serializing final TX: %w", err)
×
2398
        }
×
2399

2400
        return &FinalizePsbtResponse{
×
2401
                SignedPsbt: finalPsbtBytes.Bytes(),
×
2402
                RawFinalTx: finalTxBytes.Bytes(),
×
2403
        }, nil
×
2404
}
2405

2406
// marshalWalletAccount converts the properties of an account into its RPC
2407
// representation.
2408
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2409
        account *waddrmgr.AccountProperties) (*Account, error) {
×
2410

×
2411
        var addrType AddressType
×
2412
        switch account.KeyScope {
×
2413
        case waddrmgr.KeyScopeBIP0049Plus:
×
2414
                // No address schema present represents the traditional BIP-0049
×
2415
                // address derivation scheme.
×
2416
                if account.AddrSchema == nil {
×
2417
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
×
2418
                        break
×
2419
                }
2420

2421
                switch *account.AddrSchema {
×
2422
                case waddrmgr.KeyScopeBIP0049AddrSchema:
×
2423
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
×
2424

2425
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
×
2426
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
×
2427

2428
                default:
×
2429
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2430
                                *account.AddrSchema)
×
2431
                }
2432

2433
        case waddrmgr.KeyScopeBIP0084:
×
2434
                addrType = AddressType_WITNESS_PUBKEY_HASH
×
2435

2436
        case waddrmgr.KeyScopeBIP0086:
×
2437
                addrType = AddressType_TAPROOT_PUBKEY
×
2438

2439
        case internalScope:
×
2440
                addrType = AddressType_WITNESS_PUBKEY_HASH
×
2441

2442
        default:
×
2443
                return nil, fmt.Errorf("account %v has unsupported "+
×
2444
                        "key scope %v", account.AccountName, account.KeyScope)
×
2445
        }
2446

2447
        rpcAccount := &Account{
×
2448
                Name:             account.AccountName,
×
2449
                AddressType:      addrType,
×
2450
                ExternalKeyCount: account.ExternalKeyCount,
×
2451
                InternalKeyCount: account.InternalKeyCount,
×
2452
                WatchOnly:        account.IsWatchOnly,
×
2453
        }
×
2454

×
2455
        // The remaining fields can only be done on accounts other than the
×
2456
        // default imported one existing within each key scope.
×
2457
        if account.AccountName != waddrmgr.ImportedAddrAccountName {
×
2458
                nonHardenedIndex := account.AccountPubKey.ChildIndex() -
×
2459
                        hdkeychain.HardenedKeyStart
×
2460
                rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
×
2461
                if account.MasterKeyFingerprint != 0 {
×
2462
                        var mkfp [4]byte
×
2463
                        binary.BigEndian.PutUint32(
×
2464
                                mkfp[:], account.MasterKeyFingerprint,
×
2465
                        )
×
2466
                        rpcAccount.MasterKeyFingerprint = mkfp[:]
×
2467
                }
×
2468
                rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
×
2469
                        account.KeyScope, nonHardenedIndex)
×
2470
        }
2471

2472
        return rpcAccount, nil
×
2473
}
2474

2475
// marshalWalletAddressList converts the list of address into its RPC
2476
// representation.
2477
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2478
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
×
2479

×
2480
        // Get the RPC representation of account.
×
2481
        rpcAccount, err := marshalWalletAccount(
×
2482
                w.internalScope(), account,
×
2483
        )
×
2484
        if err != nil {
×
2485
                return nil, err
×
2486
        }
×
2487

2488
        addresses := make([]*AddressProperty, len(addressList))
×
2489
        for idx, addr := range addressList {
×
2490
                var pubKeyBytes []byte
×
2491
                if addr.PublicKey != nil {
×
2492
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
×
2493
                }
×
2494
                addresses[idx] = &AddressProperty{
×
2495
                        Address:        addr.Address,
×
2496
                        IsInternal:     addr.Internal,
×
2497
                        Balance:        int64(addr.Balance),
×
2498
                        DerivationPath: addr.DerivationPath,
×
2499
                        PublicKey:      pubKeyBytes,
×
2500
                }
×
2501
        }
2502

2503
        rpcAddressList := &AccountWithAddresses{
×
2504
                Name:           rpcAccount.Name,
×
2505
                AddressType:    rpcAccount.AddressType,
×
2506
                DerivationPath: rpcAccount.DerivationPath,
×
2507
                Addresses:      addresses,
×
2508
        }
×
2509

×
2510
        return rpcAddressList, nil
×
2511
}
2512

2513
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2514
// name and key scope filter can be provided to filter through all of the wallet
2515
// accounts and return only those matching.
2516
func (w *WalletKit) ListAccounts(ctx context.Context,
2517
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
×
2518

×
2519
        // Map the supported address types into their corresponding key scope.
×
2520
        var keyScopeFilter *waddrmgr.KeyScope
×
2521
        switch req.AddressType {
×
2522
        case AddressType_UNKNOWN:
×
2523
                break
×
2524

2525
        case AddressType_WITNESS_PUBKEY_HASH:
×
2526
                keyScope := waddrmgr.KeyScopeBIP0084
×
2527
                keyScopeFilter = &keyScope
×
2528

2529
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2530
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
2531

×
2532
                keyScope := waddrmgr.KeyScopeBIP0049Plus
×
2533
                keyScopeFilter = &keyScope
×
2534

2535
        case AddressType_TAPROOT_PUBKEY:
×
2536
                keyScope := waddrmgr.KeyScopeBIP0086
×
2537
                keyScopeFilter = &keyScope
×
2538

2539
        default:
×
2540
                return nil, fmt.Errorf("unhandled address type %v",
×
2541
                        req.AddressType)
×
2542
        }
2543

2544
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
×
2545
        if err != nil {
×
2546
                return nil, err
×
2547
        }
×
2548

2549
        rpcAccounts := make([]*Account, 0, len(accounts))
×
2550
        for _, account := range accounts {
×
2551
                // Don't include the default imported accounts created by the
×
2552
                // wallet in the response if they don't have any keys imported.
×
2553
                if account.AccountName == waddrmgr.ImportedAddrAccountName &&
×
2554
                        account.ImportedKeyCount == 0 {
×
2555

×
2556
                        continue
×
2557
                }
2558

2559
                rpcAccount, err := marshalWalletAccount(
×
2560
                        w.internalScope(), account,
×
2561
                )
×
2562
                if err != nil {
×
2563
                        return nil, err
×
2564
                }
×
2565
                rpcAccounts = append(rpcAccounts, rpcAccount)
×
2566
        }
2567

2568
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
×
2569
}
2570

2571
// RequiredReserve returns the minimum amount of satoshis that should be
2572
// kept in the wallet in order to fee bump anchor channels if necessary.
2573
// The value scales with the number of public anchor channels but is
2574
// capped at a maximum.
2575
func (w *WalletKit) RequiredReserve(ctx context.Context,
2576
        req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
×
2577

×
2578
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
×
2579
        if err != nil {
×
2580
                return nil, err
×
2581
        }
×
2582

2583
        additionalChans := req.AdditionalPublicChannels
×
2584
        totalChans := uint32(numAnchorChans) + additionalChans
×
2585
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
×
2586

×
2587
        return &RequiredReserveResponse{
×
2588
                RequiredReserve: int64(reserved),
×
2589
        }, nil
×
2590
}
2591

2592
// ListAddresses retrieves all the addresses along with their balance. An
2593
// account name filter can be provided to filter through all of the
2594
// wallet accounts and return the addresses of only those matching.
2595
func (w *WalletKit) ListAddresses(ctx context.Context,
2596
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
×
2597

×
2598
        addressLists, err := w.cfg.Wallet.ListAddresses(
×
2599
                req.AccountName,
×
2600
                req.ShowCustomAccounts,
×
2601
        )
×
2602
        if err != nil {
×
2603
                return nil, err
×
2604
        }
×
2605

2606
        // Create a slice of accounts from addressLists map.
2607
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
×
2608
        for account := range addressLists {
×
2609
                accounts = append(accounts, account)
×
2610
        }
×
2611

2612
        // Sort the accounts by derivation path.
2613
        sort.Slice(accounts, func(i, j int) bool {
×
2614
                scopeI := accounts[i].KeyScope
×
2615
                scopeJ := accounts[j].KeyScope
×
2616
                if scopeI.Purpose == scopeJ.Purpose {
×
2617
                        if scopeI.Coin == scopeJ.Coin {
×
2618
                                acntNumI := accounts[i].AccountNumber
×
2619
                                acntNumJ := accounts[j].AccountNumber
×
2620
                                return acntNumI < acntNumJ
×
2621
                        }
×
2622

2623
                        return scopeI.Coin < scopeJ.Coin
×
2624
                }
2625

2626
                return scopeI.Purpose < scopeJ.Purpose
×
2627
        })
2628

2629
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
×
2630
        for _, account := range accounts {
×
2631
                addressList := addressLists[account]
×
2632
                rpcAddressList, err := marshalWalletAddressList(
×
2633
                        w, account, addressList,
×
2634
                )
×
2635
                if err != nil {
×
2636
                        return nil, err
×
2637
                }
×
2638

2639
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
×
2640
        }
2641

2642
        return &ListAddressesResponse{
×
2643
                AccountWithAddresses: rpcAddressLists,
×
2644
        }, nil
×
2645
}
2646

2647
// parseAddrType parses an address type from its RPC representation to a
2648
// *waddrmgr.AddressType.
2649
func parseAddrType(addrType AddressType,
2650
        required bool) (*waddrmgr.AddressType, error) {
×
2651

×
2652
        switch addrType {
×
2653
        case AddressType_UNKNOWN:
×
2654
                if required {
×
2655
                        return nil, fmt.Errorf("an address type must be " +
×
2656
                                "specified")
×
2657
                }
×
2658
                return nil, nil
×
2659

2660
        case AddressType_WITNESS_PUBKEY_HASH:
×
2661
                addrTyp := waddrmgr.WitnessPubKey
×
2662
                return &addrTyp, nil
×
2663

2664
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
2665
                addrTyp := waddrmgr.NestedWitnessPubKey
×
2666
                return &addrTyp, nil
×
2667

2668
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
2669
                addrTyp := waddrmgr.WitnessPubKey
×
2670
                return &addrTyp, nil
×
2671

2672
        case AddressType_TAPROOT_PUBKEY:
×
2673
                addrTyp := waddrmgr.TaprootPubKey
×
2674
                return &addrTyp, nil
×
2675

2676
        default:
×
2677
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2678
        }
2679
}
2680

2681
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2682
// transaction or a signature. It is prepended in front of the message and
2683
// follows the same standard as bitcoin core and btcd.
2684
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2685

2686
// SignMessageWithAddr signs a message with the private key of the provided
2687
// address. The address needs to belong to the lnd wallet.
2688
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2689
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
×
2690

×
2691
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
×
2692
        if err != nil {
×
2693
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2694
        }
×
2695

2696
        if !addr.IsForNet(w.cfg.ChainParams) {
×
2697
                return nil, fmt.Errorf("encoded address is for "+
×
2698
                        "the wrong network %s", req.Addr)
×
2699
        }
×
2700

2701
        // Fetch address infos from own wallet and check whether it belongs
2702
        // to the lnd wallet.
2703
        managedAddr, err := w.cfg.Wallet.AddressInfo(addr)
×
2704
        if err != nil {
×
2705
                return nil, fmt.Errorf("address could not be found in the "+
×
2706
                        "wallet database: %w", err)
×
2707
        }
×
2708

2709
        // Verifying by checking the interface type that the wallet knows about
2710
        // the public and private keys so it can sign the message with the
2711
        // private key of this address.
2712
        pubKey, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
×
2713
        if !ok {
×
2714
                return nil, fmt.Errorf("private key to address is unknown")
×
2715
        }
×
2716

2717
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
×
2718
        if err != nil {
×
2719
                return nil, err
×
2720
        }
×
2721

2722
        // For all address types (P2WKH, NP2WKH,P2TR) the ECDSA compact signing
2723
        // algorithm is used. For P2TR addresses this represents a special case.
2724
        // ECDSA is used to create a compact signature which makes the public
2725
        // key of the signature recoverable. For Schnorr no known compact
2726
        // signing algorithm exists yet.
2727
        privKey, err := pubKey.PrivKey()
×
2728
        if err != nil {
×
2729
                return nil, fmt.Errorf("no private key could be "+
×
2730
                        "fetched from wallet database: %w", err)
×
2731
        }
×
2732

2733
        sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
×
2734

×
2735
        // Bitcoin signatures are base64 encoded (being compatible with
×
2736
        // bitcoin-core and btcd).
×
2737
        sig := base64.StdEncoding.EncodeToString(sigBytes)
×
2738

×
2739
        return &SignMessageWithAddrResponse{
×
2740
                Signature: sig,
×
2741
        }, nil
×
2742
}
2743

2744
// VerifyMessageWithAddr verifies a signature on a message with a provided
2745
// address, it checks both the validity of the signature itself and then
2746
// verifies whether the signature corresponds to the public key of the
2747
// provided address. There is no dependence on the private key of the address
2748
// therefore also external addresses are allowed to verify signatures.
2749
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
2750
func (w *WalletKit) VerifyMessageWithAddr(_ context.Context,
2751
        req *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse,
2752
        error) {
×
2753

×
2754
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
×
2755
        if err != nil {
×
2756
                return nil, fmt.Errorf("malformed base64 encoding of "+
×
2757
                        "the signature: %w", err)
×
2758
        }
×
2759

2760
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
×
2761
        if err != nil {
×
2762
                return nil, err
×
2763
        }
×
2764

2765
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
×
2766
        if err != nil {
×
2767
                return nil, fmt.Errorf("unable to recover public key "+
×
2768
                        "from compact signature: %w", err)
×
2769
        }
×
2770

2771
        var serializedPubkey []byte
×
2772
        if wasCompressed {
×
2773
                serializedPubkey = pk.SerializeCompressed()
×
2774
        } else {
×
2775
                serializedPubkey = pk.SerializeUncompressed()
×
2776
        }
×
2777

2778
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
×
2779
        if err != nil {
×
2780
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2781
        }
×
2782

2783
        if !addr.IsForNet(w.cfg.ChainParams) {
×
2784
                return nil, fmt.Errorf("encoded address is for"+
×
2785
                        "the wrong network %s", req.Addr)
×
2786
        }
×
2787

2788
        var (
×
2789
                address    btcutil.Address
×
2790
                pubKeyHash = btcutil.Hash160(serializedPubkey)
×
2791
        )
×
2792

×
2793
        // Ensure the address is one of the supported types.
×
2794
        switch addr.(type) {
×
2795
        case *btcutil.AddressPubKeyHash:
×
2796
                address, err = btcutil.NewAddressPubKeyHash(
×
2797
                        pubKeyHash, w.cfg.ChainParams,
×
2798
                )
×
2799
                if err != nil {
×
2800
                        return nil, err
×
2801
                }
×
2802

2803
        case *btcutil.AddressWitnessPubKeyHash:
×
2804
                address, err = btcutil.NewAddressWitnessPubKeyHash(
×
2805
                        pubKeyHash, w.cfg.ChainParams,
×
2806
                )
×
2807
                if err != nil {
×
2808
                        return nil, err
×
2809
                }
×
2810

2811
        case *btcutil.AddressScriptHash:
×
2812
                // Check if address is a Nested P2WKH (NP2WKH).
×
2813
                address, err = btcutil.NewAddressWitnessPubKeyHash(
×
2814
                        pubKeyHash, w.cfg.ChainParams,
×
2815
                )
×
2816
                if err != nil {
×
2817
                        return nil, err
×
2818
                }
×
2819

2820
                witnessScript, err := txscript.PayToAddrScript(address)
×
2821
                if err != nil {
×
2822
                        return nil, err
×
2823
                }
×
2824

2825
                address, err = btcutil.NewAddressScriptHashFromHash(
×
2826
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
×
2827
                )
×
2828
                if err != nil {
×
2829
                        return nil, err
×
2830
                }
×
2831

2832
        case *btcutil.AddressTaproot:
×
2833
                // Only addresses without a tapscript are allowed because
×
2834
                // the verification is using the internal key.
×
2835
                tapKey := txscript.ComputeTaprootKeyNoScript(pk)
×
2836
                address, err = btcutil.NewAddressTaproot(
×
2837
                        schnorr.SerializePubKey(tapKey),
×
2838
                        w.cfg.ChainParams,
×
2839
                )
×
2840
                if err != nil {
×
2841
                        return nil, err
×
2842
                }
×
2843

2844
        default:
×
2845
                return nil, fmt.Errorf("unsupported address type")
×
2846
        }
2847

2848
        return &VerifyMessageWithAddrResponse{
×
2849
                Valid:  req.Addr == address.EncodeAddress(),
×
2850
                Pubkey: serializedPubkey,
×
2851
        }, nil
×
2852
}
2853

2854
// ImportAccount imports an account backed by an account extended public key.
2855
// The master key fingerprint denotes the fingerprint of the root key
2856
// corresponding to the account public key (also known as the key with
2857
// derivation path m/). This may be required by some hardware wallets for proper
2858
// identification and signing.
2859
//
2860
// The address type can usually be inferred from the key's version, but may be
2861
// required for certain keys to map them into the proper scope.
2862
//
2863
// For BIP-0044 keys, an address type must be specified as we intend to not
2864
// support importing BIP-0044 keys into the wallet using the legacy
2865
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
2866
// the standard BIP-0049 derivation scheme, while a witness address type will
2867
// force the standard BIP-0084 derivation scheme.
2868
//
2869
// For BIP-0049 keys, an address type must also be specified to make a
2870
// distinction between the standard BIP-0049 address schema (nested witness
2871
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
2872
// externally, witness pubkeys internally).
2873
func (w *WalletKit) ImportAccount(_ context.Context,
2874
        req *ImportAccountRequest) (*ImportAccountResponse, error) {
×
2875

×
2876
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
×
2877
        if err != nil {
×
2878
                return nil, err
×
2879
        }
×
2880

2881
        var mkfp uint32
×
2882
        switch len(req.MasterKeyFingerprint) {
×
2883
        // No master key fingerprint provided, which is fine as it's not
2884
        // required.
2885
        case 0:
×
2886
        // Expected length.
2887
        case 4:
×
2888
                mkfp = binary.BigEndian.Uint32(req.MasterKeyFingerprint)
×
2889
        default:
×
2890
                return nil, errors.New("invalid length for master key " +
×
2891
                        "fingerprint, expected 4 bytes in big-endian")
×
2892
        }
2893

2894
        addrType, err := parseAddrType(req.AddressType, false)
×
2895
        if err != nil {
×
2896
                return nil, err
×
2897
        }
×
2898

2899
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
×
2900
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
×
2901
        )
×
2902
        if err != nil {
×
2903
                return nil, err
×
2904
        }
×
2905

2906
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
×
2907
        if err != nil {
×
2908
                return nil, err
×
2909
        }
×
2910

2911
        resp := &ImportAccountResponse{Account: rpcAccount}
×
2912
        if !req.DryRun {
×
2913
                return resp, nil
×
2914
        }
×
2915

2916
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
2917
        for i := 0; i < len(extAddrs); i++ {
×
2918
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
2919
        }
×
2920
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
2921
        for i := 0; i < len(intAddrs); i++ {
×
2922
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
2923
        }
×
2924

2925
        return resp, nil
×
2926
}
2927

2928
// ImportPublicKey imports a single derived public key into the wallet. The
2929
// address type can usually be inferred from the key's version, but in the case
2930
// of legacy versions (xpub, tpub), an address type must be specified as we
2931
// intend to not support importing BIP-44 keys into the wallet using the legacy
2932
// pay-to-pubkey-hash (P2PKH) scheme. For Taproot keys, this will only watch
2933
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
2934
// spend or script spend outputs.
2935
func (w *WalletKit) ImportPublicKey(_ context.Context,
2936
        req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
×
2937

×
2938
        var (
×
2939
                pubKey *btcec.PublicKey
×
2940
                err    error
×
2941
        )
×
2942
        switch req.AddressType {
×
2943
        case AddressType_TAPROOT_PUBKEY:
×
2944
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
×
2945

2946
        default:
×
2947
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
×
2948
        }
2949
        if err != nil {
×
2950
                return nil, err
×
2951
        }
×
2952

2953
        addrType, err := parseAddrType(req.AddressType, true)
×
2954
        if err != nil {
×
2955
                return nil, err
×
2956
        }
×
2957

2958
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
×
2959
                return nil, err
×
2960
        }
×
2961

2962
        return &ImportPublicKeyResponse{
×
2963
                Status: fmt.Sprintf("public key %x imported",
×
2964
                        pubKey.SerializeCompressed()),
×
2965
        }, nil
×
2966
}
2967

2968
// ImportTapscript imports a Taproot script and internal key and adds the
2969
// resulting Taproot output key as a watch-only output script into the wallet.
2970
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
2971
// path) use ImportPublicKey.
2972
//
2973
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
2974
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
2975
func (w *WalletKit) ImportTapscript(_ context.Context,
2976
        req *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
×
2977

×
2978
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
×
2979
        if err != nil {
×
2980
                return nil, fmt.Errorf("error parsing internal key: %w", err)
×
2981
        }
×
2982

2983
        var tapscript *waddrmgr.Tapscript
×
2984
        switch {
×
2985
        case req.GetFullTree() != nil:
×
2986
                tree := req.GetFullTree()
×
2987
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
×
2988
                for idx, leaf := range tree.AllLeaves {
×
2989
                        leaves[idx] = txscript.TapLeaf{
×
2990
                                LeafVersion: txscript.TapscriptLeafVersion(
×
2991
                                        leaf.LeafVersion,
×
2992
                                ),
×
2993
                                Script: leaf.Script,
×
2994
                        }
×
2995
                }
×
2996

2997
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
×
2998

2999
        case req.GetPartialReveal() != nil:
×
3000
                partialReveal := req.GetPartialReveal()
×
3001
                if partialReveal.RevealedLeaf == nil {
×
3002
                        return nil, fmt.Errorf("missing revealed leaf")
×
3003
                }
×
3004

3005
                revealedLeaf := txscript.TapLeaf{
×
3006
                        LeafVersion: txscript.TapscriptLeafVersion(
×
3007
                                partialReveal.RevealedLeaf.LeafVersion,
×
3008
                        ),
×
3009
                        Script: partialReveal.RevealedLeaf.Script,
×
3010
                }
×
3011
                if len(partialReveal.FullInclusionProof)%32 != 0 {
×
3012
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
3013
                                "length, expected multiple of 32, got %d",
×
3014
                                len(partialReveal.FullInclusionProof)%32)
×
3015
                }
×
3016

3017
                tapscript = input.TapscriptPartialReveal(
×
3018
                        internalKey, revealedLeaf,
×
3019
                        partialReveal.FullInclusionProof,
×
3020
                )
×
3021

3022
        case req.GetRootHashOnly() != nil:
×
3023
                rootHash := req.GetRootHashOnly()
×
3024
                if len(rootHash) == 0 {
×
3025
                        return nil, fmt.Errorf("missing root hash")
×
3026
                }
×
3027

3028
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
×
3029

3030
        case req.GetFullKeyOnly():
×
3031
                tapscript = input.TapscriptFullKeyOnly(internalKey)
×
3032

3033
        default:
×
3034
                return nil, fmt.Errorf("invalid script")
×
3035
        }
3036

3037
        taprootScope := waddrmgr.KeyScopeBIP0086
×
3038
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
×
3039
        if err != nil {
×
3040
                return nil, fmt.Errorf("error importing script into wallet: %w",
×
3041
                        err)
×
3042
        }
×
3043

3044
        return &ImportTapscriptResponse{
×
3045
                P2TrAddress: addr.Address().String(),
×
3046
        }, nil
×
3047
}
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