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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

50.59
/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,
UNCOV
82
        allowUtxo func(wtxmgr.Credit) bool) (int32, error) {
×
UNCOV
83

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

×
UNCOV
88
        var (
×
UNCOV
89
                keyScope   *waddrmgr.KeyScope
×
UNCOV
90
                accountNum uint32
×
UNCOV
91
        )
×
UNCOV
92

×
UNCOV
93
        switch accountName {
×
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.
UNCOV
98
        case lnwallet.DefaultAccountName:
×
UNCOV
99
                if changeScope == nil {
×
UNCOV
100
                        changeScope = &waddrmgr.KeyScopeBIP0084
×
UNCOV
101
                }
×
102

UNCOV
103
                accountNum = defaultAccount
×
104

UNCOV
105
        case waddrmgr.ImportedAddrAccountName:
×
UNCOV
106
                if changeScope == nil {
×
UNCOV
107
                        changeScope = &waddrmgr.KeyScopeBIP0084
×
UNCOV
108
                }
×
109

UNCOV
110
                accountNum = importedAccount
×
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.
UNCOV
119
        default:
×
UNCOV
120
                if changeScope != nil {
×
121
                        return 0, fmt.Errorf("couldn't select a " +
×
122
                                "custom change type for custom accounts")
×
123
                }
×
124

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

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

142
        // Let the wallet handle coin selection and/or fee estimation based on
143
        // the partial TX information in the packet.
UNCOV
144
        return b.wallet.FundPsbt(
×
UNCOV
145
                packet, keyScope, minConfs, accountNum, feeSatPerKB,
×
UNCOV
146
                strategy, opts...,
×
UNCOV
147
        )
×
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) {
5✔
161
        // In signedInputs we return the indices of psbt inputs that were signed
5✔
162
        // by our wallet. This way the caller can check if any inputs were signed.
5✔
163
        var signedInputs []uint32
5✔
164

5✔
165
        // Let's check that this is actually something we can and want to sign.
5✔
166
        // We need at least one input and one output. In addition each
5✔
167
        // input needs nonWitness Utxo or witness Utxo data specified.
5✔
168
        err := psbt.InputsReadyToSign(packet)
5✔
169
        if err != nil {
5✔
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
5✔
178
        prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
5✔
179
        sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)
5✔
180
        for idx := range tx.TxIn {
10✔
181
                in := &packet.Inputs[idx]
5✔
182

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

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

196
                // Skip this input if there is no BIP32 derivation info
197
                // available.
198
                if len(in.Bip32Derivation) == 0 {
5✔
UNCOV
199
                        continue
×
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]
5✔
210
                privKey, err := b.deriveKeyByBIP32Path(derivationInfo.Bip32Path)
5✔
211
                if err != nil {
5✔
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(
5✔
220
                        derivationInfo.PubKey,
5✔
221
                        privKey.PubKey().SerializeCompressed(),
5✔
222
                )
5✔
223
                if !pubKeysEqual {
5✔
UNCOV
224
                        log.Warnf("SignPsbt: Skipping input %d, derived "+
×
UNCOV
225
                                "public key %x does not match bip32 "+
×
UNCOV
226
                                "derivation info public key %x", idx,
×
UNCOV
227
                                privKey.PubKey().SerializeCompressed(),
×
UNCOV
228
                                derivationInfo.PubKey)
×
UNCOV
229
                        continue
×
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)
5✔
235

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

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

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

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

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

273
                default:
×
274
                        err = fmt.Errorf("unsupported signing method for "+
×
275
                                "PSBT signing: %v", signMethod)
×
276
                }
277
                if err != nil {
5✔
278
                        return nil, err
×
279
                }
×
280
                signedInputs = append(signedInputs, uint32(idx))
5✔
281
        }
282
        return signedInputs, nil
5✔
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) {
8✔
289
        script, err := txscript.ParsePkScript(in.WitnessUtxo.PkScript)
8✔
290
        if err != nil {
8✔
291
                return 0, fmt.Errorf("error detecting signing method, "+
×
292
                        "couldn't parse pkScript: %v", err)
×
293
        }
×
294

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

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

301
        case txscript.WitnessV1TaprootTy:
3✔
302
                if len(in.TaprootBip32Derivation) == 0 {
4✔
303
                        return 0, fmt.Errorf("cannot sign for taproot input " +
1✔
304
                                "without taproot BIP0032 derivation info")
1✔
305
                }
1✔
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 {
2✔
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]
2✔
319
                switch {
2✔
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:
1✔
325

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

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

UNCOV
339
                        return input.TaprootKeySpendSignMethod, nil
×
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:
1✔
347
                        // If we're supposed to be signing for a leaf hash, we
1✔
348
                        // also expect the leaf script that hashes to that hash
1✔
349
                        // in the appropriate field.
1✔
350
                        if len(in.TaprootLeafScript) != 1 {
1✔
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]
1✔
357
                        leaf := txscript.TapLeaf{
1✔
358
                                LeafVersion: leafScript.LeafVersion,
1✔
359
                                Script:      leafScript.Script,
1✔
360
                        }
1✔
361
                        leafHash := leaf.TapHash()
1✔
362
                        if !bytes.Equal(leafHash[:], derivation.LeafHashes[0]) {
1✔
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
1✔
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 {
8✔
388
        if in.WitnessUtxo == nil {
9✔
389
                return ErrInputMissingUTXOInfo
1✔
390
        }
1✔
391

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

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

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

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

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

1✔
417
                        w.AddTaprootKeySpendInput(in.SighashType)
1✔
418

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

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

431
        default:
1✔
432
                return fmt.Errorf("unknown input type for script %x: %w",
1✔
433
                        pkScript, ErrUnsupportedScript)
1✔
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 {
5✔
445

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

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

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

5✔
468
        return nil
5✔
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,
UNCOV
475
        tapscriptRootHash []byte) error {
×
UNCOV
476

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

UNCOV
487
        in.TaprootKeySpendSig = rawSig
×
UNCOV
488

×
UNCOV
489
        return nil
×
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,
UNCOV
496
        leaf txscript.TapLeaf) error {
×
UNCOV
497

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

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

×
UNCOV
519
        return nil
×
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 {
5✔
525
        switch {
5✔
526
        // It's a NP2WKH input:
527
        case len(in.RedeemScript) > 0:
1✔
528
                return in.RedeemScript
1✔
529

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

534
        // It's a P2WKH input:
535
        default:
2✔
536
                return in.WitnessUtxo.PkScript
2✔
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 {
5✔
545

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

555
                if bytes.Equal(u.Key, PsbtKeyTypeInputSignatureTweakDouble) {
2✔
556
                        doubleTweakKey, _ := btcec.PrivKeyFromBytes(
1✔
557
                                u.Value,
1✔
558
                        )
1✔
559
                        return input.DeriveRevocationPrivKey(
1✔
560
                                privKey, doubleTweakKey,
1✔
561
                        )
1✔
562
                }
1✔
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.
UNCOV
580
func (b *BtcWallet) FinalizePsbt(packet *psbt.Packet, accountName string) error {
×
UNCOV
581
        var (
×
UNCOV
582
                keyScope   *waddrmgr.KeyScope
×
UNCOV
583
                accountNum uint32
×
UNCOV
584
        )
×
UNCOV
585
        switch accountName {
×
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).
UNCOV
589
        case lnwallet.DefaultAccountName:
×
UNCOV
590
                accountNum = defaultAccount
×
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

UNCOV
607
        return b.wallet.FinalizePsbt(keyScope, accountNum, packet)
×
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,
UNCOV
617
        failOnUnknown bool) error {
×
UNCOV
618

×
UNCOV
619
        return b.wallet.DecorateInputs(packet, failOnUnknown)
×
UNCOV
620
}
×
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(
UNCOV
628
        name string) (waddrmgr.KeyScope, uint32, error) {
×
UNCOV
629

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

UNCOV
644
                keyScope = scope
×
UNCOV
645

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

UNCOV
652
        return keyScope, account.AccountNumber, nil
×
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,
659
        string) {
2✔
660

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

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

2✔
678
        return bip32Derivation, &psbt.TaprootBip32Derivation{
2✔
679
                XOnlyPubKey:          bip32Derivation.PubKey[1:],
2✔
680
                MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
2✔
681
                Bip32Path:            bip32Derivation.Bip32Path,
2✔
682
                LeafHashes:           make([][]byte, 0),
2✔
683
        }, derivationPath
2✔
684
}
2✔
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) {
2✔
691

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

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

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

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

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