lnd/lnwallet/btcwallet/signer.go
Olaoluwa Osuntokun 630fc36dcf
multi: introduce and use new TapTweak and SignMethod fields
In this commit, we add a new field `TapTweak` to be used for key path
spends. Before this commit, we'd overload the existing `WitnessScript`
field to pass this information to the signing context. This was
confusing as for tapscript spends, this was the leaf script, which
mirrors the other script based spending types.

With this new filed, users need to set this to the script root for
keypath spends where the output key commits to a real merkle root, and
nothing when bip 86 spending is being used.

To make the signing even more explicit, we also add a new field called
sign_method with an enum type that differentiates between the different
segwit v0 and v1 signing methods.

Fixes https://github.com/lightningnetwork/lnd/issues/6446.
2022-05-11 10:07:49 +02:00

764 lines
24 KiB
Go

package btcwallet
import (
"crypto/sha256"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
)
// FetchInputInfo queries for the WalletController's knowledge of the passed
// outpoint. If the base wallet determines this output is under its control,
// then the original txout should be returned. Otherwise, a non-nil error value
// of ErrNotMine should be returned instead.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
prevTx, txOut, bip32, confirmations, err := b.wallet.FetchInputInfo(
prevOut,
)
if err != nil {
return nil, err
}
// Then, we'll populate all of the information required by the struct.
addressType := lnwallet.UnknownAddressType
switch {
case txscript.IsPayToWitnessPubKeyHash(txOut.PkScript):
addressType = lnwallet.WitnessPubKey
case txscript.IsPayToScriptHash(txOut.PkScript):
addressType = lnwallet.NestedWitnessPubKey
case txscript.IsPayToTaproot(txOut.PkScript):
addressType = lnwallet.TaprootPubkey
}
return &lnwallet.Utxo{
AddressType: addressType,
Value: btcutil.Amount(txOut.Value),
PkScript: txOut.PkScript,
Confirmations: confirmations,
OutPoint: *prevOut,
Derivation: bip32,
PrevTx: prevTx,
}, nil
}
// ScriptForOutput returns the address, witness program and redeem script for a
// given UTXO. An error is returned if the UTXO does not belong to our wallet or
// it is not a managed pubKey address.
func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
waddrmgr.ManagedPubKeyAddress, []byte, []byte, error) {
return b.wallet.ScriptForOutput(output)
}
// deriveFromKeyLoc attempts to derive a private key using a fully specified
// KeyLocator.
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
addrmgrNs walletdb.ReadWriteBucket,
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyLoc.Family),
Account: uint32(keyLoc.Family),
Branch: 0,
Index: keyLoc.Index,
}
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
if err != nil {
return nil, err
}
return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
}
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
// first three elements of the path to be hardened according to BIP44, so they
// must be a number >= 2^31.
func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
error) {
// Make sure we get a full path with exactly 5 elements. A path is
// either custom purpose one with 4 dynamic and one static elements:
// m/1017'/coinType'/keyFamily'/0/index
// Or a default BIP49/89 one with 5 elements:
// m/purpose'/coinType'/account'/change/index
const expectedDerivationPathDepth = 5
if len(path) != expectedDerivationPathDepth {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected path length %d, instead was %d",
expectedDerivationPathDepth, len(path))
}
// Assert that the first three parts of the path are actually hardened
// to avoid under-flowing the uint32 type.
if err := assertHardened(path[0], path[1], path[2]); err != nil {
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"expected first three elements to be hardened: %w", err)
}
purpose := path[0] - hdkeychain.HardenedKeyStart
coinType := path[1] - hdkeychain.HardenedKeyStart
account := path[2] - hdkeychain.HardenedKeyStart
change, index := path[3], path[4]
// Is this a custom lnd internal purpose key?
switch purpose {
case keychain.BIP0043Purpose:
// Make sure it's for the same coin type as our wallet's
// keychain scope.
if coinType != b.chainKeyScope.Coin {
return nil, fmt.Errorf("invalid BIP32 derivation "+
"path, expected coin type %d, instead was %d",
b.chainKeyScope.Coin, coinType)
}
return b.deriveKeyByLocator(keychain.KeyLocator{
Family: keychain.KeyFamily(account),
Index: index,
})
// Is it a standard, BIP defined purpose that the wallet understands?
case waddrmgr.KeyScopeBIP0044.Purpose,
waddrmgr.KeyScopeBIP0049Plus.Purpose,
waddrmgr.KeyScopeBIP0084.Purpose,
waddrmgr.KeyScopeBIP0086.Purpose:
// We're going to continue below the switch statement to avoid
// unnecessary indentation for this default case.
// Currently, there is no way to import any other key scopes than the
// one custom purpose or three standard ones into lnd's wallet. So we
// shouldn't accept any other scopes to sign for.
default:
return nil, fmt.Errorf("invalid BIP32 derivation path, "+
"unknown purpose %d", purpose)
}
// Okay, we made sure it's a BIP49/84 key, so we need to derive it now.
// Interestingly, the btcwallet never actually uses a coin type other
// than 0 for those keys, so we need to make sure this behavior is
// replicated here.
if coinType != 0 {
return nil, fmt.Errorf("invalid BIP32 derivation path, coin " +
"type must be 0 for BIP49/84 btcwallet keys")
}
// We only expect to be asked to sign with key scopes that we know
// about. So if the scope doesn't exist, we don't create it.
scope := waddrmgr.KeyScope{
Purpose: purpose,
Coin: coinType,
}
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
if err != nil {
return nil, fmt.Errorf("error fetching manager for scope %v: "+
"%w", scope, err)
}
// Let's see if we can hit the private key cache.
keyPath := waddrmgr.DerivationPath{
InternalAccount: account,
Account: account,
Branch: change,
Index: index,
}
privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
if err == nil {
return privKey, nil
}
// The key wasn't in the cache, let's fully derive it now.
err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath)
if err != nil {
return fmt.Errorf("error deriving private key: %w", err)
}
privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
return err
})
if err != nil {
return nil, fmt.Errorf("error deriving key from path %#v: %w",
keyPath, err)
}
return privKey, nil
}
// assertHardened makes sure each given element is >= 2^31.
func assertHardened(elements ...uint32) error {
for idx, element := range elements {
if element < hdkeychain.HardenedKeyStart {
return fmt.Errorf("element at index %d is not hardened",
idx)
}
}
return nil
}
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
// valid key locator.
func (b *BtcWallet) deriveKeyByLocator(
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
// We'll assume the special lightning key scope in this case.
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
b.chainKeyScope,
)
if err != nil {
return nil, err
}
// First try to read the key from the cached store, if this fails, then
// we'll fall through to the method below that requires a database
// transaction.
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyLoc.Family),
Account: uint32(keyLoc.Family),
Branch: 0,
Index: keyLoc.Index,
}
privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
if err == nil {
return privKey, nil
}
var key *btcec.PrivateKey
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
// If we've reached this point, then the account
// doesn't yet exist, so we'll create it now to ensure
// we can sign.
acctErr := scopedMgr.NewRawAccount(
addrmgrNs, uint32(keyLoc.Family),
)
if acctErr != nil {
return acctErr
}
// Now that we know the account exists, we'll attempt
// to re-derive the private key.
key, err = deriveFromKeyLoc(
scopedMgr, addrmgrNs, keyLoc,
)
if err != nil {
return err
}
}
return err
})
if err != nil {
return nil, err
}
return key, nil
}
// fetchPrivKey attempts to retrieve the raw private key corresponding to the
// passed public key if populated, or the key descriptor path (if non-empty).
func (b *BtcWallet) fetchPrivKey(
keyDesc *keychain.KeyDescriptor) (*btcec.PrivateKey, error) {
// If the key locator within the descriptor *isn't* empty, then we can
// directly derive the keys raw.
emptyLocator := keyDesc.KeyLocator.IsEmpty()
if !emptyLocator || keyDesc.PubKey == nil {
return b.deriveKeyByLocator(keyDesc.KeyLocator)
}
hash160 := btcutil.Hash160(keyDesc.PubKey.SerializeCompressed())
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams)
if err != nil {
return nil, err
}
// Otherwise, we'll attempt to derive the key based on the address.
// This will only work if we've already derived this address in the
// past, since the wallet relies on a mapping of addr -> key.
key, err := b.wallet.PrivKeyForAddress(addr)
switch {
// If we didn't find this key in the wallet, then there's a chance that
// this is actually an "empty" key locator. The legacy KeyLocator
// format failed to properly distinguish an empty key locator from the
// very first in the index (0, 0).IsEmpty() == true.
case waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) && emptyLocator:
return b.deriveKeyByLocator(keyDesc.KeyLocator)
case err != nil:
return nil, err
default:
return key, nil
}
}
// maybeTweakPrivKey examines the single and double tweak parameters on the
// passed sign descriptor and may perform a mapping on the passed private key
// in order to utilize the tweaks, if populated.
func maybeTweakPrivKey(signDesc *input.SignDescriptor,
privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) {
var retPriv *btcec.PrivateKey
switch {
case signDesc.SingleTweak != nil:
retPriv = input.TweakPrivKey(privKey,
signDesc.SingleTweak)
case signDesc.DoubleTweak != nil:
retPriv = input.DeriveRevocationPrivKey(privKey,
signDesc.DoubleTweak)
default:
retPriv = privKey
}
return retPriv, nil
}
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignDescriptor.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
witnessScript := signDesc.WitnessScript
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := b.fetchPrivKey(&signDesc.KeyDesc)
if err != nil {
return nil, err
}
// If a tweak (single or double) is specified, then we'll need to use
// this tweak to derive the final private key to be used for signing
// this output.
privKey, err = maybeTweakPrivKey(signDesc, privKey)
if err != nil {
return nil, err
}
// In case of a taproot output any signature is always a Schnorr
// signature, based on the new tapscript sighash algorithm.
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
sigHashes := txscript.NewTxSigHashes(
tx, signDesc.PrevOutputFetcher,
)
// Are we spending a script path or the key path? The API is
// slightly different, so we need to account for that to get the
// raw signature.
var rawSig []byte
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// This function tweaks the private key using the tap
// root key supplied as the tweak.
rawSig, err = txscript.RawTxInTaprootSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.TapTweak, signDesc.HashType,
privKey,
)
if err != nil {
return nil, err
}
case input.TaprootScriptSpendSignMethod:
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: witnessScript,
}
rawSig, err = txscript.RawTxInTapscriptSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
leaf, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
}
sig, err := schnorr.ParseSignature(rawSig)
if err != nil {
return nil, err
}
return sig, nil
}
// TODO(roasbeef): generate sighash midstate if not present?
amt := signDesc.Output.Value
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
witnessScript, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
// Chop off the sighash flag at the end of the signature.
return ecdsa.ParseDERSignature(sig[:len(sig)-1])
}
// ComputeInputScript generates a complete InputScript for the passed
// transaction with the signature as defined within the passed SignDescriptor.
// This method is capable of generating the proper input script for both
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
// If a tweak (single or double) is specified, then we'll need to use
// this tweak to derive the final private key to be used for signing
// this output.
privKeyTweaker := func(k *btcec.PrivateKey) (*btcec.PrivateKey, error) {
return maybeTweakPrivKey(signDesc, k)
}
// Let the wallet compute the input script now.
witness, sigScript, err := b.wallet.ComputeInputScript(
tx, signDesc.Output, signDesc.InputIndex, signDesc.SigHashes,
signDesc.HashType, privKeyTweaker,
)
if err != nil {
return nil, err
}
return &input.Script{
Witness: witness,
SigScript: sigScript,
}, nil
}
// muSig2State is a struct that holds on to the internal signing session state
// of a MuSig2 session.
type muSig2State struct {
// MuSig2SessionInfo is the associated meta information of the signing
// session.
input.MuSig2SessionInfo
// context is the signing context responsible for keeping track of the
// public keys involved in the signing process.
context *musig2.Context
// session is the signing session responsible for keeping track of the
// nonces and partial signatures involved in the signing process.
session *musig2.Session
}
// MuSig2CreateSession creates a new MuSig2 signing session using the local
// key identified by the key locator. The complete list of all public keys of
// all signing parties must be provided, including the public key of the local
// signing key. If nonces of other parties are already known, they can be
// submitted as well to reduce the number of method calls necessary later on.
func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator,
allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks,
otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo,
error) {
// We need to derive the private key for signing. In the remote signing
// setup, this whole RPC call will be forwarded to the signing
// instance, which requires it to be stateful.
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, fmt.Errorf("error deriving private key: %v", err)
}
// The context keeps track of all signing keys and our local key.
allOpts := append(
[]musig2.ContextOption{
musig2.WithKnownSigners(allSignerPubKeys),
},
tweaks.ToContextOptions()...,
)
musigContext, err := musig2.NewContext(privKey, true, allOpts...)
if err != nil {
return nil, fmt.Errorf("error creating MuSig2 signing "+
"context: %v", err)
}
// The session keeps track of the own and other nonces.
musigSession, err := musigContext.NewSession()
if err != nil {
return nil, fmt.Errorf("error creating MuSig2 signing "+
"session: %v", err)
}
// Add all nonces we might've learned so far.
haveAllNonces := false
for _, otherSignerNonce := range otherSignerNonces {
haveAllNonces, err = musigSession.RegisterPubNonce(
otherSignerNonce,
)
if err != nil {
return nil, fmt.Errorf("error registering other "+
"signer public nonce: %v", err)
}
}
// Register the new session.
combinedKey, err := musigContext.CombinedKey()
if err != nil {
return nil, fmt.Errorf("error getting combined key: %v", err)
}
session := &muSig2State{
MuSig2SessionInfo: input.MuSig2SessionInfo{
SessionID: input.NewMuSig2SessionID(
combinedKey, musigSession.PublicNonce(),
),
PublicNonce: musigSession.PublicNonce(),
CombinedKey: combinedKey,
TaprootTweak: tweaks.HasTaprootTweak(),
HaveAllNonces: haveAllNonces,
},
context: musigContext,
session: musigSession,
}
// The internal key is only calculated if we are using a taproot tweak
// and need to know it for a potential script spend.
if tweaks.HasTaprootTweak() {
internalKey, err := musigContext.TaprootInternalKey()
if err != nil {
return nil, fmt.Errorf("error getting internal key: %v",
err)
}
session.TaprootInternalKey = internalKey
}
// Since we generate new nonces for every session, there is no way that
// a session with the same ID already exists. So even if we call the API
// twice with the same signers, we still get a new ID.
b.musig2SessionsMtx.Lock()
b.musig2Sessions[session.SessionID] = session
b.musig2SessionsMtx.Unlock()
return &session.MuSig2SessionInfo, nil
}
// MuSig2RegisterNonces registers one or more public nonces of other signing
// participants for a session identified by its ID. This method returns true
// once we have all nonces for all other signing participants.
func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
b.musig2SessionsMtx.Lock()
defer b.musig2SessionsMtx.Unlock()
session, ok := b.musig2Sessions[sessionID]
if !ok {
return false, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// Make sure we don't exceed the number of expected nonces as that would
// indicate something is wrong with the signing setup.
if session.HaveAllNonces {
return true, fmt.Errorf("already have all nonces")
}
numSigners := len(session.context.SigningKeys())
remainingNonces := numSigners - session.session.NumRegisteredNonces()
if len(otherSignerNonces) > remainingNonces {
return false, fmt.Errorf("only %d other nonces remaining but "+
"trying to register %d more", remainingNonces,
len(otherSignerNonces))
}
// Add all nonces we've learned so far.
var err error
for _, otherSignerNonce := range otherSignerNonces {
session.HaveAllNonces, err = session.session.RegisterPubNonce(
otherSignerNonce,
)
if err != nil {
return false, fmt.Errorf("error registering other "+
"signer public nonce: %v", err)
}
}
return session.HaveAllNonces, nil
}
// MuSig2Sign creates a partial signature using the local signing key
// that was specified when the session was created. This can only be
// called when all public nonces of all participants are known and have
// been registered with the session. If this node isn't responsible for
// combining all the partial signatures, then the cleanup parameter
// should be set, indicating that the session can be removed from memory
// once the signature was produced.
func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID,
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
b.musig2SessionsMtx.Lock()
defer b.musig2SessionsMtx.Unlock()
session, ok := b.musig2Sessions[sessionID]
if !ok {
return nil, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// We can only sign once we have all other signer's nonces.
if !session.HaveAllNonces {
return nil, fmt.Errorf("only have %d of %d required nonces",
session.session.NumRegisteredNonces(),
len(session.context.SigningKeys()))
}
// Create our own partial signature with the local signing key.
partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys())
if err != nil {
return nil, fmt.Errorf("error signing with local key: %v", err)
}
// Clean up our local state if requested.
if cleanUp {
delete(b.musig2Sessions, sessionID)
}
return partialSig, nil
}
// MuSig2CombineSig combines the given partial signature(s) with the
// local one, if it already exists. Once a partial signature of all
// participants is registered, the final signature will be combined and
// returned.
func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
error) {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
b.musig2SessionsMtx.Lock()
defer b.musig2SessionsMtx.Unlock()
session, ok := b.musig2Sessions[sessionID]
if !ok {
return nil, false, fmt.Errorf("session with ID %x not found",
sessionID[:])
}
// Make sure we don't exceed the number of expected partial signatures
// as that would indicate something is wrong with the signing setup.
if session.HaveAllSigs {
return nil, true, fmt.Errorf("already have all partial" +
"signatures")
}
// Add all sigs we got so far.
var (
finalSig *schnorr.Signature
err error
)
for _, otherPartialSig := range partialSigs {
session.HaveAllSigs, err = session.session.CombineSig(
otherPartialSig,
)
if err != nil {
return nil, false, fmt.Errorf("error combining "+
"partial signature: %v", err)
}
}
// If we have all partial signatures, we should be able to get the
// complete signature now. We also remove this session from memory since
// there is nothing more left to do.
if session.HaveAllSigs {
finalSig = session.session.FinalSig()
delete(b.musig2Sessions, sessionID)
}
return finalSig, session.HaveAllSigs, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
b.musig2SessionsMtx.Lock()
defer b.musig2SessionsMtx.Unlock()
_, ok := b.musig2Sessions[sessionID]
if !ok {
return fmt.Errorf("session with ID %x not found", sessionID[:])
}
delete(b.musig2Sessions, sessionID)
return nil
}
// A compile time check to ensure that BtcWallet implements the Signer
// interface.
var _ input.Signer = (*BtcWallet)(nil)
// SignMessage attempts to sign a target message with the private key that
// corresponds to the passed key locator. If the target private key is unable to
// be found, then an error will be returned. The actual digest signed is the
// double SHA-256 of the passed message.
//
// NOTE: This is a part of the MessageSigner interface.
func (b *BtcWallet) SignMessage(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool) (*ecdsa.Signature, error) {
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{
KeyLocator: keyLoc,
})
if err != nil {
return nil, err
}
// Double hash and sign the data.
var msgDigest []byte
if doubleHash {
msgDigest = chainhash.DoubleHashB(msg)
} else {
msgDigest = chainhash.HashB(msg)
}
return ecdsa.Sign(privKey, msgDigest), nil
}
// A compile time check to ensure that BtcWallet implements the MessageSigner
// interface.
var _ lnwallet.MessageSigner = (*BtcWallet)(nil)