diff --git a/lnrpc/signrpc/log.go b/lnrpc/signrpc/log.go new file mode 100644 index 00000000..bb9fc94d --- /dev/null +++ b/lnrpc/signrpc/log.go @@ -0,0 +1,45 @@ +package signrpc + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger("SGNR", nil)) +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + UseLogger(btclog.Disabled) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} + +// logClosure is used to provide a closure over expensive logging operations so +// don't have to be performed when the logging level doesn't warrant it. +type logClosure func() string + +// String invokes the underlying function and returns the result. +func (c logClosure) String() string { + return c() +} + +// newLogClosure returns a new closure over a function that returns a string +// which itself provides a Stringer interface so that it can be used with the +// logging system. +func newLogClosure(c func() string) logClosure { + return logClosure(c) +} diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go new file mode 100644 index 00000000..3d75bc58 --- /dev/null +++ b/lnrpc/signrpc/signer_server.go @@ -0,0 +1,315 @@ +// +build signerrpc + +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" + grpc "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 permission s 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 +}