mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
440 lines
14 KiB
Go
440 lines
14 KiB
Go
package btcwallet
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
|
"github.com/go-errors/errors"
|
|
"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) {
|
|
_, txOut, _, 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
|
|
}
|
|
|
|
return &lnwallet.Utxo{
|
|
AddressType: addressType,
|
|
Value: btcutil.Amount(txOut.Value),
|
|
PkScript: txOut.PkScript,
|
|
Confirmations: confirmations,
|
|
OutPoint: *prevOut,
|
|
}, 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:
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256())
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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) (*btcec.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)
|
|
}
|
|
sign, err := privKey.Sign(msgDigest)
|
|
if err != nil {
|
|
return nil, errors.Errorf("unable sign the message: %v", err)
|
|
}
|
|
|
|
return sign, nil
|
|
}
|
|
|
|
// A compile time check to ensure that BtcWallet implements the MessageSigner
|
|
// interface.
|
|
var _ lnwallet.MessageSigner = (*BtcWallet)(nil)
|