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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 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) {
3✔
29

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

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

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

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

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

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

3✔
103
        // Is this a custom lnd internal purpose key?
3✔
104
        switch purpose {
3✔
105
        case keychain.BIP0043Purpose:
3✔
106
                // Make sure it's for the same coin type as our wallet's
3✔
107
                // keychain scope.
3✔
108
                if coinType != b.chainKeyScope.Coin {
3✔
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{
3✔
115
                        Family: keychain.KeyFamily(account),
3✔
116
                        Index:  index,
3✔
117
                })
3✔
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:
3✔
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 {
3✔
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{
3✔
148
                Purpose: purpose,
3✔
149
                Coin:    coinType,
3✔
150
        }
3✔
151
        keyPath := waddrmgr.DerivationPath{
3✔
152
                InternalAccount: account,
3✔
153
                Account:         account,
3✔
154
                Branch:          change,
3✔
155
                Index:           index,
3✔
156
        }
3✔
157

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

164
        return privKey, nil
3✔
165
}
166

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

176
        return nil
3✔
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) {
3✔
183

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

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

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

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

213
        hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
3✔
214
        addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
3✔
215
        if err != nil {
3✔
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)
3✔
223
        switch {
3✔
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:
3✔
235
                return key, nil
3✔
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) {
3✔
244

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

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

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

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

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

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

3✔
272
        // First attempt to fetch the private key which corresponds to the
3✔
273
        // specified public key.
3✔
274
        privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
3✔
275
        if err != nil {
3✔
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)
3✔
283
        if err != nil {
3✔
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) {
6✔
290
                sigHashes := txscript.NewTxSigHashes(
3✔
291
                        tx, signDesc.PrevOutputFetcher,
3✔
292
                )
3✔
293

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

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

314
                case input.TaprootScriptSpendSignMethod:
3✔
315
                        leaf := txscript.TapLeaf{
3✔
316
                                LeafVersion: txscript.BaseLeafVersion,
3✔
317
                                Script:      witnessScript,
3✔
318
                        }
3✔
319
                        rawSig, err = txscript.RawTxInTapscriptSignature(
3✔
320
                                tx, sigHashes, signDesc.InputIndex,
3✔
321
                                signDesc.Output.Value, signDesc.Output.PkScript,
3✔
322
                                leaf, signDesc.HashType, privKey,
3✔
323
                        )
3✔
324
                        if err != nil {
3✔
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(
3✔
338
                        rawSig[:schnorr.SignatureSize],
3✔
339
                )
3✔
340
                if err != nil {
3✔
341
                        return nil, err
×
342
                }
×
343

344
                return sig, nil
3✔
345
        }
346

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

349
        amt := signDesc.Output.Value
3✔
350
        sig, err := txscript.RawTxInWitnessSignature(
3✔
351
                tx, signDesc.SigHashes, signDesc.InputIndex, amt,
3✔
352
                witnessScript, signDesc.HashType, privKey,
3✔
353
        )
3✔
354
        if err != nil {
3✔
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])
3✔
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) {
3✔
370

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

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

387
        return &input.Script{
3✔
388
                Witness:   witness,
3✔
389
                SigScript: sigScript,
3✔
390
        }, nil
3✔
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) {
3✔
405

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

415
        // Double hash and sign the data.
416
        var msgDigest []byte
3✔
417
        if doubleHash {
6✔
418
                msgDigest = chainhash.DoubleHashB(msg)
3✔
419
        } else {
3✔
420
                msgDigest = chainhash.HashB(msg)
×
421
        }
×
422
        return ecdsa.Sign(privKey, msgDigest), nil
3✔
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