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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

67.22
/lnwallet/btcwallet/psbt.go
1
package btcwallet
2

3
import (
4
        "bytes"
5
        "crypto/sha256"
6
        "errors"
7
        "fmt"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
11
        "github.com/btcsuite/btcd/btcutil"
12
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
13
        "github.com/btcsuite/btcd/btcutil/psbt"
14
        "github.com/btcsuite/btcd/txscript"
15
        "github.com/btcsuite/btcd/wire"
16
        "github.com/btcsuite/btcwallet/waddrmgr"
17
        "github.com/btcsuite/btcwallet/wallet"
18
        "github.com/btcsuite/btcwallet/wtxmgr"
19
        "github.com/lightningnetwork/lnd/input"
20
        "github.com/lightningnetwork/lnd/keychain"
21
        "github.com/lightningnetwork/lnd/lnwallet"
22
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
23
)
24

25
var (
26
        // PsbtKeyTypeInputSignatureTweakSingle is a custom/proprietary PSBT key
27
        // for an input that specifies what single tweak should be applied to
28
        // the key before signing the input. The value 51 is leet speak for
29
        // "si", short for "single".
30
        PsbtKeyTypeInputSignatureTweakSingle = []byte{0x51}
31

32
        // PsbtKeyTypeInputSignatureTweakDouble is a custom/proprietary PSBT key
33
        // for an input that specifies what double tweak should be applied to
34
        // the key before signing the input. The value d0 is leet speak for
35
        // "do", short for "double".
36
        PsbtKeyTypeInputSignatureTweakDouble = []byte{0xd0}
37

38
        // ErrInputMissingUTXOInfo is returned if a PSBT input is supplied that
39
        // does not specify the witness UTXO info.
40
        ErrInputMissingUTXOInfo = errors.New(
41
                "input doesn't specify any UTXO info",
42
        )
43

44
        // ErrScriptSpendFeeEstimationUnsupported is returned if a PSBT input is
45
        // of a script spend type.
46
        ErrScriptSpendFeeEstimationUnsupported = errors.New(
47
                "cannot estimate fee for script spend inputs",
48
        )
49

50
        // ErrUnsupportedScript is returned if a supplied pk script is not
51
        // known or supported.
52
        ErrUnsupportedScript = errors.New("unsupported or unknown pk script")
53
)
54

55
// FundPsbt creates a fully populated PSBT packet that contains enough inputs to
56
// fund the outputs specified in the passed in packet with the specified fee
57
// rate. If there is change left, a change output from the internal wallet is
58
// added and the index of the change output is returned. Otherwise no additional
59
// output is created and the index -1 is returned. If no custom change
60
// scope is specified, the BIP0084 will be used for default accounts and single
61
// imported public keys. For custom account, no key scope should be provided
62
// as the coin selection key scope will always be used to generate the change
63
// address.
64
// The function argument `allowUtxo` specifies a filter function for utxos
65
// during coin selection. It should return true for utxos that can be used and
66
// false for those that should be excluded.
67
//
68
// NOTE: If the packet doesn't contain any inputs, coin selection is performed
69
// automatically. The account parameter must be non-empty as it determines which
70
// set of coins are eligible for coin selection. If the packet does contain any
71
// inputs, it is assumed that full coin selection happened externally and no
72
// additional inputs are added. If the specified inputs aren't enough to fund
73
// the outputs with the given fee rate, an error is returned. No lock lease is
74
// acquired for any of the selected/validated inputs. It is in the caller's
75
// responsibility to lock the inputs before handing them out.
76
//
77
// This is a part of the WalletController interface.
78
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
79
        feeRate chainfee.SatPerKWeight, accountName string,
80
        changeScope *waddrmgr.KeyScope,
81
        strategy wallet.CoinSelectionStrategy,
82
        allowUtxo func(wtxmgr.Credit) bool) (int32, error) {
3✔
83

3✔
84
        // The fee rate is passed in using units of sat/kw, so we'll convert
3✔
85
        // this to sat/KB as the CreateSimpleTx method requires this unit.
3✔
86
        feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
3✔
87

3✔
88
        var (
3✔
89
                keyScope   *waddrmgr.KeyScope
3✔
90
                accountNum uint32
3✔
91
        )
3✔
92

3✔
93
        switch accountName {
3✔
94
        // For default accounts and single imported public keys, we'll provide a
95
        // nil key scope to FundPsbt, allowing it to select inputs from all
96
        // scopes (NP2WKH, P2WKH, P2TR). By default, the change key scope for
97
        // these accounts will be P2WKH.
98
        case lnwallet.DefaultAccountName:
3✔
99
                if changeScope == nil {
6✔
100
                        changeScope = &waddrmgr.KeyScopeBIP0084
3✔
101
                }
3✔
102

103
                accountNum = defaultAccount
3✔
104

105
        case waddrmgr.ImportedAddrAccountName:
3✔
106
                if changeScope == nil {
6✔
107
                        changeScope = &waddrmgr.KeyScopeBIP0084
3✔
108
                }
3✔
109

110
                accountNum = importedAccount
3✔
111

112
        // Otherwise, map the account name to its key scope and internal account
113
        // number to only select inputs from said account. No change key scope
114
        // should have been specified as a custom account should only have one
115
        // key scope. Providing a change key scope would break this assumption
116
        // and lead to non-deterministic behavior by using a different change
117
        // key scope than the custom account key scope. The change key scope
118
        // will always be the same as the coin selection.
119
        default:
3✔
120
                if changeScope != nil {
3✔
121
                        return 0, fmt.Errorf("couldn't select a " +
×
122
                                "custom change type for custom accounts")
×
123
                }
×
124

125
                scope, account, err := b.lookupFirstCustomAccount(accountName)
3✔
126
                if err != nil {
3✔
127
                        return 0, err
×
128
                }
×
129
                keyScope = &scope
3✔
130
                changeScope = keyScope
3✔
131
                accountNum = account
3✔
132
        }
133

134
        var opts []wallet.TxCreateOption
3✔
135
        if changeScope != nil {
6✔
136
                opts = append(opts, wallet.WithCustomChangeScope(changeScope))
3✔
137
        }
3✔
138
        if allowUtxo != nil {
6✔
139
                opts = append(opts, wallet.WithUtxoFilter(allowUtxo))
3✔
140
        }
3✔
141

142
        // Let the wallet handle coin selection and/or fee estimation based on
143
        // the partial TX information in the packet.
144
        return b.wallet.FundPsbt(
3✔
145
                packet, keyScope, minConfs, accountNum, feeSatPerKB,
3✔
146
                strategy, opts...,
3✔
147
        )
3✔
148
}
149

150
// SignPsbt expects a partial transaction with all inputs and outputs fully
151
// declared and tries to sign all unsigned inputs that have all required fields
152
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
153
// If no error is returned, the PSBT is ready to be given to the next signer or
154
// to be finalized if lnd was the last signer.
155
//
156
// NOTE: This method only signs inputs (and only those it can sign), it does not
157
// perform any other tasks (such as coin selection, UTXO locking or
158
// input/output/fee value validation, PSBT finalization). Any input that is
159
// incomplete will be skipped.
160
func (b *BtcWallet) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
3✔
161
        // In signedInputs we return the indices of psbt inputs that were signed
3✔
162
        // by our wallet. This way the caller can check if any inputs were signed.
3✔
163
        var signedInputs []uint32
3✔
164

3✔
165
        // Let's check that this is actually something we can and want to sign.
3✔
166
        // We need at least one input and one output. In addition each
3✔
167
        // input needs nonWitness Utxo or witness Utxo data specified.
3✔
168
        err := psbt.InputsReadyToSign(packet)
3✔
169
        if err != nil {
3✔
170
                return nil, err
×
171
        }
×
172

173
        // Go through each input that doesn't have final witness data attached
174
        // to it already and try to sign it. If there is nothing more to sign or
175
        // there are inputs that we don't know how to sign, we won't return any
176
        // error. So it's possible we're not the final signer.
177
        tx := packet.UnsignedTx
3✔
178
        prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
3✔
179
        sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
3✔
180
        for idx := range tx.TxIn {
6✔
181
                in := &packet.Inputs[idx]
3✔
182

3✔
183
                // We can only sign if we have UTXO information available. Since
3✔
184
                // we don't finalize, we just skip over any input that we know
3✔
185
                // we can't do anything with. Since we only support signing
3✔
186
                // witness inputs, we only look at the witness UTXO being set.
3✔
187
                if in.WitnessUtxo == nil {
3✔
188
                        continue
×
189
                }
190

191
                // Skip this input if it's got final witness data attached.
192
                if len(in.FinalScriptWitness) > 0 {
6✔
193
                        continue
3✔
194
                }
195

196
                // Skip this input if there is no BIP32 derivation info
197
                // available.
198
                if len(in.Bip32Derivation) == 0 {
6✔
199
                        continue
3✔
200
                }
201

202
                // TODO(guggero): For multisig, we'll need to find out what key
203
                // to use and there should be multiple derivation paths in the
204
                // BIP32 derivation field.
205

206
                // Let's try and derive the key now. This method will decide if
207
                // it's a BIP49/84 key for normal on-chain funds or a key of the
208
                // custom purpose 1017 key scope.
209
                derivationInfo := in.Bip32Derivation[0]
3✔
210
                privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path)
3✔
211
                if err != nil {
3✔
212
                        log.Warnf("SignPsbt: Skipping input %d, error "+
×
213
                                "deriving signing key: %v", idx, err)
×
214
                        continue
×
215
                }
216

217
                // We need to make sure we actually derived the key that was
218
                // expected to be derived.
219
                pubKeysEqual := bytes.Equal(
3✔
220
                        derivationInfo.PubKey,
3✔
221
                        privKey.PubKey().SerializeCompressed(),
3✔
222
                )
3✔
223
                if !pubKeysEqual {
6✔
224
                        log.Warnf("SignPsbt: Skipping input %d, derived "+
3✔
225
                                "public key %x does not match bip32 "+
3✔
226
                                "derivation info public key %x", idx,
3✔
227
                                privKey.PubKey().SerializeCompressed(),
3✔
228
                                derivationInfo.PubKey)
3✔
229
                        continue
3✔
230
                }
231

232
                // Do we need to tweak anything? Single or double tweaks are
233
                // sent as custom/proprietary fields in the PSBT input section.
234
                privKey = maybeTweakPrivKeyPsbt(in.Unknowns, privKey)
3✔
235

3✔
236
                // What kind of signature is expected from us and do we have all
3✔
237
                // information we need?
3✔
238
                signMethod, err := validateSigningMethod(in)
3✔
239
                if err != nil {
3✔
240
                        return nil, err
×
241
                }
×
242

243
                switch signMethod {
3✔
244
                // For p2wkh, np2wkh and p2wsh.
245
                case input.WitnessV0SignMethod:
3✔
246
                        err = signSegWitV0(in, tx, sigHashes, idx, privKey)
3✔
247

248
                // For p2tr BIP0086 key spend only.
249
                case input.TaprootKeySpendBIP0086SignMethod:
3✔
250
                        rootHash := make([]byte, 0)
3✔
251
                        err = signSegWitV1KeySpend(
3✔
252
                                in, tx, sigHashes, idx, privKey, rootHash,
3✔
253
                        )
3✔
254

255
                // For p2tr with script commitment key spend path.
256
                case input.TaprootKeySpendSignMethod:
3✔
257
                        rootHash := in.TaprootMerkleRoot
3✔
258
                        err = signSegWitV1KeySpend(
3✔
259
                                in, tx, sigHashes, idx, privKey, rootHash,
3✔
260
                        )
3✔
261

262
                // For p2tr script spend path.
263
                case input.TaprootScriptSpendSignMethod:
3✔
264
                        leafScript := in.TaprootLeafScript[0]
3✔
265
                        leaf := txscript.TapLeaf{
3✔
266
                                LeafVersion: leafScript.LeafVersion,
3✔
267
                                Script:      leafScript.Script,
3✔
268
                        }
3✔
269
                        err = signSegWitV1ScriptSpend(
3✔
270
                                in, tx, sigHashes, idx, privKey, leaf,
3✔
271
                        )
3✔
272

273
                default:
×
274
                        err = fmt.Errorf("unsupported signing method for "+
×
275
                                "PSBT signing: %v", signMethod)
×
276
                }
277
                if err != nil {
3✔
278
                        return nil, err
×
279
                }
×
280
                signedInputs = append(signedInputs, uint32(idx))
3✔
281
        }
282
        return signedInputs, nil
3✔
283
}
284

285
// validateSigningMethod attempts to detect the signing method that is required
286
// to sign for the given PSBT input and makes sure all information is available
287
// to do so.
288
func validateSigningMethod(in *psbt.PInput) (input.SignMethod, error) {
3✔
289
        script, err := txscript.ParsePkScript(in.WitnessUtxo.PkScript)
3✔
290
        if err != nil {
3✔
291
                return 0, fmt.Errorf("error detecting signing method, "+
×
292
                        "couldn't parse pkScript: %v", err)
×
293
        }
×
294

295
        switch script.Class() {
3✔
296
        case txscript.WitnessV0PubKeyHashTy, txscript.ScriptHashTy,
297
                txscript.WitnessV0ScriptHashTy:
3✔
298

3✔
299
                return input.WitnessV0SignMethod, nil
3✔
300

301
        case txscript.WitnessV1TaprootTy:
3✔
302
                if len(in.TaprootBip32Derivation) == 0 {
3✔
UNCOV
303
                        return 0, fmt.Errorf("cannot sign for taproot input " +
×
UNCOV
304
                                "without taproot BIP0032 derivation info")
×
UNCOV
305
                }
×
306

307
                // Currently, we only support creating one signature per input.
308
                //
309
                // TODO(guggero): Should we support signing multiple paths at
310
                // the same time? What are the performance and security
311
                // implications?
312
                if len(in.TaprootBip32Derivation) > 1 {
3✔
313
                        return 0, fmt.Errorf("unsupported multiple taproot " +
×
314
                                "BIP0032 derivation info found, can only " +
×
315
                                "sign for one at a time")
×
316
                }
×
317

318
                derivation := in.TaprootBip32Derivation[0]
3✔
319
                switch {
3✔
320
                // No leaf hashes means this is the internal key we're signing
321
                // with, so it's a key spend. And no merkle root means this is
322
                // a BIP0086 output we're signing for.
323
                case len(derivation.LeafHashes) == 0 &&
324
                        len(in.TaprootMerkleRoot) == 0:
3✔
325

3✔
326
                        return input.TaprootKeySpendBIP0086SignMethod, nil
3✔
327

328
                // A non-empty merkle root means we committed to a taproot hash
329
                // that we need to use in the tap tweak.
330
                case len(derivation.LeafHashes) == 0:
3✔
331
                        // Getting here means the merkle root isn't empty, but
3✔
332
                        // is it exactly the length we need?
3✔
333
                        if len(in.TaprootMerkleRoot) != sha256.Size {
3✔
334
                                return 0, fmt.Errorf("invalid taproot merkle "+
×
335
                                        "root length, got %d expected %d",
×
336
                                        len(in.TaprootMerkleRoot), sha256.Size)
×
337
                        }
×
338

339
                        return input.TaprootKeySpendSignMethod, nil
3✔
340

341
                // Currently, we only support signing for one leaf at a time.
342
                //
343
                // TODO(guggero): Should we support signing multiple paths at
344
                // the same time? What are the performance and security
345
                // implications?
346
                case len(derivation.LeafHashes) == 1:
3✔
347
                        // If we're supposed to be signing for a leaf hash, we
3✔
348
                        // also expect the leaf script that hashes to that hash
3✔
349
                        // in the appropriate field.
3✔
350
                        if len(in.TaprootLeafScript) != 1 {
3✔
351
                                return 0, fmt.Errorf("specified leaf hash in " +
×
352
                                        "taproot BIP0032 derivation but " +
×
353
                                        "missing taproot leaf script")
×
354
                        }
×
355

356
                        leafScript := in.TaprootLeafScript[0]
3✔
357
                        leaf := txscript.TapLeaf{
3✔
358
                                LeafVersion: leafScript.LeafVersion,
3✔
359
                                Script:      leafScript.Script,
3✔
360
                        }
3✔
361
                        leafHash := leaf.TapHash()
3✔
362
                        if !bytes.Equal(leafHash[:], derivation.LeafHashes[0]) {
3✔
363
                                return 0, fmt.Errorf("specified leaf hash in" +
×
364
                                        "taproot BIP0032 derivation but " +
×
365
                                        "corresponding taproot leaf script " +
×
366
                                        "was not found")
×
367
                        }
×
368

369
                        return input.TaprootScriptSpendSignMethod, nil
3✔
370

371
                default:
×
372
                        return 0, fmt.Errorf("unsupported number of leaf " +
×
373
                                "hashes in taproot BIP0032 derivation info, " +
×
374
                                "can only sign for one at a time")
×
375
                }
376

377
        default:
×
378
                return 0, fmt.Errorf("unsupported script class for signing "+
×
379
                        "PSBT: %v", script.Class())
×
380
        }
381
}
382

383
// EstimateInputWeight estimates the weight of a PSBT input and adds it to the
384
// passed in TxWeightEstimator. It returns an error if the input type is
385
// unknown or unsupported. Only inputs that have a known witness size are
386
// supported, which is P2WKH, NP2WKH and P2TR (key spend path).
387
func EstimateInputWeight(in *psbt.PInput, w *input.TxWeightEstimator) error {
3✔
388
        if in.WitnessUtxo == nil {
3✔
UNCOV
389
                return ErrInputMissingUTXOInfo
×
UNCOV
390
        }
×
391

392
        pkScript := in.WitnessUtxo.PkScript
3✔
393
        switch {
3✔
UNCOV
394
        case txscript.IsPayToScriptHash(pkScript):
×
UNCOV
395
                w.AddNestedP2WKHInput()
×
396

397
        case txscript.IsPayToWitnessPubKeyHash(pkScript):
3✔
398
                w.AddP2WKHInput()
3✔
399

UNCOV
400
        case txscript.IsPayToWitnessScriptHash(pkScript):
×
UNCOV
401
                return fmt.Errorf("P2WSH inputs are not supported, cannot "+
×
UNCOV
402
                        "estimate witness size for script spend: %w",
×
UNCOV
403
                        ErrScriptSpendFeeEstimationUnsupported)
×
404

UNCOV
405
        case txscript.IsPayToTaproot(pkScript):
×
UNCOV
406
                signMethod, err := validateSigningMethod(in)
×
UNCOV
407
                if err != nil {
×
UNCOV
408
                        return fmt.Errorf("error determining p2tr signing "+
×
UNCOV
409
                                "method: %w", err)
×
UNCOV
410
                }
×
411

UNCOV
412
                switch signMethod {
×
413
                // For p2tr key spend paths.
414
                case input.TaprootKeySpendBIP0086SignMethod,
UNCOV
415
                        input.TaprootKeySpendSignMethod:
×
UNCOV
416

×
UNCOV
417
                        w.AddTaprootKeySpendInput(in.SighashType)
×
418

419
                // For p2tr script spend path.
UNCOV
420
                case input.TaprootScriptSpendSignMethod:
×
UNCOV
421
                        return fmt.Errorf("P2TR inputs are not supported, "+
×
UNCOV
422
                                "cannot estimate witness size for script "+
×
UNCOV
423
                                "spend: %w",
×
UNCOV
424
                                ErrScriptSpendFeeEstimationUnsupported)
×
425

426
                default:
×
427
                        return fmt.Errorf("unsupported signing method for "+
×
428
                                "PSBT signing: %v", signMethod)
×
429
                }
430

UNCOV
431
        default:
×
UNCOV
432
                return fmt.Errorf("unknown input type for script %x: %w",
×
UNCOV
433
                        pkScript, ErrUnsupportedScript)
×
434
        }
435

436
        return nil
3✔
437
}
438

439
// SignSegWitV0 attempts to generate a signature for a SegWit version 0 input
440
// and stores it in the PartialSigs (and FinalScriptSig for np2wkh addresses)
441
// field.
442
func signSegWitV0(in *psbt.PInput, tx *wire.MsgTx,
443
        sigHashes *txscript.TxSigHashes, idx int,
444
        privKey *btcec.PrivateKey) error {
3✔
445

3✔
446
        pubKeyBytes := privKey.PubKey().SerializeCompressed()
3✔
447

3✔
448
        // Extract the correct witness and/or legacy scripts now, depending on
3✔
449
        // the type of input we sign. The txscript package has the peculiar
3✔
450
        // requirement that the PkScript of a P2PKH must be given as the witness
3✔
451
        // script in order for it to arrive at the correct sighash. That's why
3✔
452
        // we call it subScript here instead of witness script.
3✔
453
        subScript := prepareScriptsV0(in)
3✔
454

3✔
455
        // We have everything we need for signing the input now.
3✔
456
        sig, err := txscript.RawTxInWitnessSignature(
3✔
457
                tx, sigHashes, idx, in.WitnessUtxo.Value, subScript,
3✔
458
                in.SighashType, privKey,
3✔
459
        )
3✔
460
        if err != nil {
3✔
461
                return fmt.Errorf("error signing input %d: %w", idx, err)
×
462
        }
×
463
        in.PartialSigs = append(in.PartialSigs, &psbt.PartialSig{
3✔
464
                PubKey:    pubKeyBytes,
3✔
465
                Signature: sig,
3✔
466
        })
3✔
467

3✔
468
        return nil
3✔
469
}
470

471
// signSegWitV1KeySpend attempts to generate a signature for a SegWit version 1
472
// (p2tr) input and stores it in the TaprootKeySpendSig field.
473
func signSegWitV1KeySpend(in *psbt.PInput, tx *wire.MsgTx,
474
        sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
475
        tapscriptRootHash []byte) error {
3✔
476

3✔
477
        rawSig, err := txscript.RawTxInTaprootSignature(
3✔
478
                tx, sigHashes, idx, in.WitnessUtxo.Value,
3✔
479
                in.WitnessUtxo.PkScript, tapscriptRootHash, in.SighashType,
3✔
480
                privKey,
3✔
481
        )
3✔
482
        if err != nil {
3✔
483
                return fmt.Errorf("error signing taproot input %d: %w", idx,
×
484
                        err)
×
485
        }
×
486

487
        in.TaprootKeySpendSig = rawSig
3✔
488

3✔
489
        return nil
3✔
490
}
491

492
// signSegWitV1ScriptSpend attempts to generate a signature for a SegWit version
493
// 1 (p2tr) input and stores it in the TaprootScriptSpendSig field.
494
func signSegWitV1ScriptSpend(in *psbt.PInput, tx *wire.MsgTx,
495
        sigHashes *txscript.TxSigHashes, idx int, privKey *btcec.PrivateKey,
496
        leaf txscript.TapLeaf) error {
3✔
497

3✔
498
        rawSig, err := txscript.RawTxInTapscriptSignature(
3✔
499
                tx, sigHashes, idx, in.WitnessUtxo.Value,
3✔
500
                in.WitnessUtxo.PkScript, leaf, in.SighashType, privKey,
3✔
501
        )
3✔
502
        if err != nil {
3✔
503
                return fmt.Errorf("error signing taproot script input %d: %w",
×
504
                        idx, err)
×
505
        }
×
506

507
        leafHash := leaf.TapHash()
3✔
508
        in.TaprootScriptSpendSig = append(
3✔
509
                in.TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
3✔
510
                        XOnlyPubKey: in.TaprootBip32Derivation[0].XOnlyPubKey,
3✔
511
                        LeafHash:    leafHash[:],
3✔
512
                        // We snip off the sighash flag from the end (if it was
3✔
513
                        // specified in the first place.)
3✔
514
                        Signature: rawSig[:schnorr.SignatureSize],
3✔
515
                        SigHash:   in.SighashType,
3✔
516
                },
3✔
517
        )
3✔
518

3✔
519
        return nil
3✔
520
}
521

522
// prepareScriptsV0 returns the appropriate witness v0 and/or legacy scripts,
523
// depending on the type of input that should be signed.
524
func prepareScriptsV0(in *psbt.PInput) []byte {
3✔
525
        switch {
3✔
526
        // It's a NP2WKH input:
527
        case len(in.RedeemScript) > 0:
3✔
528
                return in.RedeemScript
3✔
529

530
        // It's a P2WSH input:
531
        case len(in.WitnessScript) > 0:
3✔
532
                return in.WitnessScript
3✔
533

534
        // It's a P2WKH input:
535
        default:
3✔
536
                return in.WitnessUtxo.PkScript
3✔
537
        }
538
}
539

540
// maybeTweakPrivKeyPsbt examines if there are any tweak parameters given in the
541
// custom/proprietary PSBT fields and may perform a mapping on the passed
542
// private key in order to utilize the tweaks, if populated.
543
func maybeTweakPrivKeyPsbt(unknowns []*psbt.Unknown,
544
        privKey *btcec.PrivateKey) *btcec.PrivateKey {
3✔
545

3✔
546
        // There can be other custom/unknown keys in a PSBT that we just ignore.
3✔
547
        // Key tweaking is optional and only one tweak (single _or_ double) can
3✔
548
        // ever be applied (at least for any use cases described in the BOLT
3✔
549
        // spec).
3✔
550
        for _, u := range unknowns {
6✔
551
                if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakSingle) {
6✔
552
                        return input.TweakPrivKey(privKey, u.Value)
3✔
553
                }
3✔
554

UNCOV
555
                if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) {
×
UNCOV
556
                        doubleTweakKey, _ := btcec.PrivKeyFromBytes(
×
UNCOV
557
                                u.Value,
×
UNCOV
558
                        )
×
UNCOV
559
                        return input.DeriveRevocationPrivKey(
×
UNCOV
560
                                privKey, doubleTweakKey,
×
UNCOV
561
                        )
×
UNCOV
562
                }
×
563
        }
564

565
        return privKey
3✔
566
}
567

568
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
569
// declared and tries to sign all inputs that belong to the specified account.
570
// Lnd must be the last signer of the transaction. That means, if there are any
571
// unsigned non-witness inputs or inputs without UTXO information attached or
572
// inputs without witness data that do not belong to lnd's wallet, this method
573
// will fail. If no error is returned, the PSBT is ready to be extracted and the
574
// final TX within to be broadcast.
575
//
576
// NOTE: This method does NOT publish the transaction after it's been
577
// finalized successfully.
578
//
579
// This is a part of the WalletController interface.
580
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
3✔
581
        var (
3✔
582
                keyScope   *waddrmgr.KeyScope
3✔
583
                accountNum uint32
3✔
584
        )
3✔
585
        switch accountName {
3✔
586
        // If the default/imported account name was specified, we'll provide a
587
        // nil key scope to FundPsbt, allowing it to sign inputs from both key
588
        // scopes (NP2WKH, P2WKH).
589
        case lnwallet.DefaultAccountName:
3✔
590
                accountNum = defaultAccount
3✔
591

592
        case waddrmgr.ImportedAddrAccountName:
×
593
                accountNum = importedAccount
×
594

595
        // Otherwise, map the account name to its key scope and internal account
596
        // number to determine if the inputs belonging to this account should be
597
        // signed.
598
        default:
×
599
                scope, account, err := b.lookupFirstCustomAccount(accountName)
×
600
                if err != nil {
×
601
                        return err
×
602
                }
×
603
                keyScope = &scope
×
604
                accountNum = account
×
605
        }
606

607
        return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
3✔
608
}
609

610
// DecorateInputs fetches the UTXO information of all inputs it can identify and
611
// adds the required information to the package's inputs. The failOnUnknown
612
// boolean controls whether the method should return an error if it cannot
613
// identify an input or if it should just skip it.
614
//
615
// This is a part of the WalletController interface.
616
func (b *BtcWallet) DecorateInputs(packet *psbt.Packet,
617
        failOnUnknown bool) error {
3✔
618

3✔
619
        return b.wallet.DecorateInputs(packet, failOnUnknown)
3✔
620
}
3✔
621

622
// lookupFirstCustomAccount returns the first custom account found. In theory,
623
// there should be only one custom account for the given name. However, due to a
624
// lack of check, users could have created custom accounts with various key
625
// scopes. This behaviour has been fixed but, we still need to handle this
626
// specific case to avoid non-deterministic behaviour implied by LookupAccount.
627
func (b *BtcWallet) lookupFirstCustomAccount(
628
        name string) (waddrmgr.KeyScope, uint32, error) {
3✔
629

3✔
630
        var (
3✔
631
                account  *waddrmgr.AccountProperties
3✔
632
                keyScope waddrmgr.KeyScope
3✔
633
        )
3✔
634
        for _, scope := range waddrmgr.DefaultKeyScopes {
6✔
635
                var err error
3✔
636
                account, err = b.wallet.AccountPropertiesByName(scope, name)
3✔
637
                if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
6✔
638
                        continue
3✔
639
                }
640
                if err != nil {
3✔
641
                        return keyScope, 0, err
×
642
                }
×
643

644
                keyScope = scope
3✔
645

3✔
646
                break
3✔
647
        }
648
        if account == nil {
3✔
649
                return waddrmgr.KeyScope{}, 0, newAccountNotFoundError(name)
×
650
        }
×
651

652
        return keyScope, account.AccountNumber, nil
3✔
653
}
654

655
// Bip32DerivationFromKeyDesc returns the default and Taproot BIP-0032 key
656
// derivation information from the given key descriptor information.
657
func Bip32DerivationFromKeyDesc(keyDesc keychain.KeyDescriptor,
658
        coinType uint32) (*psbt.Bip32Derivation, *psbt.TaprootBip32Derivation,
UNCOV
659
        string) {
×
UNCOV
660

×
UNCOV
661
        bip32Derivation := &psbt.Bip32Derivation{
×
UNCOV
662
                PubKey: keyDesc.PubKey.SerializeCompressed(),
×
UNCOV
663
                Bip32Path: []uint32{
×
UNCOV
664
                        keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
×
UNCOV
665
                        coinType + hdkeychain.HardenedKeyStart,
×
UNCOV
666
                        uint32(keyDesc.Family) +
×
UNCOV
667
                                uint32(hdkeychain.HardenedKeyStart),
×
UNCOV
668
                        0,
×
UNCOV
669
                        keyDesc.Index,
×
UNCOV
670
                },
×
UNCOV
671
        }
×
UNCOV
672

×
UNCOV
673
        derivationPath := fmt.Sprintf(
×
UNCOV
674
                "m/%d'/%d'/%d'/%d/%d", keychain.BIP0043Purpose, coinType,
×
UNCOV
675
                keyDesc.Family, 0, keyDesc.Index,
×
UNCOV
676
        )
×
UNCOV
677

×
UNCOV
678
        return bip32Derivation, &psbt.TaprootBip32Derivation{
×
UNCOV
679
                XOnlyPubKey:          bip32Derivation.PubKey[1:],
×
UNCOV
680
                MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
×
UNCOV
681
                Bip32Path:            bip32Derivation.Bip32Path,
×
UNCOV
682
                LeafHashes:           make([][]byte, 0),
×
UNCOV
683
        }, derivationPath
×
UNCOV
684
}
×
685

686
// Bip32DerivationFromAddress returns the default and Taproot BIP-0032 key
687
// derivation information from the given managed address.
688
func Bip32DerivationFromAddress(
689
        addr waddrmgr.ManagedAddress) (*psbt.Bip32Derivation,
690
        *psbt.TaprootBip32Derivation, string, error) {
3✔
691

3✔
692
        pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
3✔
693
        if !ok {
3✔
694
                return nil, nil, "", fmt.Errorf("address is not a pubkey " +
×
695
                        "address")
×
696
        }
×
697

698
        scope, derivationInfo, haveInfo := pubKeyAddr.DerivationInfo()
3✔
699
        if !haveInfo {
3✔
700
                return nil, nil, "", fmt.Errorf("address is an imported " +
×
701
                        "public key, can't derive BIP32 path")
×
702
        }
×
703

704
        bip32Derivation := &psbt.Bip32Derivation{
3✔
705
                PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
3✔
706
                Bip32Path: []uint32{
3✔
707
                        scope.Purpose + hdkeychain.HardenedKeyStart,
3✔
708
                        scope.Coin + hdkeychain.HardenedKeyStart,
3✔
709
                        derivationInfo.InternalAccount +
3✔
710
                                hdkeychain.HardenedKeyStart,
3✔
711
                        derivationInfo.Branch,
3✔
712
                        derivationInfo.Index,
3✔
713
                },
3✔
714
        }
3✔
715

3✔
716
        derivationPath := fmt.Sprintf(
3✔
717
                "m/%d'/%d'/%d'/%d/%d", scope.Purpose, scope.Coin,
3✔
718
                derivationInfo.InternalAccount, derivationInfo.Branch,
3✔
719
                derivationInfo.Index,
3✔
720
        )
3✔
721

3✔
722
        return bip32Derivation, &psbt.TaprootBip32Derivation{
3✔
723
                XOnlyPubKey:          bip32Derivation.PubKey[1:],
3✔
724
                MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
3✔
725
                Bip32Path:            bip32Derivation.Bip32Path,
3✔
726
                LeafHashes:           make([][]byte, 0),
3✔
727
        }, derivationPath, nil
3✔
728
}
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