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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

61.09
/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) {
88✔
30

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

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

47
        return &lnwallet.Utxo{
88✔
48
                AddressType:   addressType,
88✔
49
                Value:         btcutil.Amount(txOut.Value),
88✔
50
                PkScript:      txOut.PkScript,
88✔
51
                Confirmations: confirmations,
88✔
52
                OutPoint:      *prevOut,
88✔
53
                PrevTx:        prevTx,
88✔
54
        }, nil
88✔
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) {
×
61

×
62
        return b.wallet.FetchDerivationInfo(pkScript)
×
63
}
×
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) {
×
70

×
71
        return b.wallet.ScriptForOutput(output)
×
72
}
×
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) {
9✔
79

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

91
        return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
9✔
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) {
25✔
99

25✔
100
        // Make sure we get a full path with exactly 5 elements. A path is
25✔
101
        // either custom purpose one with 4 dynamic and one static elements:
25✔
102
        //    m/1017'/coinType'/keyFamily'/0/index
25✔
103
        // Or a default BIP49/89 one with 5 elements:
25✔
104
        //    m/purpose'/coinType'/account'/change/index
25✔
105
        const expectedDerivationPathDepth = 5
25✔
106
        if len(path) != expectedDerivationPathDepth {
25✔
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 {
27✔
115
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
2✔
116
                        "expected first three elements to be hardened: %w", err)
2✔
117
        }
2✔
118

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

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

135
                return b.deriveKeyByLocator(keychain.KeyLocator{
7✔
136
                        Family: keychain.KeyFamily(account),
7✔
137
                        Index:  index,
7✔
138
                })
7✔
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:
15✔
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 {
16✔
162
                return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
1✔
163
                        "type must be 0 for BIP49/84 btcwallet keys")
1✔
164
        }
1✔
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{
14✔
169
                Purpose: purpose,
14✔
170
                Coin:    coinType,
14✔
171
        }
14✔
172
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
14✔
173
        if err != nil {
14✔
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{
14✔
180
                InternalAccount: account,
14✔
181
                Account:         account,
14✔
182
                Branch:          change,
14✔
183
                Index:           index,
14✔
184
        }
14✔
185
        privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
14✔
186
        if err == nil {
23✔
187
                return privKey, nil
9✔
188
        }
9✔
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 {
10✔
192
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
5✔
193

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

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

207
        return privKey, nil
3✔
208
}
209

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

219
        return nil
23✔
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) {
43✔
226

43✔
227
        // We'll assume the special lightning key scope in this case.
43✔
228
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
43✔
229
                b.chainKeyScope,
43✔
230
        )
43✔
231
        if err != nil {
43✔
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{
43✔
239
                InternalAccount: uint32(keyLoc.Family),
43✔
240
                Account:         uint32(keyLoc.Family),
43✔
241
                Branch:          0,
43✔
242
                Index:           keyLoc.Index,
43✔
243
        }
43✔
244
        privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
43✔
245
        if err == nil {
77✔
246
                return privKey, nil
34✔
247
        }
34✔
248

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

9✔
253
                key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
9✔
254
                if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
9✔
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
9✔
276
        })
277
        if err != nil {
9✔
278
                return nil, err
×
279
        }
×
280

281
        return key, nil
9✔
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) {
79✔
288

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

296
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
43✔
297
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
43✔
298
        if err != nil {
43✔
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)
43✔
306
        switch {
43✔
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:
×
312
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
×
313

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

317
        default:
43✔
318
                return key, nil
43✔
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) {
95✔
327

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

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

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

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

343
        return retPriv, nil
95✔
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) {
63✔
352

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

63✔
355
        // First attempt to fetch the private key which corresponds to the
63✔
356
        // specified public key.
63✔
357
        privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
63✔
358
        if err != nil {
63✔
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)
63✔
366
        if err != nil {
63✔
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) {
63✔
373
                sigHashes := txscript.NewTxSigHashes(
×
374
                        tx, signDesc.PrevOutputFetcher,
×
375
                )
×
376

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

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

397
                case input.TaprootScriptSpendSignMethod:
×
398
                        leaf := txscript.TapLeaf{
×
399
                                LeafVersion: txscript.BaseLeafVersion,
×
400
                                Script:      witnessScript,
×
401
                        }
×
402
                        rawSig, err = txscript.RawTxInTapscriptSignature(
×
403
                                tx, sigHashes, signDesc.InputIndex,
×
404
                                signDesc.Output.Value, signDesc.Output.PkScript,
×
405
                                leaf, signDesc.HashType, privKey,
×
406
                        )
×
407
                        if err != nil {
×
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(
×
421
                        rawSig[:schnorr.SignatureSize],
×
422
                )
×
423
                if err != nil {
×
424
                        return nil, err
×
425
                }
×
426

427
                return sig, nil
×
428
        }
429

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

432
        amt := signDesc.Output.Value
63✔
433
        sig, err := txscript.RawTxInWitnessSignature(
63✔
434
                tx, signDesc.SigHashes, signDesc.InputIndex, amt,
63✔
435
                witnessScript, signDesc.HashType, privKey,
63✔
436
        )
63✔
437
        if err != nil {
63✔
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])
63✔
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) {
32✔
453

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

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

470
        return &input.Script{
32✔
471
                Witness:   witness,
32✔
472
                SigScript: sigScript,
32✔
473
        }, nil
32✔
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) {
×
488

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

498
        // Double hash and sign the data.
499
        var msgDigest []byte
×
500
        if doubleHash {
×
501
                msgDigest = chainhash.DoubleHashB(msg)
×
502
        } else {
×
503
                msgDigest = chainhash.HashB(msg)
×
504
        }
×
505
        return ecdsa.Sign(privKey, msgDigest), nil
×
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