Merge pull request from guggero/musig2-session-cleanup

MuSig2: Add session cleanup RPC
This commit is contained in:
Oliver Gugger 2022-05-04 21:30:52 +02:00 committed by GitHub
commit 48695dd801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 525 additions and 69 deletions

@ -224,6 +224,10 @@ from occurring that would result in an erroneous force close.](https://github.co
* [Clarify comment](https://github.com/lightningnetwork/lnd/pull/6481) on
`chainnotifier.RegisterConfirmationsNtfn`.
* [The experimental MuSig2 API now also has a `MuSig2Cleanup` RPC that allows
the in-memory state to be cleaned up early if a session isn't expected to
succeed anymore](https://github.com/lightningnetwork/lnd/pull/6495).
## RPC Server
* [Add value to the field

@ -58,6 +58,9 @@ type MuSig2Signer interface {
// returned.
MuSig2CombineSig(MuSig2SessionID,
[]*musig2.PartialSignature) (*schnorr.Signature, bool, error)
// MuSig2Cleanup removes a session from memory to free up resources.
MuSig2Cleanup(MuSig2SessionID) error
}
// MuSig2SessionInfo is a struct for keeping track of a signing session

@ -178,6 +178,11 @@ func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID,
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (m *MockSigner) MuSig2Cleanup(MuSig2SessionID) error {
return nil
}
// findKey searches through all stored private keys and returns one
// corresponding to the hashed pubkey if it can be found. The public key may
// either correspond directly to the private key or to the private key with a

@ -1743,6 +1743,93 @@ func (x *MuSig2CombineSigResponse) GetFinalSignature() []byte {
return nil
}
type MuSig2CleanupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
//
//The unique ID of the signing session that should be removed/cleaned up.
SessionId []byte `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
}
func (x *MuSig2CleanupRequest) Reset() {
*x = MuSig2CleanupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CleanupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CleanupRequest) ProtoMessage() {}
func (x *MuSig2CleanupRequest) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CleanupRequest.ProtoReflect.Descriptor instead.
func (*MuSig2CleanupRequest) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{26}
}
func (x *MuSig2CleanupRequest) GetSessionId() []byte {
if x != nil {
return x.SessionId
}
return nil
}
type MuSig2CleanupResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *MuSig2CleanupResponse) Reset() {
*x = MuSig2CleanupResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_signrpc_signer_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *MuSig2CleanupResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*MuSig2CleanupResponse) ProtoMessage() {}
func (x *MuSig2CleanupResponse) ProtoReflect() protoreflect.Message {
mi := &file_signrpc_signer_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use MuSig2CleanupResponse.ProtoReflect.Descriptor instead.
func (*MuSig2CleanupResponse) Descriptor() ([]byte, []int) {
return file_signrpc_signer_proto_rawDescGZIP(), []int{27}
}
var File_signrpc_signer_proto protoreflect.FileDescriptor
var file_signrpc_signer_proto_rawDesc = []byte{
@ -1937,7 +2024,12 @@ var file_signrpc_signer_proto_rawDesc = []byte{
0x52, 0x11, 0x68, 0x61, 0x76, 0x65, 0x41, 0x6c, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
0x72, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x67,
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x69,
0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x32, 0x8b, 0x06, 0x0a,
0x6e, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x35, 0x0a, 0x14,
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65,
0x61, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xdb, 0x06, 0x0a,
0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x4f,
0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72,
0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x69, 0x67,
@ -1986,7 +2078,12 @@ var file_signrpc_signer_proto_rawDesc = []byte{
0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53, 0x69, 0x67,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70,
0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x53,
0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69,
0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x4d, 0x75,
0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x1d, 0x2e, 0x73, 0x69,
0x67, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61,
0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x69, 0x67,
0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x75, 0x53, 0x69, 0x67, 0x32, 0x43, 0x6c, 0x65, 0x61, 0x6e,
0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69,
0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f,
@ -2005,7 +2102,7 @@ func file_signrpc_signer_proto_rawDescGZIP() []byte {
return file_signrpc_signer_proto_rawDescData
}
var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
var file_signrpc_signer_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
var file_signrpc_signer_proto_goTypes = []interface{}{
(*KeyLocator)(nil), // 0: signrpc.KeyLocator
(*KeyDescriptor)(nil), // 1: signrpc.KeyDescriptor
@ -2033,6 +2130,8 @@ var file_signrpc_signer_proto_goTypes = []interface{}{
(*MuSig2SignResponse)(nil), // 23: signrpc.MuSig2SignResponse
(*MuSig2CombineSigRequest)(nil), // 24: signrpc.MuSig2CombineSigRequest
(*MuSig2CombineSigResponse)(nil), // 25: signrpc.MuSig2CombineSigResponse
(*MuSig2CleanupRequest)(nil), // 26: signrpc.MuSig2CleanupRequest
(*MuSig2CleanupResponse)(nil), // 27: signrpc.MuSig2CleanupResponse
}
var file_signrpc_signer_proto_depIdxs = []int32{
0, // 0: signrpc.KeyDescriptor.key_loc:type_name -> signrpc.KeyLocator
@ -2059,18 +2158,20 @@ var file_signrpc_signer_proto_depIdxs = []int32{
20, // 21: signrpc.Signer.MuSig2RegisterNonces:input_type -> signrpc.MuSig2RegisterNoncesRequest
22, // 22: signrpc.Signer.MuSig2Sign:input_type -> signrpc.MuSig2SignRequest
24, // 23: signrpc.Signer.MuSig2CombineSig:input_type -> signrpc.MuSig2CombineSigRequest
5, // 24: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp
7, // 25: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp
9, // 26: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp
11, // 27: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp
13, // 28: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse
17, // 29: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse
19, // 30: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse
21, // 31: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse
23, // 32: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse
25, // 33: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse
24, // [24:34] is the sub-list for method output_type
14, // [14:24] is the sub-list for method input_type
26, // 24: signrpc.Signer.MuSig2Cleanup:input_type -> signrpc.MuSig2CleanupRequest
5, // 25: signrpc.Signer.SignOutputRaw:output_type -> signrpc.SignResp
7, // 26: signrpc.Signer.ComputeInputScript:output_type -> signrpc.InputScriptResp
9, // 27: signrpc.Signer.SignMessage:output_type -> signrpc.SignMessageResp
11, // 28: signrpc.Signer.VerifyMessage:output_type -> signrpc.VerifyMessageResp
13, // 29: signrpc.Signer.DeriveSharedKey:output_type -> signrpc.SharedKeyResponse
17, // 30: signrpc.Signer.MuSig2CombineKeys:output_type -> signrpc.MuSig2CombineKeysResponse
19, // 31: signrpc.Signer.MuSig2CreateSession:output_type -> signrpc.MuSig2SessionResponse
21, // 32: signrpc.Signer.MuSig2RegisterNonces:output_type -> signrpc.MuSig2RegisterNoncesResponse
23, // 33: signrpc.Signer.MuSig2Sign:output_type -> signrpc.MuSig2SignResponse
25, // 34: signrpc.Signer.MuSig2CombineSig:output_type -> signrpc.MuSig2CombineSigResponse
27, // 35: signrpc.Signer.MuSig2Cleanup:output_type -> signrpc.MuSig2CleanupResponse
25, // [25:36] is the sub-list for method output_type
14, // [14:25] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name
14, // [14:14] is the sub-list for extension extendee
0, // [0:14] is the sub-list for field type_name
@ -2394,6 +2495,30 @@ func file_signrpc_signer_proto_init() {
return nil
}
}
file_signrpc_signer_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CleanupRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signrpc_signer_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*MuSig2CleanupResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -2401,7 +2526,7 @@ func file_signrpc_signer_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_signrpc_signer_proto_rawDesc,
NumEnums: 0,
NumMessages: 26,
NumMessages: 28,
NumExtensions: 0,
NumServices: 1,
},

@ -371,6 +371,40 @@ func local_request_Signer_MuSig2CombineSig_0(ctx context.Context, marshaler runt
}
func request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, client SignerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CleanupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.MuSig2Cleanup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Signer_MuSig2Cleanup_0(ctx context.Context, marshaler runtime.Marshaler, server SignerServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq MuSig2CleanupRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.MuSig2Cleanup(ctx, &protoReq)
return msg, metadata, err
}
// RegisterSignerHandlerServer registers the http handlers for service Signer to "mux".
// UnaryRPC :call SignerServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -607,6 +641,29 @@ func RegisterSignerHandlerServer(ctx context.Context, mux *runtime.ServeMux, ser
})
mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Signer_MuSig2Cleanup_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Cleanup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -848,6 +905,26 @@ func RegisterSignerHandlerClient(ctx context.Context, mux *runtime.ServeMux, cli
})
mux.Handle("POST", pattern_Signer_MuSig2Cleanup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/signrpc.Signer/MuSig2Cleanup", runtime.WithHTTPPathPattern("/v2/signer/musig2/cleanup"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Signer_MuSig2Cleanup_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Signer_MuSig2Cleanup_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -871,6 +948,8 @@ var (
pattern_Signer_MuSig2Sign_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "sign"}, ""))
pattern_Signer_MuSig2CombineSig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "combinesig"}, ""))
pattern_Signer_MuSig2Cleanup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "signer", "musig2", "cleanup"}, ""))
)
var (
@ -893,4 +972,6 @@ var (
forward_Signer_MuSig2Sign_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2CombineSig_0 = runtime.ForwardResponseMessage
forward_Signer_MuSig2Cleanup_0 = runtime.ForwardResponseMessage
)

@ -272,4 +272,29 @@ func RegisterSignerJSONCallbacks(registry map[string]func(ctx context.Context,
}
callback(string(respBytes), nil)
}
registry["signrpc.Signer.MuSig2Cleanup"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &MuSig2CleanupRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSignerClient(conn)
resp, err := client.MuSig2Cleanup(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}

@ -132,6 +132,17 @@ service Signer {
*/
rpc MuSig2CombineSig (MuSig2CombineSigRequest)
returns (MuSig2CombineSigResponse);
/*
MuSig2Cleanup (experimental!) allows a caller to clean up a session early in
cases where it's obvious that the signing session won't succeed and the
resources can be released.
NOTE: The MuSig2 BIP is not final yet and therefore this API must be
considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
releases. Backward compatibility is not guaranteed!
*/
rpc MuSig2Cleanup (MuSig2CleanupRequest) returns (MuSig2CleanupResponse);
}
message KeyLocator {
@ -552,4 +563,14 @@ message MuSig2CombineSigResponse {
The final, full signature that is valid for the combined public key.
*/
bytes final_signature = 2;
}
message MuSig2CleanupRequest {
/*
The unique ID of the signing session that should be removed/cleaned up.
*/
bytes session_id = 1;
}
message MuSig2CleanupResponse {
}

@ -50,6 +50,40 @@
]
}
},
"/v2/signer/musig2/cleanup": {
"post": {
"summary": "MuSig2Cleanup (experimental!) allows a caller to clean up a session early in\ncases where it's obvious that the signing session won't succeed and the\nresources can be released.",
"description": "NOTE: The MuSig2 BIP is not final yet and therefore this API must be\nconsidered to be HIGHLY EXPERIMENTAL and subject to change in upcoming\nreleases. Backward compatibility is not guaranteed!",
"operationId": "Signer_MuSig2Cleanup",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/signrpcMuSig2CleanupResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/signrpcMuSig2CleanupRequest"
}
}
],
"tags": [
"Signer"
]
}
},
"/v2/signer/musig2/combinekeys": {
"post": {
"summary": "MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used\nto calculate the combined MuSig2 public key from a list of all participating\nsigners' public keys. This RPC is completely stateless and deterministic and\ndoes not create any signing session. It can be used to determine the Taproot\npublic key that should be put in an on-chain output once all public keys are\nknown. A signing session is only needed later when that output should be\n_spent_ again.",
@ -446,6 +480,19 @@
}
}
},
"signrpcMuSig2CleanupRequest": {
"type": "object",
"properties": {
"session_id": {
"type": "string",
"format": "byte",
"description": "The unique ID of the signing session that should be removed/cleaned up."
}
}
},
"signrpcMuSig2CleanupResponse": {
"type": "object"
},
"signrpcMuSig2CombineKeysRequest": {
"type": "object",
"properties": {

@ -33,3 +33,6 @@ http:
- selector: signrpc.Signer.MuSig2CombineSig
post: "/v2/signer/musig2/combinesig"
body: "*"
- selector: signrpc.Signer.MuSig2Cleanup
post: "/v2/signer/musig2/cleanup"
body: "*"

@ -120,6 +120,15 @@ type SignerClient interface {
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
//releases. Backward compatibility is not guaranteed!
MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSigRequest, opts ...grpc.CallOption) (*MuSig2CombineSigResponse, error)
//
//MuSig2Cleanup (experimental!) allows a caller to clean up a session early in
//cases where it's obvious that the signing session won't succeed and the
//resources can be released.
//
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
//releases. Backward compatibility is not guaranteed!
MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error)
}
type signerClient struct {
@ -220,6 +229,15 @@ func (c *signerClient) MuSig2CombineSig(ctx context.Context, in *MuSig2CombineSi
return out, nil
}
func (c *signerClient) MuSig2Cleanup(ctx context.Context, in *MuSig2CleanupRequest, opts ...grpc.CallOption) (*MuSig2CleanupResponse, error) {
out := new(MuSig2CleanupResponse)
err := c.cc.Invoke(ctx, "/signrpc.Signer/MuSig2Cleanup", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SignerServer is the server API for Signer service.
// All implementations must embed UnimplementedSignerServer
// for forward compatibility
@ -326,6 +344,15 @@ type SignerServer interface {
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
//releases. Backward compatibility is not guaranteed!
MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error)
//
//MuSig2Cleanup (experimental!) allows a caller to clean up a session early in
//cases where it's obvious that the signing session won't succeed and the
//resources can be released.
//
//NOTE: The MuSig2 BIP is not final yet and therefore this API must be
//considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming
//releases. Backward compatibility is not guaranteed!
MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error)
mustEmbedUnimplementedSignerServer()
}
@ -363,6 +390,9 @@ func (UnimplementedSignerServer) MuSig2Sign(context.Context, *MuSig2SignRequest)
func (UnimplementedSignerServer) MuSig2CombineSig(context.Context, *MuSig2CombineSigRequest) (*MuSig2CombineSigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2CombineSig not implemented")
}
func (UnimplementedSignerServer) MuSig2Cleanup(context.Context, *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method MuSig2Cleanup not implemented")
}
func (UnimplementedSignerServer) mustEmbedUnimplementedSignerServer() {}
// UnsafeSignerServer may be embedded to opt out of forward compatibility for this service.
@ -556,6 +586,24 @@ func _Signer_MuSig2CombineSig_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _Signer_MuSig2Cleanup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MuSig2CleanupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SignerServer).MuSig2Cleanup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/signrpc.Signer/MuSig2Cleanup",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignerServer).MuSig2Cleanup(ctx, req.(*MuSig2CleanupRequest))
}
return interceptor(ctx, in, info, handler)
}
// Signer_ServiceDesc is the grpc.ServiceDesc for Signer service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -603,6 +651,10 @@ var Signer_ServiceDesc = grpc.ServiceDesc{
MethodName: "MuSig2CombineSig",
Handler: _Signer_MuSig2CombineSig_Handler,
},
{
MethodName: "MuSig2Cleanup",
Handler: _Signer_MuSig2Cleanup_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "signrpc/signer.proto",

@ -92,6 +92,10 @@ var (
Entity: "signer",
Action: "generate",
}},
"/signrpc.Signer/MuSig2Cleanup": {{
Entity: "signer",
Action: "generate",
}},
}
// DefaultSignerMacFilename is the default name of the signer macaroon
@ -940,6 +944,24 @@ func (s *Server) MuSig2CombineSig(_ context.Context,
}, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (s *Server) MuSig2Cleanup(_ context.Context,
in *MuSig2CleanupRequest) (*MuSig2CleanupResponse, error) {
// Check session ID length.
sessionID, err := parseMuSig2SessionID(in.SessionId)
if err != nil {
return nil, fmt.Errorf("error parsing session ID: %v", err)
}
err = s.cfg.Signer.MuSig2Cleanup(sessionID)
if err != nil {
return nil, fmt.Errorf("error cleaning up session: %v", err)
}
return &MuSig2CleanupResponse{}, nil
}
// parseRawKeyBytes checks that the provided raw public key is valid and returns
// the public key. A nil public key is returned if the length of the rawKeyBytes
// is zero.

@ -906,11 +906,29 @@ func testTaprootMuSig2CombinedLeafKeySpend(ctxt context.Context, t *harnessTest,
ctxt, &signrpc.MuSig2SignRequest{
SessionId: sessResp3.SessionId,
MessageDigest: sigHash,
Cleanup: true,
},
)
require.NoError(t.t, err)
// We manually clean up session 3, just to make sure that works as well.
_, err = alice.SignerClient.MuSig2Cleanup(
ctxt, &signrpc.MuSig2CleanupRequest{
SessionId: sessResp3.SessionId,
},
)
require.NoError(t.t, err)
// A second call to that cleaned up session should now fail with a
// specific error.
_, err = alice.SignerClient.MuSig2Sign(
ctxt, &signrpc.MuSig2SignRequest{
SessionId: sessResp3.SessionId,
MessageDigest: sigHash,
},
)
require.Error(t.t, err)
require.Contains(t.t, err.Error(), "not found")
// Luckily only one of the signers needs to combine the signature, so
// let's do that now.
combineReq1, err := alice.SignerClient.MuSig2CombineSig(

@ -94,6 +94,11 @@ func (d *DummySigner) MuSig2CombineSig(input.MuSig2SessionID,
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (d *DummySigner) MuSig2Cleanup(input.MuSig2SessionID) error {
return nil
}
// SingleSigner is an implementation of the Signer interface that signs
// everything with a single private key.
type SingleSigner struct {
@ -227,3 +232,8 @@ func (s *SingleSigner) MuSig2CombineSig(input.MuSig2SessionID,
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (s *SingleSigner) MuSig2Cleanup(input.MuSig2SessionID) error {
return nil
}

@ -704,6 +704,24 @@ func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID,
return finalSig, session.HaveAllSigs, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
// We hold the lock during the whole operation, we don't want any
// interference with calls that might come through in parallel for the
// same session.
b.musig2SessionsMtx.Lock()
defer b.musig2SessionsMtx.Unlock()
_, ok := b.musig2Sessions[sessionID]
if !ok {
return fmt.Errorf("session with ID %x not found", sessionID[:])
}
delete(b.musig2Sessions, sessionID)
return nil
}
// A compile time check to ensure that BtcWallet implements the Signer
// interface.
var _ input.Signer = (*BtcWallet)(nil)

@ -33,7 +33,9 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
)
@ -199,12 +201,9 @@ func (r *RPCKeyRing) SignPsbt(packet *psbt.Packet) error {
FundedPsbt: buf.Bytes(),
})
if err != nil {
err = fmt.Errorf("error signing PSBT in remote signer "+
considerShutdown(err)
return fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return err
}
signedPacket, err := psbt.NewFromRawBytes(
@ -410,12 +409,9 @@ func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
resp, err := r.signerClient.DeriveSharedKey(ctxt, req)
if err != nil {
err = fmt.Errorf("error deriving shared key in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return key, err
considerShutdown(err)
return key, fmt.Errorf("error deriving shared key in remote "+
"signer instance: %v", err)
}
copy(key[:], resp.SharedKey)
@ -443,12 +439,9 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator,
DoubleHash: doubleHash,
})
if err != nil {
err = fmt.Errorf("error signing message in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature)
@ -484,12 +477,9 @@ func (r *RPCKeyRing) SignMessageCompact(keyLoc keychain.KeyLocator,
CompactSig: true,
})
if err != nil {
err = fmt.Errorf("error signing message in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err
considerShutdown(err)
return nil, fmt.Errorf("error signing message in remote "+
"signer instance: %v", err)
}
// The signature in the response is zbase32 encoded, so we need to
@ -622,12 +612,9 @@ func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator,
resp, err := r.signerClient.MuSig2CreateSession(ctxt, req)
if err != nil {
err = fmt.Errorf("error creating MuSig2 session in remote "+
"signer instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err
considerShutdown(err)
return nil, fmt.Errorf("error creating MuSig2 session in "+
"remote signer instance: %v", err)
}
// De-Serialize all the info back into our native struct.
@ -678,12 +665,9 @@ func (r *RPCKeyRing) MuSig2RegisterNonces(sessionID input.MuSig2SessionID,
resp, err := r.signerClient.MuSig2RegisterNonces(ctxt, req)
if err != nil {
err = fmt.Errorf("error registering MuSig2 nonces in remote "+
"signer instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return false, err
considerShutdown(err)
return false, fmt.Errorf("error registering MuSig2 nonces in "+
"remote signer instance: %v", err)
}
return resp.HaveAllNonces, nil
@ -712,12 +696,9 @@ func (r *RPCKeyRing) MuSig2Sign(sessionID input.MuSig2SessionID,
resp, err := r.signerClient.MuSig2Sign(ctxt, req)
if err != nil {
err = fmt.Errorf("error signing MuSig2 session in remote "+
"signer instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err
considerShutdown(err)
return nil, fmt.Errorf("error signing MuSig2 session in "+
"remote signer instance: %v", err)
}
partialSig, err := input.DeserializePartialSignature(
@ -759,12 +740,9 @@ func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
resp, err := r.signerClient.MuSig2CombineSig(ctxt, req)
if err != nil {
err = fmt.Errorf("error combining MuSig2 signatures in remote "+
"signer instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, false, err
considerShutdown(err)
return nil, false, fmt.Errorf("error combining MuSig2 "+
"signatures in remote signer instance: %v", err)
}
// The final signature is only available when we have all the other
@ -782,6 +760,25 @@ func (r *RPCKeyRing) MuSig2CombineSig(sessionID input.MuSig2SessionID,
return finalSig, resp.HaveAllSignatures, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (r *RPCKeyRing) MuSig2Cleanup(sessionID input.MuSig2SessionID) error {
req := &signrpc.MuSig2CleanupRequest{
SessionId: sessionID[:],
}
ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout)
defer cancel()
_, err := r.signerClient.MuSig2Cleanup(ctxt, req)
if err != nil {
considerShutdown(err)
return fmt.Errorf("error cleaning up MuSig2 session in remote "+
"signer instance: %v", err)
}
return nil
}
// remoteSign signs the input specified in signDesc of the given transaction tx
// using the remote signing instance.
func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
@ -982,12 +979,9 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
ctxt, &walletrpc.SignPsbtRequest{FundedPsbt: buf.Bytes()},
)
if err != nil {
err = fmt.Errorf("error signing PSBT in remote signer "+
considerShutdown(err)
return nil, fmt.Errorf("error signing PSBT in remote signer "+
"instance: %v", err)
// Log as critical as we should shut down if there is no signer.
log.Criticalf("RPC signer error: %v", err)
return nil, err
}
signedPacket, err := psbt.NewFromRawBytes(
@ -1114,3 +1108,26 @@ func packetFromTx(original *wire.MsgTx) (*psbt.Packet, error) {
return packet, nil
}
// considerShutdown inspects the error and issues a shutdown (through logging
// a critical error, which will cause the logger to issue a clean shutdown
// request) if the error looks like a connection or general availability error
// and not some application specific problem.
func considerShutdown(err error) {
statusErr, isStatusErr := status.FromError(err)
switch {
// The context attached to the client request has timed out. This can be
// due to not being able to reach the signing server, or it's taking too
// long to respond. In either case, request a shutdown.
case err == context.DeadlineExceeded:
fallthrough
// The signing server's context timed out before the client's due to
// clock skew, request a shutdown anyway.
case isStatusErr && statusErr.Code() == codes.DeadlineExceeded:
log.Critical("RPC signing timed out: %v", err)
case isStatusErr && statusErr.Code() == codes.Unavailable:
log.Critical("RPC signing server not available: %v", err)
}
}

@ -108,6 +108,11 @@ func (s *MockSigner) MuSig2CombineSig(input.MuSig2SessionID,
return nil, false, nil
}
// MuSig2Cleanup removes a session from memory to free up resources.
func (s *MockSigner) MuSig2Cleanup(input.MuSig2SessionID) error {
return nil
}
// AddPrivKey records the passed privKey in the MockSigner's registry of keys it
// can sign with in the future. A unique key locator is returned, allowing the
// caller to sign with this key when presented via an input.SignDescriptor.