lnd/lnrpc/signrpc/signer_server.go
Olaoluwa Osuntokun 4002caec69
config+rpc: create new generalized dynamic sub rpc server config framework
In this commit, we add the glue infrastructure to make the sub RPC
server system work properly. Our high level goal is the following: using
only the lnrpc package (with no visibility into the sub RPC servers),
the RPC server is able to find, create, run, and manage the entire set
of present and future sub RPC servers. In order to achieve this, we use
the reflect package and build tags heavily to permit a loosely coupled
configuration parsing system for the sub RPC servers.

We start with a new `subRpcServerConfigs` struct which is _always_
present.  This struct has its own group, and will house a series of
sub-configs, one for each sub RPC server. Each sub-config is actually
gated behind a build flag, and can be used to allow users on the command
line or in the config to specify arguments related to the sub-server. If
the config isn't present, then we don't attempt to parse it at all, if
it is, then that means the RPC server has been registered, and we should
parse the contents of its config.

The `subRpcServerConfigs` struct has two main methods:
`PopulateDependancies` and `FetchConfig`. The `PopulateDependancies` is
used to dynamically locate and set the config fields for each new
sub-server. As the config may not actually have any fields (if the build
flag is off), we use the reflect pacakge to determine if things are
compiled in or not, then if so, we dynamically set each of the config
parameters. The `PopulateDependancies` method implements the
`lnrpc.SubServerConfigDispatcher` interface. Our goal is to allow sub
servers to look up their actual config in this main config struct. We
achieve this by using reflect to look up the target field _as if it were
a key in a map_. If the field is found, then we check if it has any
actual attributes (it won't if the build flag is off), if it is, then we
return it as we expect it to be populated already.
2018-11-28 20:57:05 -08:00

316 lines
9.5 KiB
Go

// +build signrpc
package signrpc
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
const (
// subServerName is the name of the sub rpc server. We'll use this name
// to register ourselves, and we also require that the main
// SubServerConfigDispatcher instance recognize this as the name of the
// config file that we need.
subServerName = "SignRPC"
)
var (
// macaroonOps are the set of capabilities that our minted macaroon (if
// it doesn't already exist) will have.
macaroonOps = []bakery.Op{
{
Entity: "signer",
Action: "generate",
},
}
// macPermissions maps RPC calls to the permissions they require.
macPermissions = map[string][]bakery.Op{
"/signrpc.Signer/SignOutputRaw": {{
Entity: "signer",
Action: "generate",
}},
}
// DefaultSignerMacFilename is the default name of the signer macaroon
// that we expect to find via a file handle within the main
// configuration file in this package.
DefaultSignerMacFilename = "signer.macaroon"
)
// Server is a sub-server of the main RPC server: the signer RPC. This sub RPC
// server allows external callers to access the full signing capabilities of
// lnd. This allows callers to create custom protocols, external to lnd, even
// backed by multiple distinct lnd across independent failure domains.
type Server struct {
cfg *Config
}
// A compile time check to ensure that Server fully implements the SignerServer
// gRPC service.
var _ SignerServer = (*Server)(nil)
// fileExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// New returns a new instance of the signrpc Signer sub-server. We also return
// the set of permissions for the macaroons that we may create within this
// method. If the macaroons we need aren't found in the filepath, then we'll
// create them on start up. If we're unable to locate, or create the macaroons
// we need, then we'll return with an error.
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
// If the path of the signer macaroon wasn't generated, then we'll
// assume that it's found at the default network directory.
if cfg.SignerMacPath == "" {
cfg.SignerMacPath = filepath.Join(
cfg.NetworkDir, DefaultSignerMacFilename,
)
}
// Now that we know the full path of the signer macaroon, we can check
// to see if we need to create it or not.
macFilePath := cfg.SignerMacPath
if cfg.MacService != nil && !fileExists(macFilePath) {
log.Infof("Making macaroons for Signer RPC Server at: %v",
macFilePath)
// At this point, we know that the signer macaroon doesn't yet,
// exist, so we need to create it with the help of the main
// macaroon service.
signerMac, err := cfg.MacService.Oven.NewMacaroon(
context.Background(), bakery.LatestVersion, nil,
macaroonOps...,
)
if err != nil {
return nil, nil, err
}
signerMacBytes, err := signerMac.M().MarshalBinary()
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(macFilePath, signerMacBytes, 0644)
if err != nil {
os.Remove(macFilePath)
return nil, nil, err
}
}
signerServer := &Server{
cfg: cfg,
}
return signerServer, macPermissions, nil
}
// Start launches any helper goroutines required for the rpcServer to function.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Start() error {
return nil
}
// Stop signals any active goroutines for a graceful closure.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Stop() error {
return nil
}
// Name returns a unique string representation of the sub-server. This can be
// used to identify the sub-server and also de-duplicate them.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Name() string {
return subServerName
}
// RegisterWithRootServer will be called by the root gRPC server to direct a
// sub RPC server to register itself with the main gRPC root server. Until this
// is called, each sub-server won't be able to have
// requests routed towards it.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
// We make sure that we register it with the main gRPC server to ensure
// all our methods are routed properly.
RegisterSignerServer(grpcServer, s)
log.Debugf("Signer RPC server successfully register with root gRPC " +
"server")
return nil
}
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignReq. If we're unable to find the keys that
// correspond to the KeyLocators in the SignReq then we'll return an error.
// Additionally, if the user doesn't provide the set of required parameters, or
// provides an invalid transaction, then we'll return with an error.
//
// NOTE: The resulting signature should be void of a sighash byte.
func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, error) {
switch {
// If the client doesn't specify a transaction, then there's nothing to
// sign, so we'll exit early.
case len(in.RawTxBytes) == 0:
return nil, fmt.Errorf("a transaction to sign MUST be " +
"passed in")
// If the client doesn't tell us *how* to sign the transaction, then we
// can't sign anything, so we'll exit early.
case len(in.SignDescs) == 0:
return nil, fmt.Errorf("at least one SignDescs MUST be " +
"passed in")
}
// Now that we know we have an actual transaction to decode, we'll
// deserialize it into something that we can properly utilize.
var (
txToSign wire.MsgTx
err error
)
txReader := bytes.NewReader(in.RawTxBytes)
if err := txToSign.Deserialize(txReader); err != nil {
return nil, fmt.Errorf("unable to decode tx: %v", err)
}
sigHashCache := txscript.NewTxSigHashes(&txToSign)
log.Debugf("Generating sigs for %v inputs: ", len(in.SignDescs))
// With the transaction deserialized, we'll now convert sign descs so
// we can feed it into the actual signer.
signDescs := make([]*lnwallet.SignDescriptor, 0, len(in.SignDescs))
for _, signDesc := range in.SignDescs {
keyDesc := signDesc.KeyDesc
// The caller can either specify the key using the raw pubkey,
// or the description of the key. Below we'll feel out the
// oneof field to decide which one we will attempt to parse.
var (
targetPubKey *btcec.PublicKey
keyLoc keychain.KeyLocator
)
switch {
// If this method doesn't return nil, then we know that user is
// attempting to include a raw serialized pub key.
case keyDesc.GetRawKeyBytes() != nil:
rawKeyBytes := keyDesc.GetRawKeyBytes()
switch {
// If the user provided a raw key, but it's of the
// wrong length, then we'll return with an error.
case len(rawKeyBytes) != 0 && len(rawKeyBytes) != 33:
return nil, fmt.Errorf("pubkey must be " +
"serialized in compressed format if " +
"specified")
// If a proper raw key was provided, then we'll attempt
// to decode and parse it.
case len(rawKeyBytes) != 0 && len(rawKeyBytes) == 33:
targetPubKey, err = btcec.ParsePubKey(
rawKeyBytes, btcec.S256(),
)
if err != nil {
return nil, fmt.Errorf("unable to "+
"parse pubkey: %v", err)
}
}
// Similarly, if they specified a key locator, then we'll use
// that instead.
case keyDesc.GetKeyLoc() != nil:
protoLoc := keyDesc.GetKeyLoc()
keyLoc = keychain.KeyLocator{
Family: keychain.KeyFamily(
protoLoc.KeyFamily,
),
Index: uint32(protoLoc.KeyIndex),
}
}
// If a witness script isn't passed, then we can't proceed, as
// in the p2wsh case, we can't properly generate the sighash.
if len(signDesc.WitnessScript) == 0 {
// TODO(roasbeef): if regualr p2wkh, then at times
// internally we allow script to go by
return nil, fmt.Errorf("witness script MUST be " +
"specified")
}
// If the users provided a double tweak, then we'll need to
// parse that out now to ensure their input is properly signed.
var tweakPrivKey *btcec.PrivateKey
if len(signDesc.DoubleTweak) != 0 {
tweakPrivKey, _ = btcec.PrivKeyFromBytes(
btcec.S256(), signDesc.DoubleTweak,
)
}
// Finally, with verification and parsing complete, we can
// construct the final sign descriptor to generate the proper
// signature for this input.
signDescs = append(signDescs, &lnwallet.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: keyLoc,
PubKey: targetPubKey,
},
SingleTweak: signDesc.SingleTweak,
DoubleTweak: tweakPrivKey,
WitnessScript: signDesc.WitnessScript,
Output: &wire.TxOut{
Value: signDesc.Output.Value,
PkScript: signDesc.Output.PkScript,
},
HashType: txscript.SigHashType(signDesc.Sighash),
SigHashes: sigHashCache,
InputIndex: int(signDesc.InputIndex),
})
}
// Now that we've mapped all the proper sign descriptors, we can
// request signatures for each of them, passing in the transaction to
// be signed.
numSigs := len(in.SignDescs)
resp := &SignResp{
RawSigs: make([][]byte, numSigs),
}
for i, signDesc := range signDescs {
sig, err := s.cfg.Signer.SignOutputRaw(&txToSign, signDesc)
if err != nil {
log.Errorf("unable to generate sig for input "+
"#%v: %v", i, err)
return nil, err
}
resp.RawSigs[i] = sig
}
return resp, nil
}