lnd/lnwallet/rpcwallet/rpcwallet.go
2023-08-22 16:34:47 -07:00

1365 lines
46 KiB
Go

package rpcwallet
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"time"
"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/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
basewallet "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/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
)
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
netParams *chaincfg.Params
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,
netParams *chaincfg.Params) (*RPCKeyRing, error) {
rpcConn, err := connectRPC(
remoteSigner.RPCHost, remoteSigner.TLSCertPath,
remoteSigner.MacaroonPath, remoteSigner.Timeout,
)
if err != nil {
return nil, fmt.Errorf("error connecting to the remote "+
"signing node through RPC: %v", err)
}
return &RPCKeyRing{
WalletController: watchOnlyWalletController,
watchOnlyKeyRing: watchOnlyKeyRing,
netParams: netParams,
rpcTimeout: remoteSigner.Timeout,
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) {
return r.WalletController.NewAddress(addrType, change, account)
}
// 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.
//
// NOTE: This is a part of the WalletController interface.
//
// NOTE: This method only signs with BIP49/84 keys.
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 != basewallet.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.
outputFetcher := lnwallet.NewWalletPrevOutputFetcher(r.WalletController)
for i, txIn := range tx.TxIn {
signDesc := input.SignDescriptor{
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(
tx, outputFetcher,
),
PrevOutputFetcher: outputFetcher,
}
// 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.WalletController.FetchInputInfo(
&txIn.PreviousOutPoint,
)
if err != nil {
return nil, fmt.Errorf("error looking up utxo: %v", err)
}
if txscript.IsPayToTaproot(info.PkScript) {
signDesc.HashType = txscript.SigHashDefault
}
// Now that we know the input is ours, we'll populate the
// signDesc with the per input unique information.
signDesc.Output = &wire.TxOut{
Value: int64(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)
}
// SignPsbt expects a partial transaction with all inputs and outputs fully
// declared and tries to sign all unsigned inputs that have all required fields
// (UTXO information, BIP32 derivation information, witness or sig scripts) set.
// If no error is returned, the PSBT is ready to be given to the next signer or
// to be finalized if lnd was the last signer.
//
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
// perform any other tasks (such as coin selection, UTXO locking or
// input/output/fee value validation, PSBT finalization). Any input that is
// incomplete will be skipped.
func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) ([]uint32, error) {
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
var buf bytes.Buffer
if err := packet.Serialize(&buf); err != nil {
return nil, fmt.Errorf("error serializing PSBT: %v", err)
}
resp, err := r.walletClient.SignPsbt(ctxt, &walletrpc.SignPsbtRequest{
FundedPsbt: buf.Bytes(),
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
}
signedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(resp.SignedPsbt), false,
)
if err != nil {
return nil, 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 resp.SignedInputs, nil
}
// 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.
//
// NOTE: This is a part of the WalletController interface.
//
// NOTE: We need to overwrite this method because we need to redirect the call
// to ComputeInputScript to the RPC key ring's implementation. If we forward
// the call to the default WalletController implementation, we get an error
// since that wallet is watch-only. If we forward the call to the remote signer,
// we get an error because the signer doesn't know the UTXO information required
// in ComputeInputScript.
//
// TODO(guggero): Refactor btcwallet to accept ComputeInputScript as a function
// parameter in FinalizePsbt so we can get rid of this code duplication.
func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
// Let's check that this is actually something we can and want to sign.
// We need at least one input and one output. In addition each
// input needs nonWitness Utxo or witness Utxo data specified.
err := psbt.InputsReadyToSign(packet)
if err != nil {
return err
}
// Go through each input that doesn't have final witness data attached
// to it already and try to sign it. We do expect that we're the last
// ones to sign. If there is any input without witness data that we
// cannot sign because it's not our UTXO, this will be a hard failure.
tx := packet.UnsignedTx
prevOutFetcher := basewallet.PsbtPrevOutputFetcher(packet)
sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
for idx, txIn := range tx.TxIn {
in := packet.Inputs[idx]
// We can only sign if we have UTXO information available. We
// can just continue here as a later step will fail with a more
// precise error message.
if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
continue
}
// Skip this input if it's got final witness data attached.
if len(in.FinalScriptWitness) > 0 {
continue
}
// We can only sign this input if it's ours, so we try to map it
// to a coin we own. If we can't, then we'll continue as it
// isn't our input.
utxo, err := r.FetchInputInfo(&txIn.PreviousOutPoint)
if err != nil {
continue
}
fullTx := utxo.PrevTx
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{},
Output: &wire.TxOut{
Value: int64(utxo.Value),
PkScript: utxo.PkScript,
},
HashType: in.SighashType,
SigHashes: sigHashes,
InputIndex: idx,
PrevOutputFetcher: prevOutFetcher,
}
// Find out what UTXO we are signing. Wallets _should_ always
// provide the full non-witness UTXO for segwit v0.
var signOutput *wire.TxOut
if in.NonWitnessUtxo != nil {
prevIndex := txIn.PreviousOutPoint.Index
signOutput = in.NonWitnessUtxo.TxOut[prevIndex]
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
if fullTx.TxHash() != txIn.PreviousOutPoint.Hash {
return fmt.Errorf("found UTXO tx %v but it "+
"doesn't match PSBT's input %v",
fullTx.TxHash(),
txIn.PreviousOutPoint.Hash)
}
}
// Fall back to witness UTXO only for older wallets.
if in.WitnessUtxo != nil {
signOutput = in.WitnessUtxo
if !psbt.TxOutsEqual(signDesc.Output, signOutput) {
return fmt.Errorf("found UTXO %#v but it "+
"doesn't match PSBT's input %v",
signDesc.Output, signOutput)
}
}
// Do the actual signing in ComputeInputScript which in turn
// will invoke the remote signer.
script, err := r.ComputeInputScript(tx, signDesc)
if err != nil {
return fmt.Errorf("error computing input script for "+
"input %d: %v", idx, err)
}
// Serialize the witness format from the stack representation to
// the wire representation.
var witnessBytes bytes.Buffer
err = psbt.WriteTxWitness(&witnessBytes, script.Witness)
if err != nil {
return fmt.Errorf("error serializing witness: %v", err)
}
packet.Inputs[idx].FinalScriptWitness = witnessBytes.Bytes()
packet.Inputs[idx].FinalScriptSig = script.SigScript
}
// Make sure the PSBT itself thinks it's finalized and ready to be
// broadcast.
err = psbt.MaybeFinalizeAll(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %v", err)
}
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) {
return r.watchOnlyKeyRing.DeriveNextKey(keyFam)
}
// 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) {
return r.watchOnlyKeyRing.DeriveKey(keyLoc)
}
// 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 {
considerShutdown(err)
return key, fmt.Errorf("error deriving shared key in remote "+
"signer instance: %v", 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) (*ecdsa.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 {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
wireSig, err := lnwire.NewSigFromECDSARawSignature(resp.Signature)
if err != nil {
return nil, fmt.Errorf("unable to create sig: %w", err)
}
sig, err := wireSig.ToSignature()
if err != nil {
return nil, fmt.Errorf("unable to parse sig: %w", err)
}
ecdsaSig, ok := sig.(*ecdsa.Signature)
if !ok {
return nil, fmt.Errorf("unexpected signature type: %T", sig)
}
return ecdsaSig, nil
}
// 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 {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
// The signature in the response is zbase32 encoded, so we need to
// decode it before returning.
return resp.Signature, nil
}
// SignMessageSchnorr 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) SignMessageSchnorr(keyLoc keychain.KeyLocator,
msg []byte, doubleHash bool, taprootTweak []byte) (*schnorr.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,
SchnorrSig: true,
SchnorrSigTapTweak: taprootTweak,
})
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
sigParsed, err := schnorr.ParseSignature(resp.Signature)
if err != nil {
return nil, fmt.Errorf("can't parse schnorr signature: %v",
err)
}
return sigParsed, 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.
//
// NOTE: This method only signs with BIP1017 (internal) keys!
func (r *RPCKeyRing) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
// Forward the call to the remote signing instance. This call is only
// ever called for signing witness (p2pkh or p2wsh) inputs and never
// nested witness inputs, so the sigScript is always nil.
return r.remoteSign(tx, signDesc, nil)
}
// 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, BIP0086 p2tr, etc).
//
// NOTE: This method is part of the input.Signer interface.
func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (*input.Script, error) {
addr, witnessProgram, sigScript, err := r.WalletController.ScriptForOutput(
signDesc.Output,
)
if err != nil {
return nil, err
}
signDesc.WitnessScript = witnessProgram
// If this is a p2tr address, then it must be a BIP0086 key spend if we
// are coming through this path (instead of SignOutputRaw).
switch addr.AddrType() {
case waddrmgr.TaprootPubKey:
signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod
signDesc.WitnessScript = nil
sig, err := r.remoteSign(tx, signDesc, nil)
if err != nil {
return nil, fmt.Errorf("error signing with remote"+
"instance: %v", err)
}
rawSig := sig.Serialize()
if signDesc.HashType != txscript.SigHashDefault {
rawSig = append(rawSig, byte(signDesc.HashType))
}
return &input.Script{
Witness: wire.TxWitness{
rawSig,
},
}, nil
case waddrmgr.TaprootScript:
return nil, fmt.Errorf("computing input script for taproot " +
"script address not supported")
}
// Let's give the TX to the remote instance now, so it can sign the
// input.
sig, err := r.remoteSign(tx, signDesc, witnessProgram)
if err != nil {
return nil, fmt.Errorf("error signing with remote instance: %v",
err)
}
// ComputeInputScript currently is only used for P2WKH and NP2WKH
// addresses. So the last item on the stack is always the compressed
// public key.
return &input.Script{
Witness: wire.TxWitness{
append(sig.Serialize(), byte(signDesc.HashType)),
addr.PubKey().SerializeCompressed(),
},
SigScript: sigScript,
}, nil
}
// 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 (r *RPCKeyRing) MuSig2CreateSession(bipVersion input.MuSig2Version,
keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey,
tweaks *input.MuSig2Tweaks, otherNonces [][musig2.PubNonceSize]byte,
_ ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) {
apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
if err != nil {
return nil, err
}
// TODO(roasbeef): add protos to specify session options
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2SessionRequest{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLoc.Family),
KeyIndex: int32(keyLoc.Index),
},
AllSignerPubkeys: make([][]byte, len(pubKeys)),
Tweaks: make(
[]*signrpc.TweakDesc, len(tweaks.GenericTweaks),
),
OtherSignerPublicNonces: make([][]byte, len(otherNonces)),
Version: apiVersion,
}
for idx, pubKey := range pubKeys {
switch bipVersion {
case input.MuSig2Version040:
req.AllSignerPubkeys[idx] = schnorr.SerializePubKey(
pubKey,
)
case input.MuSig2Version100RC2:
req.AllSignerPubkeys[idx] = pubKey.SerializeCompressed()
}
}
for idx, genericTweak := range tweaks.GenericTweaks {
req.Tweaks[idx] = &signrpc.TweakDesc{
Tweak: genericTweak.Tweak[:],
IsXOnly: genericTweak.IsXOnly,
}
}
for idx, nonce := range otherNonces {
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
copy(req.OtherSignerPublicNonces[idx], nonce[:])
}
if tweaks.HasTaprootTweak() {
req.TaprootTweak = &signrpc.TaprootTweakDesc{
KeySpendOnly: tweaks.TaprootBIP0086Tweak,
ScriptRoot: tweaks.TaprootTweak,
}
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2CreateSession(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error creating MuSig2 session in "+
"remote signer instance: %v", err)
}
// De-Serialize all the info back into our native struct.
info := &input.MuSig2SessionInfo{
Version: bipVersion,
TaprootTweak: tweaks.HasTaprootTweak(),
HaveAllNonces: resp.HaveAllNonces,
}
copy(info.SessionID[:], resp.SessionId)
copy(info.PublicNonce[:], resp.LocalPublicNonces)
info.CombinedKey, err = schnorr.ParsePubKey(resp.CombinedKey)
if err != nil {
return nil, fmt.Errorf("error parsing combined key: %v", err)
}
if tweaks.HasTaprootTweak() {
info.TaprootInternalKey, err = schnorr.ParsePubKey(
resp.TaprootInternalKey,
)
if err != nil {
return nil, fmt.Errorf("error parsing internal key: %v",
err)
}
}
return info, 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 (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
pubNonces [][musig2.PubNonceSize]byte) (bool, error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2RegisterNoncesRequest{
SessionId: sessionID[:],
OtherSignerPublicNonces: make([][]byte, len(pubNonces)),
}
for idx, nonce := range pubNonces {
req.OtherSignerPublicNonces[idx] = make([]byte, len(nonce))
copy(req.OtherSignerPublicNonces[idx], nonce[:])
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req)
if err != nil {
considerShutdown(err)
return false, fmt.Errorf("error registering MuSig2 nonces in "+
"remote signer instance: %v", err)
}
return resp.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 (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID,
msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2SignRequest{
SessionId: sessionID[:],
MessageDigest: msg[:],
Cleanup: cleanUp,
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2Sign(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing MuSig2 session in "+
"remote signer instance: %v", err)
}
partialSig, err := input.DeserializePartialSignature(
resp.LocalPartialSignature,
)
if err != nil {
return nil, fmt.Errorf("error parsing partial signature from "+
"remote signer: %v", err)
}
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 (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool,
error) {
// We need to serialize all data for the RPC call. We can do that by
// putting everything directly into the request struct.
req := &signrpc.MuSig2CombineSigRequest{
SessionId: sessionID[:],
OtherPartialSignatures: make([][]byte, len(partialSigs)),
}
for idx, partialSig := range partialSigs {
rawSig, err := input.SerializePartialSignature(partialSig)
if err != nil {
return nil, false, fmt.Errorf("error serializing "+
"partial signature: %v", err)
}
req.OtherPartialSignatures[idx] = rawSig[:]
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
resp, err := r.signerClient.MuSig2CombineSig(ctxt, req)
if err != nil {
considerShutdown(err)
return nil, false, fmt.Errorf("error combining MuSig2 "+
"signatures in remote signer instance: %v", err)
}
// The final signature is only available when we have all the other
// partial signatures from all participants.
if !resp.HaveAllSignatures {
return nil, resp.HaveAllSignatures, nil
}
finalSig, err := schnorr.ParseSignature(resp.FinalSignature)
if err != nil {
return nil, false, fmt.Errorf("error parsing final signature: "+
"%v", err)
}
return finalSig, resp.HaveAllSignatures, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
req := &signrpc.MuSig2CleanupRequest{
SessionId: sessionID[:],
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
_, err := r.signerClient.MuSig2Cleanup(ctxt, req)
if err != nil {
considerShutdown(err)
return fmt.Errorf("error cleaning up MuSig2 session in remote "+
"signer instance: %v", err)
}
return nil
}
// remoteSign signs the input specified in signDesc of the given transaction tx
// using the remote signing instance.
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
sigScript []byte) (input.Signature, error) {
packet, err := packetFromTx(tx)
if err != nil {
return nil, fmt.Errorf("error converting TX into PSBT: %v", err)
}
// We need to add witness information for all inputs! Otherwise, we'll
// have a problem when attempting to sign a taproot input!
for idx := range packet.Inputs {
// Skip the input we're signing for, that will get a special
// treatment later on.
if idx == signDesc.InputIndex {
continue
}
txIn := tx.TxIn[idx]
info, err := r.WalletController.FetchInputInfo(
&txIn.PreviousOutPoint,
)
if err != nil {
// Maybe we have an UTXO in the previous output fetcher?
if signDesc.PrevOutputFetcher != nil {
utxo := signDesc.PrevOutputFetcher.FetchPrevOutput(
txIn.PreviousOutPoint,
)
if utxo != nil && utxo.Value != 0 &&
len(utxo.PkScript) > 0 {
packet.Inputs[idx].WitnessUtxo = utxo
continue
}
}
log.Warnf("No UTXO info found for index %d "+
"(prev_outpoint=%v), won't be able to sign "+
"for taproot output!", idx,
txIn.PreviousOutPoint)
continue
}
packet.Inputs[idx].WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
}
// Catch incorrect signing input index, just in case.
if signDesc.InputIndex < 0 || signDesc.InputIndex >= len(packet.Inputs) {
return nil, fmt.Errorf("invalid input index in sign descriptor")
}
in := &packet.Inputs[signDesc.InputIndex]
txIn := tx.TxIn[signDesc.InputIndex]
// Things are a bit tricky with the sign descriptor. There basically are
// four ways to describe a key:
// 1. By public key only. To match this case both family and index
// must be set to 0.
// 2. By family and index only. To match this case the public key
// must be nil and either the family or index must be non-zero.
// 3. All values are set and locator is non-empty. To match this case
// the public key must be set and either the family or index must
// be non-zero.
// 4. All values are set and locator is empty. This is a special case
// for the very first channel ever created (with the multi-sig key
// family which is 0 and the index which is 0 as well). This looks
// identical to case 1 and will also be handled like that case.
// We only really handle case 1 and 2 here, since 3 is no problem and 4
// is identical to 1.
switch {
// Case 1: Public key only. We need to find out the derivation path for
// this public key by asking the wallet. This is only possible for our
// internal, custom 1017 scope since we know all keys derived there are
// internally stored as p2wkh addresses.
case signDesc.KeyDesc.PubKey != nil && signDesc.KeyDesc.IsEmpty():
pubKeyBytes := signDesc.KeyDesc.PubKey.SerializeCompressed()
addr, err := btcutil.NewAddressWitnessPubKeyHash(
btcutil.Hash160(pubKeyBytes), r.netParams,
)
if err != nil {
return nil, fmt.Errorf("error deriving address from "+
"public key %x: %v", pubKeyBytes, err)
}
managedAddr, err := r.AddressInfo(addr)
if err != nil {
return nil, fmt.Errorf("error fetching address info "+
"for public key %x: %v", pubKeyBytes, err)
}
pubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return nil, fmt.Errorf("address derived for public "+
"key %x is not a p2wkh address", pubKeyBytes)
}
scope, path, _ := pubKeyAddr.DerivationInfo()
if scope.Purpose != keychain.BIP0043Purpose {
return nil, fmt.Errorf("address derived for public "+
"key %x is not in custom key scope %d'",
pubKeyBytes, keychain.BIP0043Purpose)
}
// We now have all the information we need to complete our key
// locator information.
signDesc.KeyDesc.KeyLocator = keychain.KeyLocator{
Family: keychain.KeyFamily(path.InternalAccount),
Index: path.Index,
}
// Case 2: Family and index only. This case is easy, we can just go
// ahead and derive the public key from the family and index and then
// supply that information in the BIP32 derivation field.
case signDesc.KeyDesc.PubKey == nil && !signDesc.KeyDesc.IsEmpty():
fullDesc, err := r.watchOnlyKeyRing.DeriveKey(
signDesc.KeyDesc.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("error deriving key with "+
"family %d and index %d from the watch-only "+
"wallet: %v",
signDesc.KeyDesc.KeyLocator.Family,
signDesc.KeyDesc.KeyLocator.Index, err)
}
signDesc.KeyDesc.PubKey = fullDesc.PubKey
}
// Make sure we actually know about the input. We either have been
// watching the UTXO on-chain or we have been given all the required
// info in the sign descriptor.
info, err := r.WalletController.FetchInputInfo(&txIn.PreviousOutPoint)
switch {
// No error, we do have the full UTXO and derivation info available.
case err == nil:
in.WitnessUtxo = &wire.TxOut{
Value: int64(info.Value),
PkScript: info.PkScript,
}
in.NonWitnessUtxo = info.PrevTx
in.Bip32Derivation = []*psbt.Bip32Derivation{info.Derivation}
// The wallet doesn't know about this UTXO, so it's probably a TX that
// we haven't published yet (e.g. a channel funding TX). So we need to
// assemble everything from the sign descriptor. We won't be able to
// supply a non-witness UTXO (=full TX of the input being spent) in this
// case. That is no problem if the signing instance is another lnd
// instance since we don't require it for pure witness inputs. But a
// hardware wallet might require it for security reasons.
case signDesc.KeyDesc.PubKey != nil && signDesc.Output != nil:
in.WitnessUtxo = signDesc.Output
in.Bip32Derivation = []*psbt.Bip32Derivation{{
Bip32Path: []uint32{
keychain.BIP0043Purpose +
hdkeychain.HardenedKeyStart,
r.netParams.HDCoinType +
hdkeychain.HardenedKeyStart,
uint32(signDesc.KeyDesc.Family) +
hdkeychain.HardenedKeyStart,
0,
signDesc.KeyDesc.Index,
},
PubKey: signDesc.KeyDesc.PubKey.SerializeCompressed(),
}}
// We need to specify a pk script in the witness UTXO, otherwise
// the field becomes invalid when serialized as a PSBT. To avoid
// running into a generic "Invalid PSBT serialization format"
// error later, we return a more descriptive error now.
if len(in.WitnessUtxo.PkScript) == 0 {
return nil, fmt.Errorf("error assembling UTXO " +
"information, output not known to wallet and " +
"no UTXO pk script provided in sign descriptor")
}
default:
return nil, fmt.Errorf("error assembling UTXO information, "+
"wallet returned err='%v' and sign descriptor is "+
"incomplete", err)
}
// Assemble all other information about the input we have.
in.RedeemScript = sigScript
in.SighashType = signDesc.HashType
in.WitnessScript = signDesc.WitnessScript
if len(signDesc.SingleTweak) > 0 {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakSingle,
Value: signDesc.SingleTweak,
})
}
if signDesc.DoubleTweak != nil {
in.Unknowns = append(in.Unknowns, &psbt.Unknown{
Key: btcwallet.PsbtKeyTypeInputSignatureTweakDouble,
Value: signDesc.DoubleTweak.Serialize(),
})
}
// Add taproot specific fields.
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// The key identifying factor for a key spend is that we don't
// provide any leaf hashes to signal we want a signature for the
// key spend path (with the internal key).
d := in.Bip32Derivation[0]
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
// The x-only public key is just our compressed public
// key without the first byte (type/parity).
XOnlyPubKey: d.PubKey[1:],
LeafHashes: nil,
MasterKeyFingerprint: d.MasterKeyFingerprint,
Bip32Path: d.Bip32Path,
}}
// If this is a BIP0086 key spend then the tap tweak is empty,
// otherwise it's set to the Taproot root hash.
in.TaprootMerkleRoot = signDesc.TapTweak
case input.TaprootScriptSpendSignMethod:
// The script spend path is a bit more involved when doing it
// through the PSBT method. We need to specify the leaf hash
// that the signer should sign for.
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: signDesc.WitnessScript,
}
leafHash := leaf.TapHash()
d := in.Bip32Derivation[0]
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
XOnlyPubKey: d.PubKey[1:],
LeafHashes: [][]byte{leafHash[:]},
MasterKeyFingerprint: d.MasterKeyFingerprint,
Bip32Path: d.Bip32Path,
}}
// We also need to supply a control block. But because we don't
// know the internal key nor the merkle proofs (both is not
// supplied through the SignOutputRaw RPC) and is technically
// not really needed by the signer (since we only want a
// signature, the full witness stack is assembled by the caller
// of this RPC), we can get by with faking certain information
// that we don't have.
fakeInternalKey, _ := btcec.ParsePubKey(d.PubKey)
fakeKeyIsOdd := d.PubKey[0] == input.PubKeyFormatCompressedOdd
controlBlock := txscript.ControlBlock{
InternalKey: fakeInternalKey,
OutputKeyYIsOdd: fakeKeyIsOdd,
LeafVersion: leaf.LeafVersion,
}
blockBytes, err := controlBlock.ToBytes()
if err != nil {
return nil, fmt.Errorf("error serializing control "+
"block: %v", err)
}
in.TaprootLeafScript = []*psbt.TaprootTapLeafScript{{
ControlBlock: blockBytes,
Script: leaf.Script,
LeafVersion: leaf.LeafVersion,
}}
}
// Okay, let's sign the input by the remote signer now.
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
var buf bytes.Buffer
if err := packet.Serialize(&buf); err != nil {
return nil, fmt.Errorf("error serializing PSBT: %v", err)
}
resp, err := r.walletClient.SignPsbt(
ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()},
)
if err != nil {
considerShutdown(err)
return nil, fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
}
signedPacket, err := psbt.NewFromRawBytes(
bytes.NewReader(resp.SignedPsbt), false,
)
if err != nil {
return nil, fmt.Errorf("error parsing signed PSBT: %v", err)
}
// We expect a signature in the input now.
if signDesc.InputIndex >= len(signedPacket.Inputs) {
return nil, fmt.Errorf("remote signer returned invalid PSBT")
}
in = &signedPacket.Inputs[signDesc.InputIndex]
return extractSignature(in, signDesc.SignMethod)
}
// extractSignature attempts to extract the signature from the PSBT input,
// looking at different fields depending on the signing method that was used.
func extractSignature(in *psbt.PInput,
signMethod input.SignMethod) (input.Signature, error) {
switch signMethod {
case input.WitnessV0SignMethod:
if len(in.PartialSigs) != 1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid partial signature, wanted 1, got %d",
len(in.PartialSigs))
}
sigWithSigHash := in.PartialSigs[0]
if sigWithSigHash == nil {
return nil, fmt.Errorf("remote signer returned nil " +
"signature")
}
// The remote signer always adds the sighash type, so we need to
// account for that.
sigLen := len(sigWithSigHash.Signature)
if sigLen < ecdsa.MinSigLen+1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid partial signature: signature too "+
"short with %d bytes", sigLen)
}
// Parse the signature, but chop off the last byte which is the
// sighash type.
sig := sigWithSigHash.Signature[0 : sigLen-1]
return ecdsa.ParseDERSignature(sig)
// The type of key spend doesn't matter, the signature should be in the
// same field for both of those signing methods.
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
sigLen := len(in.TaprootKeySpendSig)
if sigLen < schnorr.SignatureSize {
return nil, fmt.Errorf("remote signer returned "+
"invalid key spend signature: signature too "+
"short with %d bytes", sigLen)
}
return schnorr.ParseSignature(
in.TaprootKeySpendSig[:schnorr.SignatureSize],
)
case input.TaprootScriptSpendSignMethod:
if len(in.TaprootScriptSpendSig) != 1 {
return nil, fmt.Errorf("remote signer returned "+
"invalid taproot script spend signature, "+
"wanted 1, got %d",
len(in.TaprootScriptSpendSig))
}
scriptSpendSig := in.TaprootScriptSpendSig[0]
if scriptSpendSig == nil {
return nil, fmt.Errorf("remote signer returned nil " +
"taproot script spend signature")
}
return schnorr.ParseSignature(scriptSpendSig.Signature)
default:
return nil, fmt.Errorf("can't extract signature, unsupported "+
"signing method: %v", signMethod)
}
}
// connectRPC tries to establish an RPC connection to the given host:port with
// the supplied certificate and macaroon.
func connectRPC(hostPort, tlsCertPath, macaroonPath string,
timeout time.Duration) (*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),
grpc.WithBlock(),
}
ctxt, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(ctxt, hostPort, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v",
err)
}
return conn, nil
}
// packetFromTx creates a PSBT from a tx that potentially already contains
// signed inputs.
func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) {
// The psbt.NewFromUnsignedTx function complains if there are any
// scripts or witness content on a TX. So we create a copy of the TX and
// nil out all the offending data, but also keep a backup around that we
// add to the PSBT afterwards.
noSigs := original.Copy()
for idx := range noSigs.TxIn {
noSigs.TxIn[idx].SignatureScript = nil
noSigs.TxIn[idx].Witness = nil
}
// With all the data that is seen as "signed", we can now create the
// empty packet.
packet, err := psbt.NewFromUnsignedTx(noSigs)
if err != nil {
return nil, err
}
var buf bytes.Buffer
for idx, txIn := range original.TxIn {
if len(txIn.SignatureScript) > 0 {
packet.Inputs[idx].FinalScriptSig = txIn.SignatureScript
}
if len(txIn.Witness) > 0 {
buf.Reset()
err = psbt.WriteTxWitness(&buf, txIn.Witness)
if err != nil {
return nil, err
}
packet.Inputs[idx].FinalScriptWitness = buf.Bytes()
}
}
return packet, nil
}
// considerShutdown inspects the error and issues a shutdown (through logging
// a critical error, which will cause the logger to issue a clean shutdown
// request) if the error looks like a connection or general availability error
// and not some application specific problem.
func considerShutdown(err error) {
statusErr, isStatusErr := status.FromError(err)
switch {
// The context attached to the client request has timed out. This can be
// due to not being able to reach the signing server, or it's taking too
// long to respond. In either case, request a shutdown.
case err == context.DeadlineExceeded:
fallthrough
// The signing server's context timed out before the client's due to
// clock skew, request a shutdown anyway.
case isStatusErr && statusErr.Code() == codes.DeadlineExceeded:
log.Critical("RPC signing timed out: %v", err)
case isStatusErr && statusErr.Code() == codes.Unavailable:
log.Critical("RPC signing server not available: %v", err)
}
}