• 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

75.56
/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) {
3✔
30

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

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

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

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

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

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

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

3✔
100
        // Make sure we get a full path with exactly 5 elements. A path is
3✔
101
        // either custom purpose one with 4 dynamic and one static elements:
3✔
102
        //    m/1017'/coinType'/keyFamily'/0/index
3✔
103
        // Or a default BIP49/89 one with 5 elements:
3✔
104
        //    m/purpose'/coinType'/account'/change/index
3✔
105
        const expectedDerivationPathDepth = 5
3✔
106
        if len(path) != expectedDerivationPathDepth {
3✔
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 {
3✔
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
3✔
120
        coinType := path[1] - hdkeychain.HardenedKeyStart
3✔
121
        account := path[2] - hdkeychain.HardenedKeyStart
3✔
122
        change, index := path[3], path[4]
3✔
123

3✔
124
        // Is this a custom lnd internal purpose key?
3✔
125
        switch purpose {
3✔
126
        case keychain.BIP0043Purpose:
3✔
127
                // Make sure it's for the same coin type as our wallet's
3✔
128
                // keychain scope.
3✔
129
                if coinType != b.chainKeyScope.Coin {
3✔
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{
3✔
136
                        Family: keychain.KeyFamily(account),
3✔
137
                        Index:  index,
3✔
138
                })
3✔
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:
3✔
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 {
3✔
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{
3✔
169
                Purpose: purpose,
3✔
170
                Coin:    coinType,
3✔
171
        }
3✔
172
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
3✔
173
        if err != nil {
3✔
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{
3✔
180
                InternalAccount: account,
3✔
181
                Account:         account,
3✔
182
                Branch:          change,
3✔
183
                Index:           index,
3✔
184
        }
3✔
185
        privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
3✔
186
        if err == nil {
6✔
187
                return privKey, nil
3✔
188
        }
3✔
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 {
6✔
192
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
3✔
193

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

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

207
        return privKey, nil
3✔
208
}
209

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

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

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

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

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

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

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

296
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
3✔
297
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
3✔
298
        if err != nil {
3✔
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)
3✔
306
        switch {
3✔
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:
3✔
318
                return key, nil
3✔
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) {
3✔
327

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

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

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

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

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

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

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

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

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

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

427
                return sig, nil
3✔
428
        }
429

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

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

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

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

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

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

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