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) } }