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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

76.21
/lnwallet/btcwallet/signer.go
1
package btcwallet
2

3
import (
4
        "fmt"
5

6
        "github.com/btcsuite/btcd/btcec/v2"
7
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
8
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
9
        "github.com/btcsuite/btcd/btcutil"
10
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
11
        "github.com/btcsuite/btcd/btcutil/psbt"
12
        "github.com/btcsuite/btcd/chaincfg/chainhash"
13
        "github.com/btcsuite/btcd/txscript"
14
        "github.com/btcsuite/btcd/wire"
15
        "github.com/btcsuite/btcwallet/waddrmgr"
16
        "github.com/btcsuite/btcwallet/walletdb"
17
        "github.com/lightningnetwork/lnd/input"
18
        "github.com/lightningnetwork/lnd/keychain"
19
        "github.com/lightningnetwork/lnd/lnwallet"
20
)
21

22
// FetchOutpointInfo queries for the WalletController's knowledge of the passed
23
// outpoint. If the base wallet determines this output is under its control,
24
// then the original txout should be returned. Otherwise, a non-nil error value
25
// of ErrNotMine should be returned instead.
26
//
27
// This is a part of the WalletController interface.
28
func (b *BtcWallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
29
        error) {
2✔
30

2✔
31
        prevTx, txOut, confirmations, err := b.wallet.FetchOutpointInfo(prevOut)
2✔
32
        if err != nil {
4✔
33
                return nil, err
2✔
34
        }
2✔
35

36
        // Then, we'll populate all of the information required by the struct.
37
        addressType := lnwallet.UnknownAddressType
2✔
38
        switch {
2✔
39
        case txscript.IsPayToWitnessPubKeyHash(txOut.PkScript):
2✔
40
                addressType = lnwallet.WitnessPubKey
2✔
41
        case txscript.IsPayToScriptHash(txOut.PkScript):
2✔
42
                addressType = lnwallet.NestedWitnessPubKey
2✔
43
        case txscript.IsPayToTaproot(txOut.PkScript):
2✔
44
                addressType = lnwallet.TaprootPubkey
2✔
45
        }
46

47
        return &lnwallet.Utxo{
2✔
48
                AddressType:   addressType,
2✔
49
                Value:         btcutil.Amount(txOut.Value),
2✔
50
                PkScript:      txOut.PkScript,
2✔
51
                Confirmations: confirmations,
2✔
52
                OutPoint:      *prevOut,
2✔
53
                PrevTx:        prevTx,
2✔
54
        }, nil
2✔
55
}
56

57
// FetchDerivationInfo queries for the wallet's knowledge of the passed
58
// pkScript and constructs the derivation info and returns it.
59
func (b *BtcWallet) FetchDerivationInfo(
60
        pkScript []byte) (*psbt.Bip32Derivation, error) {
2✔
61

2✔
62
        return b.wallet.FetchDerivationInfo(pkScript)
2✔
63
}
2✔
64

65
// ScriptForOutput returns the address, witness program and redeem script for a
66
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
67
// it is not a managed pubKey address.
68
func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
69
        waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
2✔
70

2✔
71
        return b.wallet.ScriptForOutput(output)
2✔
72
}
2✔
73

74
// deriveFromKeyLoc attempts to derive a private key using a fully specified
75
// KeyLocator.
76
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
77
        addrmgrNs walletdb.ReadWriteBucket,
78
        keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
2✔
79

2✔
80
        path := waddrmgr.DerivationPath{
2✔
81
                InternalAccount: uint32(keyLoc.Family),
2✔
82
                Account:         uint32(keyLoc.Family),
2✔
83
                Branch:          0,
2✔
84
                Index:           keyLoc.Index,
2✔
85
        }
2✔
86
        addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
2✔
87
        if err != nil {
2✔
88
                return nil, err
×
89
        }
×
90

91
        return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
2✔
92
}
93

94
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
95
// first three elements of the path to be hardened according to BIP44, so they
96
// must be a number >= 2^31.
97
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
98
        error) {
2✔
99

2✔
100
        // Make sure we get a full path with exactly 5 elements. A path is
2✔
101
        // either custom purpose one with 4 dynamic and one static elements:
2✔
102
        //    m/1017'/coinType'/keyFamily'/0/index
2✔
103
        // Or a default BIP49/89 one with 5 elements:
2✔
104
        //    m/purpose'/coinType'/account'/change/index
2✔
105
        const expectedDerivationPathDepth = 5
2✔
106
        if len(path) != expectedDerivationPathDepth {
2✔
107
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
108
                        "expected path length %d, instead was %d",
×
109
                        expectedDerivationPathDepth, len(path))
×
110
        }
×
111

112
        // Assert that the first three parts of the path are actually hardened
113
        // to avoid under-flowing the uint32 type.
114
        if err := assertHardened(path[0], path[1], path[2]); err != nil {
2✔
UNCOV
115
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
UNCOV
116
                        "expected first three elements to be hardened: %w", err)
×
UNCOV
117
        }
×
118

119
        purpose := path[0] - hdkeychain.HardenedKeyStart
2✔
120
        coinType := path[1] - hdkeychain.HardenedKeyStart
2✔
121
        account := path[2] - hdkeychain.HardenedKeyStart
2✔
122
        change, index := path[3], path[4]
2✔
123

2✔
124
        // Is this a custom lnd internal purpose key?
2✔
125
        switch purpose {
2✔
126
        case keychain.BIP0043Purpose:
2✔
127
                // Make sure it's for the same coin type as our wallet's
2✔
128
                // keychain scope.
2✔
129
                if coinType != b.chainKeyScope.Coin {
2✔
UNCOV
130
                        return nil, fmt.Errorf("invalid BIP32 derivation "+
×
UNCOV
131
                                "path, expected coin type %d, instead was %d",
×
UNCOV
132
                                b.chainKeyScope.Coin, coinType)
×
UNCOV
133
                }
×
134

135
                return b.deriveKeyByLocator(keychain.KeyLocator{
2✔
136
                        Family: keychain.KeyFamily(account),
2✔
137
                        Index:  index,
2✔
138
                })
2✔
139

140
        // Is it a standard, BIP defined purpose that the wallet understands?
141
        case waddrmgr.KeyScopeBIP0044.Purpose,
142
                waddrmgr.KeyScopeBIP0049Plus.Purpose,
143
                waddrmgr.KeyScopeBIP0084.Purpose,
144
                waddrmgr.KeyScopeBIP0086.Purpose:
2✔
145

146
                // We're going to continue below the switch statement to avoid
147
                // unnecessary indentation for this default case.
148

149
        // Currently, there is no way to import any other key scopes than the
150
        // one custom purpose or three standard ones into lnd's wallet. So we
151
        // shouldn't accept any other scopes to sign for.
152
        default:
×
153
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
154
                        "unknown purpose %d", purpose)
×
155
        }
156

157
        // Okay, we made sure it's a BIP49/84 key, so we need to derive it now.
158
        // Interestingly, the btcwallet never actually uses a coin type other
159
        // than 0 for those keys, so we need to make sure this behavior is
160
        // replicated here.
161
        if coinType != 0 {
2✔
UNCOV
162
                return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
×
UNCOV
163
                        "type must be 0 for BIP49/84 btcwallet keys")
×
UNCOV
164
        }
×
165

166
        // We only expect to be asked to sign with key scopes that we know
167
        // about. So if the scope doesn't exist, we don't create it.
168
        scope := waddrmgr.KeyScope{
2✔
169
                Purpose: purpose,
2✔
170
                Coin:    coinType,
2✔
171
        }
2✔
172
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
2✔
173
        if err != nil {
2✔
174
                return nil, fmt.Errorf("error fetching manager for scope %v: "+
×
175
                        "%w", scope, err)
×
176
        }
×
177

178
        // Let's see if we can hit the private key cache.
179
        keyPath := waddrmgr.DerivationPath{
2✔
180
                InternalAccount: account,
2✔
181
                Account:         account,
2✔
182
                Branch:          change,
2✔
183
                Index:           index,
2✔
184
        }
2✔
185
        privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
2✔
186
        if err == nil {
4✔
187
                return privKey, nil
2✔
188
        }
2✔
189

190
        // The key wasn't in the cache, let's fully derive it now.
191
        err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
4✔
192
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
2✔
193

2✔
194
                addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath)
2✔
195
                if err != nil {
2✔
UNCOV
196
                        return fmt.Errorf("error deriving private key: %w", err)
×
UNCOV
197
                }
×
198

199
                privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
2✔
200
                return err
2✔
201
        })
202
        if err != nil {
2✔
UNCOV
203
                return nil, fmt.Errorf("error deriving key from path %#v: %w",
×
UNCOV
204
                        keyPath, err)
×
UNCOV
205
        }
×
206

207
        return privKey, nil
2✔
208
}
209

210
// assertHardened makes sure each given element is >= 2^31.
211
func assertHardened(elements ...uint32) error {
2✔
212
        for idx, element := range elements {
4✔
213
                if element < hdkeychain.HardenedKeyStart {
2✔
UNCOV
214
                        return fmt.Errorf("element at index %d is not hardened",
×
UNCOV
215
                                idx)
×
UNCOV
216
                }
×
217
        }
218

219
        return nil
2✔
220
}
221

222
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
223
// valid key locator.
224
func (b *BtcWallet) deriveKeyByLocator(
225
        keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
2✔
226

2✔
227
        // We'll assume the special lightning key scope in this case.
2✔
228
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
2✔
229
                b.chainKeyScope,
2✔
230
        )
2✔
231
        if err != nil {
2✔
232
                return nil, err
×
233
        }
×
234

235
        // First try to read the key from the cached store, if this fails, then
236
        // we'll fall through to the method below that requires a database
237
        // transaction.
238
        path := waddrmgr.DerivationPath{
2✔
239
                InternalAccount: uint32(keyLoc.Family),
2✔
240
                Account:         uint32(keyLoc.Family),
2✔
241
                Branch:          0,
2✔
242
                Index:           keyLoc.Index,
2✔
243
        }
2✔
244
        privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
2✔
245
        if err == nil {
4✔
246
                return privKey, nil
2✔
247
        }
2✔
248

249
        var key *btcec.PrivateKey
2✔
250
        err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
4✔
251
                addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
2✔
252

2✔
253
                key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
2✔
254
                if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
2✔
255
                        // If we've reached this point, then the account
×
256
                        // doesn't yet exist, so we'll create it now to ensure
×
257
                        // we can sign.
×
258
                        acctErr := scopedMgr.NewRawAccount(
×
259
                                addrmgrNs, uint32(keyLoc.Family),
×
260
                        )
×
261
                        if acctErr != nil {
×
262
                                return acctErr
×
263
                        }
×
264

265
                        // Now that we know the account exists, we'll attempt
266
                        // to re-derive the private key.
267
                        key, err = deriveFromKeyLoc(
×
268
                                scopedMgr, addrmgrNs, keyLoc,
×
269
                        )
×
270
                        if err != nil {
×
271
                                return err
×
272
                        }
×
273
                }
274

275
                return err
2✔
276
        })
277
        if err != nil {
2✔
278
                return nil, err
×
279
        }
×
280

281
        return key, nil
2✔
282
}
283

284
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
285
// passed public key if populated, or the key descriptor path (if non-empty).
286
func (b *BtcWallet) fetchPrivKey(
287
        keyDesc *keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
2✔
288

2✔
289
        // If the key locator within the descriptor *isn't* empty, then we can
2✔
290
        // directly derive the keys raw.
2✔
291
        emptyLocator := keyDesc.KeyLocator.IsEmpty()
2✔
292
        if !emptyLocator || keyDesc.PubKey == nil {
4✔
293
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
2✔
294
        }
2✔
295

296
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
2✔
297
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
2✔
298
        if err != nil {
2✔
299
                return nil, err
×
300
        }
×
301

302
        // Otherwise, we'll attempt to derive the key based on the address.
303
        // This will only work if we've already derived this address in the
304
        // past, since the wallet relies on a mapping of addr -> key.
305
        key, err := b.wallet.PrivKeyForAddress(addr)
2✔
306
        switch {
2✔
307
        // If we didn't find this key in the wallet, then there's a chance that
308
        // this is actually an "empty" key locator. The legacy KeyLocator
309
        // format failed to properly distinguish an empty key locator from the
310
        // very first in the index (0, 0).IsEmpty() == true.
311
        case waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) && emptyLocator:
2✔
312
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
2✔
313

314
        case err != nil:
×
315
                return nil, err
×
316

317
        default:
2✔
318
                return key, nil
2✔
319
        }
320
}
321

322
// maybeTweakPrivKey examines the single and double tweak parameters on the
323
// passed sign descriptor and may perform a mapping on the passed private key
324
// in order to utilize the tweaks, if populated.
325
func maybeTweakPrivKey(signDesc *input.SignDescriptor,
326
        privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
2✔
327

2✔
328
        var retPriv *btcec.PrivateKey
2✔
329
        switch {
2✔
330

331
        case signDesc.SingleTweak != nil:
2✔
332
                retPriv = input.TweakPrivKey(privKey,
2✔
333
                        signDesc.SingleTweak)
2✔
334

335
        case signDesc.DoubleTweak != nil:
2✔
336
                retPriv = input.DeriveRevocationPrivKey(privKey,
2✔
337
                        signDesc.DoubleTweak)
2✔
338

339
        default:
2✔
340
                retPriv = privKey
2✔
341
        }
342

343
        return retPriv, nil
2✔
344
}
345

346
// SignOutputRaw generates a signature for the passed transaction according to
347
// the data within the passed SignDescriptor.
348
//
349
// This is a part of the WalletController interface.
350
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
351
        signDesc *input.SignDescriptor) (input.Signature, error) {
2✔
352

2✔
353
        witnessScript := signDesc.WitnessScript
2✔
354

2✔
355
        // First attempt to fetch the private key which corresponds to the
2✔
356
        // specified public key.
2✔
357
        privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
2✔
358
        if err != nil {
2✔
359
                return nil, err
×
360
        }
×
361

362
        // If a tweak (single or double) is specified, then we'll need to use
363
        // this tweak to derive the final private key to be used for signing
364
        // this output.
365
        privKey, err = maybeTweakPrivKey(signDesc, privKey)
2✔
366
        if err != nil {
2✔
367
                return nil, err
×
368
        }
×
369

370
        // In case of a taproot output any signature is always a Schnorr
371
        // signature, based on the new tapscript sighash algorithm.
372
        if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
4✔
373
                sigHashes := txscript.NewTxSigHashes(
2✔
374
                        tx, signDesc.PrevOutputFetcher,
2✔
375
                )
2✔
376

2✔
377
                // Are we spending a script path or the key path? The API is
2✔
378
                // slightly different, so we need to account for that to get the
2✔
379
                // raw signature.
2✔
380
                var rawSig []byte
2✔
381
                switch signDesc.SignMethod {
2✔
382
                case input.TaprootKeySpendBIP0086SignMethod,
383
                        input.TaprootKeySpendSignMethod:
2✔
384

2✔
385
                        // This function tweaks the private key using the tap
2✔
386
                        // root key supplied as the tweak.
2✔
387
                        rawSig, err = txscript.RawTxInTaprootSignature(
2✔
388
                                tx, sigHashes, signDesc.InputIndex,
2✔
389
                                signDesc.Output.Value, signDesc.Output.PkScript,
2✔
390
                                signDesc.TapTweak, signDesc.HashType,
2✔
391
                                privKey,
2✔
392
                        )
2✔
393
                        if err != nil {
2✔
394
                                return nil, err
×
395
                        }
×
396

397
                case input.TaprootScriptSpendSignMethod:
2✔
398
                        leaf := txscript.TapLeaf{
2✔
399
                                LeafVersion: txscript.BaseLeafVersion,
2✔
400
                                Script:      witnessScript,
2✔
401
                        }
2✔
402
                        rawSig, err = txscript.RawTxInTapscriptSignature(
2✔
403
                                tx, sigHashes, signDesc.InputIndex,
2✔
404
                                signDesc.Output.Value, signDesc.Output.PkScript,
2✔
405
                                leaf, signDesc.HashType, privKey,
2✔
406
                        )
2✔
407
                        if err != nil {
2✔
408
                                return nil, err
×
409
                        }
×
410

411
                default:
×
412
                        return nil, fmt.Errorf("unknown sign method: %v",
×
413
                                signDesc.SignMethod)
×
414
                }
415

416
                // The signature returned above might have a sighash flag
417
                // attached if a non-default type was used. We'll slice this
418
                // off if it exists to ensure we can properly parse the raw
419
                // signature.
420
                sig, err := schnorr.ParseSignature(
2✔
421
                        rawSig[:schnorr.SignatureSize],
2✔
422
                )
2✔
423
                if err != nil {
2✔
424
                        return nil, err
×
425
                }
×
426

427
                return sig, nil
2✔
428
        }
429

430
        // TODO(roasbeef): generate sighash midstate if not present?
431

432
        amt := signDesc.Output.Value
2✔
433
        sig, err := txscript.RawTxInWitnessSignature(
2✔
434
                tx, signDesc.SigHashes, signDesc.InputIndex, amt,
2✔
435
                witnessScript, signDesc.HashType, privKey,
2✔
436
        )
2✔
437
        if err != nil {
2✔
438
                return nil, err
×
439
        }
×
440

441
        // Chop off the sighash flag at the end of the signature.
442
        return ecdsa.ParseDERSignature(sig[:len(sig)-1])
2✔
443
}
444

445
// ComputeInputScript generates a complete InputScript for the passed
446
// transaction with the signature as defined within the passed SignDescriptor.
447
// This method is capable of generating the proper input script for both
448
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
449
//
450
// This is a part of the WalletController interface.
451
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
452
        signDesc *input.SignDescriptor) (*input.Script, error) {
2✔
453

2✔
454
        // If a tweak (single or double) is specified, then we'll need to use
2✔
455
        // this tweak to derive the final private key to be used for signing
2✔
456
        // this output.
2✔
457
        privKeyTweaker := func(k *btcec.PrivateKey) (*btcec.PrivateKey, error) {
4✔
458
                return maybeTweakPrivKey(signDesc, k)
2✔
459
        }
2✔
460

461
        // Let the wallet compute the input script now.
462
        witness, sigScript, err := b.wallet.ComputeInputScript(
2✔
463
                tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
2✔
464
                signDesc.HashType, privKeyTweaker,
2✔
465
        )
2✔
466
        if err != nil {
2✔
467
                return nil, err
×
468
        }
×
469

470
        return &input.Script{
2✔
471
                Witness:   witness,
2✔
472
                SigScript: sigScript,
2✔
473
        }, nil
2✔
474
}
475

476
// A compile time check to ensure that BtcWallet implements the Signer
477
// interface.
478
var _ input.Signer = (*BtcWallet)(nil)
479

480
// SignMessage attempts to sign a target message with the private key that
481
// corresponds to the passed key locator. If the target private key is unable to
482
// be found, then an error will be returned. The actual digest signed is the
483
// double SHA-256 of the passed message.
484
//
485
// NOTE: This is a part of the MessageSigner interface.
486
func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator,
487
        msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
2✔
488

2✔
489
        // First attempt to fetch the private key which corresponds to the
2✔
490
        // specified public key.
2✔
491
        privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
2✔
492
                KeyLocator: keyLoc,
2✔
493
        })
2✔
494
        if err != nil {
2✔
495
                return nil, err
×
496
        }
×
497

498
        // Double hash and sign the data.
499
        var msgDigest []byte
2✔
500
        if doubleHash {
4✔
501
                msgDigest = chainhash.DoubleHashB(msg)
2✔
502
        } else {
2✔
503
                msgDigest = chainhash.HashB(msg)
×
504
        }
×
505
        return ecdsa.Sign(privKey, msgDigest), nil
2✔
506
}
507

508
// A compile time check to ensure that BtcWallet implements the MessageSigner
509
// interface.
510
var _ lnwallet.MessageSigner = (*BtcWallet)(nil)
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