Merge pull request #2081 from Roasbeef/signer-service

lnrpc+lnd+rpc: add new Signer RPC service, and sub RPC server infrastructure
This commit is contained in:
Olaoluwa Osuntokun 2018-11-28 21:22:25 -08:00 committed by GitHub
commit fc4fe07010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1812 additions and 71 deletions

@ -24,6 +24,7 @@ import (
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor"
@ -220,6 +221,8 @@ type config struct {
Tor *torConfig `group:"Tor" namespace:"tor"`
SubRPCServers *subRPCServerConfigs `group:"subrpc"`
Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
@ -295,6 +298,9 @@ func loadConfig() (*config, error) {
},
MaxPendingChannels: defaultMaxPendingChannels,
NoSeedBackup: defaultNoSeedBackup,
SubRPCServers: &subRPCServerConfigs{
SignRPC: &signrpc.Config{},
},
Autopilot: &autoPilotConfig{
MaxChannels: 5,
Allocation: 0.6,

68
lnd.go

@ -312,67 +312,21 @@ func lndMain() error {
return err
}
// Check macaroon authentication if macaroons aren't disabled.
if macaroonService != nil {
serverOpts = append(serverOpts,
grpc.UnaryInterceptor(macaroonService.
UnaryServerInterceptor(permissions)),
grpc.StreamInterceptor(macaroonService.
StreamServerInterceptor(permissions)),
)
}
// Initialize, and register our implementation of the gRPC interface
// exported by the rpcServer.
rpcServer := newRPCServer(server)
rpcServer, err := newRPCServer(
server, macaroonService, cfg.SubRPCServers, serverOpts,
proxyOpts, tlsConf,
)
if err != nil {
srvrLog.Errorf("unable to start RPC server: %v", err)
return err
}
if err := rpcServer.Start(); err != nil {
return err
}
defer rpcServer.Stop()
grpcServer := grpc.NewServer(serverOpts...)
lnrpc.RegisterLightningServer(grpcServer, rpcServer)
// Next, Start the gRPC server listening for HTTP/2 connections.
for _, listener := range cfg.RPCListeners {
lis, err := lncfg.ListenOnAddress(listener)
if err != nil {
ltndLog.Errorf(
"RPC server unable to listen on %s", listener,
)
return err
}
defer lis.Close()
go func() {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
grpcServer.Serve(lis)
}()
}
// Finally, start the REST proxy for our gRPC server above.
mux := proxy.NewServeMux()
err = lnrpc.RegisterLightningHandlerFromEndpoint(
ctx, mux, cfg.RPCListeners[0].String(), proxyOpts,
)
if err != nil {
return err
}
for _, restEndpoint := range cfg.RESTListeners {
lis, err := lncfg.TLSListenOnAddress(restEndpoint, tlsConf)
if err != nil {
ltndLog.Errorf(
"gRPC proxy unable to listen on %s",
restEndpoint,
)
return err
}
defer lis.Close()
go func() {
rpcsLog.Infof("gRPC proxy started at %s", lis.Addr())
http.Serve(lis, mux)
}()
}
// If we're not in simnet mode, We'll wait until we're fully synced to
// continue the start up of the remainder of the daemon. This ensures
// that we don't accept any possibly invalid state transitions, or
@ -602,9 +556,9 @@ func genCertPair(certFile, keyFile string) error {
return nil
}
// genMacaroons generates three macaroon files; one admin-level, one
// for invoice access and one read-only. These can also be used
// to generate more granular macaroons.
// genMacaroons generates three macaroon files; one admin-level, one for
// invoice access and one read-only. These can also be used to generate more
// granular macaroons.
func genMacaroons(ctx context.Context, svc *macaroons.Service,
admFile, roFile, invoiceFile string) error {

@ -1,5 +1,7 @@
#!/bin/sh
echo "Generating root gRPC server protos"
# Generate the protos.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
@ -7,8 +9,6 @@ protoc -I/usr/local/include -I. \
--go_out=plugins=grpc:. \
rpc.proto
# Generate the REST reverse proxy.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
@ -22,3 +22,16 @@ protoc -I/usr/local/include -I. \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
rpc.proto
# For each of the sub-servers, we then generate their protos, but a restricted
# set as they don't yet require REST proxies, or swagger docs.
for file in **/*.proto
do
DIRECTORY=$(dirname ${file})
echo "Generating protos from ${file}, into ${DIRECTORY}"
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
--go_out=plugins=grpc:. \
${file}
done

@ -522,7 +522,9 @@ func (m *FeeLimit) String() string { return proto.CompactTextString(m
func (*FeeLimit) ProtoMessage() {}
func (*FeeLimit) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
type isFeeLimit_Limit interface{ isFeeLimit_Limit() }
type isFeeLimit_Limit interface {
isFeeLimit_Limit()
}
type FeeLimit_Fixed struct {
Fixed int64 `protobuf:"varint,1,opt,name=fixed,oneof"`
@ -789,7 +791,9 @@ func (m *ChannelPoint) String() string { return proto.CompactTextStri
func (*ChannelPoint) ProtoMessage() {}
func (*ChannelPoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
type isChannelPoint_FundingTxid interface{ isChannelPoint_FundingTxid() }
type isChannelPoint_FundingTxid interface {
isChannelPoint_FundingTxid()
}
type ChannelPoint_FundingTxidBytes struct {
FundingTxidBytes []byte `protobuf:"bytes,1,opt,name=funding_txid_bytes,proto3,oneof"`
@ -2046,7 +2050,9 @@ func (m *CloseStatusUpdate) String() string { return proto.CompactTex
func (*CloseStatusUpdate) ProtoMessage() {}
func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} }
type isCloseStatusUpdate_Update interface{ isCloseStatusUpdate_Update() }
type isCloseStatusUpdate_Update interface {
isCloseStatusUpdate_Update()
}
type CloseStatusUpdate_ClosePending struct {
ClosePending *PendingUpdate `protobuf:"bytes,1,opt,name=close_pending,oneof"`
@ -2327,7 +2333,9 @@ func (m *OpenStatusUpdate) String() string { return proto.CompactText
func (*OpenStatusUpdate) ProtoMessage() {}
func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} }
type isOpenStatusUpdate_Update interface{ isOpenStatusUpdate_Update() }
type isOpenStatusUpdate_Update interface {
isOpenStatusUpdate_Update()
}
type OpenStatusUpdate_ChanPending struct {
ChanPending *PendingUpdate `protobuf:"bytes,1,opt,name=chan_pending,oneof"`
@ -4668,7 +4676,9 @@ func (m *PolicyUpdateRequest) String() string { return proto.CompactT
func (*PolicyUpdateRequest) ProtoMessage() {}
func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{102} }
type isPolicyUpdateRequest_Scope interface{ isPolicyUpdateRequest_Scope() }
type isPolicyUpdateRequest_Scope interface {
isPolicyUpdateRequest_Scope()
}
type PolicyUpdateRequest_Global struct {
Global bool `protobuf:"varint,1,opt,name=global,oneof"`

@ -0,0 +1,33 @@
// +build signrpc
package signrpc
import (
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/macaroons"
)
// Config is the primary configuration struct for the signer RPC server. It
// contains all the items required for the signer rpc server to carry out its
// duties. The fields with struct tags are meant to be parsed as normal
// configuration options, while if able to be populated, the latter fields MUST
// also be specified.
type Config struct {
// SignerMacPath is the path for the signer macaroon. If unspecified
// then we assume that the macaroon will be found under the network
// directory, named DefaultSignerMacFilename.
SignerMacPath string `long:"signermacaroonpath" description:"Path to the signer macaroon"`
// NetworkDir is the main network directory wherein the signer rpc
// server will find the macaroon named DefaultSignerMacFilename.
NetworkDir string
// MacService is the main macaroon service that we'll use to handle
// authentication for the signer rpc server.
MacService *macaroons.Service
// Signer is the signer instance that backs the signer RPC server. The
// job of the signer RPC server is simply to proxy valid requests to
// the active signer instance.
Signer lnwallet.Signer
}

@ -0,0 +1,6 @@
// +build !signrpc
package signrpc
// Config is empty for non-signrpc builds.
type Config struct{}

71
lnrpc/signrpc/driver.go Normal file

@ -0,0 +1,71 @@
// +build signrpc
package signrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
// createNewSubServer is a helper method that will create the new signer sub
// server given the main config dispatcher method. If we're unable to find the
// config that is meant for us in the config dispatcher, then we'll exit with
// an error.
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
// We'll attempt to look up the config that we expect, according to our
// subServerName name. If we can't find this, then we'll exit with an
// error, as we're unable to properly initialize ourselves without this
// config.
signServerConf, ok := configRegistry.FetchConfig(subServerName)
if !ok {
return nil, nil, fmt.Errorf("unable to find config for "+
"subserver type %s", subServerName)
}
// Now that we've found an object mapping to our service name, we'll
// ensure that it's the type we need.
config, ok := signServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, signServerConf)
}
// Before we try to make the new signer service instance, we'll perform
// some sanity checks on the arguments to ensure that they're useable.
switch {
// If the macaroon service is set (we should use macaroons), then
// ensure that we know where to look for them, or create them if not
// found.
case config.MacService != nil && config.NetworkDir == "":
return nil, nil, fmt.Errorf("NetworkDir must be set to create " +
"Signrpc")
case config.Signer == nil:
return nil, nil, fmt.Errorf("Signer must be set to create " +
"Signrpc")
}
return New(config)
}
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
New: func(c lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
return createNewSubServer(c)
},
}
// If the build tag is active, then we'll register ourselves as a
// sub-RPC server within the global lnrpc package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver '%s': %v",
subServerName, err))
}
}

45
lnrpc/signrpc/log.go Normal file

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

588
lnrpc/signrpc/signer.pb.go Normal file

@ -0,0 +1,588 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: signrpc/signer.proto
/*
Package signrpc is a generated protocol buffer package.
It is generated from these files:
signrpc/signer.proto
It has these top-level messages:
KeyLocator
KeyDescriptor
TxOut
SignDescriptor
SignReq
SignResp
InputScript
InputScriptResp
*/
package signrpc
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type KeyLocator struct {
// / The family of key being identified.
KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily" json:"key_family,omitempty"`
// / The precise index of the key being identified.
KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex" json:"key_index,omitempty"`
}
func (m *KeyLocator) Reset() { *m = KeyLocator{} }
func (m *KeyLocator) String() string { return proto.CompactTextString(m) }
func (*KeyLocator) ProtoMessage() {}
func (*KeyLocator) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *KeyLocator) GetKeyFamily() int32 {
if m != nil {
return m.KeyFamily
}
return 0
}
func (m *KeyLocator) GetKeyIndex() int32 {
if m != nil {
return m.KeyIndex
}
return 0
}
type KeyDescriptor struct {
// Types that are valid to be assigned to Key:
// *KeyDescriptor_RawKeyBytes
// *KeyDescriptor_KeyLoc
Key isKeyDescriptor_Key `protobuf_oneof:"key"`
}
func (m *KeyDescriptor) Reset() { *m = KeyDescriptor{} }
func (m *KeyDescriptor) String() string { return proto.CompactTextString(m) }
func (*KeyDescriptor) ProtoMessage() {}
func (*KeyDescriptor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
type isKeyDescriptor_Key interface {
isKeyDescriptor_Key()
}
type KeyDescriptor_RawKeyBytes struct {
RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3,oneof"`
}
type KeyDescriptor_KeyLoc struct {
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,oneof"`
}
func (*KeyDescriptor_RawKeyBytes) isKeyDescriptor_Key() {}
func (*KeyDescriptor_KeyLoc) isKeyDescriptor_Key() {}
func (m *KeyDescriptor) GetKey() isKeyDescriptor_Key {
if m != nil {
return m.Key
}
return nil
}
func (m *KeyDescriptor) GetRawKeyBytes() []byte {
if x, ok := m.GetKey().(*KeyDescriptor_RawKeyBytes); ok {
return x.RawKeyBytes
}
return nil
}
func (m *KeyDescriptor) GetKeyLoc() *KeyLocator {
if x, ok := m.GetKey().(*KeyDescriptor_KeyLoc); ok {
return x.KeyLoc
}
return nil
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*KeyDescriptor) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _KeyDescriptor_OneofMarshaler, _KeyDescriptor_OneofUnmarshaler, _KeyDescriptor_OneofSizer, []interface{}{
(*KeyDescriptor_RawKeyBytes)(nil),
(*KeyDescriptor_KeyLoc)(nil),
}
}
func _KeyDescriptor_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*KeyDescriptor)
// key
switch x := m.Key.(type) {
case *KeyDescriptor_RawKeyBytes:
b.EncodeVarint(1<<3 | proto.WireBytes)
b.EncodeRawBytes(x.RawKeyBytes)
case *KeyDescriptor_KeyLoc:
b.EncodeVarint(2<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.KeyLoc); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("KeyDescriptor.Key has unexpected type %T", x)
}
return nil
}
func _KeyDescriptor_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*KeyDescriptor)
switch tag {
case 1: // key.raw_key_bytes
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeRawBytes(true)
m.Key = &KeyDescriptor_RawKeyBytes{x}
return true, err
case 2: // key.key_loc
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(KeyLocator)
err := b.DecodeMessage(msg)
m.Key = &KeyDescriptor_KeyLoc{msg}
return true, err
default:
return false, nil
}
}
func _KeyDescriptor_OneofSizer(msg proto.Message) (n int) {
m := msg.(*KeyDescriptor)
// key
switch x := m.Key.(type) {
case *KeyDescriptor_RawKeyBytes:
n += proto.SizeVarint(1<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(len(x.RawKeyBytes)))
n += len(x.RawKeyBytes)
case *KeyDescriptor_KeyLoc:
s := proto.Size(x.KeyLoc)
n += proto.SizeVarint(2<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
type TxOut struct {
// / The value of the output being spent.
Value int64 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"`
// / The script of the output being spent.
PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
}
func (m *TxOut) Reset() { *m = TxOut{} }
func (m *TxOut) String() string { return proto.CompactTextString(m) }
func (*TxOut) ProtoMessage() {}
func (*TxOut) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *TxOut) GetValue() int64 {
if m != nil {
return m.Value
}
return 0
}
func (m *TxOut) GetPkScript() []byte {
if m != nil {
return m.PkScript
}
return nil
}
type SignDescriptor struct {
// *
// A descriptor that precisely describes *which* key to use for signing. This
// may provide the raw public key directly, or require the Signer to re-derive
// the key according to the populated derivation path.
KeyDesc *KeyDescriptor `protobuf:"bytes,1,opt,name=key_desc,json=keyDesc" json:"key_desc,omitempty"`
// *
// A scalar value that will be added to the private key corresponding to the
// above public key to obtain the private key to be used to sign this input.
// This value is typically derived via the following computation:
//
// derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
SingleTweak []byte `protobuf:"bytes,2,opt,name=single_tweak,json=singleTweak,proto3" json:"single_tweak,omitempty"`
// *
// A private key that will be used in combination with its corresponding
// private key to derive the private key that is to be used to sign the target
// input. Within the Lightning protocol, this value is typically the
// commitment secret from a previously revoked commitment transaction. This
// value is in combination with two hash values, and the original private key
// to derive the private key to be used when signing.
//
// k = (privKey*sha256(pubKey || tweakPub) +
// tweakPriv*sha256(tweakPub || pubKey)) mod N
DoubleTweak []byte `protobuf:"bytes,3,opt,name=double_tweak,json=doubleTweak,proto3" json:"double_tweak,omitempty"`
// *
// The full script required to properly redeem the output. This field will
// only be populated if a p2wsh or a p2sh output is being signed.
WitnessScript []byte `protobuf:"bytes,4,opt,name=witness_script,json=witnessScript,proto3" json:"witness_script,omitempty"`
// *
// A description of the output being spent. The value and script MUST be provided.
Output *TxOut `protobuf:"bytes,5,opt,name=output" json:"output,omitempty"`
// *
// The target sighash type that should be used when generating the final
// sighash, and signature.
Sighash uint32 `protobuf:"varint,7,opt,name=sighash" json:"sighash,omitempty"`
// *
// The target input within the transaction that should be signed.
InputIndex int32 `protobuf:"varint,8,opt,name=input_index,json=inputIndex" json:"input_index,omitempty"`
}
func (m *SignDescriptor) Reset() { *m = SignDescriptor{} }
func (m *SignDescriptor) String() string { return proto.CompactTextString(m) }
func (*SignDescriptor) ProtoMessage() {}
func (*SignDescriptor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *SignDescriptor) GetKeyDesc() *KeyDescriptor {
if m != nil {
return m.KeyDesc
}
return nil
}
func (m *SignDescriptor) GetSingleTweak() []byte {
if m != nil {
return m.SingleTweak
}
return nil
}
func (m *SignDescriptor) GetDoubleTweak() []byte {
if m != nil {
return m.DoubleTweak
}
return nil
}
func (m *SignDescriptor) GetWitnessScript() []byte {
if m != nil {
return m.WitnessScript
}
return nil
}
func (m *SignDescriptor) GetOutput() *TxOut {
if m != nil {
return m.Output
}
return nil
}
func (m *SignDescriptor) GetSighash() uint32 {
if m != nil {
return m.Sighash
}
return 0
}
func (m *SignDescriptor) GetInputIndex() int32 {
if m != nil {
return m.InputIndex
}
return 0
}
type SignReq struct {
// / The raw bytes of the transaction to be signed.
RawTxBytes []byte `protobuf:"bytes,1,opt,name=raw_tx_bytes,json=rawTxBytes,proto3" json:"raw_tx_bytes,omitempty"`
// / A set of sign descriptors, for each input to be signed.
SignDescs []*SignDescriptor `protobuf:"bytes,2,rep,name=sign_descs,json=signDescs" json:"sign_descs,omitempty"`
}
func (m *SignReq) Reset() { *m = SignReq{} }
func (m *SignReq) String() string { return proto.CompactTextString(m) }
func (*SignReq) ProtoMessage() {}
func (*SignReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *SignReq) GetRawTxBytes() []byte {
if m != nil {
return m.RawTxBytes
}
return nil
}
func (m *SignReq) GetSignDescs() []*SignDescriptor {
if m != nil {
return m.SignDescs
}
return nil
}
type SignResp struct {
// *
// A set of signatures realized in a fixed 64-byte format ordered in ascending
// input order.
RawSigs [][]byte `protobuf:"bytes,1,rep,name=raw_sigs,json=rawSigs,proto3" json:"raw_sigs,omitempty"`
}
func (m *SignResp) Reset() { *m = SignResp{} }
func (m *SignResp) String() string { return proto.CompactTextString(m) }
func (*SignResp) ProtoMessage() {}
func (*SignResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (m *SignResp) GetRawSigs() [][]byte {
if m != nil {
return m.RawSigs
}
return nil
}
type InputScript struct {
// / The serializes witness stack for the specified input.
Witness [][]byte `protobuf:"bytes,1,rep,name=witness,proto3" json:"witness,omitempty"`
// **
// The optional sig script for the specified witness that will only be set if
// the input specified is a nested p2sh witness program.
SigScript []byte `protobuf:"bytes,2,opt,name=sig_script,json=sigScript,proto3" json:"sig_script,omitempty"`
}
func (m *InputScript) Reset() { *m = InputScript{} }
func (m *InputScript) String() string { return proto.CompactTextString(m) }
func (*InputScript) ProtoMessage() {}
func (*InputScript) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *InputScript) GetWitness() [][]byte {
if m != nil {
return m.Witness
}
return nil
}
func (m *InputScript) GetSigScript() []byte {
if m != nil {
return m.SigScript
}
return nil
}
type InputScriptResp struct {
InputScripts []*InputScript `protobuf:"bytes,1,rep,name=input_scripts,json=inputScripts" json:"input_scripts,omitempty"`
}
func (m *InputScriptResp) Reset() { *m = InputScriptResp{} }
func (m *InputScriptResp) String() string { return proto.CompactTextString(m) }
func (*InputScriptResp) ProtoMessage() {}
func (*InputScriptResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
func (m *InputScriptResp) GetInputScripts() []*InputScript {
if m != nil {
return m.InputScripts
}
return nil
}
func init() {
proto.RegisterType((*KeyLocator)(nil), "signrpc.KeyLocator")
proto.RegisterType((*KeyDescriptor)(nil), "signrpc.KeyDescriptor")
proto.RegisterType((*TxOut)(nil), "signrpc.TxOut")
proto.RegisterType((*SignDescriptor)(nil), "signrpc.SignDescriptor")
proto.RegisterType((*SignReq)(nil), "signrpc.SignReq")
proto.RegisterType((*SignResp)(nil), "signrpc.SignResp")
proto.RegisterType((*InputScript)(nil), "signrpc.InputScript")
proto.RegisterType((*InputScriptResp)(nil), "signrpc.InputScriptResp")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Signer service
type SignerClient interface {
// *
// SignOutputRaw is a method that can be used to generated a signature for a
// set of inputs/outputs to a transaction. Each request specifies details
// concerning how the outputs should be signed, which keys they should be
// signed with, and also any optional tweaks. The return value is a fixed
// 64-byte signature (the same format as we use on the wire in Lightning).
//
// If we are unable to sign using the specified keys, then an error will be
// returned.
SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error)
// *
// 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 that when using this method to sign inputs belonging to the wallet,
// the only items of the SignDescriptor that need to be populated are pkScript
// in the TxOut field, the value in that same field, and finally the input
// index.
ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error)
}
type signerClient struct {
cc *grpc.ClientConn
}
func NewSignerClient(cc *grpc.ClientConn) SignerClient {
return &signerClient{cc}
}
func (c *signerClient) SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error) {
out := new(SignResp)
err := grpc.Invoke(ctx, "/signrpc.Signer/SignOutputRaw", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *signerClient) ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error) {
out := new(InputScriptResp)
err := grpc.Invoke(ctx, "/signrpc.Signer/ComputeInputScript", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Signer service
type SignerServer interface {
// *
// SignOutputRaw is a method that can be used to generated a signature for a
// set of inputs/outputs to a transaction. Each request specifies details
// concerning how the outputs should be signed, which keys they should be
// signed with, and also any optional tweaks. The return value is a fixed
// 64-byte signature (the same format as we use on the wire in Lightning).
//
// If we are unable to sign using the specified keys, then an error will be
// returned.
SignOutputRaw(context.Context, *SignReq) (*SignResp, error)
// *
// 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 that when using this method to sign inputs belonging to the wallet,
// the only items of the SignDescriptor that need to be populated are pkScript
// in the TxOut field, the value in that same field, and finally the input
// index.
ComputeInputScript(context.Context, *SignReq) (*InputScriptResp, error)
}
func RegisterSignerServer(s *grpc.Server, srv SignerServer) {
s.RegisterService(&_Signer_serviceDesc, srv)
}
func _Signer_SignOutputRaw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).SignOutputRaw(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/SignOutputRaw",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).SignOutputRaw(ctx, req.(*SignReq))
}
return interceptor(ctx, in, info, handler)
}
func _Signer_ComputeInputScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).ComputeInputScript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/ComputeInputScript",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).ComputeInputScript(ctx, req.(*SignReq))
}
return interceptor(ctx, in, info, handler)
}
var _Signer_serviceDesc = grpc.ServiceDesc{
ServiceName: "signrpc.Signer",
HandlerType: (*SignerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SignOutputRaw",
Handler: _Signer_SignOutputRaw_Handler,
},
{
MethodName: "ComputeInputScript",
Handler: _Signer_ComputeInputScript_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "signrpc/signer.proto",
}
func init() { proto.RegisterFile("signrpc/signer.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 536 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0xd1, 0x8f, 0xd2, 0x40,
0x10, 0xc6, 0x0f, 0x10, 0xca, 0x4d, 0x5b, 0xd4, 0x95, 0x68, 0xd5, 0x18, 0xb1, 0xf1, 0x0c, 0x4f,
0x18, 0xd1, 0x98, 0xe8, 0x93, 0x39, 0xcd, 0x85, 0x0b, 0x97, 0x5c, 0xb2, 0xf0, 0xde, 0x94, 0xb2,
0xf6, 0x36, 0xe5, 0xda, 0x5e, 0x77, 0x6b, 0xe9, 0x9b, 0xff, 0x83, 0xff, 0xb0, 0x99, 0xdd, 0x05,
0x8a, 0xde, 0x13, 0x7c, 0x5f, 0x67, 0x67, 0x7e, 0x3b, 0x5f, 0x0b, 0x43, 0xc1, 0xe3, 0xb4, 0xc8,
0xa3, 0xf7, 0xf8, 0xcb, 0x8a, 0x49, 0x5e, 0x64, 0x32, 0x23, 0x96, 0x71, 0xfd, 0x19, 0xc0, 0x9c,
0xd5, 0x57, 0x59, 0x14, 0xca, 0xac, 0x20, 0xaf, 0x00, 0x12, 0x56, 0x07, 0x3f, 0xc3, 0x5b, 0xbe,
0xa9, 0xbd, 0xd6, 0xa8, 0x35, 0xee, 0xd2, 0xd3, 0x84, 0xd5, 0x17, 0xca, 0x20, 0x2f, 0x01, 0x45,
0xc0, 0xd3, 0x35, 0xdb, 0x7a, 0x6d, 0xf5, 0xb4, 0x9f, 0xb0, 0xfa, 0x12, 0xb5, 0xbf, 0x01, 0x77,
0xce, 0xea, 0x1f, 0x4c, 0x44, 0x05, 0xcf, 0xb1, 0xd9, 0x5b, 0x70, 0x8b, 0xb0, 0x0a, 0xf0, 0xc4,
0xaa, 0x96, 0x4c, 0xa8, 0x7e, 0xce, 0xec, 0x84, 0xda, 0x45, 0x58, 0xcd, 0x59, 0x7d, 0x8e, 0x26,
0x99, 0x80, 0x85, 0x15, 0x9b, 0x2c, 0x52, 0x1d, 0xed, 0xe9, 0x93, 0x89, 0x61, 0x9b, 0x1c, 0xc0,
0x66, 0x27, 0xb4, 0x97, 0x28, 0x75, 0xde, 0x85, 0x4e, 0xc2, 0x6a, 0xff, 0x2b, 0x74, 0x97, 0xdb,
0xeb, 0x52, 0x92, 0x21, 0x74, 0x7f, 0x85, 0x9b, 0x92, 0xa9, 0xee, 0x1d, 0xaa, 0x05, 0x92, 0xe6,
0x49, 0xa0, 0x51, 0x54, 0x5f, 0x87, 0xf6, 0xf3, 0x64, 0xa1, 0xb4, 0xff, 0xa7, 0x0d, 0x83, 0x05,
0x8f, 0xd3, 0x06, 0xeb, 0x07, 0xc0, 0x8b, 0x04, 0x6b, 0x26, 0x22, 0xd5, 0xc8, 0x9e, 0x3e, 0x6d,
0x62, 0x1c, 0x2a, 0x29, 0xd2, 0xa2, 0x24, 0x6f, 0xc0, 0x11, 0x3c, 0x8d, 0x37, 0x2c, 0x90, 0x15,
0x0b, 0x13, 0x33, 0xc5, 0xd6, 0xde, 0x12, 0x2d, 0x2c, 0x59, 0x67, 0xe5, 0x6a, 0x5f, 0xd2, 0xd1,
0x25, 0xda, 0xd3, 0x25, 0x67, 0x30, 0xa8, 0xb8, 0x4c, 0x99, 0x10, 0x3b, 0xda, 0x07, 0xaa, 0xc8,
0x35, 0xae, 0x46, 0x26, 0xef, 0xa0, 0x97, 0x95, 0x32, 0x2f, 0xa5, 0xd7, 0x55, 0x74, 0x83, 0x3d,
0x9d, 0xda, 0x02, 0x35, 0x4f, 0x89, 0x07, 0x98, 0xec, 0x4d, 0x28, 0x6e, 0x3c, 0x6b, 0xd4, 0x1a,
0xbb, 0x74, 0x27, 0xc9, 0x6b, 0xb0, 0x79, 0x9a, 0x97, 0xd2, 0xa4, 0xd7, 0x57, 0xe9, 0x81, 0xb2,
0x74, 0x7e, 0x11, 0x58, 0xb8, 0x14, 0xca, 0xee, 0xc8, 0x08, 0x1c, 0x4c, 0x4e, 0x6e, 0x9b, 0xc1,
0x51, 0x28, 0xc2, 0x6a, 0xb9, 0xd5, 0xa9, 0x7d, 0x06, 0x40, 0x00, 0xb5, 0x30, 0xe1, 0xb5, 0x47,
0x9d, 0xb1, 0x3d, 0x7d, 0xb6, 0x67, 0x3a, 0x5e, 0x2e, 0x3d, 0x15, 0x46, 0x0b, 0xff, 0x0c, 0xfa,
0x7a, 0x88, 0xc8, 0xc9, 0x73, 0xe8, 0xe3, 0x14, 0xc1, 0x63, 0x9c, 0xd0, 0x19, 0x3b, 0xd4, 0x2a,
0xc2, 0x6a, 0xc1, 0x63, 0xe1, 0x5f, 0x80, 0x7d, 0x89, 0x64, 0xe6, 0xf6, 0x1e, 0x58, 0x66, 0x1d,
0xbb, 0x42, 0x23, 0xf1, 0x85, 0x15, 0x3c, 0x3e, 0x0e, 0x1a, 0xc7, 0x99, 0xa4, 0xaf, 0xe0, 0x61,
0xa3, 0x8f, 0x9a, 0xfa, 0x05, 0x5c, 0xbd, 0x07, 0x7d, 0x46, 0x77, 0xb4, 0xa7, 0xc3, 0x3d, 0x7c,
0xf3, 0x80, 0xc3, 0x0f, 0x42, 0x4c, 0x7f, 0xb7, 0xa0, 0xb7, 0x50, 0x5f, 0x11, 0xf9, 0x04, 0x2e,
0xfe, 0xbb, 0x56, 0x5b, 0xa7, 0x61, 0x45, 0x1e, 0x1d, 0x5d, 0x9e, 0xb2, 0xbb, 0x17, 0x8f, 0xff,
0x71, 0x44, 0x4e, 0xbe, 0x01, 0xf9, 0x9e, 0xdd, 0xe6, 0xa5, 0x64, 0xcd, 0xdb, 0xfd, 0x7f, 0xd4,
0xbb, 0x17, 0x86, 0x89, 0x7c, 0xd5, 0x53, 0x9f, 0xef, 0xc7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff,
0x7b, 0x48, 0x93, 0x2a, 0xd6, 0x03, 0x00, 0x00,
}

148
lnrpc/signrpc/signer.proto Normal file

@ -0,0 +1,148 @@
syntax = "proto3";
package signrpc;
message KeyLocator {
/// The family of key being identified.
int32 key_family = 1;
/// The precise index of the key being identified.
int32 key_index = 2;
}
message KeyDescriptor {
oneof key {
/**
The raw bytes of the key being identified. Either this or the KeyLocator
must be specified.
*/
bytes raw_key_bytes = 1;
/**
The key locator that identifies which key to use for signing. Either this
or the raw bytes of the target key must be specified.
*/
KeyLocator key_loc = 2;
}
}
message TxOut {
/// The value of the output being spent.
int64 value = 1;
/// The script of the output being spent.
bytes pk_script = 2;
}
message SignDescriptor {
/**
A descriptor that precisely describes *which* key to use for signing. This
may provide the raw public key directly, or require the Signer to re-derive
the key according to the populated derivation path.
*/
KeyDescriptor key_desc = 1;
/**
A scalar value that will be added to the private key corresponding to the
above public key to obtain the private key to be used to sign this input.
This value is typically derived via the following computation:
* derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
*/
bytes single_tweak = 2;
/**
A private key that will be used in combination with its corresponding
private key to derive the private key that is to be used to sign the target
input. Within the Lightning protocol, this value is typically the
commitment secret from a previously revoked commitment transaction. This
value is in combination with two hash values, and the original private key
to derive the private key to be used when signing.
* k = (privKey*sha256(pubKey || tweakPub) +
tweakPriv*sha256(tweakPub || pubKey)) mod N
*/
bytes double_tweak = 3;
/**
The full script required to properly redeem the output. This field will
only be populated if a p2wsh or a p2sh output is being signed.
*/
bytes witness_script = 4;
/**
A description of the output being spent. The value and script MUST be provided.
*/
TxOut output = 5;
/**
The target sighash type that should be used when generating the final
sighash, and signature.
*/
uint32 sighash = 7;
/**
The target input within the transaction that should be signed.
*/
int32 input_index = 8;
}
message SignReq {
/// The raw bytes of the transaction to be signed.
bytes raw_tx_bytes = 1;
/// A set of sign descriptors, for each input to be signed.
repeated SignDescriptor sign_descs = 2;
}
message SignResp {
/**
A set of signatures realized in a fixed 64-byte format ordered in ascending
input order.
*/
repeated bytes raw_sigs = 1;
}
message InputScript {
/// The serializes witness stack for the specified input.
repeated bytes witness = 1;
/***
The optional sig script for the specified witness that will only be set if
the input specified is a nested p2sh witness program.
*/
bytes sig_script = 2;
}
message InputScriptResp {
/// The set of fully valid input scripts requested.
repeated InputScript input_scripts = 1;
}
service Signer {
/**
SignOutputRaw is a method that can be used to generated a signature for a
set of inputs/outputs to a transaction. Each request specifies details
concerning how the outputs should be signed, which keys they should be
signed with, and also any optional tweaks. The return value is a fixed
64-byte signature (the same format as we use on the wire in Lightning).
If we are unable to sign using the specified keys, then an error will be
returned.
*/
rpc SignOutputRaw(SignReq) returns (SignResp);
/**
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 that when using this method to sign inputs belonging to the wallet,
the only items of the SignDescriptor that need to be populated are pkScript
in the TxOut field, the value in that same field, and finally the input
index.
*/
rpc ComputeInputScript(SignReq) returns (InputScriptResp);
}

@ -0,0 +1,390 @@
// +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
}
// 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 that when using this method to sign inputs belonging to the wallet, the
// only items of the SignDescriptor that need to be populated are pkScript in
// the TxOut field, the value in that same field, and finally the input index.
func (s *Server) ComputeInputScript(ctx context.Context,
in *SignReq) (*InputScriptResp, 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
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)
signDescs := make([]*lnwallet.SignDescriptor, 0, len(in.SignDescs))
for _, signDesc := range in.SignDescs {
// For this method, the only fields that we care about are the
// hash type, and the information concerning the output as we
// only know how to provide full witnesses for outputs that we
// solely control.
signDescs = append(signDescs, &lnwallet.SignDescriptor{
Output: &wire.TxOut{
Value: signDesc.Output.Value,
PkScript: signDesc.Output.PkScript,
},
HashType: txscript.SigHashType(signDesc.Sighash),
SigHashes: sigHashCache,
})
}
// With all of our signDescs assembled, we can now generate a valid
// input script for each of them, and collate the responses to return
// back to the caller.
numWitnesses := len(in.SignDescs)
resp := &InputScriptResp{
InputScripts: make([]*InputScript, numWitnesses),
}
for i, signDesc := range signDescs {
inputScript, err := s.cfg.Signer.ComputeInputScript(
&txToSign, signDesc,
)
if err != nil {
return nil, err
}
resp.InputScripts[i] = &InputScript{
Witness: inputScript.Witness,
SigScript: inputScript.ScriptSig,
}
}
return resp, nil
}

131
lnrpc/sub_server.go Normal file

@ -0,0 +1,131 @@
package lnrpc
import (
"fmt"
"sync"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
// MacaroonPerms is a map from the FullMethod of an invoked gRPC command. It
// maps the set of operations that the macaroon presented with the command MUST
// satisfy. With this map, all sub-servers are able to communicate to the
// primary macaroon service what type of macaroon must be passed with each
// method present on the service of the sub-server.
type MacaroonPerms map[string][]bakery.Op
// SubServer is a child server of the main lnrpc gRPC server. Sub-servers allow
// lnd to expose discrete services that can be used with or independent of the
// main RPC server. The main rpcserver will create, start, stop, and manage
// each sub-server in a generalized manner.
type SubServer interface {
// Start starts the sub-server and all goroutines it needs to operate.
Start() error
// Stop signals that the sub-server should wrap up any lingering
// requests, and being a graceful shutdown.
Stop() error
// Name returns a unique string representation of the sub-server. This
// can be used to identify the sub-server and also de-duplicate them.
Name() string
// 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.
RegisterWithRootServer(*grpc.Server) error
}
// SubServerConfigDispatcher is an interface that all sub-servers will use to
// dynamically locate their configuration files. This abstraction will allow
// the primary RPC sever to initialize all sub-servers in a generic manner
// without knowing of each individual sub server.
type SubServerConfigDispatcher interface {
// FetchConfig attempts to locate an existing configuration file mapped
// to the target sub-server. If we're unable to find a config file
// matching the subServerName name, then false will be returned for the
// second parameter.
FetchConfig(subServerName string) (interface{}, bool)
}
// SubServerDriver is a template struct that allows the root server to create a
// sub-server with minimal knowledge. The root server only need a fully
// populated SubServerConfigDispatcher and with the aide of the
// RegisterSubServers method, it's able to create and initialize all
// sub-servers.
type SubServerDriver struct {
// SubServerName is the full name of a sub-sever.
//
// NOTE: This MUST be unique.
SubServerName string
// New creates, and fully initializes a new sub-server instance with
// the aide of the SubServerConfigDispatcher. This closure should
// return the SubServer, ready for action, along with the set of
// macaroon permissions that the sub-server wishes to pass on to the
// root server for all methods routed towards it.
New func(subCfgs SubServerConfigDispatcher) (SubServer, MacaroonPerms, error)
}
var (
// subServers is a package level global variable that houses all the
// registered sub-servers.
subServers = make(map[string]*SubServerDriver)
// registerMtx is a mutex that protects access to the above subServer
// map.
registerMtx sync.Mutex
)
// RegisteredSubServers returns all registered sub-servers.
//
// NOTE: This function is safe for concurrent access.
func RegisteredSubServers() []*SubServerDriver {
registerMtx.Lock()
defer registerMtx.Unlock()
drivers := make([]*SubServerDriver, 0, len(subServers))
for _, driver := range subServers {
drivers = append(drivers, driver)
}
return drivers
}
// RegisterSubServer should be called by a sub-server within its package's
// init() method to register its existence with the main sub-server map. Each
// sub-server, if active, is meant to register via this method in their init()
// method. This allows callers to easily initialize and register all
// sub-servers without knowing any details beyond that the fact that they
// satisfy the necessary interfaces.
//
// NOTE: This function is safe for concurrent access.
func RegisterSubServer(driver *SubServerDriver) error {
registerMtx.Lock()
defer registerMtx.Unlock()
if _, ok := subServers[driver.SubServerName]; ok {
return fmt.Errorf("subserver already registered")
}
subServers[driver.SubServerName] = driver
return nil
}
// SupportedServers returns slice of the names of all registered sub-servers.
//
// NOTE: This function is safe for concurrent access.
func SupportedServers() []string {
registerMtx.Lock()
defer registerMtx.Unlock()
supportedSubServers := make([]string, 0, len(subServers))
for driverName := range subServers {
supportedSubServers = append(supportedSubServers, driverName)
}
return supportedSubServers
}

4
log.go

@ -19,6 +19,7 @@ import (
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/signal"
@ -65,6 +66,7 @@ var (
cnctLog = build.NewSubLogger("CNCT", backendLog.Logger)
sphxLog = build.NewSubLogger("SPHX", backendLog.Logger)
swprLog = build.NewSubLogger("SWPR", backendLog.Logger)
sgnrLog = build.NewSubLogger("SGNR", backendLog.Logger)
)
// Initialize package-global logger variables.
@ -82,6 +84,7 @@ func init() {
sphinx.UseLogger(sphxLog)
signal.UseLogger(ltndLog)
sweep.UseLogger(swprLog)
signrpc.UseLogger(sgnrLog)
}
// subsystemLoggers maps each subsystem identifier to its associated logger.
@ -105,6 +108,7 @@ var subsystemLoggers = map[string]btclog.Logger{
"CNCT": cnctLog,
"SPHX": sphxLog,
"SWPR": swprLog,
"SGNR": sgnrLog,
}
// initLogRotator initializes the logging rotator to write logs to logFile and

@ -140,8 +140,9 @@ func (svc *Service) StreamServerInterceptor(
"for method", info.FullMethod)
}
err := svc.ValidateMacaroon(ss.Context(),
permissionMap[info.FullMethod])
err := svc.ValidateMacaroon(
ss.Context(), permissionMap[info.FullMethod],
)
if err != nil {
return err
}

@ -4,11 +4,13 @@ import (
"bytes"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"net/http"
"sort"
"strings"
"sync"
@ -24,17 +26,21 @@ import (
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/coreos/bbolt"
"github.com/davecgh/go-spew/spew"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/tv42/zbase32"
"golang.org/x/net/context"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
@ -339,6 +345,31 @@ type rpcServer struct {
wg sync.WaitGroup
// subServers are a set of sub-RPC servers that use the same gRPC and
// listening sockets as the main RPC server, but which maintain their
// own independent service. This allows us to expose a set of
// micro-service like abstractions to the outside world for users to
// consume.
subServers []lnrpc.SubServer
// grpcServer is the main gRPC server that this RPC server, and all the
// sub-servers will use to register themselves and accept client
// requests from.
grpcServer *grpc.Server
// listenerCleanUp are a set of closures functions that will allow this
// main RPC server to clean up all the listening socket created for the
// server.
listenerCleanUp []func()
// restServerOpts are a set of gRPC dial options that the REST server
// proxy will use to connect to the main gRPC server.
restServerOpts []grpc.DialOption
// tlsCfg is the TLS config that allows the REST server proxy to
// connect to the main gRPC server to proxy all incoming requests.
tlsCfg *tls.Config
quit chan struct{}
}
@ -346,12 +377,106 @@ type rpcServer struct {
// LightningServer gRPC service.
var _ lnrpc.LightningServer = (*rpcServer)(nil)
// newRPCServer creates and returns a new instance of the rpcServer.
func newRPCServer(s *server) *rpcServer {
return &rpcServer{
server: s,
quit: make(chan struct{}, 1),
// newRPCServer creates and returns a new instance of the rpcServer. The
// rpcServer will handle creating all listening sockets needed by it, and any
// of the sub-servers that it maintains. The set of serverOpts should be the
// base level options passed to the grPC server. This typically includes things
// like requiring TLS, etc.
func newRPCServer(s *server, macService *macaroons.Service,
subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption,
restServerOpts []grpc.DialOption,
tlsCfg *tls.Config) (*rpcServer, error) {
var (
subServers []lnrpc.SubServer
subServerPerms []lnrpc.MacaroonPerms
)
// Before we create any of the sub-servers, we need to ensure that all
// the dependencies they need are properly populated within each sub
// server configuration struct.
err := subServerCgs.PopulateDependencies(
s.cc, networkDir, macService,
)
if err != nil {
return nil, err
}
// Now that the sub-servers have all their dependencies in place, we
// can create each sub-server!
registeredSubServers := lnrpc.RegisteredSubServers()
for _, subServer := range registeredSubServers {
subServerInstance, macPerms, err := subServer.New(subServerCgs)
if err != nil {
return nil, err
}
// We'll collect the sub-server, and also the set of
// permissions it needs for macaroons so we can apply the
// interceptors below.
subServers = append(subServers, subServerInstance)
subServerPerms = append(subServerPerms, macPerms)
}
// Next, we need to merge the set of sub server macaroon permissions
// with the main RPC server permissions so we can unite them under a
// single set of interceptors.
for _, subServerPerm := range subServerPerms {
for method, ops := range subServerPerm {
// For each new method:ops combo, we also ensure that
// non of the sub-servers try to override each other.
if _, ok := permissions[method]; ok {
return nil, fmt.Errorf("detected duplicate "+
"macaroon constraints for path: %v",
method)
}
permissions[method] = ops
}
}
// If macaroons aren't disabled (a non-nil service), then we'll set up
// our set of interceptors which will allow us handle the macaroon
// authentication in a single location .
if macService != nil {
unaryInterceptor := grpc.UnaryInterceptor(
macService.UnaryServerInterceptor(permissions),
)
streamInterceptor := grpc.StreamInterceptor(
macService.StreamServerInterceptor(permissions),
)
serverOpts = append(serverOpts,
unaryInterceptor, streamInterceptor,
)
}
// Finally, with all the pre-set up complete, we can create the main
// gRPC server, and register the main lnrpc server along side.
grpcServer := grpc.NewServer(serverOpts...)
rootRPCServer := &rpcServer{
restServerOpts: restServerOpts,
subServers: subServers,
tlsCfg: tlsCfg,
grpcServer: grpcServer,
server: s,
quit: make(chan struct{}, 1),
}
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
// Now the main RPC server has been registered, we'll iterate through
// all the sub-RPC servers and register them to ensure that requests
// are properly routed towards them.
for _, subServer := range subServers {
err := subServer.RegisterWithRootServer(grpcServer)
if err != nil {
return nil, fmt.Errorf("unable to register "+
"sub-server %v with root: %v",
subServer.Name(), err)
}
}
return rootRPCServer, nil
}
// Start launches any helper goroutines required for the rpcServer to function.
@ -360,6 +485,72 @@ func (r *rpcServer) Start() error {
return nil
}
// First, we'll start all the sub-servers to ensure that they're ready
// to take new requests in.
//
// TODO(roasbeef): some may require that the entire daemon be started
// at that point
for _, subServer := range r.subServers {
rpcsLog.Debugf("Starting sub RPC server: %v", subServer.Name())
if err := subServer.Start(); err != nil {
return err
}
}
// With all the sub-servers started, we'll spin up the listeners for
// the main RPC server itself.
for _, listener := range cfg.RPCListeners {
lis, err := lncfg.ListenOnAddress(listener)
if err != nil {
ltndLog.Errorf(
"RPC server unable to listen on %s", listener,
)
return err
}
r.listenerCleanUp = append(r.listenerCleanUp, func() {
lis.Close()
})
go func() {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
r.grpcServer.Serve(lis)
}()
}
// Finally, start the REST proxy for our gRPC server above.
//
// TODO(roasbeef): eventually also allow the sub-servers to themselves
// have a REST proxy.
mux := proxy.NewServeMux()
err := lnrpc.RegisterLightningHandlerFromEndpoint(
context.Background(), mux, cfg.RPCListeners[0].String(),
r.restServerOpts,
)
if err != nil {
return err
}
for _, restEndpoint := range cfg.RESTListeners {
lis, err := lncfg.TLSListenOnAddress(restEndpoint, r.tlsCfg)
if err != nil {
ltndLog.Errorf(
"gRPC proxy unable to listen on %s",
restEndpoint,
)
return err
}
r.listenerCleanUp = append(r.listenerCleanUp, func() {
lis.Close()
})
go func() {
rpcsLog.Infof("gRPC proxy started at %s", lis.Addr())
http.Serve(lis, mux)
}()
}
return nil
}
@ -369,8 +560,30 @@ func (r *rpcServer) Stop() error {
return nil
}
rpcsLog.Infof("Stopping RPC Server")
close(r.quit)
// After we've signalled all of our active goroutines to exit, we'll
// then do the same to signal a graceful shutdown of all the sub
// servers.
for _, subServer := range r.subServers {
rpcsLog.Infof("Stopping %v Sub-RPC Server",
subServer.Name())
if err := subServer.Stop(); err != nil {
rpcsLog.Errorf("unable to stop sub-server %v: %v",
subServer.Name(), err)
continue
}
}
// Finally, we can clean up all the listening sockets to ensure that we
// give the file descriptors back to the OS.
for _, cleanUp := range r.listenerCleanUp {
cleanUp()
}
return nil
}

128
subrpcserver_config.go Normal file

@ -0,0 +1,128 @@
package main
import (
"fmt"
"reflect"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/macaroons"
)
// subRPCServerConfigs is special sub-config in the main configuration that
// houses the configuration for the optional sub-servers. These sub-RPC servers
// are meant to house experimental new features that may eventually make it
// into the main RPC server that lnd exposes. Special methods are present on
// this struct to allow the main RPC server to create and manipulate the
// sub-RPC servers in a generalized manner.
type subRPCServerConfigs struct {
// SignRPC is a sub-RPC server that exposes signing of arbitrary inputs
// as a gRPC service.
SignRPC *signrpc.Config `group:"signrpc" namespace:"signrpc"`
}
// PopulateDependencies attempts to iterate through all the sub-server configs
// within this struct, and populate the items it requires based on the main
// configuration file, and the chain control.
//
// NOTE: This MUST be called before any callers are permitted to execute the
// FetchConfig method.
func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
networkDir string, macService *macaroons.Service) error {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
selfType := selfVal.Type()
numFields := selfVal.NumField()
for i := 0; i < numFields; i++ {
field := selfVal.Field(i)
fieldElem := field.Elem()
fieldName := selfType.Field(i).Name
ltndLog.Debugf("Populating dependencies for sub RPC "+
"server: %v", fieldName)
// If this sub-config doesn't actually have any fields, then we
// can skip it, as the build tag for it is likely off.
if fieldElem.NumField() == 0 {
continue
}
if !fieldElem.CanSet() {
continue
}
switch cfg := field.Interface().(type) {
case *signrpc.Config:
subCfgValue := extractReflectValue(cfg)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("Signer").Set(
reflect.ValueOf(cc.signer),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,
cfg)
}
}
return nil
}
// FetchConfig attempts to locate an existing configuration file mapped to the
// target sub-server. If we're unable to find a config file matching the
// subServerName name, then false will be returned for the second parameter.
//
// NOTE: Part of the lnrpc.SubServerConfigDispatcher interface.
func (s *subRPCServerConfigs) FetchConfig(subServerName string) (interface{}, bool) {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
// Now that we have the value of the struct, we can check to see if it
// has an attribute with the same name as the subServerName.
configVal := selfVal.FieldByName(subServerName)
configValElem := configVal.Elem()
// We'll now ensure that this field actually exists in this value. If
// not, then we'll return false for the ok value to indicate to the
// caller that this field doesn't actually exist.
if !configVal.IsValid() {
return nil, false
}
// If a config of this type is found, it doesn't have any fields, then
// it's the same as if it wasn't present. This can happen if the build
// tag for the sub-server is inactive.
if configValElem.NumField() == 0 {
return nil, false
}
// At this pint, we know that the field is actually present in the
// config struct, so we can return it directly.
return configVal.Interface(), true
}
// extractReflectValue attempts to extract the value from an interface using
// the reflect package. The resulting reflect.Value allows the caller to
// programmatically examine and manipulate the underlying value.
func extractReflectValue(instance interface{}) reflect.Value {
var val reflect.Value
// If the type of the instance is a pointer, then we need to deference
// the pointer one level to get its value. Otherwise, we can access the
// value directly.
if reflect.TypeOf(instance).Kind() == reflect.Ptr {
val = reflect.ValueOf(instance).Elem()
} else {
val = reflect.ValueOf(instance)
}
return val
}