lnd/lnwallet/rpcwallet/rpcwallet.go
Olaoluwa Osuntokun 8405590152
lnrpc/signrpc+lnwallet/rpcwallet: expose local nonces over RPC
In this commit, we update the set of protos to accept the local secret
nonces over RPC. This is actually a 97 byte value, as it includes the
two 32 byte nonces, as well as the 33 byte value of the public key of
the signer.

This is needed in order to be able to open taproot channels over the RPC
interface.
2023-09-18 11:42:15 -07:00

1367 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,
localNonces *musig2.Nonces) (*input.MuSig2SessionInfo, error) {
apiVersion, err := signrpc.MarshalMuSig2Version(bipVersion)
if err != nil {
return nil, err
}
// 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,
}
}
if localNonces != nil {
req.PregeneratedLocalNonce = localNonces.SecNonce[:]
}
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)
}
}