From a432f9a4c8dc2b9d07c909bc0ae465783a4994d2 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:28:12 -0700 Subject: [PATCH 01/14] lnrpc/signrpc: create new signrpc package with build-flag guarded config In this commit, we introduce a new sub-package within the greater RPC package. This new sub-package will house a new set of sub-RPC servers to expose experimental features behind build flags for upstream consumers. In this commit, we add the first config for the service, which will simply expose the lnwallet.Signer interface over RPC. In the default file, we have what the config will be if the build tag (signerrpc) is off. In this case, the config parser won't detect any times, and if specified will error out. In the active file, we have the true config that the server will use. With this new set up, we'll exploit these build flags heavily in order to create a generalized framework for adding additional sub RPC servers. --- lnrpc/signrpc/config_active.go | 33 +++++++++++++++++++++++++++++++++ lnrpc/signrpc/config_default.go | 10 ++++++++++ 2 files changed, 43 insertions(+) create mode 100644 lnrpc/signrpc/config_active.go create mode 100644 lnrpc/signrpc/config_default.go diff --git a/lnrpc/signrpc/config_active.go b/lnrpc/signrpc/config_active.go new file mode 100644 index 000000000..ab576d9d1 --- /dev/null +++ b/lnrpc/signrpc/config_active.go @@ -0,0 +1,33 @@ +// +build signerrpc + +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 +} diff --git a/lnrpc/signrpc/config_default.go b/lnrpc/signrpc/config_default.go new file mode 100644 index 000000000..3341296fc --- /dev/null +++ b/lnrpc/signrpc/config_default.go @@ -0,0 +1,10 @@ +// +build !signerrpc + +package signrpc + +// 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{} From bbbdd7f6e9aa755aa9d3766b9908469ef1a53516 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:30:02 -0700 Subject: [PATCH 02/14] lnrpc/signrpc: add and generate new set of protos for Signer service --- lnrpc/signrpc/driver.go | 66 +++++ lnrpc/signrpc/signer.pb.go | 481 +++++++++++++++++++++++++++++++++++++ lnrpc/signrpc/signer.proto | 118 +++++++++ 3 files changed, 665 insertions(+) create mode 100644 lnrpc/signrpc/driver.go create mode 100644 lnrpc/signrpc/signer.pb.go create mode 100644 lnrpc/signrpc/signer.proto diff --git a/lnrpc/signrpc/driver.go b/lnrpc/signrpc/driver.go new file mode 100644 index 000000000..7065d8e4e --- /dev/null +++ b/lnrpc/signrpc/driver.go @@ -0,0 +1,66 @@ +// +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) + } + + // 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. + switch { + 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)) + } +} diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go new file mode 100644 index 000000000..c0ed582d3 --- /dev/null +++ b/lnrpc/signrpc/signer.pb.go @@ -0,0 +1,481 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: signer.proto + +/* +Package signrpc is a generated protocol buffer package. + +It is generated from these files: + signer.proto + +It has these top-level messages: + KeyLocator + KeyDescriptor + TxOut + SignDescriptor + SignReq + SignResp +*/ +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 +} + +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") +} + +// 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're unable to sign using the specified keys, then an error will be + // returned. + SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, 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 +} + +// 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're unable to sign using the specified keys, then an error will be + // returned. + SignOutputRaw(context.Context, *SignReq) (*SignResp, 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) +} + +var _Signer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "signrpc.Signer", + HandlerType: (*SignerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SignOutputRaw", + Handler: _Signer_SignOutputRaw_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "signer.proto", +} + +func init() { proto.RegisterFile("signer.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 465 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0x41, 0x8f, 0xd3, 0x3e, + 0x10, 0xc5, 0xb7, 0xcd, 0x3f, 0x4d, 0x76, 0x92, 0x54, 0x7f, 0xcc, 0x0a, 0x02, 0x08, 0x51, 0x22, + 0x16, 0xf5, 0x54, 0x44, 0x41, 0x1c, 0x38, 0x70, 0x58, 0x21, 0x54, 0x54, 0xa4, 0x95, 0xdc, 0xde, + 0x23, 0x37, 0x35, 0x59, 0x2b, 0x21, 0xc9, 0xc6, 0x0e, 0xa9, 0x3f, 0x07, 0x5f, 0x18, 0x8d, 0x9d, + 0xed, 0x76, 0x39, 0xb5, 0xef, 0x79, 0x32, 0xf3, 0xf3, 0x3c, 0xc3, 0x85, 0x14, 0x79, 0xd5, 0x36, + 0xd9, 0x3b, 0xfc, 0xe5, 0xed, 0xa2, 0x69, 0x6b, 0x55, 0x13, 0x6f, 0x70, 0x93, 0x15, 0xc0, 0x9a, + 0xeb, 0x1f, 0x75, 0xc6, 0x54, 0xdd, 0x92, 0x97, 0x00, 0x05, 0xd7, 0xe9, 0x4f, 0xf6, 0x4b, 0x94, + 0x3a, 0x1e, 0xcd, 0x46, 0x73, 0x97, 0x9e, 0x17, 0x5c, 0x7f, 0x33, 0x06, 0x79, 0x01, 0x28, 0x52, + 0x51, 0xed, 0xf9, 0x21, 0x1e, 0x9b, 0x53, 0xbf, 0xe0, 0xfa, 0x3b, 0xea, 0xa4, 0x84, 0x68, 0xcd, + 0xf5, 0x57, 0x2e, 0xb3, 0x56, 0x34, 0xd8, 0xec, 0x0d, 0x44, 0x2d, 0xeb, 0x53, 0xfc, 0x62, 0xa7, + 0x15, 0x97, 0xa6, 0x5f, 0xb8, 0x3a, 0xa3, 0x41, 0xcb, 0xfa, 0x35, 0xd7, 0x57, 0x68, 0x92, 0x05, + 0x78, 0x58, 0x51, 0xd6, 0x99, 0xe9, 0x18, 0x2c, 0x1f, 0x2f, 0x06, 0xb6, 0xc5, 0x3d, 0xd8, 0xea, + 0x8c, 0x4e, 0x0a, 0xa3, 0xae, 0x5c, 0x70, 0x0a, 0xae, 0x93, 0xcf, 0xe0, 0x6e, 0x0f, 0xd7, 0x9d, + 0x22, 0x17, 0xe0, 0xfe, 0x66, 0x65, 0xc7, 0x4d, 0x77, 0x87, 0x5a, 0x81, 0xa4, 0x4d, 0x91, 0x5a, + 0x14, 0xd3, 0x37, 0xa4, 0x7e, 0x53, 0x6c, 0x8c, 0x4e, 0xfe, 0x8c, 0x61, 0xba, 0x11, 0x79, 0x75, + 0xc2, 0xfa, 0x1e, 0xf0, 0x22, 0xe9, 0x9e, 0xcb, 0xcc, 0x34, 0x0a, 0x96, 0x4f, 0x4e, 0x31, 0xee, + 0x2b, 0x29, 0xd2, 0xa2, 0x24, 0xaf, 0x21, 0x94, 0xa2, 0xca, 0x4b, 0x9e, 0xaa, 0x9e, 0xb3, 0x62, + 0x98, 0x12, 0x58, 0x6f, 0x8b, 0x16, 0x96, 0xec, 0xeb, 0x6e, 0x77, 0x2c, 0x71, 0x6c, 0x89, 0xf5, + 0x6c, 0xc9, 0x25, 0x4c, 0x7b, 0xa1, 0x2a, 0x2e, 0xe5, 0x1d, 0xed, 0x7f, 0xa6, 0x28, 0x1a, 0x5c, + 0x8b, 0x4c, 0xde, 0xc2, 0xa4, 0xee, 0x54, 0xd3, 0xa9, 0xd8, 0x35, 0x74, 0xd3, 0x23, 0x9d, 0xd9, + 0x02, 0x1d, 0x4e, 0x49, 0x0c, 0x98, 0xec, 0x0d, 0x93, 0x37, 0xb1, 0x37, 0x1b, 0xcd, 0x23, 0x7a, + 0x27, 0xc9, 0x2b, 0x08, 0x44, 0xd5, 0x74, 0x6a, 0x48, 0xcf, 0x37, 0xe9, 0x81, 0xb1, 0x6c, 0x7e, + 0x19, 0x78, 0xb8, 0x14, 0xca, 0x6f, 0xc9, 0x0c, 0x42, 0x4c, 0x4e, 0x1d, 0x4e, 0x83, 0xa3, 0xd0, + 0xb2, 0x7e, 0x7b, 0xb0, 0xa9, 0x7d, 0x02, 0x40, 0x00, 0xb3, 0x30, 0x19, 0x8f, 0x67, 0xce, 0x3c, + 0x58, 0x3e, 0x3d, 0x32, 0x3d, 0x5c, 0x2e, 0x3d, 0x97, 0x83, 0x96, 0xc9, 0x25, 0xf8, 0x76, 0x88, + 0x6c, 0xc8, 0x33, 0xf0, 0x71, 0x8a, 0x14, 0x39, 0x4e, 0x70, 0xe6, 0x21, 0xf5, 0x5a, 0xd6, 0x6f, + 0x44, 0x2e, 0x97, 0x5f, 0x60, 0xb2, 0x31, 0xcf, 0x95, 0x7c, 0x84, 0x08, 0xff, 0x5d, 0x9b, 0xeb, + 0x51, 0xd6, 0x93, 0xff, 0x1f, 0x4c, 0xa1, 0xfc, 0xf6, 0xf9, 0xa3, 0x7f, 0x1c, 0xd9, 0xec, 0x26, + 0xe6, 0x95, 0x7f, 0xf8, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xd0, 0x6b, 0x86, 0xc5, 0xfd, 0x02, 0x00, + 0x00, +} diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto new file mode 100644 index 000000000..2048b3ad3 --- /dev/null +++ b/lnrpc/signrpc/signer.proto @@ -0,0 +1,118 @@ +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; +} + +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're unable to sign using the specified keys, then an error will be + returned. + */ + rpc SignOutputRaw(SignReq) returns (SignResp); +} From 9f24049bbca49448fcd5f3992a10d8f1dfdde287 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:32:34 -0700 Subject: [PATCH 03/14] lnrpc/signrpc: add new gen_protos.sh to generate minimal protos In this commit, we add a new proto generation script to match the one in the main lnrpc package. This script differs, as we don't need to generate the REST proxy stuff (for now). --- lnrpc/signrpc/gen_protos.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 lnrpc/signrpc/gen_protos.sh diff --git a/lnrpc/signrpc/gen_protos.sh b/lnrpc/signrpc/gen_protos.sh new file mode 100755 index 000000000..86a583a8c --- /dev/null +++ b/lnrpc/signrpc/gen_protos.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +protoc -I/usr/local/include -I. \ + -I$GOPATH/src \ + --go_out=plugins=grpc:. \ + signer.proto From 184f160fb9740e8399db60e6ee35131d22fb8dd7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:37:02 -0700 Subject: [PATCH 04/14] lnrpc: add new sub RPC server registration system In this commit, we add the scafolding for the future sub-server RPC system. The idea is that each sub server will implement this particular interface. From there on, a "root" RPC server is able to query this registry, and dynamically create each sub-sever instance without knowing the details of each sub-server. In the init() method of the pacakge of a sub-server, the sub-server is to call: RegisterSubServer to claim its namespace. Afterwards, the root RPC server can use the RegisteredSubServers() method to obtain a slice of ALL regsitered sub-servers. Once this list is obtained, it can use the New() method of the SubServerDriver struct to create a new sub-server instance. Each sub-server needs to be able to locate it's primary config using the SubServerConfigDispatcher interface. This can be a map of maps, or a regular config structr. The main requirement is that the sub-server be able to find a config under the same name that it registered with. This string of abstractions will allow the main RPC server to find, create, and run each sub-server without knowing the details of its configuration or its role. --- lnrpc/sub_server.go | 131 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 lnrpc/sub_server.go diff --git a/lnrpc/sub_server.go b/lnrpc/sub_server.go new file mode 100644 index 000000000..1a3f58bb6 --- /dev/null +++ b/lnrpc/sub_server.go @@ -0,0 +1,131 @@ +package lnrpc + +import ( + fmt "fmt" + "sync" + + "google.golang.org/grpc" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// MacaroonPerms is a map from the FullMethod of an invoked gRPC command. to +// 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 use with or independent of the +// main RPC server. The main rpcserver will create, start, stop, and manage +// each ubs-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 +} From b7757683b28111354b3cfbe0096760e5097e8ca1 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:39:51 -0700 Subject: [PATCH 05/14] lnrpc/signrpc: implement new SignerServer sub RPC server In this commit, we add a full implementation of the new SignerServer sub RPC service within the main root RPC service. This service is able to fully manage its macaroons, and service any connected clients. Atm, this service only has a single method: SignOutputRaw which mimics the existing lnwallet.Signer interface within lnd itself. As the API's are so similar, it will be possible for a client to directly use the lnwallet.Signer interface, and have a proxy that sends the request over RPC, and translates the proto layer on both sides. To the client, it doesn't know that it's using a remote, or local RPC. --- lnrpc/signrpc/log.go | 45 +++++ lnrpc/signrpc/signer_server.go | 315 +++++++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 lnrpc/signrpc/log.go create mode 100644 lnrpc/signrpc/signer_server.go diff --git a/lnrpc/signrpc/log.go b/lnrpc/signrpc/log.go new file mode 100644 index 000000000..bb9fc94dc --- /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 000000000..3d75bc587 --- /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 +} From 8971931aa3552d888058bf05d5517d47479c0e89 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:41:14 -0700 Subject: [PATCH 06/14] lnrpc/signrpc: add lnrpc.SubServerDriver for signrpc In this commit, we create a lnrpc.SubServerDriver for signrpc. Note that this file will only have its init() method executed if the proper build flag is on. As a result, only if the build flag is set, will the RPC server be registered, and visible at the packge lnrpc level for the root server to manipulate. --- lnrpc/signrpc/config_active.go | 2 +- lnrpc/signrpc/config_default.go | 8 ++------ lnrpc/signrpc/driver.go | 9 +++++++-- lnrpc/sub_server.go | 10 +++++----- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lnrpc/signrpc/config_active.go b/lnrpc/signrpc/config_active.go index ab576d9d1..a72d8fbd0 100644 --- a/lnrpc/signrpc/config_active.go +++ b/lnrpc/signrpc/config_active.go @@ -1,4 +1,4 @@ -// +build signerrpc +// +build signrpc package signrpc diff --git a/lnrpc/signrpc/config_default.go b/lnrpc/signrpc/config_default.go index 3341296fc..7698f19fb 100644 --- a/lnrpc/signrpc/config_default.go +++ b/lnrpc/signrpc/config_default.go @@ -1,10 +1,6 @@ -// +build !signerrpc +// +build !signrpc package signrpc -// 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. +// Config is empty for non-signrpc builds. type Config struct{} diff --git a/lnrpc/signrpc/driver.go b/lnrpc/signrpc/driver.go index 7065d8e4e..b598c33c4 100644 --- a/lnrpc/signrpc/driver.go +++ b/lnrpc/signrpc/driver.go @@ -34,10 +34,13 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( &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. - switch { case config.MacService != nil && config.NetworkDir == "": return nil, nil, fmt.Errorf("NetworkDir must be set to create " + "Signrpc") @@ -52,7 +55,9 @@ func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( func init() { subServer := &lnrpc.SubServerDriver{ SubServerName: subServerName, - New: func(c lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + New: func(c lnrpc.SubServerConfigDispatcher) ( + lnrpc.SubServer, lnrpc.MacaroonPerms, error) { + return createNewSubServer(c) }, } diff --git a/lnrpc/sub_server.go b/lnrpc/sub_server.go index 1a3f58bb6..1e58a2a63 100644 --- a/lnrpc/sub_server.go +++ b/lnrpc/sub_server.go @@ -1,24 +1,24 @@ package lnrpc import ( - fmt "fmt" + "fmt" "sync" "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" ) -// MacaroonPerms is a map from the FullMethod of an invoked gRPC command. to -// the set of operations that the macaroon presented with the command MUST +// 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 use with or independent of the +// 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 ubs-server in a generalized manner. +// each sub-server in a generalized manner. type SubServer interface { // Start starts the sub-server and all goroutines it needs to operate. Start() error From 16d16cf1b65607f593419d6ad8bfb15ed60c9f1f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:42:21 -0700 Subject: [PATCH 07/14] log: register logger for new Signer RPC Service, namespace SGNR --- log.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/log.go b/log.go index a041cbe8e..52d78941f 100644 --- a/log.go +++ b/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 From 4002caec69b7140fe0ac6779a23d22070f8cf78e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 17:58:30 -0700 Subject: [PATCH 08/14] 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. --- config.go | 6 ++ lnrpc/signrpc/signer_server.go | 6 +- subrpcserver_config.go | 128 +++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 subrpcserver_config.go diff --git a/config.go b/config.go index a571bacca..4bd06c707 100644 --- a/config.go +++ b/config.go @@ -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, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 3d75bc587..de48a0ddc 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -1,4 +1,4 @@ -// +build signerrpc +// +build signrpc package signrpc @@ -16,7 +16,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" - grpc "google.golang.org/grpc" + "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" ) @@ -75,7 +75,7 @@ func fileExists(name string) bool { } // 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 +// 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. diff --git a/subrpcserver_config.go b/subrpcserver_config.go new file mode 100644 index 000000000..da8f195c3 --- /dev/null +++ b/subrpcserver_config.go @@ -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"` +} + +// PopulateDependancies 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) PopulateDependancies(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 +} From ff47ade13b9e326907ff408a9f080a17a960e1ff Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 18:03:07 -0700 Subject: [PATCH 09/14] lnd+rpc: modify rpcServer to fully manaage listeners and gRPC, handle sub-servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we modify the existing rpcServer to fully manage the macaroons, gRPC server, and also seek out and create all sub-servers. With this change, the RPC server gains more responsibility, as it becomes the "root" server in the hierarchy of gRPC sub-servers. In addition to creating each sub-server, it will also merge the set of macaroon permissions for each sub-server, with the permissions of the rest of the RPC infra. As a result, each sub-server is able to independently specify what it needs w.r.t macaroon permissions and have that taken care of by the RPC server. In order to achieve this, we need to unify the creation of the RPC interceptors, and also fully manage the gRPC server ourselves. Some examples with various build configs: ``` ⛰i make build Building debug lnd and lncli. go build -v -tags="dev" -o lnd-debug -ldflags "-X github.com/lightningnetwork/lnd/build.Commit=v0.5-beta-143-gb2069914c4b76109b7c59320dc48f8a5f30deb75-dirty" github.com/lightningnetwork/lnd go build -v -tags="dev" -o lncli-debug -ldflags "-X github.com/lightningnetwork/lnd/build.Commit=v0.5-beta-143-gb2069914c4b76109b7c59320dc48f8a5f30deb75-dirty" github.com/lightningnetwork/lnd/cmd/lncli ⛰i ./lnd-debug --debuglevel=debug --signrpc.signermacaroonpath=~/sign.macaroon unknown flag `signrpc.signermacaroonpath' unknown flag `signrpc.signermacaroonpath' ⛰i make build tags=signerrpc Building debug lnd and lncli. go build -v -tags="dev signerrpc" -o lnd-debug -ldflags "-X github.com/lightningnetwork/lnd/build.Commit=v0.5-beta-143-gb2069914c4b76109b7c59320dc48f8a5f30deb75-dirty" github.com/lightningnetwork/lnd go build -v -tags="dev signerrpc" -o lncli-debug -ldflags "-X github.com/lightningnetwork/lnd/build.Commit=v0.5-beta-143-gb2069914c4b76109b7c59320dc48f8a5f30deb75-dirty" github.com/lightningnetwork/lnd/cmd/lncli ⛰i ./lnd-debug --debuglevel=debug --signrpc.signermacaroonpath=~/sign.macaroon 2018-10-22 17:31:01.132 [INF] LTND: Version: 0.5.0-beta commit=v0.5-beta-143-gb2069914c4b76109b7c59320dc48f8a5f30deb75-dirty, build=development, logging=default 2018-10-22 17:31:01.133 [INF] LTND: Active chain: Bitcoin (network=simnet) 2018-10-22 17:31:01.140 [INF] CHDB: Checking for schema update: latest_version=6, db_version=6 2018-10-22 17:31:01.236 [INF] LTND: Primary chain is set to: bitcoin 2018-10-22 17:31:02.391 [INF] LNWL: Opened wallet 2018-10-22 17:31:03.315 [INF] LNWL: The wallet has been unlocked without a time limit 2018-10-22 17:31:03.315 [INF] LTND: LightningWallet opened 2018-10-22 17:31:03.319 [INF] LNWL: Catching up block hashes to height 3060, this will take a while... 2018-10-22 17:31:03.320 [INF] HSWC: Restoring in-memory circuit state from disk 2018-10-22 17:31:03.320 [INF] LNWL: Done catching up block hashes 2018-10-22 17:31:03.320 [INF] HSWC: Payment circuits loaded: num_pending=0, num_open=0 2018-10-22 17:31:03.322 [DBG] LTND: Populating dependencies for sub RPC server: Signrpc ``` As for the config, an example is: ``` [signrpc] signrpc.signermacaroonpath=~/signer.macaroon ``` --- lnd.go | 68 +++------------- rpcserver.go | 221 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 227 insertions(+), 62 deletions(-) diff --git a/lnd.go b/lnd.go index 082cddf81..6b5dd1884 100644 --- a/lnd.go +++ b/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 { diff --git a/rpcserver.go b/rpcserver.go index 1e7f7986d..ccdcfc15c 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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.PopulateDependancies( + 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,28 @@ 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 { + 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 } From 06d5f2db37604bf3a11dbedf40ad0b6b058af8d8 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 22 Oct 2018 18:05:37 -0700 Subject: [PATCH 10/14] macaroons: update line folding to project style --- macaroons/service.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/macaroons/service.go b/macaroons/service.go index 3b3ea7fe0..618eb0f8f 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -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 } From 35b4b35eaeff4c96609bd72e2d45ddd2fb5d8daa Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Oct 2018 21:14:48 -0700 Subject: [PATCH 11/14] lnrpc: add new recursive proto generation script In this commit, we add a recursive proto generation script. This avoids having to add a new script for each upcoming sub-server. --- lnrpc/gen_protos.sh | 17 +++++++++++++++-- lnrpc/rpc.pb.go | 20 +++++++++++++++----- lnrpc/signrpc/gen_protos.sh | 6 ------ lnrpc/signrpc/signer.pb.go | 8 ++++---- 4 files changed, 34 insertions(+), 17 deletions(-) delete mode 100755 lnrpc/signrpc/gen_protos.sh diff --git a/lnrpc/gen_protos.sh b/lnrpc/gen_protos.sh index 6bc0d1c14..3db69c858 100755 --- a/lnrpc/gen_protos.sh +++ b/lnrpc/gen_protos.sh @@ -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 diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index 3f3ba8aa0..f39eed682 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -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"` diff --git a/lnrpc/signrpc/gen_protos.sh b/lnrpc/signrpc/gen_protos.sh deleted file mode 100755 index 86a583a8c..000000000 --- a/lnrpc/signrpc/gen_protos.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -protoc -I/usr/local/include -I. \ - -I$GOPATH/src \ - --go_out=plugins=grpc:. \ - signer.proto diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go index c0ed582d3..4a5687642 100644 --- a/lnrpc/signrpc/signer.pb.go +++ b/lnrpc/signrpc/signer.pb.go @@ -1,11 +1,11 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: signer.proto +// source: signrpc/signer.proto /* Package signrpc is a generated protocol buffer package. It is generated from these files: - signer.proto + signrpc/signer.proto It has these top-level messages: KeyLocator @@ -441,10 +441,10 @@ var _Signer_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "signer.proto", + Metadata: "signrpc/signer.proto", } -func init() { proto.RegisterFile("signer.proto", fileDescriptor0) } +func init() { proto.RegisterFile("signrpc/signer.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 465 bytes of a gzipped FileDescriptorProto From a8ac3cfe7dd0f560bd0a4acb5ab341f4f7cd7b40 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 2 Nov 2018 15:41:38 -0700 Subject: [PATCH 12/14] lnd+rpc: fix linter errors --- config.go | 4 ++-- lnd.go | 2 +- rpcserver.go | 12 +++++++----- subrpcserver_config.go | 10 +++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/config.go b/config.go index 4bd06c707..050d4a2b2 100644 --- a/config.go +++ b/config.go @@ -221,7 +221,7 @@ type config struct { Tor *torConfig `group:"Tor" namespace:"tor"` - SubRpcServers *subRpcServerConfigs `group:"subrpc"` + SubRPCServers *subRPCServerConfigs `group:"subrpc"` Hodl *hodl.Config `group:"hodl" namespace:"hodl"` @@ -298,7 +298,7 @@ func loadConfig() (*config, error) { }, MaxPendingChannels: defaultMaxPendingChannels, NoSeedBackup: defaultNoSeedBackup, - SubRpcServers: &subRpcServerConfigs{ + SubRPCServers: &subRPCServerConfigs{ SignRPC: &signrpc.Config{}, }, Autopilot: &autoPilotConfig{ diff --git a/lnd.go b/lnd.go index 6b5dd1884..b9f0a59ab 100644 --- a/lnd.go +++ b/lnd.go @@ -315,7 +315,7 @@ func lndMain() error { // Initialize, and register our implementation of the gRPC interface // exported by the rpcServer. rpcServer, err := newRPCServer( - server, macaroonService, cfg.SubRpcServers, serverOpts, + server, macaroonService, cfg.SubRPCServers, serverOpts, proxyOpts, tlsConf, ) if err != nil { diff --git a/rpcserver.go b/rpcserver.go index ccdcfc15c..73125c6c6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -383,7 +383,7 @@ var _ lnrpc.LightningServer = (*rpcServer)(nil) // 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, + subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption, restServerOpts []grpc.DialOption, tlsCfg *tls.Config) (*rpcServer, error) { @@ -395,7 +395,7 @@ func newRPCServer(s *server, macService *macaroons.Service, // 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.PopulateDependancies( + err := subServerCgs.PopulateDependencies( s.cc, networkDir, macService, ) if err != nil { @@ -454,7 +454,7 @@ func newRPCServer(s *server, macService *macaroons.Service, // 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{ + rootRPCServer := &rpcServer{ restServerOpts: restServerOpts, subServers: subServers, tlsCfg: tlsCfg, @@ -462,7 +462,7 @@ func newRPCServer(s *server, macService *macaroons.Service, server: s, quit: make(chan struct{}, 1), } - lnrpc.RegisterLightningServer(grpcServer, rootRpcServer) + 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 @@ -476,7 +476,7 @@ func newRPCServer(s *server, macService *macaroons.Service, } } - return rootRpcServer, nil + return rootRPCServer, nil } // Start launches any helper goroutines required for the rpcServer to function. @@ -572,6 +572,8 @@ func (r *rpcServer) Stop() error { subServer.Name()) if err := subServer.Stop(); err != nil { + rpcsLog.Errorf("unable to stop sub-server %v: %v", + subServer.Name(), err) continue } } diff --git a/subrpcserver_config.go b/subrpcserver_config.go index da8f195c3..0abb969b4 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -8,25 +8,25 @@ import ( "github.com/lightningnetwork/lnd/macaroons" ) -// subRpcServerConfigs is special sub-config in the main configuration that +// 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 { +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"` } -// PopulateDependancies attempts to iterate through all the sub-server configs +// 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) PopulateDependancies(cc *chainControl, +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 @@ -80,7 +80,7 @@ func (s *subRpcServerConfigs) PopulateDependancies(cc *chainControl, // 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) { +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) From 6c201e435a3c4cf51ac357592bb4d004244abf22 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 28 Nov 2018 20:08:00 -0800 Subject: [PATCH 13/14] lnrpc/signrpc: add ComputeInputScript to the Signer sub-server In this commit, we add the ComputeInputScript which will allow callers to obtain witnesses for all outputs under control of the wallet. This allows external scripting of things like coin join, etc. --- lnrpc/signrpc/signer.pb.go | 173 ++++++++++++++++++++++++++++++------- lnrpc/signrpc/signer.proto | 32 ++++++- 2 files changed, 171 insertions(+), 34 deletions(-) diff --git a/lnrpc/signrpc/signer.pb.go b/lnrpc/signrpc/signer.pb.go index 4a5687642..8db42b2d3 100644 --- a/lnrpc/signrpc/signer.pb.go +++ b/lnrpc/signrpc/signer.pb.go @@ -14,6 +14,8 @@ It has these top-level messages: SignDescriptor SignReq SignResp + InputScript + InputScriptResp */ package signrpc @@ -345,6 +347,50 @@ func (m *SignResp) GetRawSigs() [][]byte { 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") @@ -352,6 +398,8 @@ func init() { 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. @@ -372,9 +420,21 @@ type SignerClient interface { // 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're unable to sign using the specified keys, then an error will be + // 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 { @@ -394,6 +454,15 @@ func (c *signerClient) SignOutputRaw(ctx context.Context, in *SignReq, opts ...g 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 { @@ -404,9 +473,21 @@ type SignerServer interface { // 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're unable to sign using the specified keys, then an error will be + // 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) { @@ -431,6 +512,24 @@ func _Signer_SignOutputRaw_Handler(srv interface{}, ctx context.Context, dec fun 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), @@ -439,6 +538,10 @@ var _Signer_serviceDesc = grpc.ServiceDesc{ MethodName: "SignOutputRaw", Handler: _Signer_SignOutputRaw_Handler, }, + { + MethodName: "ComputeInputScript", + Handler: _Signer_ComputeInputScript_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "signrpc/signer.proto", @@ -447,35 +550,39 @@ var _Signer_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("signrpc/signer.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 465 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x92, 0x41, 0x8f, 0xd3, 0x3e, - 0x10, 0xc5, 0xb7, 0xcd, 0x3f, 0x4d, 0x76, 0x92, 0x54, 0x7f, 0xcc, 0x0a, 0x02, 0x08, 0x51, 0x22, - 0x16, 0xf5, 0x54, 0x44, 0x41, 0x1c, 0x38, 0x70, 0x58, 0x21, 0x54, 0x54, 0xa4, 0x95, 0xdc, 0xde, - 0x23, 0x37, 0x35, 0x59, 0x2b, 0x21, 0xc9, 0xc6, 0x0e, 0xa9, 0x3f, 0x07, 0x5f, 0x18, 0x8d, 0x9d, - 0xed, 0x76, 0x39, 0xb5, 0xef, 0x79, 0x32, 0xf3, 0xf3, 0x3c, 0xc3, 0x85, 0x14, 0x79, 0xd5, 0x36, - 0xd9, 0x3b, 0xfc, 0xe5, 0xed, 0xa2, 0x69, 0x6b, 0x55, 0x13, 0x6f, 0x70, 0x93, 0x15, 0xc0, 0x9a, - 0xeb, 0x1f, 0x75, 0xc6, 0x54, 0xdd, 0x92, 0x97, 0x00, 0x05, 0xd7, 0xe9, 0x4f, 0xf6, 0x4b, 0x94, - 0x3a, 0x1e, 0xcd, 0x46, 0x73, 0x97, 0x9e, 0x17, 0x5c, 0x7f, 0x33, 0x06, 0x79, 0x01, 0x28, 0x52, - 0x51, 0xed, 0xf9, 0x21, 0x1e, 0x9b, 0x53, 0xbf, 0xe0, 0xfa, 0x3b, 0xea, 0xa4, 0x84, 0x68, 0xcd, - 0xf5, 0x57, 0x2e, 0xb3, 0x56, 0x34, 0xd8, 0xec, 0x0d, 0x44, 0x2d, 0xeb, 0x53, 0xfc, 0x62, 0xa7, - 0x15, 0x97, 0xa6, 0x5f, 0xb8, 0x3a, 0xa3, 0x41, 0xcb, 0xfa, 0x35, 0xd7, 0x57, 0x68, 0x92, 0x05, - 0x78, 0x58, 0x51, 0xd6, 0x99, 0xe9, 0x18, 0x2c, 0x1f, 0x2f, 0x06, 0xb6, 0xc5, 0x3d, 0xd8, 0xea, - 0x8c, 0x4e, 0x0a, 0xa3, 0xae, 0x5c, 0x70, 0x0a, 0xae, 0x93, 0xcf, 0xe0, 0x6e, 0x0f, 0xd7, 0x9d, - 0x22, 0x17, 0xe0, 0xfe, 0x66, 0x65, 0xc7, 0x4d, 0x77, 0x87, 0x5a, 0x81, 0xa4, 0x4d, 0x91, 0x5a, - 0x14, 0xd3, 0x37, 0xa4, 0x7e, 0x53, 0x6c, 0x8c, 0x4e, 0xfe, 0x8c, 0x61, 0xba, 0x11, 0x79, 0x75, - 0xc2, 0xfa, 0x1e, 0xf0, 0x22, 0xe9, 0x9e, 0xcb, 0xcc, 0x34, 0x0a, 0x96, 0x4f, 0x4e, 0x31, 0xee, - 0x2b, 0x29, 0xd2, 0xa2, 0x24, 0xaf, 0x21, 0x94, 0xa2, 0xca, 0x4b, 0x9e, 0xaa, 0x9e, 0xb3, 0x62, - 0x98, 0x12, 0x58, 0x6f, 0x8b, 0x16, 0x96, 0xec, 0xeb, 0x6e, 0x77, 0x2c, 0x71, 0x6c, 0x89, 0xf5, - 0x6c, 0xc9, 0x25, 0x4c, 0x7b, 0xa1, 0x2a, 0x2e, 0xe5, 0x1d, 0xed, 0x7f, 0xa6, 0x28, 0x1a, 0x5c, - 0x8b, 0x4c, 0xde, 0xc2, 0xa4, 0xee, 0x54, 0xd3, 0xa9, 0xd8, 0x35, 0x74, 0xd3, 0x23, 0x9d, 0xd9, - 0x02, 0x1d, 0x4e, 0x49, 0x0c, 0x98, 0xec, 0x0d, 0x93, 0x37, 0xb1, 0x37, 0x1b, 0xcd, 0x23, 0x7a, - 0x27, 0xc9, 0x2b, 0x08, 0x44, 0xd5, 0x74, 0x6a, 0x48, 0xcf, 0x37, 0xe9, 0x81, 0xb1, 0x6c, 0x7e, - 0x19, 0x78, 0xb8, 0x14, 0xca, 0x6f, 0xc9, 0x0c, 0x42, 0x4c, 0x4e, 0x1d, 0x4e, 0x83, 0xa3, 0xd0, - 0xb2, 0x7e, 0x7b, 0xb0, 0xa9, 0x7d, 0x02, 0x40, 0x00, 0xb3, 0x30, 0x19, 0x8f, 0x67, 0xce, 0x3c, - 0x58, 0x3e, 0x3d, 0x32, 0x3d, 0x5c, 0x2e, 0x3d, 0x97, 0x83, 0x96, 0xc9, 0x25, 0xf8, 0x76, 0x88, - 0x6c, 0xc8, 0x33, 0xf0, 0x71, 0x8a, 0x14, 0x39, 0x4e, 0x70, 0xe6, 0x21, 0xf5, 0x5a, 0xd6, 0x6f, - 0x44, 0x2e, 0x97, 0x5f, 0x60, 0xb2, 0x31, 0xcf, 0x95, 0x7c, 0x84, 0x08, 0xff, 0x5d, 0x9b, 0xeb, - 0x51, 0xd6, 0x93, 0xff, 0x1f, 0x4c, 0xa1, 0xfc, 0xf6, 0xf9, 0xa3, 0x7f, 0x1c, 0xd9, 0xec, 0x26, - 0xe6, 0x95, 0x7f, 0xf8, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xd0, 0x6b, 0x86, 0xc5, 0xfd, 0x02, 0x00, - 0x00, + // 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, } diff --git a/lnrpc/signrpc/signer.proto b/lnrpc/signrpc/signer.proto index 2048b3ad3..6ebd51c0f 100644 --- a/lnrpc/signrpc/signer.proto +++ b/lnrpc/signrpc/signer.proto @@ -103,6 +103,22 @@ message SignResp { 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 @@ -111,8 +127,22 @@ service Signer { 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're unable to sign using the specified keys, then an error will be + 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); } From b0a7a57f57d118b3e0c6238cea908005b8ed7473 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 28 Nov 2018 20:08:33 -0800 Subject: [PATCH 14/14] lnrpc/signrpc: add ComputeInputScript implementation --- lnrpc/signrpc/signer_server.go | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index de48a0ddc..1a90bc239 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -313,3 +313,78 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err 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 +}