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

lightningnetwork / lnd / 15249422085

26 May 2025 08:11AM UTC coverage: 57.977% (-11.0%) from 69.015%
15249422085

push

github

web-flow
Merge pull request #9853 from lightningnetwork/elle-graphSQL8-prep

graph/db: init SQLStore caches and batch schedulers

9 of 34 new or added lines in 4 files covered. (26.47%)

29283 existing lines in 458 files now uncovered.

96475 of 166402 relevant lines covered (57.98%)

1.22 hits per line

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

79.45
/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/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/keychain"
18
        "github.com/lightningnetwork/lnd/lnwallet"
19
)
20

21
// FetchOutpointInfo 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) FetchOutpointInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
28
        error) {
2✔
29

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

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

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

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

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

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

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

73
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
74
// first three elements of the path to be hardened according to BIP44, so they
75
// must be a number >= 2^31.
76
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
77
        error) {
2✔
78

2✔
79
        // Make sure we get a full path with exactly 5 elements. A path is
2✔
80
        // either custom purpose one with 4 dynamic and one static elements:
2✔
81
        //    m/1017'/coinType'/keyFamily'/0/index
2✔
82
        // Or a default BIP49/89 one with 5 elements:
2✔
83
        //    m/purpose'/coinType'/account'/change/index
2✔
84
        const expectedDerivationPathDepth = 5
2✔
85
        if len(path) != expectedDerivationPathDepth {
2✔
86
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
87
                        "expected path length %d, instead was %d",
×
88
                        expectedDerivationPathDepth, len(path))
×
89
        }
×
90

91
        // Assert that the first three parts of the path are actually hardened
92
        // to avoid under-flowing the uint32 type.
93
        if err := assertHardened(path[0], path[1], path[2]); err != nil {
2✔
UNCOV
94
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
UNCOV
95
                        "expected first three elements to be hardened: %w", err)
×
UNCOV
96
        }
×
97

98
        purpose := path[0] - hdkeychain.HardenedKeyStart
2✔
99
        coinType := path[1] - hdkeychain.HardenedKeyStart
2✔
100
        account := path[2] - hdkeychain.HardenedKeyStart
2✔
101
        change, index := path[3], path[4]
2✔
102

2✔
103
        // Is this a custom lnd internal purpose key?
2✔
104
        switch purpose {
2✔
105
        case keychain.BIP0043Purpose:
2✔
106
                // Make sure it's for the same coin type as our wallet's
2✔
107
                // keychain scope.
2✔
108
                if coinType != b.chainKeyScope.Coin {
2✔
UNCOV
109
                        return nil, fmt.Errorf("invalid BIP32 derivation "+
×
UNCOV
110
                                "path, expected coin type %d, instead was %d",
×
UNCOV
111
                                b.chainKeyScope.Coin, coinType)
×
UNCOV
112
                }
×
113

114
                return b.deriveKeyByLocator(keychain.KeyLocator{
2✔
115
                        Family: keychain.KeyFamily(account),
2✔
116
                        Index:  index,
2✔
117
                })
2✔
118

119
        // Is it a standard, BIP defined purpose that the wallet understands?
120
        case waddrmgr.KeyScopeBIP0044.Purpose,
121
                waddrmgr.KeyScopeBIP0049Plus.Purpose,
122
                waddrmgr.KeyScopeBIP0084.Purpose,
123
                waddrmgr.KeyScopeBIP0086.Purpose:
2✔
124

125
                // We're going to continue below the switch statement to avoid
126
                // unnecessary indentation for this default case.
127

128
        // Currently, there is no way to import any other key scopes than the
129
        // one custom purpose or three standard ones into lnd's wallet. So we
130
        // shouldn't accept any other scopes to sign for.
131
        default:
×
132
                return nil, fmt.Errorf("invalid BIP32 derivation path, "+
×
133
                        "unknown purpose %d", purpose)
×
134
        }
135

136
        // Okay, we made sure it's a BIP49/84 key, so we need to derive it now.
137
        // Interestingly, the btcwallet never actually uses a coin type other
138
        // than 0 for those keys, so we need to make sure this behavior is
139
        // replicated here.
140
        if coinType != 0 {
2✔
UNCOV
141
                return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
×
UNCOV
142
                        "type must be 0 for BIP49/84 btcwallet keys")
×
UNCOV
143
        }
×
144

145
        // We only expect to be asked to sign with key scopes that we know
146
        // about. So if the scope doesn't exist, we don't create it.
147
        scope := waddrmgr.KeyScope{
2✔
148
                Purpose: purpose,
2✔
149
                Coin:    coinType,
2✔
150
        }
2✔
151
        keyPath := waddrmgr.DerivationPath{
2✔
152
                InternalAccount: account,
2✔
153
                Account:         account,
2✔
154
                Branch:          change,
2✔
155
                Index:           index,
2✔
156
        }
2✔
157

2✔
158
        privKey, err := b.wallet.DeriveFromKeyPath(scope, keyPath)
2✔
159
        if err != nil {
2✔
UNCOV
160
                return nil, fmt.Errorf("error deriving key from path %#v: %w",
×
UNCOV
161
                        keyPath, err)
×
UNCOV
162
        }
×
163

164
        return privKey, nil
2✔
165
}
166

167
// assertHardened makes sure each given element is >= 2^31.
168
func assertHardened(elements ...uint32) error {
2✔
169
        for idx, element := range elements {
4✔
170
                if element < hdkeychain.HardenedKeyStart {
2✔
UNCOV
171
                        return fmt.Errorf("element at index %d is not hardened",
×
UNCOV
172
                                idx)
×
UNCOV
173
                }
×
174
        }
175

176
        return nil
2✔
177
}
178

179
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
180
// valid key locator.
181
func (b *BtcWallet) deriveKeyByLocator(
182
        keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
2✔
183

2✔
184
        // We'll assume the special lightning key scope in this case.
2✔
185
        scope := b.chainKeyScope
2✔
186
        path := waddrmgr.DerivationPath{
2✔
187
                InternalAccount: uint32(keyLoc.Family),
2✔
188
                Account:         uint32(keyLoc.Family),
2✔
189
                Branch:          0,
2✔
190
                Index:           keyLoc.Index,
2✔
191
        }
2✔
192

2✔
193
        key, err := b.wallet.DeriveFromKeyPathAddAccount(scope, path)
2✔
194
        if err != nil {
2✔
195
                return nil, err
×
196
        }
×
197

198
        return key, nil
2✔
199
}
200

201
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
202
// passed public key if populated, or the key descriptor path (if non-empty).
203
func (b *BtcWallet) fetchPrivKey(
204
        keyDesc *keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
2✔
205

2✔
206
        // If the key locator within the descriptor *isn't* empty, then we can
2✔
207
        // directly derive the keys raw.
2✔
208
        emptyLocator := keyDesc.KeyLocator.IsEmpty()
2✔
209
        if !emptyLocator || keyDesc.PubKey == nil {
4✔
210
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
2✔
211
        }
2✔
212

213
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
2✔
214
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
2✔
215
        if err != nil {
2✔
216
                return nil, err
×
217
        }
×
218

219
        // Otherwise, we'll attempt to derive the key based on the address.
220
        // This will only work if we've already derived this address in the
221
        // past, since the wallet relies on a mapping of addr -> key.
222
        key, err := b.wallet.PrivKeyForAddress(addr)
2✔
223
        switch {
2✔
224
        // If we didn't find this key in the wallet, then there's a chance that
225
        // this is actually an "empty" key locator. The legacy KeyLocator
226
        // format failed to properly distinguish an empty key locator from the
227
        // very first in the index (0, 0).IsEmpty() == true.
228
        case waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) && emptyLocator:
×
229
                return b.deriveKeyByLocator(keyDesc.KeyLocator)
×
230

231
        case err != nil:
×
232
                return nil, err
×
233

234
        default:
2✔
235
                return key, nil
2✔
236
        }
237
}
238

239
// maybeTweakPrivKey examines the single and double tweak parameters on the
240
// passed sign descriptor and may perform a mapping on the passed private key
241
// in order to utilize the tweaks, if populated.
242
func maybeTweakPrivKey(signDesc *input.SignDescriptor,
243
        privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
2✔
244

2✔
245
        var retPriv *btcec.PrivateKey
2✔
246
        switch {
2✔
247

248
        case signDesc.SingleTweak != nil:
2✔
249
                retPriv = input.TweakPrivKey(privKey,
2✔
250
                        signDesc.SingleTweak)
2✔
251

252
        case signDesc.DoubleTweak != nil:
2✔
253
                retPriv = input.DeriveRevocationPrivKey(privKey,
2✔
254
                        signDesc.DoubleTweak)
2✔
255

256
        default:
2✔
257
                retPriv = privKey
2✔
258
        }
259

260
        return retPriv, nil
2✔
261
}
262

263
// SignOutputRaw generates a signature for the passed transaction according to
264
// the data within the passed SignDescriptor.
265
//
266
// This is a part of the WalletController interface.
267
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
268
        signDesc *input.SignDescriptor) (input.Signature, error) {
2✔
269

2✔
270
        witnessScript := signDesc.WitnessScript
2✔
271

2✔
272
        // First attempt to fetch the private key which corresponds to the
2✔
273
        // specified public key.
2✔
274
        privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
2✔
275
        if err != nil {
2✔
276
                return nil, err
×
277
        }
×
278

279
        // If a tweak (single or double) is specified, then we'll need to use
280
        // this tweak to derive the final private key to be used for signing
281
        // this output.
282
        privKey, err = maybeTweakPrivKey(signDesc, privKey)
2✔
283
        if err != nil {
2✔
284
                return nil, err
×
285
        }
×
286

287
        // In case of a taproot output any signature is always a Schnorr
288
        // signature, based on the new tapscript sighash algorithm.
289
        if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
4✔
290
                sigHashes := txscript.NewTxSigHashes(
2✔
291
                        tx, signDesc.PrevOutputFetcher,
2✔
292
                )
2✔
293

2✔
294
                // Are we spending a script path or the key path? The API is
2✔
295
                // slightly different, so we need to account for that to get the
2✔
296
                // raw signature.
2✔
297
                var rawSig []byte
2✔
298
                switch signDesc.SignMethod {
2✔
299
                case input.TaprootKeySpendBIP0086SignMethod,
300
                        input.TaprootKeySpendSignMethod:
2✔
301

2✔
302
                        // This function tweaks the private key using the tap
2✔
303
                        // root key supplied as the tweak.
2✔
304
                        rawSig, err = txscript.RawTxInTaprootSignature(
2✔
305
                                tx, sigHashes, signDesc.InputIndex,
2✔
306
                                signDesc.Output.Value, signDesc.Output.PkScript,
2✔
307
                                signDesc.TapTweak, signDesc.HashType,
2✔
308
                                privKey,
2✔
309
                        )
2✔
310
                        if err != nil {
2✔
311
                                return nil, err
×
312
                        }
×
313

314
                case input.TaprootScriptSpendSignMethod:
2✔
315
                        leaf := txscript.TapLeaf{
2✔
316
                                LeafVersion: txscript.BaseLeafVersion,
2✔
317
                                Script:      witnessScript,
2✔
318
                        }
2✔
319
                        rawSig, err = txscript.RawTxInTapscriptSignature(
2✔
320
                                tx, sigHashes, signDesc.InputIndex,
2✔
321
                                signDesc.Output.Value, signDesc.Output.PkScript,
2✔
322
                                leaf, signDesc.HashType, privKey,
2✔
323
                        )
2✔
324
                        if err != nil {
2✔
325
                                return nil, err
×
326
                        }
×
327

328
                default:
×
329
                        return nil, fmt.Errorf("unknown sign method: %v",
×
330
                                signDesc.SignMethod)
×
331
                }
332

333
                // The signature returned above might have a sighash flag
334
                // attached if a non-default type was used. We'll slice this
335
                // off if it exists to ensure we can properly parse the raw
336
                // signature.
337
                sig, err := schnorr.ParseSignature(
2✔
338
                        rawSig[:schnorr.SignatureSize],
2✔
339
                )
2✔
340
                if err != nil {
2✔
341
                        return nil, err
×
342
                }
×
343

344
                return sig, nil
2✔
345
        }
346

347
        // TODO(roasbeef): generate sighash midstate if not present?
348

349
        amt := signDesc.Output.Value
2✔
350
        sig, err := txscript.RawTxInWitnessSignature(
2✔
351
                tx, signDesc.SigHashes, signDesc.InputIndex, amt,
2✔
352
                witnessScript, signDesc.HashType, privKey,
2✔
353
        )
2✔
354
        if err != nil {
2✔
355
                return nil, err
×
356
        }
×
357

358
        // Chop off the sighash flag at the end of the signature.
359
        return ecdsa.ParseDERSignature(sig[:len(sig)-1])
2✔
360
}
361

362
// ComputeInputScript generates a complete InputScript for the passed
363
// transaction with the signature as defined within the passed SignDescriptor.
364
// This method is capable of generating the proper input script for both
365
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
366
//
367
// This is a part of the WalletController interface.
368
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
369
        signDesc *input.SignDescriptor) (*input.Script, error) {
2✔
370

2✔
371
        // If a tweak (single or double) is specified, then we'll need to use
2✔
372
        // this tweak to derive the final private key to be used for signing
2✔
373
        // this output.
2✔
374
        privKeyTweaker := func(k *btcec.PrivateKey) (*btcec.PrivateKey, error) {
4✔
375
                return maybeTweakPrivKey(signDesc, k)
2✔
376
        }
2✔
377

378
        // Let the wallet compute the input script now.
379
        witness, sigScript, err := b.wallet.ComputeInputScript(
2✔
380
                tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
2✔
381
                signDesc.HashType, privKeyTweaker,
2✔
382
        )
2✔
383
        if err != nil {
2✔
384
                return nil, err
×
385
        }
×
386

387
        return &input.Script{
2✔
388
                Witness:   witness,
2✔
389
                SigScript: sigScript,
2✔
390
        }, nil
2✔
391
}
392

393
// A compile time check to ensure that BtcWallet implements the Signer
394
// interface.
395
var _ input.Signer = (*BtcWallet)(nil)
396

397
// SignMessage attempts to sign a target message with the private key that
398
// corresponds to the passed key locator. If the target private key is unable to
399
// be found, then an error will be returned. The actual digest signed is the
400
// double SHA-256 of the passed message.
401
//
402
// NOTE: This is a part of the MessageSigner interface.
403
func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator,
404
        msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
2✔
405

2✔
406
        // First attempt to fetch the private key which corresponds to the
2✔
407
        // specified public key.
2✔
408
        privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
2✔
409
                KeyLocator: keyLoc,
2✔
410
        })
2✔
411
        if err != nil {
2✔
412
                return nil, err
×
413
        }
×
414

415
        // Double hash and sign the data.
416
        var msgDigest []byte
2✔
417
        if doubleHash {
4✔
418
                msgDigest = chainhash.DoubleHashB(msg)
2✔
419
        } else {
2✔
420
                msgDigest = chainhash.HashB(msg)
×
421
        }
×
422
        return ecdsa.Sign(privKey, msgDigest), nil
2✔
423
}
424

425
// A compile time check to ensure that BtcWallet implements the MessageSigner
426
// interface.
427
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