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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

76.13
/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/chaincfg/chainhash"
12
        "github.com/btcsuite/btcd/txscript"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/btcsuite/btcwallet/waddrmgr"
15
        "github.com/btcsuite/btcwallet/walletdb"
16
        "github.com/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/keychain"
18
        "github.com/lightningnetwork/lnd/lnwallet"
19
)
20

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

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

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

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

59
// ScriptForOutput returns the address, witness program and redeem script for a
60
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
61
// it is not a managed pubKey address.
62
func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
63
        waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
3✔
64

3✔
65
        return b.wallet.ScriptForOutput(output)
3✔
66
}
3✔
67

68
// deriveFromKeyLoc attempts to derive a private key using a fully specified
69
// KeyLocator.
70
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
71
        addrmgrNs walletdb.ReadWriteBucket,
72
        keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
3✔
73

3✔
74
        path := waddrmgr.DerivationPath{
3✔
75
                InternalAccount: uint32(keyLoc.Family),
3✔
76
                Account:         uint32(keyLoc.Family),
3✔
77
                Branch:          0,
3✔
78
                Index:           keyLoc.Index,
3✔
79
        }
3✔
80
        addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
3✔
81
        if err != nil {
3✔
82
                return nil, err
×
83
        }
×
84

85
        return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
3✔
86
}
87

88
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
89
// first three elements of the path to be hardened according to BIP44, so they
90
// must be a number >= 2^31.
91
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
92
        error) {
3✔
93

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

106
        // Assert that the first three parts of the path are actually hardened
107
        // to avoid under-flowing the uint32 type.
108
        if err := assertHardened(path[0], path[1], path[2]); err != nil {
3✔
109
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
110
                        "expected first three elements to be hardened: %w", err)
×
111
        }
×
112

113
        purpose := path[0] - hdkeychain.HardenedKeyStart
3✔
114
        coinType := path[1] - hdkeychain.HardenedKeyStart
3✔
115
        account := path[2] - hdkeychain.HardenedKeyStart
3✔
116
        change, index := path[3], path[4]
3✔
117

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

129
                return b.deriveKeyByLocator(keychain.KeyLocator{
3✔
130
                        Family: keychain.KeyFamily(account),
3✔
131
                        Index:  index,
3✔
132
                })
3✔
133

134
        // Is it a standard, BIP defined purpose that the wallet understands?
135
        case waddrmgr.KeyScopeBIP0044.Purpose,
136
                waddrmgr.KeyScopeBIP0049Plus.Purpose,
137
                waddrmgr.KeyScopeBIP0084.Purpose,
138
                waddrmgr.KeyScopeBIP0086.Purpose:
3✔
139

140
                // We're going to continue below the switch statement to avoid
141
                // unnecessary indentation for this default case.
142

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

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

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

172
        // Let's see if we can hit the private key cache.
173
        keyPath := waddrmgr.DerivationPath{
3✔
174
                InternalAccount: account,
3✔
175
                Account:         account,
3✔
176
                Branch:          change,
3✔
177
                Index:           index,
3✔
178
        }
3✔
179
        privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
3✔
180
        if err == nil {
6✔
181
                return privKey, nil
3✔
182
        }
3✔
183

184
        // The key wasn't in the cache, let's fully derive it now.
185
        err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
6✔
186
                addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
3✔
187

3✔
188
                addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath)
3✔
189
                if err != nil {
3✔
190
                        return fmt.Errorf("error deriving private key: %w", err)
×
191
                }
×
192

193
                privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
3✔
194
                return err
3✔
195
        })
196
        if err != nil {
3✔
197
                return nil, fmt.Errorf("error deriving key from path %#v: %w",
×
198
                        keyPath, err)
×
199
        }
×
200

201
        return privKey, nil
3✔
202
}
203

204
// assertHardened makes sure each given element is >= 2^31.
205
func assertHardened(elements ...uint32) error {
3✔
206
        for idx, element := range elements {
6✔
207
                if element < hdkeychain.HardenedKeyStart {
3✔
208
                        return fmt.Errorf("element at index %d is not hardened",
×
209
                                idx)
×
210
                }
×
211
        }
212

213
        return nil
3✔
214
}
215

216
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
217
// valid key locator.
218
func (b *BtcWallet) deriveKeyByLocator(
219
        keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
3✔
220

3✔
221
        // We'll assume the special lightning key scope in this case.
3✔
222
        scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
3✔
223
                b.chainKeyScope,
3✔
224
        )
3✔
225
        if err != nil {
3✔
226
                return nil, err
×
227
        }
×
228

229
        // First try to read the key from the cached store, if this fails, then
230
        // we'll fall through to the method below that requires a database
231
        // transaction.
232
        path := waddrmgr.DerivationPath{
3✔
233
                InternalAccount: uint32(keyLoc.Family),
3✔
234
                Account:         uint32(keyLoc.Family),
3✔
235
                Branch:          0,
3✔
236
                Index:           keyLoc.Index,
3✔
237
        }
3✔
238
        privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
3✔
239
        if err == nil {
6✔
240
                return privKey, nil
3✔
241
        }
3✔
242

243
        var key *btcec.PrivateKey
3✔
244
        err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
6✔
245
                addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
3✔
246

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

259
                        // Now that we know the account exists, we'll attempt
260
                        // to re-derive the private key.
261
                        key, err = deriveFromKeyLoc(
×
262
                                scopedMgr, addrmgrNs, keyLoc,
×
263
                        )
×
264
                        if err != nil {
×
265
                                return err
×
266
                        }
×
267
                }
268

269
                return err
3✔
270
        })
271
        if err != nil {
3✔
272
                return nil, err
×
273
        }
×
274

275
        return key, nil
3✔
276
}
277

278
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
279
// passed public key if populated, or the key descriptor path (if non-empty).
280
func (b *BtcWallet) fetchPrivKey(
281
        keyDesc *keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
3✔
282

3✔
283
        // If the key locator within the descriptor *isn't* empty, then we can
3✔
284
        // directly derive the keys raw.
3✔
285
        emptyLocator := keyDesc.KeyLocator.IsEmpty()
3✔
286
        if !emptyLocator || keyDesc.PubKey == nil {
6✔
287
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
3✔
288
        }
3✔
289

290
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
3✔
291
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
3✔
292
        if err != nil {
3✔
293
                return nil, err
×
294
        }
×
295

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

308
        case err != nil:
×
309
                return nil, err
×
310

311
        default:
3✔
312
                return key, nil
3✔
313
        }
314
}
315

316
// maybeTweakPrivKey examines the single and double tweak parameters on the
317
// passed sign descriptor and may perform a mapping on the passed private key
318
// in order to utilize the tweaks, if populated.
319
func maybeTweakPrivKey(signDesc *input.SignDescriptor,
320
        privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
3✔
321

3✔
322
        var retPriv *btcec.PrivateKey
3✔
323
        switch {
3✔
324

325
        case signDesc.SingleTweak != nil:
3✔
326
                retPriv = input.TweakPrivKey(privKey,
3✔
327
                        signDesc.SingleTweak)
3✔
328

329
        case signDesc.DoubleTweak != nil:
3✔
330
                retPriv = input.DeriveRevocationPrivKey(privKey,
3✔
331
                        signDesc.DoubleTweak)
3✔
332

333
        default:
3✔
334
                retPriv = privKey
3✔
335
        }
336

337
        return retPriv, nil
3✔
338
}
339

340
// SignOutputRaw generates a signature for the passed transaction according to
341
// the data within the passed SignDescriptor.
342
//
343
// This is a part of the WalletController interface.
344
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
345
        signDesc *input.SignDescriptor) (input.Signature, error) {
3✔
346

3✔
347
        witnessScript := signDesc.WitnessScript
3✔
348

3✔
349
        // First attempt to fetch the private key which corresponds to the
3✔
350
        // specified public key.
3✔
351
        privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
3✔
352
        if err != nil {
3✔
353
                return nil, err
×
354
        }
×
355

356
        // If a tweak (single or double) is specified, then we'll need to use
357
        // this tweak to derive the final private key to be used for signing
358
        // this output.
359
        privKey, err = maybeTweakPrivKey(signDesc, privKey)
3✔
360
        if err != nil {
3✔
361
                return nil, err
×
362
        }
×
363

364
        // In case of a taproot output any signature is always a Schnorr
365
        // signature, based on the new tapscript sighash algorithm.
366
        if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
6✔
367
                sigHashes := txscript.NewTxSigHashes(
3✔
368
                        tx, signDesc.PrevOutputFetcher,
3✔
369
                )
3✔
370

3✔
371
                // Are we spending a script path or the key path? The API is
3✔
372
                // slightly different, so we need to account for that to get the
3✔
373
                // raw signature.
3✔
374
                var rawSig []byte
3✔
375
                switch signDesc.SignMethod {
3✔
376
                case input.TaprootKeySpendBIP0086SignMethod,
377
                        input.TaprootKeySpendSignMethod:
3✔
378

3✔
379
                        // This function tweaks the private key using the tap
3✔
380
                        // root key supplied as the tweak.
3✔
381
                        rawSig, err = txscript.RawTxInTaprootSignature(
3✔
382
                                tx, sigHashes, signDesc.InputIndex,
3✔
383
                                signDesc.Output.Value, signDesc.Output.PkScript,
3✔
384
                                signDesc.TapTweak, signDesc.HashType,
3✔
385
                                privKey,
3✔
386
                        )
3✔
387
                        if err != nil {
3✔
388
                                return nil, err
×
389
                        }
×
390

391
                case input.TaprootScriptSpendSignMethod:
3✔
392
                        leaf := txscript.TapLeaf{
3✔
393
                                LeafVersion: txscript.BaseLeafVersion,
3✔
394
                                Script:      witnessScript,
3✔
395
                        }
3✔
396
                        rawSig, err = txscript.RawTxInTapscriptSignature(
3✔
397
                                tx, sigHashes, signDesc.InputIndex,
3✔
398
                                signDesc.Output.Value, signDesc.Output.PkScript,
3✔
399
                                leaf, signDesc.HashType, privKey,
3✔
400
                        )
3✔
401
                        if err != nil {
3✔
402
                                return nil, err
×
403
                        }
×
404

405
                default:
×
406
                        return nil, fmt.Errorf("unknown sign method: %v",
×
407
                                signDesc.SignMethod)
×
408
                }
409

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

421
                return sig, nil
3✔
422
        }
423

424
        // TODO(roasbeef): generate sighash midstate if not present?
425

426
        amt := signDesc.Output.Value
3✔
427
        sig, err := txscript.RawTxInWitnessSignature(
3✔
428
                tx, signDesc.SigHashes, signDesc.InputIndex, amt,
3✔
429
                witnessScript, signDesc.HashType, privKey,
3✔
430
        )
3✔
431
        if err != nil {
3✔
432
                return nil, err
×
433
        }
×
434

435
        // Chop off the sighash flag at the end of the signature.
436
        return ecdsa.ParseDERSignature(sig[:len(sig)-1])
3✔
437
}
438

439
// ComputeInputScript generates a complete InputScript for the passed
440
// transaction with the signature as defined within the passed SignDescriptor.
441
// This method is capable of generating the proper input script for both
442
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
443
//
444
// This is a part of the WalletController interface.
445
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
446
        signDesc *input.SignDescriptor) (*input.Script, error) {
3✔
447

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

455
        // Let the wallet compute the input script now.
456
        witness, sigScript, err := b.wallet.ComputeInputScript(
3✔
457
                tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
3✔
458
                signDesc.HashType, privKeyTweaker,
3✔
459
        )
3✔
460
        if err != nil {
3✔
461
                return nil, err
×
462
        }
×
463

464
        return &input.Script{
3✔
465
                Witness:   witness,
3✔
466
                SigScript: sigScript,
3✔
467
        }, nil
3✔
468
}
469

470
// A compile time check to ensure that BtcWallet implements the Signer
471
// interface.
472
var _ input.Signer = (*BtcWallet)(nil)
473

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

3✔
483
        // First attempt to fetch the private key which corresponds to the
3✔
484
        // specified public key.
3✔
485
        privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
3✔
486
                KeyLocator: keyLoc,
3✔
487
        })
3✔
488
        if err != nil {
3✔
489
                return nil, err
×
490
        }
×
491

492
        // Double hash and sign the data.
493
        var msgDigest []byte
3✔
494
        if doubleHash {
6✔
495
                msgDigest = chainhash.DoubleHashB(msg)
3✔
496
        } else {
3✔
497
                msgDigest = chainhash.HashB(msg)
×
498
        }
×
499
        return ecdsa.Sign(privKey, msgDigest), nil
3✔
500
}
501

502
// A compile time check to ensure that BtcWallet implements the MessageSigner
503
// interface.
504
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