mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-24 06:47:44 +01:00
804 lines
27 KiB
Go
804 lines
27 KiB
Go
package rpcwallet
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcutil/psbt"
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
|
btcwallet "github.com/btcsuite/btcwallet/wallet"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lncfg"
|
|
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"gopkg.in/macaroon.v2"
|
|
)
|
|
|
|
const (
|
|
// DefaultRPCTimeout is the default timeout that is used when forwarding
|
|
// a request to the remote signer through RPC.
|
|
DefaultRPCTimeout = 5 * time.Second
|
|
)
|
|
|
|
var (
|
|
// ErrRemoteSigningPrivateKeyNotAvailable is the error that is returned
|
|
// if an operation is requested from the RPC wallet that is not
|
|
// supported in remote signing mode.
|
|
ErrRemoteSigningPrivateKeyNotAvailable = errors.New("deriving " +
|
|
"private key is not supported by RPC based key ring")
|
|
)
|
|
|
|
// RPCKeyRing is an implementation of the SecretKeyRing interface that uses a
|
|
// local watch-only wallet for keeping track of addresses and transactions but
|
|
// delegates any signing or ECDH operations to a remote node through RPC.
|
|
type RPCKeyRing struct {
|
|
// WalletController is the embedded wallet controller of the watch-only
|
|
// base wallet. We need to overwrite/shadow certain of the implemented
|
|
// methods to make sure we can mirror them to the remote wallet.
|
|
lnwallet.WalletController
|
|
|
|
watchOnlyKeyRing keychain.SecretKeyRing
|
|
|
|
rpcTimeout time.Duration
|
|
|
|
signerClient signrpc.SignerClient
|
|
walletClient walletrpc.WalletKitClient
|
|
}
|
|
|
|
var _ keychain.SecretKeyRing = (*RPCKeyRing)(nil)
|
|
var _ input.Signer = (*RPCKeyRing)(nil)
|
|
var _ keychain.MessageSignerRing = (*RPCKeyRing)(nil)
|
|
var _ lnwallet.WalletController = (*RPCKeyRing)(nil)
|
|
|
|
// NewRPCKeyRing creates a new remote signing secret key ring that uses the
|
|
// given watch-only base wallet to keep track of addresses and transactions but
|
|
// delegates any signing or ECDH operations to the remove signer through RPC.
|
|
func NewRPCKeyRing(watchOnlyKeyRing keychain.SecretKeyRing,
|
|
watchOnlyWalletController lnwallet.WalletController,
|
|
remoteSigner *lncfg.RemoteSigner,
|
|
rpcTimeout time.Duration) (*RPCKeyRing, error) {
|
|
|
|
rpcConn, err := connectRPC(
|
|
remoteSigner.RPCHost, remoteSigner.TLSCertPath,
|
|
remoteSigner.MacaroonPath,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error connecting to the remote "+
|
|
"signing node through RPC: %v", err)
|
|
}
|
|
|
|
return &RPCKeyRing{
|
|
WalletController: watchOnlyWalletController,
|
|
watchOnlyKeyRing: watchOnlyKeyRing,
|
|
rpcTimeout: rpcTimeout,
|
|
signerClient: signrpc.NewSignerClient(rpcConn),
|
|
walletClient: walletrpc.NewWalletKitClient(rpcConn),
|
|
}, nil
|
|
}
|
|
|
|
// NewAddress returns the next external or internal address for the
|
|
// wallet dictated by the value of the `change` parameter. If change is
|
|
// true, then an internal address should be used, otherwise an external
|
|
// address should be returned. The type of address returned is dictated
|
|
// by the wallet's capabilities, and may be of type: p2sh, p2wkh,
|
|
// p2wsh, etc. The account parameter must be non-empty as it determines
|
|
// which account the address should be generated from.
|
|
func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
|
|
account string) (btcutil.Address, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
rpcAddrType := walletrpc.AddressType_WITNESS_PUBKEY_HASH
|
|
if addrType == lnwallet.NestedWitnessPubKey {
|
|
rpcAddrType = walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH
|
|
}
|
|
|
|
remoteAddr, err := r.walletClient.NextAddr(ctxt, &walletrpc.AddrRequest{
|
|
Account: account,
|
|
Type: rpcAddrType,
|
|
Change: change,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error deriving address on remote "+
|
|
"signer instance: %v", err)
|
|
}
|
|
|
|
localAddr, err := r.WalletController.NewAddress(
|
|
addrType, change, account,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error deriving address on local "+
|
|
"wallet instance: %v", err)
|
|
}
|
|
|
|
// We need to make sure we've derived the same address on the remote
|
|
// signing machine, otherwise we don't know whether we're at the same
|
|
// address index (and therefore the same wallet state in general).
|
|
if localAddr.String() != remoteAddr.Addr {
|
|
return nil, fmt.Errorf("error deriving address on remote "+
|
|
"signing instance, got different address (%s) than "+
|
|
"on local wallet instance (%s)", remoteAddr.Addr,
|
|
localAddr.String())
|
|
}
|
|
|
|
return localAddr, nil
|
|
}
|
|
|
|
// LastUnusedAddress returns the last *unused* address known by the wallet. An
|
|
// address is unused if it hasn't received any payments. This can be useful in
|
|
// UIs in order to continually show the "freshest" address without having to
|
|
// worry about "address inflation" caused by continual refreshing. Similar to
|
|
// NewAddress it can derive a specified address type, and also optionally a
|
|
// change address. The account parameter must be non-empty as it determines
|
|
// which account the address should be generated from.
|
|
func (r *RPCKeyRing) LastUnusedAddress(lnwallet.AddressType,
|
|
string) (btcutil.Address, error) {
|
|
|
|
// Because the underlying wallet will create a new address if the last
|
|
// derived address has been used in the meantime, we would need to proxy
|
|
// that call as well. But since that's deep within the btcwallet code,
|
|
// we cannot easily proxy it without more refactoring. Since this is an
|
|
// address type that is probably not widely used we can probably get
|
|
// away with not supporting it.
|
|
return nil, fmt.Errorf("unused address types are not supported when " +
|
|
"remote signing is enabled")
|
|
}
|
|
|
|
// ImportAccount imports an account backed by an account extended public key.
|
|
// The master key fingerprint denotes the fingerprint of the root key
|
|
// corresponding to the account public key (also known as the key with
|
|
// derivation path m/). This may be required by some hardware wallets for proper
|
|
// identification and signing.
|
|
//
|
|
// The address type can usually be inferred from the key's version, but may be
|
|
// required for certain keys to map them into the proper scope.
|
|
//
|
|
// For BIP-0044 keys, an address type must be specified as we intend to not
|
|
// support importing BIP-0044 keys into the wallet using the legacy
|
|
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
|
|
// the standard BIP-0049 derivation scheme, while a witness address type will
|
|
// force the standard BIP-0084 derivation scheme.
|
|
//
|
|
// For BIP-0049 keys, an address type must also be specified to make a
|
|
// distinction between the standard BIP-0049 address schema (nested witness
|
|
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
|
|
// externally, witness pubkeys internally).
|
|
func (r *RPCKeyRing) ImportAccount(name string,
|
|
accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
|
|
addrType *waddrmgr.AddressType,
|
|
dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
|
|
[]btcutil.Address, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
var masterKeyFingerprintBytes [4]byte
|
|
binary.BigEndian.PutUint32(
|
|
masterKeyFingerprintBytes[:], masterKeyFingerprint,
|
|
)
|
|
|
|
rpcAddrType, err := toRPCAddrType(addrType)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error converting address "+
|
|
"type: %v", err)
|
|
}
|
|
|
|
remoteAcct, err := r.walletClient.ImportAccount(
|
|
ctxt, &walletrpc.ImportAccountRequest{
|
|
Name: name,
|
|
ExtendedPublicKey: accountPubKey.String(),
|
|
MasterKeyFingerprint: masterKeyFingerprintBytes[:],
|
|
AddressType: rpcAddrType,
|
|
DryRun: dryRun,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error importing account on "+
|
|
"remote signer instance: %v", err)
|
|
}
|
|
|
|
props, extAddrs, intAddrs, err := r.WalletController.ImportAccount(
|
|
name, accountPubKey, masterKeyFingerprint, addrType, dryRun,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("error importing account on "+
|
|
"local wallet instance: %v", err)
|
|
}
|
|
|
|
mismatchErr := fmt.Errorf("error importing account on remote signing "+
|
|
"instance, got different external addresses (%v) than on "+
|
|
"local wallet instance (%s)", remoteAcct.DryRunExternalAddrs,
|
|
extAddrs)
|
|
if len(remoteAcct.DryRunExternalAddrs) != len(extAddrs) {
|
|
return nil, nil, nil, mismatchErr
|
|
}
|
|
for idx, remoteExtAddr := range remoteAcct.DryRunExternalAddrs {
|
|
if extAddrs[idx].String() != remoteExtAddr {
|
|
return nil, nil, nil, mismatchErr
|
|
}
|
|
}
|
|
|
|
mismatchErr = fmt.Errorf("error importing account on remote signing "+
|
|
"instance, got different internal addresses (%v) than on "+
|
|
"local wallet instance (%s)", remoteAcct.DryRunInternalAddrs,
|
|
intAddrs)
|
|
if len(remoteAcct.DryRunInternalAddrs) != len(intAddrs) {
|
|
return nil, nil, nil, mismatchErr
|
|
}
|
|
for idx, remoteIntAddr := range remoteAcct.DryRunInternalAddrs {
|
|
if intAddrs[idx].String() != remoteIntAddr {
|
|
return nil, nil, nil, mismatchErr
|
|
}
|
|
}
|
|
|
|
return props, extAddrs, intAddrs, nil
|
|
}
|
|
|
|
// ImportPublicKey imports a single derived public key into the wallet. The
|
|
// address type can usually be inferred from the key's version, but in the case
|
|
// of legacy versions (xpub, tpub), an address type must be specified as we
|
|
// intend to not support importing BIP-44 keys into the wallet using the legacy
|
|
// pay-to-pubkey-hash (P2PKH) scheme.
|
|
func (r *RPCKeyRing) ImportPublicKey(pubKey *btcec.PublicKey,
|
|
addrType waddrmgr.AddressType) error {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
rpcAddrType, err := toRPCAddrType(&addrType)
|
|
if err != nil {
|
|
return fmt.Errorf("error converting address type: %v", err)
|
|
}
|
|
|
|
_, err = r.walletClient.ImportPublicKey(
|
|
ctxt, &walletrpc.ImportPublicKeyRequest{
|
|
PublicKey: pubKey.SerializeCompressed(),
|
|
AddressType: rpcAddrType,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error importing pubkey on remote signer "+
|
|
"instance: %v", err)
|
|
}
|
|
|
|
err = r.WalletController.ImportPublicKey(pubKey, addrType)
|
|
if err != nil {
|
|
return fmt.Errorf("error importing pubkey on local signer "+
|
|
"instance: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying out to
|
|
// the specified outputs. In the case the wallet has insufficient funds, or the
|
|
// outputs are non-standard, a non-nil error will be returned.
|
|
//
|
|
// NOTE: This method requires the global coin selection lock to be held.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
|
|
feeRate chainfee.SatPerKWeight, minConfs int32,
|
|
label string) (*wire.MsgTx, error) {
|
|
|
|
tx, err := r.WalletController.SendOutputs(
|
|
outputs, feeRate, minConfs, label,
|
|
)
|
|
if err != nil && err != btcwallet.ErrTxUnsigned {
|
|
return nil, err
|
|
}
|
|
if err == nil {
|
|
// This shouldn't happen since our wallet controller is watch-
|
|
// only and can't sign the TX.
|
|
return tx, nil
|
|
}
|
|
|
|
// We know at this point that we only have inputs from our own wallet.
|
|
// So we can just compute the input script using the remote signer.
|
|
signDesc := input.SignDescriptor{
|
|
HashType: txscript.SigHashAll,
|
|
SigHashes: txscript.NewTxSigHashes(tx),
|
|
}
|
|
for i, txIn := range tx.TxIn {
|
|
// We can only sign this input if it's ours, so we'll ask the
|
|
// watch-only wallet if it can map this outpoint into a coin we
|
|
// own. If not, then we can't continue because our wallet state
|
|
// is out of sync.
|
|
info, err := r.coinFromOutPoint(txIn.PreviousOutPoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error looking up utxo: %v", err)
|
|
}
|
|
|
|
// Now that we know the input is ours, we'll populate the
|
|
// signDesc with the per input unique information.
|
|
signDesc.Output = &wire.TxOut{
|
|
Value: info.Value,
|
|
PkScript: info.PkScript,
|
|
}
|
|
signDesc.InputIndex = i
|
|
|
|
// Finally, we'll sign the input as is, and populate the input
|
|
// with the witness and sigScript (if needed).
|
|
inputScript, err := r.ComputeInputScript(tx, &signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
txIn.SignatureScript = inputScript.SigScript
|
|
txIn.Witness = inputScript.Witness
|
|
}
|
|
|
|
return tx, r.WalletController.PublishTransaction(tx, label)
|
|
}
|
|
|
|
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
|
|
// declared and tries to sign all inputs that belong to the specified account.
|
|
// Lnd must be the last signer of the transaction. That means, if there are any
|
|
// unsigned non-witness inputs or inputs without UTXO information attached or
|
|
// inputs without witness data that do not belong to lnd's wallet, this method
|
|
// will fail. If no error is returned, the PSBT is ready to be extracted and the
|
|
// final TX within to be broadcast.
|
|
//
|
|
// NOTE: This method does NOT publish the transaction after it's been
|
|
// finalized successfully.
|
|
//
|
|
// This is a part of the WalletController interface.
|
|
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, accountName string) error {
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
var buf bytes.Buffer
|
|
if err := packet.Serialize(&buf); err != nil {
|
|
return fmt.Errorf("error serializing PSBT: %v", err)
|
|
}
|
|
|
|
resp, err := r.walletClient.FinalizePsbt(
|
|
ctxt, &walletrpc.FinalizePsbtRequest{
|
|
FundedPsbt: buf.Bytes(),
|
|
Account: accountName,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error finalizing PSBT in remote signer "+
|
|
"instance: %v", err)
|
|
}
|
|
|
|
signedPacket, err := psbt.NewFromRawBytes(
|
|
bytes.NewReader(resp.SignedPsbt), false,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing signed PSBT: %v", err)
|
|
}
|
|
|
|
// The caller expects the packet to be modified instead of a new
|
|
// instance to be returned. So we just overwrite all fields in the
|
|
// original packet.
|
|
packet.UnsignedTx = signedPacket.UnsignedTx
|
|
packet.Inputs = signedPacket.Inputs
|
|
packet.Outputs = signedPacket.Outputs
|
|
packet.Unknowns = signedPacket.Unknowns
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeriveNextKey attempts to derive the *next* key within the key family
|
|
// (account in BIP43) specified. This method should return the next external
|
|
// child within this branch.
|
|
//
|
|
// NOTE: This method is part of the keychain.KeyRing interface.
|
|
func (r *RPCKeyRing) DeriveNextKey(
|
|
keyFam keychain.KeyFamily) (keychain.KeyDescriptor, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
// We need to keep the local and remote wallet in sync. That's why we
|
|
// first attempt to also derive the next key on the remote wallet.
|
|
remoteDesc, err := r.walletClient.DeriveNextKey(ctxt, &walletrpc.KeyReq{
|
|
KeyFamily: int32(keyFam),
|
|
})
|
|
if err != nil {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on remote signer instance: %v", err)
|
|
}
|
|
|
|
localDesc, err := r.watchOnlyKeyRing.DeriveNextKey(keyFam)
|
|
if err != nil {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on local wallet instance: %v", err)
|
|
}
|
|
|
|
// We never know if the administrator of the remote signing wallet does
|
|
// manual calls to next address or whatever. So we cannot be certain
|
|
// that we're always fully in sync. But as long as our local index is
|
|
// lower or equal to the remote index we know the remote wallet should
|
|
// have all keys we have locally. Only if the remote wallet falls behind
|
|
// the local we might have problems that the remote wallet won't know
|
|
// outputs we're giving it to sign.
|
|
if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on remote signer instance, derived index %d was "+
|
|
"lower than local index %d", remoteDesc.KeyLoc.KeyIndex,
|
|
localDesc.Index)
|
|
}
|
|
|
|
return localDesc, nil
|
|
}
|
|
|
|
// DeriveKey attempts to derive an arbitrary key specified by the passed
|
|
// KeyLocator. This may be used in several recovery scenarios, or when manually
|
|
// rotating something like our current default node key.
|
|
//
|
|
// NOTE: This method is part of the keychain.KeyRing interface.
|
|
func (r *RPCKeyRing) DeriveKey(
|
|
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
// We need to keep the local and remote wallet in sync. That's why we
|
|
// first attempt to also derive the same key on the remote wallet.
|
|
remoteDesc, err := r.walletClient.DeriveKey(ctxt, &signrpc.KeyLocator{
|
|
KeyFamily: int32(keyLoc.Family),
|
|
KeyIndex: int32(keyLoc.Index),
|
|
})
|
|
if err != nil {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on remote signer instance: %v", err)
|
|
}
|
|
|
|
localDesc, err := r.watchOnlyKeyRing.DeriveKey(keyLoc)
|
|
if err != nil {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on local wallet instance: %v", err)
|
|
}
|
|
|
|
// We never know if the administrator of the remote signing wallet does
|
|
// manual calls to next address or whatever. So we cannot be certain
|
|
// that we're always fully in sync. But as long as our local index is
|
|
// lower or equal to the remote index we know the remote wallet should
|
|
// have all keys we have locally. Only if the remote wallet falls behind
|
|
// the local we might have problems that the remote wallet won't know
|
|
// outputs we're giving it to sign.
|
|
if uint32(remoteDesc.KeyLoc.KeyIndex) < localDesc.Index {
|
|
return keychain.KeyDescriptor{}, fmt.Errorf("error deriving "+
|
|
"key on remote signer instance, derived index %d was "+
|
|
"lower than local index %d", remoteDesc.KeyLoc.KeyIndex,
|
|
localDesc.Index)
|
|
}
|
|
|
|
return localDesc, nil
|
|
}
|
|
|
|
// ECDH performs a scalar multiplication (ECDH-like operation) between the
|
|
// target key descriptor and remote public key. The output returned will be the
|
|
// sha256 of the resulting shared point serialized in compressed format. If k is
|
|
// our private key, and P is the public key, we perform the following operation:
|
|
//
|
|
// sx := k*P
|
|
// s := sha256(sx.SerializeCompressed())
|
|
//
|
|
// NOTE: This method is part of the keychain.ECDHRing interface.
|
|
func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
|
|
pubKey *btcec.PublicKey) ([32]byte, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
key := [32]byte{}
|
|
req := &signrpc.SharedKeyRequest{
|
|
EphemeralPubkey: pubKey.SerializeCompressed(),
|
|
KeyDesc: &signrpc.KeyDescriptor{
|
|
KeyLoc: &signrpc.KeyLocator{
|
|
KeyFamily: int32(keyDesc.Family),
|
|
KeyIndex: int32(keyDesc.Index),
|
|
},
|
|
},
|
|
}
|
|
|
|
if keyDesc.Index == 0 && keyDesc.PubKey != nil {
|
|
req.KeyDesc.RawKeyBytes = keyDesc.PubKey.SerializeCompressed()
|
|
}
|
|
|
|
resp, err := r.signerClient.DeriveSharedKey(ctxt, req)
|
|
if err != nil {
|
|
return key, err
|
|
}
|
|
|
|
copy(key[:], resp.SharedKey)
|
|
return key, nil
|
|
}
|
|
|
|
// SignMessage attempts to sign a target message with the private key described
|
|
// in the key locator. If the target private key is unable to be found, then an
|
|
// error will be returned. The actual digest signed is the single or double
|
|
// SHA-256 of the passed message.
|
|
//
|
|
// NOTE: This method is part of the keychain.MessageSignerRing interface.
|
|
func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator,
|
|
msg []byte, doubleHash bool) (*btcec.Signature, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
|
|
Msg: msg,
|
|
KeyLoc: &signrpc.KeyLocator{
|
|
KeyFamily: int32(keyLoc.Family),
|
|
KeyIndex: int32(keyLoc.Index),
|
|
},
|
|
DoubleHash: doubleHash,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing raw signature: %v", err)
|
|
}
|
|
return wireSig.ToSignature()
|
|
}
|
|
|
|
// SignMessageCompact signs the given message, single or double SHA256 hashing
|
|
// it first, with the private key described in the key locator and returns the
|
|
// signature in the compact, public key recoverable format.
|
|
//
|
|
// NOTE: This method is part of the keychain.MessageSignerRing interface.
|
|
func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator,
|
|
msg []byte, doubleHash bool) ([]byte, error) {
|
|
|
|
if keyLoc.Family != keychain.KeyFamilyNodeKey {
|
|
return nil, fmt.Errorf("error compact signing with key "+
|
|
"locator %v, can only sign with node key", keyLoc)
|
|
}
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
resp, err := r.signerClient.SignMessage(ctxt, &signrpc.SignMessageReq{
|
|
Msg: msg,
|
|
KeyLoc: &signrpc.KeyLocator{
|
|
KeyFamily: int32(keyLoc.Family),
|
|
KeyIndex: int32(keyLoc.Index),
|
|
},
|
|
DoubleHash: doubleHash,
|
|
CompactSig: true,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The signature in the response is zbase32 encoded, so we need to
|
|
// decode it before returning.
|
|
return resp.Signature, nil
|
|
}
|
|
|
|
// DerivePrivKey attempts to derive the private key that corresponds to the
|
|
// passed key descriptor. If the public key is set, then this method will
|
|
// perform an in-order scan over the key set, with a max of MaxKeyRangeScan
|
|
// keys. In order for this to work, the caller MUST set the KeyFamily within the
|
|
// partially populated KeyLocator.
|
|
//
|
|
// NOTE: This method is part of the keychain.SecretKeyRing interface.
|
|
func (r *RPCKeyRing) DerivePrivKey(_ keychain.KeyDescriptor) (*btcec.PrivateKey,
|
|
error) {
|
|
|
|
// This operation is not supported with remote signing. There should be
|
|
// no need for invoking this method unless a channel backup (SCB) file
|
|
// for pre-0.13.0 channels are attempted to be restored. In that case
|
|
// it is recommended to restore the channels using a node with the full
|
|
// seed available.
|
|
return nil, ErrRemoteSigningPrivateKeyNotAvailable
|
|
}
|
|
|
|
// SignOutputRaw generates a signature for the passed transaction
|
|
// according to the data within the passed SignDescriptor.
|
|
//
|
|
// NOTE: The resulting signature should be void of a sighash byte.
|
|
//
|
|
// NOTE: This method is part of the input.Signer interface.
|
|
func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
|
|
signDesc *input.SignDescriptor) (input.Signature, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
rpcSignReq, err := toRPCSignReq(tx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := r.signerClient.SignOutputRaw(ctxt, rpcSignReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return btcec.ParseDERSignature(resp.RawSigs[0], btcec.S256())
|
|
}
|
|
|
|
// ComputeInputScript generates a complete InputIndex for the passed
|
|
// transaction with the signature as defined within the passed
|
|
// SignDescriptor. This method should be capable of generating the
|
|
// proper input script for both regular p2wkh output and p2wkh outputs
|
|
// nested within a regular p2sh output.
|
|
//
|
|
// NOTE: This method will ignore any tweak parameters set within the
|
|
// passed SignDescriptor as it assumes a set of typical script
|
|
// templates (p2wkh, np2wkh, etc).
|
|
//
|
|
// NOTE: This method is part of the input.Signer interface.
|
|
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
|
|
signDesc *input.SignDescriptor) (*input.Script, error) {
|
|
|
|
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
|
|
defer cancel()
|
|
|
|
rpcSignReq, err := toRPCSignReq(tx, signDesc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := r.signerClient.ComputeInputScript(ctxt, rpcSignReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &input.Script{
|
|
Witness: resp.InputScripts[0].Witness,
|
|
SigScript: resp.InputScripts[0].SigScript,
|
|
}, nil
|
|
}
|
|
|
|
// coinFromOutPoint attempts to locate details pertaining to a coin based on
|
|
// its outpoint. If the coin isn't under the control of the backing watch-only
|
|
// wallet, then an error is returned.
|
|
func (r *RPCKeyRing) coinFromOutPoint(op wire.OutPoint) (*chanfunding.Coin,
|
|
error) {
|
|
|
|
inputInfo, err := r.WalletController.FetchInputInfo(&op)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &chanfunding.Coin{
|
|
TxOut: wire.TxOut{
|
|
Value: int64(inputInfo.Value),
|
|
PkScript: inputInfo.PkScript,
|
|
},
|
|
OutPoint: inputInfo.OutPoint,
|
|
}, nil
|
|
}
|
|
|
|
// toRPCSignReq converts the given raw transaction and sign descriptors into
|
|
// their corresponding RPC counterparts.
|
|
func toRPCSignReq(tx *wire.MsgTx,
|
|
signDesc *input.SignDescriptor) (*signrpc.SignReq, error) {
|
|
|
|
if signDesc.Output == nil {
|
|
return nil, fmt.Errorf("need output to sign")
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tx.Serialize(&buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rpcSignDesc := &signrpc.SignDescriptor{
|
|
KeyDesc: &signrpc.KeyDescriptor{
|
|
KeyLoc: &signrpc.KeyLocator{
|
|
KeyFamily: int32(signDesc.KeyDesc.Family),
|
|
KeyIndex: int32(signDesc.KeyDesc.Index),
|
|
},
|
|
},
|
|
SingleTweak: signDesc.SingleTweak,
|
|
WitnessScript: signDesc.WitnessScript,
|
|
Output: &signrpc.TxOut{
|
|
Value: signDesc.Output.Value,
|
|
PkScript: signDesc.Output.PkScript,
|
|
},
|
|
Sighash: uint32(signDesc.HashType),
|
|
InputIndex: int32(signDesc.InputIndex),
|
|
}
|
|
|
|
if signDesc.KeyDesc.PubKey != nil {
|
|
rpcSignDesc.KeyDesc.RawKeyBytes =
|
|
signDesc.KeyDesc.PubKey.SerializeCompressed()
|
|
}
|
|
if signDesc.DoubleTweak != nil {
|
|
rpcSignDesc.DoubleTweak = signDesc.DoubleTweak.Serialize()
|
|
}
|
|
|
|
return &signrpc.SignReq{
|
|
RawTxBytes: buf.Bytes(),
|
|
SignDescs: []*signrpc.SignDescriptor{rpcSignDesc},
|
|
}, nil
|
|
}
|
|
|
|
// toRPCAddrType converts the given address type to its RPC counterpart.
|
|
func toRPCAddrType(addrType *waddrmgr.AddressType) (walletrpc.AddressType,
|
|
error) {
|
|
|
|
if addrType == nil {
|
|
return walletrpc.AddressType_UNKNOWN, nil
|
|
}
|
|
|
|
switch *addrType {
|
|
case waddrmgr.WitnessPubKey:
|
|
return walletrpc.AddressType_WITNESS_PUBKEY_HASH, nil
|
|
|
|
case waddrmgr.NestedWitnessPubKey:
|
|
return walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH,
|
|
nil
|
|
|
|
default:
|
|
return 0, fmt.Errorf("unhandled address type %v", *addrType)
|
|
}
|
|
}
|
|
|
|
// connectRPC tries to establish an RPC connection to the given host:port with
|
|
// the supplied certificate and macaroon.
|
|
func connectRPC(hostPort, tlsCertPath, macaroonPath string) (*grpc.ClientConn,
|
|
error) {
|
|
|
|
certBytes, err := ioutil.ReadFile(tlsCertPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading TLS cert file %v: %v",
|
|
tlsCertPath, err)
|
|
}
|
|
|
|
cp := x509.NewCertPool()
|
|
if !cp.AppendCertsFromPEM(certBytes) {
|
|
return nil, fmt.Errorf("credentials: failed to append " +
|
|
"certificate")
|
|
}
|
|
|
|
macBytes, err := ioutil.ReadFile(macaroonPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading macaroon file %v: %v",
|
|
macaroonPath, err)
|
|
}
|
|
mac := &macaroon.Macaroon{}
|
|
if err := mac.UnmarshalBinary(macBytes); err != nil {
|
|
return nil, fmt.Errorf("error decoding macaroon: %v", err)
|
|
}
|
|
|
|
macCred, err := macaroons.NewMacaroonCredential(mac)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating creds: %v", err)
|
|
}
|
|
|
|
opts := []grpc.DialOption{
|
|
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(
|
|
cp, "",
|
|
)),
|
|
grpc.WithPerRPCCredentials(macCred),
|
|
}
|
|
conn, err := grpc.Dial(hostPort, opts...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to connect to RPC server: %v",
|
|
err)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|