feat(mpc-system): add signing parties configuration and delegate signing support

- Add signing-config API endpoints (POST/PUT/DELETE/GET) for configuring
  which parties should participate in signing operations
- Add SigningParties field to Account entity with database migration
- Modify CreateSigningSession to use configured parties if set,
  otherwise use all active parties (backward compatible)
- Add delegate party signing support: user provides encrypted share
  at sign time for delegate party to use
- Update protobuf definitions for DelegateUserShare in session events
- Add ShareTypeDelegate to support hybrid custody model

API endpoints:
- POST /accounts/:id/signing-config - Set signing parties (first time)
- PUT /accounts/:id/signing-config - Update signing parties
- DELETE /accounts/:id/signing-config - Clear config (use all parties)
- GET /accounts/:id/signing-config - Get current configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-05 22:47:55 -08:00
parent 55f5ec49f2
commit aa74e2b2e2
33 changed files with 4331 additions and 783 deletions

View File

@ -0,0 +1,59 @@
# =============================================================================
# MPC-System Production Party Configuration
# =============================================================================
# Copy to .env.party and configure for your party's environment
#
# Usage:
# cp .env.party.example .env.party
# # Edit .env.party with your values
# ./deploy.sh party up
#
# Each party machine needs its own .env.party with unique PARTY_ID
# =============================================================================
# =============================================================================
# Party Identity (REQUIRED - must be unique across all parties)
# =============================================================================
PARTY_ID=server-party-1
# Options: persistent (default), delegate, temporary
PARTY_ROLE=persistent
# =============================================================================
# Central Service (REQUIRED - public address)
# =============================================================================
# Message Router gRPC endpoint (the ONLY connection parties need)
# Session operations are proxied through Message Router to Session Coordinator
MESSAGE_ROUTER_ADDR=grpc.mpc.example.com:50051
# =============================================================================
# Local Database (for storing encrypted key shares)
# =============================================================================
POSTGRES_USER=mpc_user
POSTGRES_PASSWORD=your_secure_local_postgres_password
# =============================================================================
# Security Keys (REQUIRED)
# =============================================================================
# Master key for encrypting key shares (64 hex characters = 256-bit)
# IMPORTANT: Use the same key across all parties in the same MPC group
# Generate with: openssl rand -hex 32
CRYPTO_MASTER_KEY=your_64_character_hex_master_key_here
# =============================================================================
# Optional: Notification Channels (for offline mode)
# =============================================================================
# If any of these are set, party operates in offline mode (24h async)
# If none are set, party operates in real-time mode (Message Router push)
NOTIFICATION_EMAIL=
NOTIFICATION_PHONE=
NOTIFICATION_PUSH_TOKEN=
# =============================================================================
# Optional: Local HTTP Port (for health checks only)
# =============================================================================
# This party doesn't need to expose any ports - it connects outbound
# HTTP port is optional for local health monitoring
PARTY_HTTP_PORT=8080
# Environment
ENVIRONMENT=production

View File

@ -0,0 +1,57 @@
# =============================================================================
# MPC-System Production Central Configuration
# =============================================================================
# Copy to .env.prod and configure for your environment
#
# Usage:
# cp .env.prod.example .env.prod
# # Edit .env.prod with your values
# ./deploy.sh prod up
# =============================================================================
# Environment
ENVIRONMENT=production
# =============================================================================
# Database Configuration
# =============================================================================
POSTGRES_USER=mpc_user
POSTGRES_PASSWORD=your_secure_postgres_password_here
# =============================================================================
# Security Keys (IMPORTANT: Generate unique values!)
# =============================================================================
# Generate with: openssl rand -base64 32
JWT_SECRET_KEY=your_jwt_secret_key_here_min_32_chars
# API Key for backend integration (shared with mpc-service)
# Generate with: openssl rand -base64 32
MPC_API_KEY=your_api_key_here
# Master key for encrypting key shares (64 hex characters = 256-bit)
# Generate with: openssl rand -hex 32
CRYPTO_MASTER_KEY=your_64_character_hex_master_key_here
# =============================================================================
# Public Ports (must be accessible from server-parties)
# =============================================================================
# Message Router gRPC - parties connect here
MESSAGE_ROUTER_GRPC_PORT=50051
MESSAGE_ROUTER_HTTP_PORT=8082
# Session Coordinator gRPC - parties connect here
SESSION_COORDINATOR_GRPC_PORT=50052
SESSION_COORDINATOR_HTTP_PORT=8081
# Account Service HTTP - backend API
ACCOUNT_SERVICE_PORT=4000
# Server Party API (optional)
SERVER_PARTY_API_PORT=8083
# =============================================================================
# IP Whitelist (optional)
# =============================================================================
# Comma-separated list of IPs allowed to access Account Service API
# Leave empty to allow all (protected by API_KEY)
ALLOWED_IPS=

View File

@ -31,8 +31,10 @@ type CreateSessionRequest struct {
MessageHash []byte `protobuf:"bytes,5,opt,name=message_hash,json=messageHash,proto3" json:"message_hash,omitempty"` // Required for sign sessions MessageHash []byte `protobuf:"bytes,5,opt,name=message_hash,json=messageHash,proto3" json:"message_hash,omitempty"` // Required for sign sessions
ExpiresInSeconds int64 `protobuf:"varint,6,opt,name=expires_in_seconds,json=expiresInSeconds,proto3" json:"expires_in_seconds,omitempty"` // Session expiration time ExpiresInSeconds int64 `protobuf:"varint,6,opt,name=expires_in_seconds,json=expiresInSeconds,proto3" json:"expires_in_seconds,omitempty"` // Session expiration time
PartyComposition *PartyComposition `protobuf:"bytes,7,opt,name=party_composition,json=partyComposition,proto3" json:"party_composition,omitempty"` // Optional: party composition requirements for auto-selection PartyComposition *PartyComposition `protobuf:"bytes,7,opt,name=party_composition,json=partyComposition,proto3" json:"party_composition,omitempty"` // Optional: party composition requirements for auto-selection
unknownFields protoimpl.UnknownFields // For sign sessions with delegate party: user must provide their encrypted share
sizeCache protoimpl.SizeCache DelegateUserShare *DelegateUserShare `protobuf:"bytes,8,opt,name=delegate_user_share,json=delegateUserShare,proto3" json:"delegate_user_share,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *CreateSessionRequest) Reset() { func (x *CreateSessionRequest) Reset() {
@ -114,6 +116,74 @@ func (x *CreateSessionRequest) GetPartyComposition() *PartyComposition {
return nil return nil
} }
func (x *CreateSessionRequest) GetDelegateUserShare() *DelegateUserShare {
if x != nil {
return x.DelegateUserShare
}
return nil
}
// DelegateUserShare contains user's share for delegate party to use in signing
type DelegateUserShare struct {
state protoimpl.MessageState `protogen:"open.v1"`
DelegatePartyId string `protobuf:"bytes,1,opt,name=delegate_party_id,json=delegatePartyId,proto3" json:"delegate_party_id,omitempty"` // The delegate party that will use this share
EncryptedShare []byte `protobuf:"bytes,2,opt,name=encrypted_share,json=encryptedShare,proto3" json:"encrypted_share,omitempty"` // User's encrypted share (same as received from keygen)
PartyIndex int32 `protobuf:"varint,3,opt,name=party_index,json=partyIndex,proto3" json:"party_index,omitempty"` // Party index for this share
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DelegateUserShare) Reset() {
*x = DelegateUserShare{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DelegateUserShare) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DelegateUserShare) ProtoMessage() {}
func (x *DelegateUserShare) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DelegateUserShare.ProtoReflect.Descriptor instead.
func (*DelegateUserShare) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{1}
}
func (x *DelegateUserShare) GetDelegatePartyId() string {
if x != nil {
return x.DelegatePartyId
}
return ""
}
func (x *DelegateUserShare) GetEncryptedShare() []byte {
if x != nil {
return x.EncryptedShare
}
return nil
}
func (x *DelegateUserShare) GetPartyIndex() int32 {
if x != nil {
return x.PartyIndex
}
return 0
}
// PartyComposition specifies requirements for automatic party selection // PartyComposition specifies requirements for automatic party selection
type PartyComposition struct { type PartyComposition struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
@ -126,7 +196,7 @@ type PartyComposition struct {
func (x *PartyComposition) Reset() { func (x *PartyComposition) Reset() {
*x = PartyComposition{} *x = PartyComposition{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[1] mi := &file_api_proto_session_coordinator_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -138,7 +208,7 @@ func (x *PartyComposition) String() string {
func (*PartyComposition) ProtoMessage() {} func (*PartyComposition) ProtoMessage() {}
func (x *PartyComposition) ProtoReflect() protoreflect.Message { func (x *PartyComposition) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[1] mi := &file_api_proto_session_coordinator_proto_msgTypes[2]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -151,7 +221,7 @@ func (x *PartyComposition) ProtoReflect() protoreflect.Message {
// Deprecated: Use PartyComposition.ProtoReflect.Descriptor instead. // Deprecated: Use PartyComposition.ProtoReflect.Descriptor instead.
func (*PartyComposition) Descriptor() ([]byte, []int) { func (*PartyComposition) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{1} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{2}
} }
func (x *PartyComposition) GetPersistentCount() int32 { func (x *PartyComposition) GetPersistentCount() int32 {
@ -186,7 +256,7 @@ type ParticipantInfo struct {
func (x *ParticipantInfo) Reset() { func (x *ParticipantInfo) Reset() {
*x = ParticipantInfo{} *x = ParticipantInfo{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[2] mi := &file_api_proto_session_coordinator_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -198,7 +268,7 @@ func (x *ParticipantInfo) String() string {
func (*ParticipantInfo) ProtoMessage() {} func (*ParticipantInfo) ProtoMessage() {}
func (x *ParticipantInfo) ProtoReflect() protoreflect.Message { func (x *ParticipantInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[2] mi := &file_api_proto_session_coordinator_proto_msgTypes[3]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -211,7 +281,7 @@ func (x *ParticipantInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ParticipantInfo.ProtoReflect.Descriptor instead. // Deprecated: Use ParticipantInfo.ProtoReflect.Descriptor instead.
func (*ParticipantInfo) Descriptor() ([]byte, []int) { func (*ParticipantInfo) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{2} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{3}
} }
func (x *ParticipantInfo) GetPartyId() string { func (x *ParticipantInfo) GetPartyId() string {
@ -241,7 +311,7 @@ type DeviceInfo struct {
func (x *DeviceInfo) Reset() { func (x *DeviceInfo) Reset() {
*x = DeviceInfo{} *x = DeviceInfo{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[3] mi := &file_api_proto_session_coordinator_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -253,7 +323,7 @@ func (x *DeviceInfo) String() string {
func (*DeviceInfo) ProtoMessage() {} func (*DeviceInfo) ProtoMessage() {}
func (x *DeviceInfo) ProtoReflect() protoreflect.Message { func (x *DeviceInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[3] mi := &file_api_proto_session_coordinator_proto_msgTypes[4]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -266,7 +336,7 @@ func (x *DeviceInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeviceInfo.ProtoReflect.Descriptor instead. // Deprecated: Use DeviceInfo.ProtoReflect.Descriptor instead.
func (*DeviceInfo) Descriptor() ([]byte, []int) { func (*DeviceInfo) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{3} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{4}
} }
func (x *DeviceInfo) GetDeviceType() string { func (x *DeviceInfo) GetDeviceType() string {
@ -311,7 +381,7 @@ type CreateSessionResponse struct {
func (x *CreateSessionResponse) Reset() { func (x *CreateSessionResponse) Reset() {
*x = CreateSessionResponse{} *x = CreateSessionResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[4] mi := &file_api_proto_session_coordinator_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -323,7 +393,7 @@ func (x *CreateSessionResponse) String() string {
func (*CreateSessionResponse) ProtoMessage() {} func (*CreateSessionResponse) ProtoMessage() {}
func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message { func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[4] mi := &file_api_proto_session_coordinator_proto_msgTypes[5]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -336,7 +406,7 @@ func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateSessionResponse.ProtoReflect.Descriptor instead. // Deprecated: Use CreateSessionResponse.ProtoReflect.Descriptor instead.
func (*CreateSessionResponse) Descriptor() ([]byte, []int) { func (*CreateSessionResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{4} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{5}
} }
func (x *CreateSessionResponse) GetSessionId() string { func (x *CreateSessionResponse) GetSessionId() string {
@ -387,7 +457,7 @@ type JoinSessionRequest struct {
func (x *JoinSessionRequest) Reset() { func (x *JoinSessionRequest) Reset() {
*x = JoinSessionRequest{} *x = JoinSessionRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[5] mi := &file_api_proto_session_coordinator_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -399,7 +469,7 @@ func (x *JoinSessionRequest) String() string {
func (*JoinSessionRequest) ProtoMessage() {} func (*JoinSessionRequest) ProtoMessage() {}
func (x *JoinSessionRequest) ProtoReflect() protoreflect.Message { func (x *JoinSessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[5] mi := &file_api_proto_session_coordinator_proto_msgTypes[6]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -412,7 +482,7 @@ func (x *JoinSessionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use JoinSessionRequest.ProtoReflect.Descriptor instead. // Deprecated: Use JoinSessionRequest.ProtoReflect.Descriptor instead.
func (*JoinSessionRequest) Descriptor() ([]byte, []int) { func (*JoinSessionRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{5} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{6}
} }
func (x *JoinSessionRequest) GetSessionId() string { func (x *JoinSessionRequest) GetSessionId() string {
@ -455,7 +525,7 @@ type JoinSessionResponse struct {
func (x *JoinSessionResponse) Reset() { func (x *JoinSessionResponse) Reset() {
*x = JoinSessionResponse{} *x = JoinSessionResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[6] mi := &file_api_proto_session_coordinator_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -467,7 +537,7 @@ func (x *JoinSessionResponse) String() string {
func (*JoinSessionResponse) ProtoMessage() {} func (*JoinSessionResponse) ProtoMessage() {}
func (x *JoinSessionResponse) ProtoReflect() protoreflect.Message { func (x *JoinSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[6] mi := &file_api_proto_session_coordinator_proto_msgTypes[7]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -480,7 +550,7 @@ func (x *JoinSessionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use JoinSessionResponse.ProtoReflect.Descriptor instead. // Deprecated: Use JoinSessionResponse.ProtoReflect.Descriptor instead.
func (*JoinSessionResponse) Descriptor() ([]byte, []int) { func (*JoinSessionResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{6} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{7}
} }
func (x *JoinSessionResponse) GetSuccess() bool { func (x *JoinSessionResponse) GetSuccess() bool {
@ -519,7 +589,7 @@ type SessionInfo struct {
func (x *SessionInfo) Reset() { func (x *SessionInfo) Reset() {
*x = SessionInfo{} *x = SessionInfo{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[7] mi := &file_api_proto_session_coordinator_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -531,7 +601,7 @@ func (x *SessionInfo) String() string {
func (*SessionInfo) ProtoMessage() {} func (*SessionInfo) ProtoMessage() {}
func (x *SessionInfo) ProtoReflect() protoreflect.Message { func (x *SessionInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[7] mi := &file_api_proto_session_coordinator_proto_msgTypes[8]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -544,7 +614,7 @@ func (x *SessionInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionInfo.ProtoReflect.Descriptor instead. // Deprecated: Use SessionInfo.ProtoReflect.Descriptor instead.
func (*SessionInfo) Descriptor() ([]byte, []int) { func (*SessionInfo) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{7} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{8}
} }
func (x *SessionInfo) GetSessionId() string { func (x *SessionInfo) GetSessionId() string {
@ -601,7 +671,7 @@ type PartyInfo struct {
func (x *PartyInfo) Reset() { func (x *PartyInfo) Reset() {
*x = PartyInfo{} *x = PartyInfo{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[8] mi := &file_api_proto_session_coordinator_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -613,7 +683,7 @@ func (x *PartyInfo) String() string {
func (*PartyInfo) ProtoMessage() {} func (*PartyInfo) ProtoMessage() {}
func (x *PartyInfo) ProtoReflect() protoreflect.Message { func (x *PartyInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[8] mi := &file_api_proto_session_coordinator_proto_msgTypes[9]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -626,7 +696,7 @@ func (x *PartyInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use PartyInfo.ProtoReflect.Descriptor instead. // Deprecated: Use PartyInfo.ProtoReflect.Descriptor instead.
func (*PartyInfo) Descriptor() ([]byte, []int) { func (*PartyInfo) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{8} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{9}
} }
func (x *PartyInfo) GetPartyId() string { func (x *PartyInfo) GetPartyId() string {
@ -660,7 +730,7 @@ type GetSessionStatusRequest struct {
func (x *GetSessionStatusRequest) Reset() { func (x *GetSessionStatusRequest) Reset() {
*x = GetSessionStatusRequest{} *x = GetSessionStatusRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[9] mi := &file_api_proto_session_coordinator_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -672,7 +742,7 @@ func (x *GetSessionStatusRequest) String() string {
func (*GetSessionStatusRequest) ProtoMessage() {} func (*GetSessionStatusRequest) ProtoMessage() {}
func (x *GetSessionStatusRequest) ProtoReflect() protoreflect.Message { func (x *GetSessionStatusRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[9] mi := &file_api_proto_session_coordinator_proto_msgTypes[10]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -685,7 +755,7 @@ func (x *GetSessionStatusRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetSessionStatusRequest.ProtoReflect.Descriptor instead. // Deprecated: Use GetSessionStatusRequest.ProtoReflect.Descriptor instead.
func (*GetSessionStatusRequest) Descriptor() ([]byte, []int) { func (*GetSessionStatusRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{9} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{10}
} }
func (x *GetSessionStatusRequest) GetSessionId() string { func (x *GetSessionStatusRequest) GetSessionId() string {
@ -701,15 +771,22 @@ type GetSessionStatusResponse struct {
Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
CompletedParties int32 `protobuf:"varint,2,opt,name=completed_parties,json=completedParties,proto3" json:"completed_parties,omitempty"` CompletedParties int32 `protobuf:"varint,2,opt,name=completed_parties,json=completedParties,proto3" json:"completed_parties,omitempty"`
TotalParties int32 `protobuf:"varint,3,opt,name=total_parties,json=totalParties,proto3" json:"total_parties,omitempty"` TotalParties int32 `protobuf:"varint,3,opt,name=total_parties,json=totalParties,proto3" json:"total_parties,omitempty"`
PublicKey []byte `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // For completed keygen SessionType string `protobuf:"bytes,4,opt,name=session_type,json=sessionType,proto3" json:"session_type,omitempty"` // "keygen" or "sign"
Signature []byte `protobuf:"bytes,5,opt,name=signature,proto3" json:"signature,omitempty"` // For completed sign PublicKey []byte `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // For completed keygen
unknownFields protoimpl.UnknownFields Signature []byte `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` // For completed sign
sizeCache protoimpl.SizeCache // has_delegate indicates whether this keygen session has a delegate party
// Only meaningful for keygen sessions. Always false for sign sessions.
HasDelegate bool `protobuf:"varint,7,opt,name=has_delegate,json=hasDelegate,proto3" json:"has_delegate,omitempty"`
// Delegate share info (returned when keygen session completed and delegate party submitted share)
// Only populated if session_type="keygen" AND has_delegate=true AND session is completed
DelegateShare *DelegateShareInfo `protobuf:"bytes,8,opt,name=delegate_share,json=delegateShare,proto3" json:"delegate_share,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GetSessionStatusResponse) Reset() { func (x *GetSessionStatusResponse) Reset() {
*x = GetSessionStatusResponse{} *x = GetSessionStatusResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[10] mi := &file_api_proto_session_coordinator_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -721,7 +798,7 @@ func (x *GetSessionStatusResponse) String() string {
func (*GetSessionStatusResponse) ProtoMessage() {} func (*GetSessionStatusResponse) ProtoMessage() {}
func (x *GetSessionStatusResponse) ProtoReflect() protoreflect.Message { func (x *GetSessionStatusResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[10] mi := &file_api_proto_session_coordinator_proto_msgTypes[11]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -734,7 +811,7 @@ func (x *GetSessionStatusResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetSessionStatusResponse.ProtoReflect.Descriptor instead. // Deprecated: Use GetSessionStatusResponse.ProtoReflect.Descriptor instead.
func (*GetSessionStatusResponse) Descriptor() ([]byte, []int) { func (*GetSessionStatusResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{10} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{11}
} }
func (x *GetSessionStatusResponse) GetStatus() string { func (x *GetSessionStatusResponse) GetStatus() string {
@ -758,6 +835,13 @@ func (x *GetSessionStatusResponse) GetTotalParties() int32 {
return 0 return 0
} }
func (x *GetSessionStatusResponse) GetSessionType() string {
if x != nil {
return x.SessionType
}
return ""
}
func (x *GetSessionStatusResponse) GetPublicKey() []byte { func (x *GetSessionStatusResponse) GetPublicKey() []byte {
if x != nil { if x != nil {
return x.PublicKey return x.PublicKey
@ -772,6 +856,81 @@ func (x *GetSessionStatusResponse) GetSignature() []byte {
return nil return nil
} }
func (x *GetSessionStatusResponse) GetHasDelegate() bool {
if x != nil {
return x.HasDelegate
}
return false
}
func (x *GetSessionStatusResponse) GetDelegateShare() *DelegateShareInfo {
if x != nil {
return x.DelegateShare
}
return nil
}
// DelegateShareInfo contains the delegate party's share for user
type DelegateShareInfo struct {
state protoimpl.MessageState `protogen:"open.v1"`
EncryptedShare []byte `protobuf:"bytes,1,opt,name=encrypted_share,json=encryptedShare,proto3" json:"encrypted_share,omitempty"` // Encrypted share for user
PartyIndex int32 `protobuf:"varint,2,opt,name=party_index,json=partyIndex,proto3" json:"party_index,omitempty"` // Party's index in the session
PartyId string `protobuf:"bytes,3,opt,name=party_id,json=partyId,proto3" json:"party_id,omitempty"` // Delegate party ID
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DelegateShareInfo) Reset() {
*x = DelegateShareInfo{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DelegateShareInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DelegateShareInfo) ProtoMessage() {}
func (x *DelegateShareInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DelegateShareInfo.ProtoReflect.Descriptor instead.
func (*DelegateShareInfo) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{12}
}
func (x *DelegateShareInfo) GetEncryptedShare() []byte {
if x != nil {
return x.EncryptedShare
}
return nil
}
func (x *DelegateShareInfo) GetPartyIndex() int32 {
if x != nil {
return x.PartyIndex
}
return 0
}
func (x *DelegateShareInfo) GetPartyId() string {
if x != nil {
return x.PartyId
}
return ""
}
// ReportCompletionRequest reports that a participant has completed // ReportCompletionRequest reports that a participant has completed
type ReportCompletionRequest struct { type ReportCompletionRequest struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
@ -785,7 +944,7 @@ type ReportCompletionRequest struct {
func (x *ReportCompletionRequest) Reset() { func (x *ReportCompletionRequest) Reset() {
*x = ReportCompletionRequest{} *x = ReportCompletionRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[11] mi := &file_api_proto_session_coordinator_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -797,7 +956,7 @@ func (x *ReportCompletionRequest) String() string {
func (*ReportCompletionRequest) ProtoMessage() {} func (*ReportCompletionRequest) ProtoMessage() {}
func (x *ReportCompletionRequest) ProtoReflect() protoreflect.Message { func (x *ReportCompletionRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[11] mi := &file_api_proto_session_coordinator_proto_msgTypes[13]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -810,7 +969,7 @@ func (x *ReportCompletionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ReportCompletionRequest.ProtoReflect.Descriptor instead. // Deprecated: Use ReportCompletionRequest.ProtoReflect.Descriptor instead.
func (*ReportCompletionRequest) Descriptor() ([]byte, []int) { func (*ReportCompletionRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{11} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{13}
} }
func (x *ReportCompletionRequest) GetSessionId() string { func (x *ReportCompletionRequest) GetSessionId() string {
@ -852,7 +1011,7 @@ type ReportCompletionResponse struct {
func (x *ReportCompletionResponse) Reset() { func (x *ReportCompletionResponse) Reset() {
*x = ReportCompletionResponse{} *x = ReportCompletionResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[12] mi := &file_api_proto_session_coordinator_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -864,7 +1023,7 @@ func (x *ReportCompletionResponse) String() string {
func (*ReportCompletionResponse) ProtoMessage() {} func (*ReportCompletionResponse) ProtoMessage() {}
func (x *ReportCompletionResponse) ProtoReflect() protoreflect.Message { func (x *ReportCompletionResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[12] mi := &file_api_proto_session_coordinator_proto_msgTypes[14]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -877,7 +1036,7 @@ func (x *ReportCompletionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ReportCompletionResponse.ProtoReflect.Descriptor instead. // Deprecated: Use ReportCompletionResponse.ProtoReflect.Descriptor instead.
func (*ReportCompletionResponse) Descriptor() ([]byte, []int) { func (*ReportCompletionResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{12} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{14}
} }
func (x *ReportCompletionResponse) GetSuccess() bool { func (x *ReportCompletionResponse) GetSuccess() bool {
@ -904,7 +1063,7 @@ type CloseSessionRequest struct {
func (x *CloseSessionRequest) Reset() { func (x *CloseSessionRequest) Reset() {
*x = CloseSessionRequest{} *x = CloseSessionRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[13] mi := &file_api_proto_session_coordinator_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -916,7 +1075,7 @@ func (x *CloseSessionRequest) String() string {
func (*CloseSessionRequest) ProtoMessage() {} func (*CloseSessionRequest) ProtoMessage() {}
func (x *CloseSessionRequest) ProtoReflect() protoreflect.Message { func (x *CloseSessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[13] mi := &file_api_proto_session_coordinator_proto_msgTypes[15]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -929,7 +1088,7 @@ func (x *CloseSessionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CloseSessionRequest.ProtoReflect.Descriptor instead. // Deprecated: Use CloseSessionRequest.ProtoReflect.Descriptor instead.
func (*CloseSessionRequest) Descriptor() ([]byte, []int) { func (*CloseSessionRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{13} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{15}
} }
func (x *CloseSessionRequest) GetSessionId() string { func (x *CloseSessionRequest) GetSessionId() string {
@ -949,7 +1108,7 @@ type CloseSessionResponse struct {
func (x *CloseSessionResponse) Reset() { func (x *CloseSessionResponse) Reset() {
*x = CloseSessionResponse{} *x = CloseSessionResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[14] mi := &file_api_proto_session_coordinator_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -961,7 +1120,7 @@ func (x *CloseSessionResponse) String() string {
func (*CloseSessionResponse) ProtoMessage() {} func (*CloseSessionResponse) ProtoMessage() {}
func (x *CloseSessionResponse) ProtoReflect() protoreflect.Message { func (x *CloseSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[14] mi := &file_api_proto_session_coordinator_proto_msgTypes[16]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -974,7 +1133,7 @@ func (x *CloseSessionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CloseSessionResponse.ProtoReflect.Descriptor instead. // Deprecated: Use CloseSessionResponse.ProtoReflect.Descriptor instead.
func (*CloseSessionResponse) Descriptor() ([]byte, []int) { func (*CloseSessionResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{14} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{16}
} }
func (x *CloseSessionResponse) GetSuccess() bool { func (x *CloseSessionResponse) GetSuccess() bool {
@ -995,7 +1154,7 @@ type MarkPartyReadyRequest struct {
func (x *MarkPartyReadyRequest) Reset() { func (x *MarkPartyReadyRequest) Reset() {
*x = MarkPartyReadyRequest{} *x = MarkPartyReadyRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[15] mi := &file_api_proto_session_coordinator_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1007,7 +1166,7 @@ func (x *MarkPartyReadyRequest) String() string {
func (*MarkPartyReadyRequest) ProtoMessage() {} func (*MarkPartyReadyRequest) ProtoMessage() {}
func (x *MarkPartyReadyRequest) ProtoReflect() protoreflect.Message { func (x *MarkPartyReadyRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[15] mi := &file_api_proto_session_coordinator_proto_msgTypes[17]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1020,7 +1179,7 @@ func (x *MarkPartyReadyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use MarkPartyReadyRequest.ProtoReflect.Descriptor instead. // Deprecated: Use MarkPartyReadyRequest.ProtoReflect.Descriptor instead.
func (*MarkPartyReadyRequest) Descriptor() ([]byte, []int) { func (*MarkPartyReadyRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{15} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{17}
} }
func (x *MarkPartyReadyRequest) GetSessionId() string { func (x *MarkPartyReadyRequest) GetSessionId() string {
@ -1050,7 +1209,7 @@ type MarkPartyReadyResponse struct {
func (x *MarkPartyReadyResponse) Reset() { func (x *MarkPartyReadyResponse) Reset() {
*x = MarkPartyReadyResponse{} *x = MarkPartyReadyResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[16] mi := &file_api_proto_session_coordinator_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1062,7 +1221,7 @@ func (x *MarkPartyReadyResponse) String() string {
func (*MarkPartyReadyResponse) ProtoMessage() {} func (*MarkPartyReadyResponse) ProtoMessage() {}
func (x *MarkPartyReadyResponse) ProtoReflect() protoreflect.Message { func (x *MarkPartyReadyResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[16] mi := &file_api_proto_session_coordinator_proto_msgTypes[18]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1075,7 +1234,7 @@ func (x *MarkPartyReadyResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use MarkPartyReadyResponse.ProtoReflect.Descriptor instead. // Deprecated: Use MarkPartyReadyResponse.ProtoReflect.Descriptor instead.
func (*MarkPartyReadyResponse) Descriptor() ([]byte, []int) { func (*MarkPartyReadyResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{16} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{18}
} }
func (x *MarkPartyReadyResponse) GetSuccess() bool { func (x *MarkPartyReadyResponse) GetSuccess() bool {
@ -1116,7 +1275,7 @@ type StartSessionRequest struct {
func (x *StartSessionRequest) Reset() { func (x *StartSessionRequest) Reset() {
*x = StartSessionRequest{} *x = StartSessionRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[17] mi := &file_api_proto_session_coordinator_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1128,7 +1287,7 @@ func (x *StartSessionRequest) String() string {
func (*StartSessionRequest) ProtoMessage() {} func (*StartSessionRequest) ProtoMessage() {}
func (x *StartSessionRequest) ProtoReflect() protoreflect.Message { func (x *StartSessionRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[17] mi := &file_api_proto_session_coordinator_proto_msgTypes[19]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1141,7 +1300,7 @@ func (x *StartSessionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use StartSessionRequest.ProtoReflect.Descriptor instead. // Deprecated: Use StartSessionRequest.ProtoReflect.Descriptor instead.
func (*StartSessionRequest) Descriptor() ([]byte, []int) { func (*StartSessionRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{17} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{19}
} }
func (x *StartSessionRequest) GetSessionId() string { func (x *StartSessionRequest) GetSessionId() string {
@ -1162,7 +1321,7 @@ type StartSessionResponse struct {
func (x *StartSessionResponse) Reset() { func (x *StartSessionResponse) Reset() {
*x = StartSessionResponse{} *x = StartSessionResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[18] mi := &file_api_proto_session_coordinator_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1174,7 +1333,7 @@ func (x *StartSessionResponse) String() string {
func (*StartSessionResponse) ProtoMessage() {} func (*StartSessionResponse) ProtoMessage() {}
func (x *StartSessionResponse) ProtoReflect() protoreflect.Message { func (x *StartSessionResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[18] mi := &file_api_proto_session_coordinator_proto_msgTypes[20]
if x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1187,7 +1346,7 @@ func (x *StartSessionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use StartSessionResponse.ProtoReflect.Descriptor instead. // Deprecated: Use StartSessionResponse.ProtoReflect.Descriptor instead.
func (*StartSessionResponse) Descriptor() ([]byte, []int) { func (*StartSessionResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{18} return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{20}
} }
func (x *StartSessionResponse) GetSuccess() bool { func (x *StartSessionResponse) GetSuccess() bool {
@ -1204,11 +1363,133 @@ func (x *StartSessionResponse) GetStatus() string {
return "" return ""
} }
// SubmitDelegateShareRequest submits user's share from delegate party
type SubmitDelegateShareRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"`
PartyId string `protobuf:"bytes,2,opt,name=party_id,json=partyId,proto3" json:"party_id,omitempty"`
EncryptedShare []byte `protobuf:"bytes,3,opt,name=encrypted_share,json=encryptedShare,proto3" json:"encrypted_share,omitempty"` // Encrypted share for user
PublicKey []byte `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // Public key from keygen
PartyIndex int32 `protobuf:"varint,5,opt,name=party_index,json=partyIndex,proto3" json:"party_index,omitempty"` // Party's index in the session
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SubmitDelegateShareRequest) Reset() {
*x = SubmitDelegateShareRequest{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SubmitDelegateShareRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubmitDelegateShareRequest) ProtoMessage() {}
func (x *SubmitDelegateShareRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubmitDelegateShareRequest.ProtoReflect.Descriptor instead.
func (*SubmitDelegateShareRequest) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{21}
}
func (x *SubmitDelegateShareRequest) GetSessionId() string {
if x != nil {
return x.SessionId
}
return ""
}
func (x *SubmitDelegateShareRequest) GetPartyId() string {
if x != nil {
return x.PartyId
}
return ""
}
func (x *SubmitDelegateShareRequest) GetEncryptedShare() []byte {
if x != nil {
return x.EncryptedShare
}
return nil
}
func (x *SubmitDelegateShareRequest) GetPublicKey() []byte {
if x != nil {
return x.PublicKey
}
return nil
}
func (x *SubmitDelegateShareRequest) GetPartyIndex() int32 {
if x != nil {
return x.PartyIndex
}
return 0
}
// SubmitDelegateShareResponse contains result of share submission
type SubmitDelegateShareResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SubmitDelegateShareResponse) Reset() {
*x = SubmitDelegateShareResponse{}
mi := &file_api_proto_session_coordinator_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *SubmitDelegateShareResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubmitDelegateShareResponse) ProtoMessage() {}
func (x *SubmitDelegateShareResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_session_coordinator_proto_msgTypes[22]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubmitDelegateShareResponse.ProtoReflect.Descriptor instead.
func (*SubmitDelegateShareResponse) Descriptor() ([]byte, []int) {
return file_api_proto_session_coordinator_proto_rawDescGZIP(), []int{22}
}
func (x *SubmitDelegateShareResponse) GetSuccess() bool {
if x != nil {
return x.Success
}
return false
}
var File_api_proto_session_coordinator_proto protoreflect.FileDescriptor var File_api_proto_session_coordinator_proto protoreflect.FileDescriptor
const file_api_proto_session_coordinator_proto_rawDesc = "" + const file_api_proto_session_coordinator_proto_rawDesc = "" +
"\n" + "\n" +
"#api/proto/session_coordinator.proto\x12\x12mpc.coordinator.v1\"\xe8\x02\n" + "#api/proto/session_coordinator.proto\x12\x12mpc.coordinator.v1\"\xbf\x03\n" +
"\x14CreateSessionRequest\x12!\n" + "\x14CreateSessionRequest\x12!\n" +
"\fsession_type\x18\x01 \x01(\tR\vsessionType\x12\x1f\n" + "\fsession_type\x18\x01 \x01(\tR\vsessionType\x12\x1f\n" +
"\vthreshold_n\x18\x02 \x01(\x05R\n" + "\vthreshold_n\x18\x02 \x01(\x05R\n" +
@ -1218,7 +1499,13 @@ const file_api_proto_session_coordinator_proto_rawDesc = "" +
"\fparticipants\x18\x04 \x03(\v2#.mpc.coordinator.v1.ParticipantInfoR\fparticipants\x12!\n" + "\fparticipants\x18\x04 \x03(\v2#.mpc.coordinator.v1.ParticipantInfoR\fparticipants\x12!\n" +
"\fmessage_hash\x18\x05 \x01(\fR\vmessageHash\x12,\n" + "\fmessage_hash\x18\x05 \x01(\fR\vmessageHash\x12,\n" +
"\x12expires_in_seconds\x18\x06 \x01(\x03R\x10expiresInSeconds\x12Q\n" + "\x12expires_in_seconds\x18\x06 \x01(\x03R\x10expiresInSeconds\x12Q\n" +
"\x11party_composition\x18\a \x01(\v2$.mpc.coordinator.v1.PartyCompositionR\x10partyComposition\"\x8d\x01\n" + "\x11party_composition\x18\a \x01(\v2$.mpc.coordinator.v1.PartyCompositionR\x10partyComposition\x12U\n" +
"\x13delegate_user_share\x18\b \x01(\v2%.mpc.coordinator.v1.DelegateUserShareR\x11delegateUserShare\"\x89\x01\n" +
"\x11DelegateUserShare\x12*\n" +
"\x11delegate_party_id\x18\x01 \x01(\tR\x0fdelegatePartyId\x12'\n" +
"\x0fencrypted_share\x18\x02 \x01(\fR\x0eencryptedShare\x12\x1f\n" +
"\vparty_index\x18\x03 \x01(\x05R\n" +
"partyIndex\"\x8d\x01\n" +
"\x10PartyComposition\x12)\n" + "\x10PartyComposition\x12)\n" +
"\x10persistent_count\x18\x01 \x01(\x05R\x0fpersistentCount\x12%\n" + "\x10persistent_count\x18\x01 \x01(\x05R\x0fpersistentCount\x12%\n" +
"\x0edelegate_count\x18\x02 \x01(\x05R\rdelegateCount\x12'\n" + "\x0edelegate_count\x18\x02 \x01(\x05R\rdelegateCount\x12'\n" +
@ -1277,14 +1564,22 @@ const file_api_proto_session_coordinator_proto_rawDesc = "" +
"deviceInfo\"8\n" + "deviceInfo\"8\n" +
"\x17GetSessionStatusRequest\x12\x1d\n" + "\x17GetSessionStatusRequest\x12\x1d\n" +
"\n" + "\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\"\xc1\x01\n" + "session_id\x18\x01 \x01(\tR\tsessionId\"\xd5\x02\n" +
"\x18GetSessionStatusResponse\x12\x16\n" + "\x18GetSessionStatusResponse\x12\x16\n" +
"\x06status\x18\x01 \x01(\tR\x06status\x12+\n" + "\x06status\x18\x01 \x01(\tR\x06status\x12+\n" +
"\x11completed_parties\x18\x02 \x01(\x05R\x10completedParties\x12#\n" + "\x11completed_parties\x18\x02 \x01(\x05R\x10completedParties\x12#\n" +
"\rtotal_parties\x18\x03 \x01(\x05R\ftotalParties\x12\x1d\n" + "\rtotal_parties\x18\x03 \x01(\x05R\ftotalParties\x12!\n" +
"\fsession_type\x18\x04 \x01(\tR\vsessionType\x12\x1d\n" +
"\n" + "\n" +
"public_key\x18\x04 \x01(\fR\tpublicKey\x12\x1c\n" + "public_key\x18\x05 \x01(\fR\tpublicKey\x12\x1c\n" +
"\tsignature\x18\x05 \x01(\fR\tsignature\"\x90\x01\n" + "\tsignature\x18\x06 \x01(\fR\tsignature\x12!\n" +
"\fhas_delegate\x18\a \x01(\bR\vhasDelegate\x12L\n" +
"\x0edelegate_share\x18\b \x01(\v2%.mpc.coordinator.v1.DelegateShareInfoR\rdelegateShare\"x\n" +
"\x11DelegateShareInfo\x12'\n" +
"\x0fencrypted_share\x18\x01 \x01(\fR\x0eencryptedShare\x12\x1f\n" +
"\vparty_index\x18\x02 \x01(\x05R\n" +
"partyIndex\x12\x19\n" +
"\bparty_id\x18\x03 \x01(\tR\apartyId\"\x90\x01\n" +
"\x17ReportCompletionRequest\x12\x1d\n" + "\x17ReportCompletionRequest\x12\x1d\n" +
"\n" + "\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12\x19\n" + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x19\n" +
@ -1315,7 +1610,18 @@ const file_api_proto_session_coordinator_proto_rawDesc = "" +
"session_id\x18\x01 \x01(\tR\tsessionId\"H\n" + "session_id\x18\x01 \x01(\tR\tsessionId\"H\n" +
"\x14StartSessionResponse\x12\x18\n" + "\x14StartSessionResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x16\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x16\n" +
"\x06status\x18\x02 \x01(\tR\x06status2\xe7\x05\n" + "\x06status\x18\x02 \x01(\tR\x06status\"\xbf\x01\n" +
"\x1aSubmitDelegateShareRequest\x12\x1d\n" +
"\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12\x19\n" +
"\bparty_id\x18\x02 \x01(\tR\apartyId\x12'\n" +
"\x0fencrypted_share\x18\x03 \x01(\fR\x0eencryptedShare\x12\x1d\n" +
"\n" +
"public_key\x18\x04 \x01(\fR\tpublicKey\x12\x1f\n" +
"\vparty_index\x18\x05 \x01(\x05R\n" +
"partyIndex\"7\n" +
"\x1bSubmitDelegateShareResponse\x12\x18\n" +
"\asuccess\x18\x01 \x01(\bR\asuccess2\xdf\x06\n" +
"\x12SessionCoordinator\x12d\n" + "\x12SessionCoordinator\x12d\n" +
"\rCreateSession\x12(.mpc.coordinator.v1.CreateSessionRequest\x1a).mpc.coordinator.v1.CreateSessionResponse\x12^\n" + "\rCreateSession\x12(.mpc.coordinator.v1.CreateSessionRequest\x1a).mpc.coordinator.v1.CreateSessionResponse\x12^\n" +
"\vJoinSession\x12&.mpc.coordinator.v1.JoinSessionRequest\x1a'.mpc.coordinator.v1.JoinSessionResponse\x12m\n" + "\vJoinSession\x12&.mpc.coordinator.v1.JoinSessionRequest\x1a'.mpc.coordinator.v1.JoinSessionResponse\x12m\n" +
@ -1323,7 +1629,8 @@ const file_api_proto_session_coordinator_proto_rawDesc = "" +
"\x0eMarkPartyReady\x12).mpc.coordinator.v1.MarkPartyReadyRequest\x1a*.mpc.coordinator.v1.MarkPartyReadyResponse\x12a\n" + "\x0eMarkPartyReady\x12).mpc.coordinator.v1.MarkPartyReadyRequest\x1a*.mpc.coordinator.v1.MarkPartyReadyResponse\x12a\n" +
"\fStartSession\x12'.mpc.coordinator.v1.StartSessionRequest\x1a(.mpc.coordinator.v1.StartSessionResponse\x12m\n" + "\fStartSession\x12'.mpc.coordinator.v1.StartSessionRequest\x1a(.mpc.coordinator.v1.StartSessionResponse\x12m\n" +
"\x10ReportCompletion\x12+.mpc.coordinator.v1.ReportCompletionRequest\x1a,.mpc.coordinator.v1.ReportCompletionResponse\x12a\n" + "\x10ReportCompletion\x12+.mpc.coordinator.v1.ReportCompletionRequest\x1a,.mpc.coordinator.v1.ReportCompletionResponse\x12a\n" +
"\fCloseSession\x12'.mpc.coordinator.v1.CloseSessionRequest\x1a(.mpc.coordinator.v1.CloseSessionResponseBEZCgithub.com/rwadurian/mpc-system/api/grpc/coordinator/v1;coordinatorb\x06proto3" "\fCloseSession\x12'.mpc.coordinator.v1.CloseSessionRequest\x1a(.mpc.coordinator.v1.CloseSessionResponse\x12v\n" +
"\x13SubmitDelegateShare\x12..mpc.coordinator.v1.SubmitDelegateShareRequest\x1a/.mpc.coordinator.v1.SubmitDelegateShareResponseBEZCgithub.com/rwadurian/mpc-system/api/grpc/coordinator/v1;coordinatorb\x06proto3"
var ( var (
file_api_proto_session_coordinator_proto_rawDescOnce sync.Once file_api_proto_session_coordinator_proto_rawDescOnce sync.Once
@ -1337,57 +1644,65 @@ func file_api_proto_session_coordinator_proto_rawDescGZIP() []byte {
return file_api_proto_session_coordinator_proto_rawDescData return file_api_proto_session_coordinator_proto_rawDescData
} }
var file_api_proto_session_coordinator_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_api_proto_session_coordinator_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
var file_api_proto_session_coordinator_proto_goTypes = []any{ var file_api_proto_session_coordinator_proto_goTypes = []any{
(*CreateSessionRequest)(nil), // 0: mpc.coordinator.v1.CreateSessionRequest (*CreateSessionRequest)(nil), // 0: mpc.coordinator.v1.CreateSessionRequest
(*PartyComposition)(nil), // 1: mpc.coordinator.v1.PartyComposition (*DelegateUserShare)(nil), // 1: mpc.coordinator.v1.DelegateUserShare
(*ParticipantInfo)(nil), // 2: mpc.coordinator.v1.ParticipantInfo (*PartyComposition)(nil), // 2: mpc.coordinator.v1.PartyComposition
(*DeviceInfo)(nil), // 3: mpc.coordinator.v1.DeviceInfo (*ParticipantInfo)(nil), // 3: mpc.coordinator.v1.ParticipantInfo
(*CreateSessionResponse)(nil), // 4: mpc.coordinator.v1.CreateSessionResponse (*DeviceInfo)(nil), // 4: mpc.coordinator.v1.DeviceInfo
(*JoinSessionRequest)(nil), // 5: mpc.coordinator.v1.JoinSessionRequest (*CreateSessionResponse)(nil), // 5: mpc.coordinator.v1.CreateSessionResponse
(*JoinSessionResponse)(nil), // 6: mpc.coordinator.v1.JoinSessionResponse (*JoinSessionRequest)(nil), // 6: mpc.coordinator.v1.JoinSessionRequest
(*SessionInfo)(nil), // 7: mpc.coordinator.v1.SessionInfo (*JoinSessionResponse)(nil), // 7: mpc.coordinator.v1.JoinSessionResponse
(*PartyInfo)(nil), // 8: mpc.coordinator.v1.PartyInfo (*SessionInfo)(nil), // 8: mpc.coordinator.v1.SessionInfo
(*GetSessionStatusRequest)(nil), // 9: mpc.coordinator.v1.GetSessionStatusRequest (*PartyInfo)(nil), // 9: mpc.coordinator.v1.PartyInfo
(*GetSessionStatusResponse)(nil), // 10: mpc.coordinator.v1.GetSessionStatusResponse (*GetSessionStatusRequest)(nil), // 10: mpc.coordinator.v1.GetSessionStatusRequest
(*ReportCompletionRequest)(nil), // 11: mpc.coordinator.v1.ReportCompletionRequest (*GetSessionStatusResponse)(nil), // 11: mpc.coordinator.v1.GetSessionStatusResponse
(*ReportCompletionResponse)(nil), // 12: mpc.coordinator.v1.ReportCompletionResponse (*DelegateShareInfo)(nil), // 12: mpc.coordinator.v1.DelegateShareInfo
(*CloseSessionRequest)(nil), // 13: mpc.coordinator.v1.CloseSessionRequest (*ReportCompletionRequest)(nil), // 13: mpc.coordinator.v1.ReportCompletionRequest
(*CloseSessionResponse)(nil), // 14: mpc.coordinator.v1.CloseSessionResponse (*ReportCompletionResponse)(nil), // 14: mpc.coordinator.v1.ReportCompletionResponse
(*MarkPartyReadyRequest)(nil), // 15: mpc.coordinator.v1.MarkPartyReadyRequest (*CloseSessionRequest)(nil), // 15: mpc.coordinator.v1.CloseSessionRequest
(*MarkPartyReadyResponse)(nil), // 16: mpc.coordinator.v1.MarkPartyReadyResponse (*CloseSessionResponse)(nil), // 16: mpc.coordinator.v1.CloseSessionResponse
(*StartSessionRequest)(nil), // 17: mpc.coordinator.v1.StartSessionRequest (*MarkPartyReadyRequest)(nil), // 17: mpc.coordinator.v1.MarkPartyReadyRequest
(*StartSessionResponse)(nil), // 18: mpc.coordinator.v1.StartSessionResponse (*MarkPartyReadyResponse)(nil), // 18: mpc.coordinator.v1.MarkPartyReadyResponse
nil, // 19: mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry (*StartSessionRequest)(nil), // 19: mpc.coordinator.v1.StartSessionRequest
(*StartSessionResponse)(nil), // 20: mpc.coordinator.v1.StartSessionResponse
(*SubmitDelegateShareRequest)(nil), // 21: mpc.coordinator.v1.SubmitDelegateShareRequest
(*SubmitDelegateShareResponse)(nil), // 22: mpc.coordinator.v1.SubmitDelegateShareResponse
nil, // 23: mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry
} }
var file_api_proto_session_coordinator_proto_depIdxs = []int32{ var file_api_proto_session_coordinator_proto_depIdxs = []int32{
2, // 0: mpc.coordinator.v1.CreateSessionRequest.participants:type_name -> mpc.coordinator.v1.ParticipantInfo 3, // 0: mpc.coordinator.v1.CreateSessionRequest.participants:type_name -> mpc.coordinator.v1.ParticipantInfo
1, // 1: mpc.coordinator.v1.CreateSessionRequest.party_composition:type_name -> mpc.coordinator.v1.PartyComposition 2, // 1: mpc.coordinator.v1.CreateSessionRequest.party_composition:type_name -> mpc.coordinator.v1.PartyComposition
3, // 2: mpc.coordinator.v1.ParticipantInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo 1, // 2: mpc.coordinator.v1.CreateSessionRequest.delegate_user_share:type_name -> mpc.coordinator.v1.DelegateUserShare
19, // 3: mpc.coordinator.v1.CreateSessionResponse.join_tokens:type_name -> mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry 4, // 3: mpc.coordinator.v1.ParticipantInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
3, // 4: mpc.coordinator.v1.JoinSessionRequest.device_info:type_name -> mpc.coordinator.v1.DeviceInfo 23, // 4: mpc.coordinator.v1.CreateSessionResponse.join_tokens:type_name -> mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry
7, // 5: mpc.coordinator.v1.JoinSessionResponse.session_info:type_name -> mpc.coordinator.v1.SessionInfo 4, // 5: mpc.coordinator.v1.JoinSessionRequest.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
8, // 6: mpc.coordinator.v1.JoinSessionResponse.other_parties:type_name -> mpc.coordinator.v1.PartyInfo 8, // 6: mpc.coordinator.v1.JoinSessionResponse.session_info:type_name -> mpc.coordinator.v1.SessionInfo
3, // 7: mpc.coordinator.v1.PartyInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo 9, // 7: mpc.coordinator.v1.JoinSessionResponse.other_parties:type_name -> mpc.coordinator.v1.PartyInfo
0, // 8: mpc.coordinator.v1.SessionCoordinator.CreateSession:input_type -> mpc.coordinator.v1.CreateSessionRequest 4, // 8: mpc.coordinator.v1.PartyInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
5, // 9: mpc.coordinator.v1.SessionCoordinator.JoinSession:input_type -> mpc.coordinator.v1.JoinSessionRequest 12, // 9: mpc.coordinator.v1.GetSessionStatusResponse.delegate_share:type_name -> mpc.coordinator.v1.DelegateShareInfo
9, // 10: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:input_type -> mpc.coordinator.v1.GetSessionStatusRequest 0, // 10: mpc.coordinator.v1.SessionCoordinator.CreateSession:input_type -> mpc.coordinator.v1.CreateSessionRequest
15, // 11: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:input_type -> mpc.coordinator.v1.MarkPartyReadyRequest 6, // 11: mpc.coordinator.v1.SessionCoordinator.JoinSession:input_type -> mpc.coordinator.v1.JoinSessionRequest
17, // 12: mpc.coordinator.v1.SessionCoordinator.StartSession:input_type -> mpc.coordinator.v1.StartSessionRequest 10, // 12: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:input_type -> mpc.coordinator.v1.GetSessionStatusRequest
11, // 13: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:input_type -> mpc.coordinator.v1.ReportCompletionRequest 17, // 13: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:input_type -> mpc.coordinator.v1.MarkPartyReadyRequest
13, // 14: mpc.coordinator.v1.SessionCoordinator.CloseSession:input_type -> mpc.coordinator.v1.CloseSessionRequest 19, // 14: mpc.coordinator.v1.SessionCoordinator.StartSession:input_type -> mpc.coordinator.v1.StartSessionRequest
4, // 15: mpc.coordinator.v1.SessionCoordinator.CreateSession:output_type -> mpc.coordinator.v1.CreateSessionResponse 13, // 15: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:input_type -> mpc.coordinator.v1.ReportCompletionRequest
6, // 16: mpc.coordinator.v1.SessionCoordinator.JoinSession:output_type -> mpc.coordinator.v1.JoinSessionResponse 15, // 16: mpc.coordinator.v1.SessionCoordinator.CloseSession:input_type -> mpc.coordinator.v1.CloseSessionRequest
10, // 17: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:output_type -> mpc.coordinator.v1.GetSessionStatusResponse 21, // 17: mpc.coordinator.v1.SessionCoordinator.SubmitDelegateShare:input_type -> mpc.coordinator.v1.SubmitDelegateShareRequest
16, // 18: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:output_type -> mpc.coordinator.v1.MarkPartyReadyResponse 5, // 18: mpc.coordinator.v1.SessionCoordinator.CreateSession:output_type -> mpc.coordinator.v1.CreateSessionResponse
18, // 19: mpc.coordinator.v1.SessionCoordinator.StartSession:output_type -> mpc.coordinator.v1.StartSessionResponse 7, // 19: mpc.coordinator.v1.SessionCoordinator.JoinSession:output_type -> mpc.coordinator.v1.JoinSessionResponse
12, // 20: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:output_type -> mpc.coordinator.v1.ReportCompletionResponse 11, // 20: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:output_type -> mpc.coordinator.v1.GetSessionStatusResponse
14, // 21: mpc.coordinator.v1.SessionCoordinator.CloseSession:output_type -> mpc.coordinator.v1.CloseSessionResponse 18, // 21: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:output_type -> mpc.coordinator.v1.MarkPartyReadyResponse
15, // [15:22] is the sub-list for method output_type 20, // 22: mpc.coordinator.v1.SessionCoordinator.StartSession:output_type -> mpc.coordinator.v1.StartSessionResponse
8, // [8:15] is the sub-list for method input_type 14, // 23: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:output_type -> mpc.coordinator.v1.ReportCompletionResponse
8, // [8:8] is the sub-list for extension type_name 16, // 24: mpc.coordinator.v1.SessionCoordinator.CloseSession:output_type -> mpc.coordinator.v1.CloseSessionResponse
8, // [8:8] is the sub-list for extension extendee 22, // 25: mpc.coordinator.v1.SessionCoordinator.SubmitDelegateShare:output_type -> mpc.coordinator.v1.SubmitDelegateShareResponse
0, // [0:8] is the sub-list for field type_name 18, // [18:26] is the sub-list for method output_type
10, // [10:18] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
} }
func init() { file_api_proto_session_coordinator_proto_init() } func init() { file_api_proto_session_coordinator_proto_init() }
@ -1401,7 +1716,7 @@ func file_api_proto_session_coordinator_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_session_coordinator_proto_rawDesc), len(file_api_proto_session_coordinator_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_session_coordinator_proto_rawDesc), len(file_api_proto_session_coordinator_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 20, NumMessages: 24,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -19,13 +19,14 @@ import (
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const ( const (
SessionCoordinator_CreateSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/CreateSession" SessionCoordinator_CreateSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/CreateSession"
SessionCoordinator_JoinSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/JoinSession" SessionCoordinator_JoinSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/JoinSession"
SessionCoordinator_GetSessionStatus_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/GetSessionStatus" SessionCoordinator_GetSessionStatus_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/GetSessionStatus"
SessionCoordinator_MarkPartyReady_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/MarkPartyReady" SessionCoordinator_MarkPartyReady_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/MarkPartyReady"
SessionCoordinator_StartSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/StartSession" SessionCoordinator_StartSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/StartSession"
SessionCoordinator_ReportCompletion_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/ReportCompletion" SessionCoordinator_ReportCompletion_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/ReportCompletion"
SessionCoordinator_CloseSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/CloseSession" SessionCoordinator_CloseSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/CloseSession"
SessionCoordinator_SubmitDelegateShare_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/SubmitDelegateShare"
) )
// SessionCoordinatorClient is the client API for SessionCoordinator service. // SessionCoordinatorClient is the client API for SessionCoordinator service.
@ -42,6 +43,8 @@ type SessionCoordinatorClient interface {
StartSession(ctx context.Context, in *StartSessionRequest, opts ...grpc.CallOption) (*StartSessionResponse, error) StartSession(ctx context.Context, in *StartSessionRequest, opts ...grpc.CallOption) (*StartSessionResponse, error)
ReportCompletion(ctx context.Context, in *ReportCompletionRequest, opts ...grpc.CallOption) (*ReportCompletionResponse, error) ReportCompletion(ctx context.Context, in *ReportCompletionRequest, opts ...grpc.CallOption) (*ReportCompletionResponse, error)
CloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error) CloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error)
// Delegate party share submission (delegate party submits user's share after keygen)
SubmitDelegateShare(ctx context.Context, in *SubmitDelegateShareRequest, opts ...grpc.CallOption) (*SubmitDelegateShareResponse, error)
} }
type sessionCoordinatorClient struct { type sessionCoordinatorClient struct {
@ -122,6 +125,16 @@ func (c *sessionCoordinatorClient) CloseSession(ctx context.Context, in *CloseSe
return out, nil return out, nil
} }
func (c *sessionCoordinatorClient) SubmitDelegateShare(ctx context.Context, in *SubmitDelegateShareRequest, opts ...grpc.CallOption) (*SubmitDelegateShareResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SubmitDelegateShareResponse)
err := c.cc.Invoke(ctx, SessionCoordinator_SubmitDelegateShare_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// SessionCoordinatorServer is the server API for SessionCoordinator service. // SessionCoordinatorServer is the server API for SessionCoordinator service.
// All implementations must embed UnimplementedSessionCoordinatorServer // All implementations must embed UnimplementedSessionCoordinatorServer
// for forward compatibility. // for forward compatibility.
@ -136,6 +149,8 @@ type SessionCoordinatorServer interface {
StartSession(context.Context, *StartSessionRequest) (*StartSessionResponse, error) StartSession(context.Context, *StartSessionRequest) (*StartSessionResponse, error)
ReportCompletion(context.Context, *ReportCompletionRequest) (*ReportCompletionResponse, error) ReportCompletion(context.Context, *ReportCompletionRequest) (*ReportCompletionResponse, error)
CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error)
// Delegate party share submission (delegate party submits user's share after keygen)
SubmitDelegateShare(context.Context, *SubmitDelegateShareRequest) (*SubmitDelegateShareResponse, error)
mustEmbedUnimplementedSessionCoordinatorServer() mustEmbedUnimplementedSessionCoordinatorServer()
} }
@ -167,6 +182,9 @@ func (UnimplementedSessionCoordinatorServer) ReportCompletion(context.Context, *
func (UnimplementedSessionCoordinatorServer) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) { func (UnimplementedSessionCoordinatorServer) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CloseSession not implemented") return nil, status.Error(codes.Unimplemented, "method CloseSession not implemented")
} }
func (UnimplementedSessionCoordinatorServer) SubmitDelegateShare(context.Context, *SubmitDelegateShareRequest) (*SubmitDelegateShareResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SubmitDelegateShare not implemented")
}
func (UnimplementedSessionCoordinatorServer) mustEmbedUnimplementedSessionCoordinatorServer() {} func (UnimplementedSessionCoordinatorServer) mustEmbedUnimplementedSessionCoordinatorServer() {}
func (UnimplementedSessionCoordinatorServer) testEmbeddedByValue() {} func (UnimplementedSessionCoordinatorServer) testEmbeddedByValue() {}
@ -314,6 +332,24 @@ func _SessionCoordinator_CloseSession_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _SessionCoordinator_SubmitDelegateShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SubmitDelegateShareRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SessionCoordinatorServer).SubmitDelegateShare(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: SessionCoordinator_SubmitDelegateShare_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SessionCoordinatorServer).SubmitDelegateShare(ctx, req.(*SubmitDelegateShareRequest))
}
return interceptor(ctx, in, info, handler)
}
// SessionCoordinator_ServiceDesc is the grpc.ServiceDesc for SessionCoordinator service. // SessionCoordinator_ServiceDesc is the grpc.ServiceDesc for SessionCoordinator service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -349,6 +385,10 @@ var SessionCoordinator_ServiceDesc = grpc.ServiceDesc{
MethodName: "CloseSession", MethodName: "CloseSession",
Handler: _SessionCoordinator_CloseSession_Handler, Handler: _SessionCoordinator_CloseSession_Handler,
}, },
{
MethodName: "SubmitDelegateShare",
Handler: _SessionCoordinator_SubmitDelegateShare_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "api/proto/session_coordinator.proto", Metadata: "api/proto/session_coordinator.proto",

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,11 @@ const (
MessageRouter_SubscribeSessionEvents_FullMethodName = "/mpc.router.v1.MessageRouter/SubscribeSessionEvents" MessageRouter_SubscribeSessionEvents_FullMethodName = "/mpc.router.v1.MessageRouter/SubscribeSessionEvents"
MessageRouter_PublishSessionEvent_FullMethodName = "/mpc.router.v1.MessageRouter/PublishSessionEvent" MessageRouter_PublishSessionEvent_FullMethodName = "/mpc.router.v1.MessageRouter/PublishSessionEvent"
MessageRouter_GetRegisteredParties_FullMethodName = "/mpc.router.v1.MessageRouter/GetRegisteredParties" MessageRouter_GetRegisteredParties_FullMethodName = "/mpc.router.v1.MessageRouter/GetRegisteredParties"
MessageRouter_JoinSession_FullMethodName = "/mpc.router.v1.MessageRouter/JoinSession"
MessageRouter_MarkPartyReady_FullMethodName = "/mpc.router.v1.MessageRouter/MarkPartyReady"
MessageRouter_ReportCompletion_FullMethodName = "/mpc.router.v1.MessageRouter/ReportCompletion"
MessageRouter_GetSessionStatus_FullMethodName = "/mpc.router.v1.MessageRouter/GetSessionStatus"
MessageRouter_SubmitDelegateShare_FullMethodName = "/mpc.router.v1.MessageRouter/SubmitDelegateShare"
) )
// MessageRouterClient is the client API for MessageRouter service. // MessageRouterClient is the client API for MessageRouter service.
@ -36,6 +41,8 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
// //
// MessageRouter service handles MPC message routing // MessageRouter service handles MPC message routing
// This is the ONLY service that server-parties need to connect to.
// All session operations are proxied through Message Router to Session Coordinator.
type MessageRouterClient interface { type MessageRouterClient interface {
// RouteMessage routes a message from one party to others // RouteMessage routes a message from one party to others
RouteMessage(ctx context.Context, in *RouteMessageRequest, opts ...grpc.CallOption) (*RouteMessageResponse, error) RouteMessage(ctx context.Context, in *RouteMessageRequest, opts ...grpc.CallOption) (*RouteMessageResponse, error)
@ -58,6 +65,16 @@ type MessageRouterClient interface {
PublishSessionEvent(ctx context.Context, in *PublishSessionEventRequest, opts ...grpc.CallOption) (*PublishSessionEventResponse, error) PublishSessionEvent(ctx context.Context, in *PublishSessionEventRequest, opts ...grpc.CallOption) (*PublishSessionEventResponse, error)
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery) // GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
GetRegisteredParties(ctx context.Context, in *GetRegisteredPartiesRequest, opts ...grpc.CallOption) (*GetRegisteredPartiesResponse, error) GetRegisteredParties(ctx context.Context, in *GetRegisteredPartiesRequest, opts ...grpc.CallOption) (*GetRegisteredPartiesResponse, error)
// JoinSession joins a session (proxied to Session Coordinator)
JoinSession(ctx context.Context, in *JoinSessionRequest, opts ...grpc.CallOption) (*JoinSessionResponse, error)
// MarkPartyReady marks a party as ready (proxied to Session Coordinator)
MarkPartyReady(ctx context.Context, in *MarkPartyReadyRequest, opts ...grpc.CallOption) (*MarkPartyReadyResponse, error)
// ReportCompletion reports completion (proxied to Session Coordinator)
ReportCompletion(ctx context.Context, in *ReportCompletionRequest, opts ...grpc.CallOption) (*ReportCompletionResponse, error)
// GetSessionStatus gets session status (proxied to Session Coordinator)
GetSessionStatus(ctx context.Context, in *GetSessionStatusRequest, opts ...grpc.CallOption) (*GetSessionStatusResponse, error)
// SubmitDelegateShare submits user's share from delegate party (proxied to Session Coordinator)
SubmitDelegateShare(ctx context.Context, in *SubmitDelegateShareRequest, opts ...grpc.CallOption) (*SubmitDelegateShareResponse, error)
} }
type messageRouterClient struct { type messageRouterClient struct {
@ -186,11 +203,63 @@ func (c *messageRouterClient) GetRegisteredParties(ctx context.Context, in *GetR
return out, nil return out, nil
} }
func (c *messageRouterClient) JoinSession(ctx context.Context, in *JoinSessionRequest, opts ...grpc.CallOption) (*JoinSessionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(JoinSessionResponse)
err := c.cc.Invoke(ctx, MessageRouter_JoinSession_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageRouterClient) MarkPartyReady(ctx context.Context, in *MarkPartyReadyRequest, opts ...grpc.CallOption) (*MarkPartyReadyResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(MarkPartyReadyResponse)
err := c.cc.Invoke(ctx, MessageRouter_MarkPartyReady_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageRouterClient) ReportCompletion(ctx context.Context, in *ReportCompletionRequest, opts ...grpc.CallOption) (*ReportCompletionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ReportCompletionResponse)
err := c.cc.Invoke(ctx, MessageRouter_ReportCompletion_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageRouterClient) GetSessionStatus(ctx context.Context, in *GetSessionStatusRequest, opts ...grpc.CallOption) (*GetSessionStatusResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetSessionStatusResponse)
err := c.cc.Invoke(ctx, MessageRouter_GetSessionStatus_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *messageRouterClient) SubmitDelegateShare(ctx context.Context, in *SubmitDelegateShareRequest, opts ...grpc.CallOption) (*SubmitDelegateShareResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SubmitDelegateShareResponse)
err := c.cc.Invoke(ctx, MessageRouter_SubmitDelegateShare_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// MessageRouterServer is the server API for MessageRouter service. // MessageRouterServer is the server API for MessageRouter service.
// All implementations must embed UnimplementedMessageRouterServer // All implementations must embed UnimplementedMessageRouterServer
// for forward compatibility. // for forward compatibility.
// //
// MessageRouter service handles MPC message routing // MessageRouter service handles MPC message routing
// This is the ONLY service that server-parties need to connect to.
// All session operations are proxied through Message Router to Session Coordinator.
type MessageRouterServer interface { type MessageRouterServer interface {
// RouteMessage routes a message from one party to others // RouteMessage routes a message from one party to others
RouteMessage(context.Context, *RouteMessageRequest) (*RouteMessageResponse, error) RouteMessage(context.Context, *RouteMessageRequest) (*RouteMessageResponse, error)
@ -213,6 +282,16 @@ type MessageRouterServer interface {
PublishSessionEvent(context.Context, *PublishSessionEventRequest) (*PublishSessionEventResponse, error) PublishSessionEvent(context.Context, *PublishSessionEventRequest) (*PublishSessionEventResponse, error)
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery) // GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
GetRegisteredParties(context.Context, *GetRegisteredPartiesRequest) (*GetRegisteredPartiesResponse, error) GetRegisteredParties(context.Context, *GetRegisteredPartiesRequest) (*GetRegisteredPartiesResponse, error)
// JoinSession joins a session (proxied to Session Coordinator)
JoinSession(context.Context, *JoinSessionRequest) (*JoinSessionResponse, error)
// MarkPartyReady marks a party as ready (proxied to Session Coordinator)
MarkPartyReady(context.Context, *MarkPartyReadyRequest) (*MarkPartyReadyResponse, error)
// ReportCompletion reports completion (proxied to Session Coordinator)
ReportCompletion(context.Context, *ReportCompletionRequest) (*ReportCompletionResponse, error)
// GetSessionStatus gets session status (proxied to Session Coordinator)
GetSessionStatus(context.Context, *GetSessionStatusRequest) (*GetSessionStatusResponse, error)
// SubmitDelegateShare submits user's share from delegate party (proxied to Session Coordinator)
SubmitDelegateShare(context.Context, *SubmitDelegateShareRequest) (*SubmitDelegateShareResponse, error)
mustEmbedUnimplementedMessageRouterServer() mustEmbedUnimplementedMessageRouterServer()
} }
@ -253,6 +332,21 @@ func (UnimplementedMessageRouterServer) PublishSessionEvent(context.Context, *Pu
func (UnimplementedMessageRouterServer) GetRegisteredParties(context.Context, *GetRegisteredPartiesRequest) (*GetRegisteredPartiesResponse, error) { func (UnimplementedMessageRouterServer) GetRegisteredParties(context.Context, *GetRegisteredPartiesRequest) (*GetRegisteredPartiesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetRegisteredParties not implemented") return nil, status.Error(codes.Unimplemented, "method GetRegisteredParties not implemented")
} }
func (UnimplementedMessageRouterServer) JoinSession(context.Context, *JoinSessionRequest) (*JoinSessionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method JoinSession not implemented")
}
func (UnimplementedMessageRouterServer) MarkPartyReady(context.Context, *MarkPartyReadyRequest) (*MarkPartyReadyResponse, error) {
return nil, status.Error(codes.Unimplemented, "method MarkPartyReady not implemented")
}
func (UnimplementedMessageRouterServer) ReportCompletion(context.Context, *ReportCompletionRequest) (*ReportCompletionResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ReportCompletion not implemented")
}
func (UnimplementedMessageRouterServer) GetSessionStatus(context.Context, *GetSessionStatusRequest) (*GetSessionStatusResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetSessionStatus not implemented")
}
func (UnimplementedMessageRouterServer) SubmitDelegateShare(context.Context, *SubmitDelegateShareRequest) (*SubmitDelegateShareResponse, error) {
return nil, status.Error(codes.Unimplemented, "method SubmitDelegateShare not implemented")
}
func (UnimplementedMessageRouterServer) mustEmbedUnimplementedMessageRouterServer() {} func (UnimplementedMessageRouterServer) mustEmbedUnimplementedMessageRouterServer() {}
func (UnimplementedMessageRouterServer) testEmbeddedByValue() {} func (UnimplementedMessageRouterServer) testEmbeddedByValue() {}
@ -440,6 +534,96 @@ func _MessageRouter_GetRegisteredParties_Handler(srv interface{}, ctx context.Co
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _MessageRouter_JoinSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(JoinSessionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageRouterServer).JoinSession(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageRouter_JoinSession_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageRouterServer).JoinSession(ctx, req.(*JoinSessionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageRouter_MarkPartyReady_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MarkPartyReadyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageRouterServer).MarkPartyReady(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageRouter_MarkPartyReady_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageRouterServer).MarkPartyReady(ctx, req.(*MarkPartyReadyRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageRouter_ReportCompletion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReportCompletionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageRouterServer).ReportCompletion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageRouter_ReportCompletion_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageRouterServer).ReportCompletion(ctx, req.(*ReportCompletionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageRouter_GetSessionStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSessionStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageRouterServer).GetSessionStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageRouter_GetSessionStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageRouterServer).GetSessionStatus(ctx, req.(*GetSessionStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MessageRouter_SubmitDelegateShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SubmitDelegateShareRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MessageRouterServer).SubmitDelegateShare(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MessageRouter_SubmitDelegateShare_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MessageRouterServer).SubmitDelegateShare(ctx, req.(*SubmitDelegateShareRequest))
}
return interceptor(ctx, in, info, handler)
}
// MessageRouter_ServiceDesc is the grpc.ServiceDesc for MessageRouter service. // MessageRouter_ServiceDesc is the grpc.ServiceDesc for MessageRouter service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -479,6 +663,26 @@ var MessageRouter_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetRegisteredParties", MethodName: "GetRegisteredParties",
Handler: _MessageRouter_GetRegisteredParties_Handler, Handler: _MessageRouter_GetRegisteredParties_Handler,
}, },
{
MethodName: "JoinSession",
Handler: _MessageRouter_JoinSession_Handler,
},
{
MethodName: "MarkPartyReady",
Handler: _MessageRouter_MarkPartyReady_Handler,
},
{
MethodName: "ReportCompletion",
Handler: _MessageRouter_ReportCompletion_Handler,
},
{
MethodName: "GetSessionStatus",
Handler: _MessageRouter_GetSessionStatus_Handler,
},
{
MethodName: "SubmitDelegateShare",
Handler: _MessageRouter_SubmitDelegateShare_Handler,
},
}, },
Streams: []grpc.StreamDesc{ Streams: []grpc.StreamDesc{
{ {

View File

@ -5,7 +5,13 @@ package mpc.router.v1;
option go_package = "github.com/rwadurian/mpc-system/api/grpc/router/v1;router"; option go_package = "github.com/rwadurian/mpc-system/api/grpc/router/v1;router";
// MessageRouter service handles MPC message routing // MessageRouter service handles MPC message routing
// This is the ONLY service that server-parties need to connect to.
// All session operations are proxied through Message Router to Session Coordinator.
service MessageRouter { service MessageRouter {
// ============================================
// Message Routing
// ============================================
// RouteMessage routes a message from one party to others // RouteMessage routes a message from one party to others
rpc RouteMessage(RouteMessageRequest) returns (RouteMessageResponse); rpc RouteMessage(RouteMessageRequest) returns (RouteMessageResponse);
@ -22,20 +28,52 @@ service MessageRouter {
// GetMessageStatus gets the delivery status of a message // GetMessageStatus gets the delivery status of a message
rpc GetMessageStatus(GetMessageStatusRequest) returns (GetMessageStatusResponse); rpc GetMessageStatus(GetMessageStatusRequest) returns (GetMessageStatusResponse);
// ============================================
// Party Registration & Heartbeat
// ============================================
// RegisterParty registers a party with the message router (party actively connects) // RegisterParty registers a party with the message router (party actively connects)
rpc RegisterParty(RegisterPartyRequest) returns (RegisterPartyResponse); rpc RegisterParty(RegisterPartyRequest) returns (RegisterPartyResponse);
// Heartbeat sends a heartbeat to keep the party alive // Heartbeat sends a heartbeat to keep the party alive
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
// ============================================
// Session Events (Push from Coordinator)
// ============================================
// SubscribeSessionEvents subscribes to session lifecycle events (session start, etc.) // SubscribeSessionEvents subscribes to session lifecycle events (session start, etc.)
rpc SubscribeSessionEvents(SubscribeSessionEventsRequest) returns (stream SessionEvent); rpc SubscribeSessionEvents(SubscribeSessionEventsRequest) returns (stream SessionEvent);
// PublishSessionEvent publishes a session event (called by Session Coordinator) // PublishSessionEvent publishes a session event (called by Session Coordinator)
rpc PublishSessionEvent(PublishSessionEventRequest) returns (PublishSessionEventResponse); rpc PublishSessionEvent(PublishSessionEventRequest) returns (PublishSessionEventResponse);
// ============================================
// Party Discovery (for Session Coordinator)
// ============================================
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery) // GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
rpc GetRegisteredParties(GetRegisteredPartiesRequest) returns (GetRegisteredPartiesResponse); rpc GetRegisteredParties(GetRegisteredPartiesRequest) returns (GetRegisteredPartiesResponse);
// ============================================
// Session Operations (Proxied to Coordinator)
// These allow server-parties to only connect to Message Router
// ============================================
// JoinSession joins a session (proxied to Session Coordinator)
rpc JoinSession(JoinSessionRequest) returns (JoinSessionResponse);
// MarkPartyReady marks a party as ready (proxied to Session Coordinator)
rpc MarkPartyReady(MarkPartyReadyRequest) returns (MarkPartyReadyResponse);
// ReportCompletion reports completion (proxied to Session Coordinator)
rpc ReportCompletion(ReportCompletionRequest) returns (ReportCompletionResponse);
// GetSessionStatus gets session status (proxied to Session Coordinator)
rpc GetSessionStatus(GetSessionStatusRequest) returns (GetSessionStatusResponse);
// SubmitDelegateShare submits user's share from delegate party (proxied to Session Coordinator)
rpc SubmitDelegateShare(SubmitDelegateShareRequest) returns (SubmitDelegateShareResponse);
} }
// RouteMessageRequest routes an MPC message // RouteMessageRequest routes an MPC message
@ -126,6 +164,15 @@ message SessionEvent {
bytes message_hash = 8; // For sign sessions bytes message_hash = 8; // For sign sessions
int64 created_at = 9; // Unix timestamp milliseconds int64 created_at = 9; // Unix timestamp milliseconds
int64 expires_at = 10; // Unix timestamp milliseconds int64 expires_at = 10; // Unix timestamp milliseconds
// For sign sessions with delegate party: user's share for delegate to use
DelegateUserShare delegate_user_share = 11;
}
// DelegateUserShare contains user's share for delegate party to use in signing
message DelegateUserShare {
string delegate_party_id = 1; // The delegate party that will use this share
bytes encrypted_share = 2; // User's encrypted share
int32 party_index = 3; // Party index for this share
} }
// PublishSessionEventRequest publishes a session event // PublishSessionEventRequest publishes a session event
@ -211,3 +258,102 @@ message HeartbeatResponse {
int64 server_timestamp = 2; // Server timestamp for clock sync int64 server_timestamp = 2; // Server timestamp for clock sync
int32 pending_messages = 3; // Number of pending messages for this party int32 pending_messages = 3; // Number of pending messages for this party
} }
// ============================================
// Session Operations (Proxied to Coordinator)
// ============================================
// DeviceInfo contains device information
message DeviceInfo {
string device_type = 1; // server, mobile, web
string device_id = 2;
string platform = 3;
string app_version = 4;
}
// PartyInfo contains party information
message PartyInfo {
string party_id = 1;
int32 party_index = 2;
DeviceInfo device_info = 3;
}
// SessionInfo contains session information
message SessionInfo {
string session_id = 1;
string session_type = 2; // keygen, sign
int32 threshold_n = 3;
int32 threshold_t = 4;
bytes message_hash = 5; // For sign sessions
string status = 6;
}
// JoinSessionRequest joins a session
message JoinSessionRequest {
string session_id = 1;
string party_id = 2;
string join_token = 3;
DeviceInfo device_info = 4;
}
// JoinSessionResponse returns session info after joining
message JoinSessionResponse {
bool success = 1;
SessionInfo session_info = 2;
repeated PartyInfo other_parties = 3; // All participants in the session
int32 party_index = 4; // This party's index
}
// MarkPartyReadyRequest marks a party as ready
message MarkPartyReadyRequest {
string session_id = 1;
string party_id = 2;
}
// MarkPartyReadyResponse confirms ready status
message MarkPartyReadyResponse {
bool success = 1;
bool all_ready = 2; // True if all parties are ready
}
// ReportCompletionRequest reports protocol completion
message ReportCompletionRequest {
string session_id = 1;
string party_id = 2;
bytes public_key = 3; // For keygen: resulting public key
bytes signature = 4; // For signing: resulting signature
}
// ReportCompletionResponse confirms completion report
message ReportCompletionResponse {
bool success = 1;
bool all_completed = 2; // True if all parties completed
}
// GetSessionStatusRequest requests session status
message GetSessionStatusRequest {
string session_id = 1;
}
// GetSessionStatusResponse returns session status
message GetSessionStatusResponse {
string session_id = 1;
string status = 2; // created, in_progress, completed, failed, expired
int32 threshold_n = 3;
int32 threshold_t = 4;
repeated PartyInfo participants = 5;
}
// SubmitDelegateShareRequest submits user's share from delegate party
message SubmitDelegateShareRequest {
string session_id = 1;
string party_id = 2;
bytes encrypted_share = 3; // Encrypted share for user
bytes public_key = 4; // Public key from keygen
int32 party_index = 5; // Party's index in the session
}
// SubmitDelegateShareResponse contains result of share submission
message SubmitDelegateShareResponse {
bool success = 1;
}

View File

@ -14,6 +14,9 @@ service SessionCoordinator {
rpc StartSession(StartSessionRequest) returns (StartSessionResponse); rpc StartSession(StartSessionRequest) returns (StartSessionResponse);
rpc ReportCompletion(ReportCompletionRequest) returns (ReportCompletionResponse); rpc ReportCompletion(ReportCompletionRequest) returns (ReportCompletionResponse);
rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse); rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse);
// Delegate party share submission (delegate party submits user's share after keygen)
rpc SubmitDelegateShare(SubmitDelegateShareRequest) returns (SubmitDelegateShareResponse);
} }
// CreateSessionRequest creates a new MPC session // CreateSessionRequest creates a new MPC session
@ -25,6 +28,15 @@ message CreateSessionRequest {
bytes message_hash = 5; // Required for sign sessions bytes message_hash = 5; // Required for sign sessions
int64 expires_in_seconds = 6; // Session expiration time int64 expires_in_seconds = 6; // Session expiration time
PartyComposition party_composition = 7; // Optional: party composition requirements for auto-selection PartyComposition party_composition = 7; // Optional: party composition requirements for auto-selection
// For sign sessions with delegate party: user must provide their encrypted share
DelegateUserShare delegate_user_share = 8;
}
// DelegateUserShare contains user's share for delegate party to use in signing
message DelegateUserShare {
string delegate_party_id = 1; // The delegate party that will use this share
bytes encrypted_share = 2; // User's encrypted share (same as received from keygen)
int32 party_index = 3; // Party index for this share
} }
// PartyComposition specifies requirements for automatic party selection // PartyComposition specifies requirements for automatic party selection
@ -99,8 +111,22 @@ message GetSessionStatusResponse {
string status = 1; string status = 1;
int32 completed_parties = 2; int32 completed_parties = 2;
int32 total_parties = 3; int32 total_parties = 3;
bytes public_key = 4; // For completed keygen string session_type = 4; // "keygen" or "sign"
bytes signature = 5; // For completed sign bytes public_key = 5; // For completed keygen
bytes signature = 6; // For completed sign
// has_delegate indicates whether this keygen session has a delegate party
// Only meaningful for keygen sessions. Always false for sign sessions.
bool has_delegate = 7;
// Delegate share info (returned when keygen session completed and delegate party submitted share)
// Only populated if session_type="keygen" AND has_delegate=true AND session is completed
DelegateShareInfo delegate_share = 8;
}
// DelegateShareInfo contains the delegate party's share for user
message DelegateShareInfo {
bytes encrypted_share = 1; // Encrypted share for user
int32 party_index = 2; // Party's index in the session
string party_id = 3; // Delegate party ID
} }
// ReportCompletionRequest reports that a participant has completed // ReportCompletionRequest reports that a participant has completed
@ -151,3 +177,17 @@ message StartSessionResponse {
bool success = 1; bool success = 1;
string status = 2; // New session status string status = 2; // New session status
} }
// SubmitDelegateShareRequest submits user's share from delegate party
message SubmitDelegateShareRequest {
string session_id = 1;
string party_id = 2;
bytes encrypted_share = 3; // Encrypted share for user
bytes public_key = 4; // Public key from keygen
int32 party_index = 5; // Party's index in the session
}
// SubmitDelegateShareResponse contains result of share submission
message SubmitDelegateShareResponse {
bool success = 1;
}

View File

@ -4,11 +4,23 @@
# ============================================================================= # =============================================================================
# This script manages the MPC System Docker services # This script manages the MPC System Docker services
# #
# External Ports: # Deployment Modes:
# 1. Development (default): All services on one machine (docker-compose.yml)
# 2. Production Central: Central services only (docker-compose.prod.yml)
# 3. Production Party: Standalone party (docker-compose.party.yml)
#
# External Ports (Development):
# 4000 - Account Service HTTP API # 4000 - Account Service HTTP API
# 8081 - Session Coordinator API # 8081 - Session Coordinator API
# 8082 - Message Router WebSocket # 8082 - Message Router HTTP
# 8083 - Server Party API (user share generation) # 8083 - Server Party API
#
# External Ports (Production Central):
# 50051 - Message Router gRPC (for party connections)
# 50052 - Session Coordinator gRPC (for party connections)
# 4000 - Account Service HTTP API
# 8081 - Session Coordinator HTTP API
# 8082 - Message Router HTTP API
# ============================================================================= # =============================================================================
set -e set -e
@ -18,226 +30,488 @@ RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_header() { echo -e "${CYAN}=== $1 ===${NC}"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
# Load environment # Determine which environment file to load
if [ -f ".env" ]; then load_env() {
log_info "Loading environment from .env file" local env_file="$1"
set -a if [ -f "$env_file" ]; then
source .env log_info "Loading environment from $env_file"
set +a set -a
elif [ ! -f ".env" ] && [ -f ".env.example" ]; then source "$env_file"
log_warn ".env file not found. Creating from .env.example" set +a
log_warn "Please edit .env and configure for your environment!" return 0
cp .env.example .env fi
log_error "Please configure .env file and run again" return 1
exit 1 }
fi
# Core services list # Load environment based on mode
CORE_SERVICES="postgres redis rabbitmq" load_environment() {
MPC_SERVICES="session-coordinator message-router server-party-1 server-party-2 server-party-3 server-party-api account-service" local mode="$1"
ALL_SERVICES="$CORE_SERVICES $MPC_SERVICES" case "$mode" in
prod)
load_env ".env.prod" || load_env ".env" || {
log_error "No .env.prod or .env file found"
exit 1
}
;;
party)
load_env ".env.party" || {
log_error "No .env.party file found. Create from .env.party.example"
exit 1
}
;;
*)
load_env ".env" || {
if [ -f ".env.example" ]; then
log_warn ".env file not found. Creating from .env.example"
cp .env.example .env
log_error "Please configure .env file and run again"
fi
exit 1
}
;;
esac
}
# Service lists
CORE_SERVICES="postgres"
DEV_MPC_SERVICES="session-coordinator message-router server-party-1 server-party-2 server-party-3 server-party-api account-service"
PROD_CENTRAL_SERVICES="postgres message-router session-coordinator account-service server-party-api"
# ============================================
# Development Mode Commands (docker-compose.yml)
# ============================================
dev_commands() {
load_environment "dev"
case "$1" in
build)
log_info "Building MPC System services..."
docker compose build
log_success "MPC System built successfully"
;;
build-no-cache)
log_info "Building MPC System (no cache)..."
docker compose build --no-cache
log_success "MPC System built successfully"
;;
up|start)
log_info "Starting MPC System (Development)..."
docker compose up -d
log_success "MPC System started"
echo ""
log_info "Services status:"
docker compose ps
;;
down|stop)
log_info "Stopping MPC System..."
docker compose down
log_success "MPC System stopped"
;;
restart)
log_info "Restarting MPC System..."
docker compose down
docker compose up -d
log_success "MPC System restarted"
;;
logs)
if [ -n "$2" ]; then
docker compose logs -f "$2"
else
docker compose logs -f
fi
;;
logs-tail)
if [ -n "$2" ]; then
docker compose logs --tail 100 "$2"
else
docker compose logs --tail 100
fi
;;
status|ps)
log_info "MPC System status:"
docker compose ps
;;
health)
log_info "Checking MPC System health..."
echo ""
log_header "Infrastructure"
for svc in $CORE_SERVICES; do
if docker compose ps "$svc" --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "$svc is healthy"
else
log_warn "$svc is not healthy"
fi
done
echo ""
log_header "MPC Services"
for svc in $DEV_MPC_SERVICES; do
if docker compose ps "$svc" --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "$svc is healthy"
else
log_warn "$svc is not healthy"
fi
done
echo ""
log_header "External API"
if curl -sf "http://localhost:4000/health" > /dev/null 2>&1; then
log_success "Account Service API (port 4000) is accessible"
else
log_error "Account Service API (port 4000) is not accessible"
fi
;;
clean)
log_warn "This will remove all containers and volumes!"
read -p "Are you sure? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker compose down -v
log_success "MPC System cleaned"
else
log_info "Cancelled"
fi
;;
shell)
if [ -n "$2" ]; then
log_info "Opening shell in $2..."
docker compose exec "$2" sh
else
log_info "Opening shell in account-service..."
docker compose exec account-service sh
fi
;;
test-api)
log_info "Testing Account Service API..."
echo ""
echo "Health check:"
curl -s "http://localhost:4000/health" | jq . 2>/dev/null || curl -s "http://localhost:4000/health"
echo ""
;;
*)
return 1
;;
esac
}
# ============================================
# Production Central Commands (docker-compose.prod.yml)
# ============================================
prod_commands() {
load_environment "prod"
case "$1" in
build)
log_info "Building Production Central services..."
docker compose -f docker-compose.prod.yml build
log_success "Production services built"
;;
up|start)
log_info "Starting Production Central services..."
docker compose -f docker-compose.prod.yml up -d
log_success "Production Central services started"
echo ""
log_header "Services Status"
docker compose -f docker-compose.prod.yml ps
echo ""
log_header "Public Endpoints"
echo " Message Router gRPC: ${MESSAGE_ROUTER_GRPC_PORT:-50051}"
echo " Session Coordinator gRPC: ${SESSION_COORDINATOR_GRPC_PORT:-50052}"
echo " Account Service HTTP: ${ACCOUNT_SERVICE_PORT:-4000}"
;;
down|stop)
log_info "Stopping Production Central services..."
docker compose -f docker-compose.prod.yml down
log_success "Production Central services stopped"
;;
restart)
log_info "Restarting Production Central services..."
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d
log_success "Production Central services restarted"
;;
logs)
if [ -n "$2" ]; then
docker compose -f docker-compose.prod.yml logs -f "$2"
else
docker compose -f docker-compose.prod.yml logs -f
fi
;;
status|ps)
log_info "Production Central status:"
docker compose -f docker-compose.prod.yml ps
;;
health)
log_info "Checking Production Central health..."
echo ""
log_header "Central Services"
for svc in $PROD_CENTRAL_SERVICES; do
if docker compose -f docker-compose.prod.yml ps "$svc" --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "$svc is healthy"
else
log_warn "$svc is not healthy"
fi
done
echo ""
log_header "Public Endpoints"
if curl -sf "http://localhost:${MESSAGE_ROUTER_HTTP_PORT:-8082}/health" > /dev/null 2>&1; then
log_success "Message Router (port ${MESSAGE_ROUTER_HTTP_PORT:-8082}) is accessible"
else
log_error "Message Router is not accessible"
fi
if curl -sf "http://localhost:${SESSION_COORDINATOR_HTTP_PORT:-8081}/health" > /dev/null 2>&1; then
log_success "Session Coordinator (port ${SESSION_COORDINATOR_HTTP_PORT:-8081}) is accessible"
else
log_error "Session Coordinator is not accessible"
fi
if curl -sf "http://localhost:${ACCOUNT_SERVICE_PORT:-4000}/health" > /dev/null 2>&1; then
log_success "Account Service (port ${ACCOUNT_SERVICE_PORT:-4000}) is accessible"
else
log_error "Account Service is not accessible"
fi
;;
clean)
log_warn "This will remove all Production Central containers and volumes!"
read -p "Are you sure? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker compose -f docker-compose.prod.yml down -v
log_success "Production Central cleaned"
else
log_info "Cancelled"
fi
;;
*)
return 1
;;
esac
}
# ============================================
# Production Party Commands (docker-compose.party.yml)
# ============================================
party_commands() {
load_environment "party"
# Validate required environment variables
if [ -z "$PARTY_ID" ]; then
log_error "PARTY_ID must be set (e.g., server-party-1)"
exit 1
fi
if [ -z "$MESSAGE_ROUTER_ADDR" ]; then
log_error "MESSAGE_ROUTER_ADDR must be set (e.g., grpc.mpc.example.com:50051)"
exit 1
fi
case "$1" in
build)
log_info "Building Party ($PARTY_ID)..."
docker compose -f docker-compose.party.yml build
log_success "Party built"
;;
up|start)
log_info "Starting Party: $PARTY_ID"
log_info "Connecting to Message Router: $MESSAGE_ROUTER_ADDR"
docker compose -f docker-compose.party.yml up -d
log_success "Party $PARTY_ID started"
echo ""
docker compose -f docker-compose.party.yml ps
;;
down|stop)
log_info "Stopping Party: $PARTY_ID..."
docker compose -f docker-compose.party.yml down
log_success "Party stopped"
;;
restart)
log_info "Restarting Party: $PARTY_ID..."
docker compose -f docker-compose.party.yml down
docker compose -f docker-compose.party.yml up -d
log_success "Party restarted"
;;
logs)
docker compose -f docker-compose.party.yml logs -f server-party
;;
status|ps)
log_info "Party $PARTY_ID status:"
docker compose -f docker-compose.party.yml ps
;;
health)
log_info "Checking Party $PARTY_ID health..."
echo ""
log_header "Local Services"
if docker compose -f docker-compose.party.yml ps postgres --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "Local PostgreSQL is healthy"
else
log_warn "Local PostgreSQL is not healthy"
fi
if docker compose -f docker-compose.party.yml ps server-party --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "Server Party is healthy"
else
log_warn "Server Party is not healthy"
fi
echo ""
log_header "Central Service Connectivity"
# Extract host and port from MESSAGE_ROUTER_ADDR
MR_HOST=$(echo "$MESSAGE_ROUTER_ADDR" | cut -d: -f1)
MR_PORT=$(echo "$MESSAGE_ROUTER_ADDR" | cut -d: -f2)
if timeout 5 bash -c "echo >/dev/tcp/$MR_HOST/$MR_PORT" 2>/dev/null; then
log_success "Message Router ($MESSAGE_ROUTER_ADDR) is reachable"
else
log_error "Message Router ($MESSAGE_ROUTER_ADDR) is NOT reachable"
fi
;;
clean)
log_warn "This will remove Party $PARTY_ID containers and LOCAL KEY STORAGE!"
log_warn "Your encrypted key shares will be DELETED!"
read -p "Are you absolutely sure? (yes/N) " confirm
echo
if [ "$confirm" = "yes" ]; then
docker compose -f docker-compose.party.yml down -v
log_success "Party $PARTY_ID cleaned"
else
log_info "Cancelled"
fi
;;
*)
return 1
;;
esac
}
# ============================================
# Main Command Router
# ============================================
show_help() {
echo "MPC System Deployment Script"
echo ""
echo "Usage: $0 <mode> <command> [options]"
echo ""
echo "Deployment Modes:"
echo " (default) Development mode - all services on one machine"
echo " prod Production Central - Message Router, Session Coordinator, Account"
echo " party Production Party - standalone server-party (distributed)"
echo ""
echo "Development Commands (default mode):"
echo " $0 build Build all Docker images"
echo " $0 up|start Start all services"
echo " $0 down|stop Stop all services"
echo " $0 restart Restart all services"
echo " $0 logs [service] Follow logs"
echo " $0 status|ps Show services status"
echo " $0 health Check all services health"
echo " $0 clean Remove containers and volumes"
echo ""
echo "Production Central Commands:"
echo " $0 prod build Build central services"
echo " $0 prod up Start central services"
echo " $0 prod down Stop central services"
echo " $0 prod logs Follow central logs"
echo " $0 prod health Check central health"
echo ""
echo "Production Party Commands (run on each party machine):"
echo " $0 party build Build party service"
echo " $0 party up Start party (connects to central)"
echo " $0 party down Stop party"
echo " $0 party logs Follow party logs"
echo " $0 party health Check party health and connectivity"
echo ""
echo "Environment Files:"
echo " .env Development configuration"
echo " .env.prod Production Central configuration"
echo " .env.party Production Party configuration"
echo ""
echo "Examples:"
echo " # Development (all on one machine)"
echo " $0 up"
echo ""
echo " # Production Central (on central server)"
echo " $0 prod up"
echo ""
echo " # Production Party (on each party machine)"
echo " PARTY_ID=server-party-1 $0 party up"
echo " PARTY_ID=server-party-2 $0 party up"
echo " PARTY_ID=server-party-3 $0 party up"
}
# Route commands based on first argument
case "$1" in case "$1" in
build) prod)
log_info "Building MPC System services..." shift
docker compose build prod_commands "$@" || {
log_success "MPC System built successfully" echo "Usage: $0 prod {build|up|down|restart|logs|status|health|clean}"
exit 1
}
;; ;;
build-no-cache) party)
log_info "Building MPC System (no cache)..." shift
docker compose build --no-cache party_commands "$@" || {
log_success "MPC System built successfully" echo "Usage: $0 party {build|up|down|restart|logs|status|health|clean}"
echo ""
echo "Required environment variables:"
echo " PARTY_ID Unique party identifier"
echo " MESSAGE_ROUTER_ADDR Central Message Router address (only connection needed)"
echo " CRYPTO_MASTER_KEY Encryption key for key shares"
exit 1
}
;; ;;
up|start) help|--help|-h)
log_info "Starting MPC System..." show_help
docker compose up -d
log_success "MPC System started"
echo ""
log_info "Services status:"
docker compose ps
;; ;;
down|stop) "")
log_info "Stopping MPC System..." show_help
docker compose down exit 1
log_success "MPC System stopped"
;;
restart)
log_info "Restarting MPC System..."
docker compose down
docker compose up -d
log_success "MPC System restarted"
;;
logs)
if [ -n "$2" ]; then
docker compose logs -f "$2"
else
docker compose logs -f
fi
;;
logs-tail)
if [ -n "$2" ]; then
docker compose logs --tail 100 "$2"
else
docker compose logs --tail 100
fi
;;
status|ps)
log_info "MPC System status:"
docker compose ps
;;
health)
log_info "Checking MPC System health..."
# Check infrastructure
echo ""
echo "=== Infrastructure ==="
for svc in $CORE_SERVICES; do
if docker compose ps "$svc" --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "$svc is healthy"
else
log_warn "$svc is not healthy"
fi
done
# Check MPC services
echo ""
echo "=== MPC Services ==="
for svc in $MPC_SERVICES; do
if docker compose ps "$svc" --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
log_success "$svc is healthy"
else
log_warn "$svc is not healthy"
fi
done
# Check external API
echo ""
echo "=== External API ==="
if curl -sf "http://localhost:4000/health" > /dev/null 2>&1; then
log_success "Account Service API (port 4000) is accessible"
else
log_error "Account Service API (port 4000) is not accessible"
fi
;;
infra)
case "$2" in
up)
log_info "Starting infrastructure services..."
docker compose up -d $CORE_SERVICES
log_success "Infrastructure started"
;;
down)
log_info "Stopping infrastructure services..."
docker compose stop $CORE_SERVICES
log_success "Infrastructure stopped"
;;
*)
echo "Usage: $0 infra {up|down}"
exit 1
;;
esac
;;
mpc)
case "$2" in
up)
log_info "Starting MPC services..."
docker compose up -d $MPC_SERVICES
log_success "MPC services started"
;;
down)
log_info "Stopping MPC services..."
docker compose stop $MPC_SERVICES
log_success "MPC services stopped"
;;
restart)
log_info "Restarting MPC services..."
docker compose stop $MPC_SERVICES
docker compose up -d $MPC_SERVICES
log_success "MPC services restarted"
;;
*)
echo "Usage: $0 mpc {up|down|restart}"
exit 1
;;
esac
;;
clean)
log_warn "This will remove all containers and volumes!"
read -p "Are you sure? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker compose down -v
log_success "MPC System cleaned"
else
log_info "Cancelled"
fi
;;
shell)
if [ -n "$2" ]; then
log_info "Opening shell in $2..."
docker compose exec "$2" sh
else
log_info "Opening shell in account-service..."
docker compose exec account-service sh
fi
;;
test-api)
log_info "Testing Account Service API..."
echo ""
echo "Health check:"
curl -s "http://localhost:4000/health" | jq . 2>/dev/null || curl -s "http://localhost:4000/health"
echo ""
;; ;;
*) *)
echo "MPC System Deployment Script" # Default to development mode
echo "" dev_commands "$@" || {
echo "Usage: $0 <command> [options]" show_help
echo "" exit 1
echo "Commands:" }
echo " build - Build all Docker images"
echo " build-no-cache - Build images without cache"
echo " up|start - Start all services"
echo " down|stop - Stop all services"
echo " restart - Restart all services"
echo " logs [service] - Follow logs (all or specific service)"
echo " logs-tail [svc] - Show last 100 log lines"
echo " status|ps - Show services status"
echo " health - Check all services health"
echo ""
echo " infra up|down - Start/stop infrastructure only"
echo " mpc up|down|restart - Start/stop/restart MPC services only"
echo ""
echo " shell [service] - Open shell in container"
echo " test-api - Test Account Service API"
echo " clean - Remove all containers and volumes"
echo ""
echo "Services:"
echo " Infrastructure: $CORE_SERVICES"
echo " MPC Services: $MPC_SERVICES"
exit 1
;; ;;
esac esac

View File

@ -0,0 +1,127 @@
# =============================================================================
# MPC-System Production Deployment - Standalone Server Party
# =============================================================================
# Purpose: Deploy a single server-party that connects to central Message Router
# This configuration is used for distributed deployment where parties run on
# different physical machines, possibly behind NAT.
#
# Usage:
# # On each party machine:
# PARTY_ID=server-party-1 ./deploy.sh party up
# PARTY_ID=server-party-2 ./deploy.sh party up
# PARTY_ID=server-party-3 ./deploy.sh party up
#
# Required Environment Variables:
# PARTY_ID - Unique party identifier (e.g., server-party-1)
# MESSAGE_ROUTER_ADDR - Public address of Message Router (e.g., grpc.mpc.example.com:50051)
# CRYPTO_MASTER_KEY - 64-character hex key for share encryption
#
# Architecture:
# This Party (NAT OK) --[outbound gRPC]--> Message Router (Public Internet)
#
# Note: Parties ONLY connect to Message Router. Session operations are
# proxied through Message Router to Session Coordinator internally.
#
# NAT Traversal:
# - Party initiates single outbound connection (no inbound ports needed)
# - gRPC keepalive maintains connection through NAT
# - Heartbeat every 30 seconds keeps connection alive
# =============================================================================
services:
# ============================================
# PostgreSQL for Party's Local Key Storage
# ============================================
postgres:
image: postgres:15-alpine
container_name: mpc-party-postgres-${PARTY_ID:-party}
environment:
POSTGRES_DB: mpc_party
POSTGRES_USER: ${POSTGRES_USER:-mpc_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
volumes:
- party-postgres-data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mpc_user} -d mpc_party"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- party-network
restart: unless-stopped
# ============================================
# Server Party - Connects to Central Services
# ============================================
server-party:
build:
context: .
dockerfile: services/server-party/Dockerfile
container_name: mpc-${PARTY_ID:-server-party}
# No ports exposed - party connects outbound to Message Router
# HTTP port is optional for local health checks
ports:
- "${PARTY_HTTP_PORT:-8080}:8080" # Optional: local health check only
environment:
# Party Identity
PARTY_ID: ${PARTY_ID:?PARTY_ID must be set (e.g., server-party-1)}
PARTY_ROLE: ${PARTY_ROLE:-persistent}
# Server Configuration
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: ${ENVIRONMENT:-production}
# Local Database for Key Storage
MPC_DATABASE_HOST: postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: ${POSTGRES_USER:-mpc_user}
MPC_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
MPC_DATABASE_DBNAME: mpc_party
MPC_DATABASE_SSLMODE: disable
# Central Service (PUBLIC address - accessible from this party's location)
# Parties ONLY connect to Message Router (session ops proxied internally)
MESSAGE_ROUTER_ADDR: ${MESSAGE_ROUTER_ADDR:?MESSAGE_ROUTER_ADDR must be set (e.g., grpc.mpc.example.com:50051)}
# Encryption Key for Key Shares
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY:?CRYPTO_MASTER_KEY must be set (64 hex characters)}
# Optional: Notification channels for offline mode
NOTIFICATION_EMAIL: ${NOTIFICATION_EMAIL:-}
NOTIFICATION_PHONE: ${NOTIFICATION_PHONE:-}
NOTIFICATION_PUSH_TOKEN: ${NOTIFICATION_PUSH_TOKEN:-}
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- party-network
restart: unless-stopped
# Important: Allow container to resolve external DNS
dns:
- 8.8.8.8
- 8.8.4.4
# ============================================
# Networks
# ============================================
networks:
party-network:
driver: bridge
# ============================================
# Volumes - Party's Local Key Storage
# IMPORTANT: Back up this volume! It contains encrypted key shares.
# ============================================
volumes:
party-postgres-data:
driver: local
name: mpc-party-${PARTY_ID:-party}-postgres-data

View File

@ -0,0 +1,206 @@
# =============================================================================
# MPC-System Production Deployment - Central Services
# =============================================================================
# Purpose: Deploy central infrastructure (Message Router, Session Coordinator, Account)
# Server Parties are deployed separately on different machines/locations
#
# Usage:
# ./deploy.sh prod up # Start central services
# ./deploy.sh prod down # Stop central services
#
# External Ports (must be accessible from server-parties):
# 50051 - Message Router gRPC (for party connections)
# 50052 - Session Coordinator gRPC (for party connections)
# 4000 - Account Service HTTP API (for backend integration)
# 8081 - Session Coordinator HTTP API (for backend integration)
# 8082 - Message Router HTTP API (health checks)
#
# Architecture:
# Server Parties (NAT-friendly) --> Message Router (Public) --> Session Coordinator
# --> PostgreSQL (Internal)
# =============================================================================
services:
# ============================================
# Infrastructure Services (Internal Only)
# ============================================
postgres:
image: postgres:15-alpine
container_name: mpc-postgres
environment:
POSTGRES_DB: mpc_system
POSTGRES_USER: ${POSTGRES_USER:-mpc_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in .env}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mpc_user} -d mpc_system"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- mpc-internal
restart: unless-stopped
# ============================================
# Message Router - Public gRPC Endpoint
# Server Parties connect here from anywhere
# ============================================
message-router:
build:
context: .
dockerfile: services/message-router/Dockerfile
container_name: mpc-message-router
ports:
- "${MESSAGE_ROUTER_GRPC_PORT:-50051}:50051" # gRPC for party connections (PUBLIC)
- "${MESSAGE_ROUTER_HTTP_PORT:-8082}:8080" # HTTP for health checks
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: ${ENVIRONMENT:-production}
MPC_DATABASE_HOST: postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: ${POSTGRES_USER:-mpc_user}
MPC_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- mpc-internal
restart: unless-stopped
# ============================================
# Session Coordinator - Public gRPC Endpoint
# ============================================
session-coordinator:
build:
context: .
dockerfile: services/session-coordinator/Dockerfile
container_name: mpc-session-coordinator
ports:
- "${SESSION_COORDINATOR_GRPC_PORT:-50052}:50051" # gRPC for party connections (PUBLIC)
- "${SESSION_COORDINATOR_HTTP_PORT:-8081}:8080" # HTTP API
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: ${ENVIRONMENT:-production}
MPC_DATABASE_HOST: postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: ${POSTGRES_USER:-mpc_user}
MPC_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
MPC_JWT_SECRET_KEY: ${JWT_SECRET_KEY}
MPC_JWT_ISSUER: mpc-system
MESSAGE_ROUTER_ADDR: message-router:50051
depends_on:
postgres:
condition: service_healthy
message-router:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- mpc-internal
restart: unless-stopped
# ============================================
# Account Service - External API Entry Point
# ============================================
account-service:
build:
context: .
dockerfile: services/account/Dockerfile
container_name: mpc-account-service
ports:
- "${ACCOUNT_SERVICE_PORT:-4000}:8080" # HTTP API for external access
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: ${ENVIRONMENT:-production}
MPC_DATABASE_HOST: postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: ${POSTGRES_USER:-mpc_user}
MPC_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
MPC_COORDINATOR_URL: session-coordinator:50051
MPC_JWT_SECRET_KEY: ${JWT_SECRET_KEY}
MPC_API_KEY: ${MPC_API_KEY}
ALLOWED_IPS: ${ALLOWED_IPS:-}
depends_on:
postgres:
condition: service_healthy
session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- mpc-internal
restart: unless-stopped
# ============================================
# Server Party API - User Share Generation
# (Optional: only needed if generating user shares)
# ============================================
server-party-api:
build:
context: .
dockerfile: services/server-party-api/Dockerfile
container_name: mpc-server-party-api
ports:
- "${SERVER_PARTY_API_PORT:-8083}:8080"
environment:
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: ${ENVIRONMENT:-production}
SESSION_COORDINATOR_ADDR: session-coordinator:50051
MESSAGE_ROUTER_ADDR: message-router:50051
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY}
MPC_API_KEY: ${MPC_API_KEY}
depends_on:
session-coordinator:
condition: service_healthy
message-router:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
- mpc-internal
restart: unless-stopped
# ============================================
# Networks
# ============================================
networks:
mpc-internal:
driver: bridge
# ============================================
# Volumes
# ============================================
volumes:
postgres-data:
driver: local

View File

@ -0,0 +1,6 @@
-- Rollback: Remove signing_parties column from accounts table
DROP INDEX IF EXISTS idx_accounts_signing_parties;
ALTER TABLE accounts
DROP COLUMN IF EXISTS signing_parties;

View File

@ -0,0 +1,11 @@
-- Add signing_parties column to accounts table
-- This column stores configured party IDs for signing operations
-- If NULL or empty, all active parties will be used for signing
ALTER TABLE accounts
ADD COLUMN signing_parties TEXT[];
-- Add index for querying accounts with configured signing parties
CREATE INDEX idx_accounts_signing_parties ON accounts USING GIN(signing_parties) WHERE signing_parties IS NOT NULL;
COMMENT ON COLUMN accounts.signing_parties IS 'Configured party IDs for signing operations. NULL means use all active parties.';

View File

@ -9,9 +9,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/pkg/logger"
"github.com/rwadurian/mpc-system/services/account/adapters/output/grpc" grpcclient "github.com/rwadurian/mpc-system/services/account/adapters/output/grpc"
"github.com/rwadurian/mpc-system/services/account/application/ports" "github.com/rwadurian/mpc-system/services/account/application/ports"
"github.com/rwadurian/mpc-system/services/account/application/use_cases" "github.com/rwadurian/mpc-system/services/account/application/use_cases"
"github.com/rwadurian/mpc-system/services/account/domain/entities"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects" "github.com/rwadurian/mpc-system/services/account/domain/value_objects"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -31,7 +32,7 @@ type AccountHTTPHandler struct {
completeRecoveryUC *use_cases.CompleteRecoveryUseCase completeRecoveryUC *use_cases.CompleteRecoveryUseCase
getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase
cancelRecoveryUC *use_cases.CancelRecoveryUseCase cancelRecoveryUC *use_cases.CancelRecoveryUseCase
sessionCoordinatorClient *grpc.SessionCoordinatorClient sessionCoordinatorClient *grpcclient.SessionCoordinatorClient
} }
// NewAccountHTTPHandler creates a new AccountHTTPHandler // NewAccountHTTPHandler creates a new AccountHTTPHandler
@ -49,7 +50,7 @@ func NewAccountHTTPHandler(
completeRecoveryUC *use_cases.CompleteRecoveryUseCase, completeRecoveryUC *use_cases.CompleteRecoveryUseCase,
getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase, getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase,
cancelRecoveryUC *use_cases.CancelRecoveryUseCase, cancelRecoveryUC *use_cases.CancelRecoveryUseCase,
sessionCoordinatorClient *grpc.SessionCoordinatorClient, sessionCoordinatorClient *grpcclient.SessionCoordinatorClient,
) *AccountHTTPHandler { ) *AccountHTTPHandler {
return &AccountHTTPHandler{ return &AccountHTTPHandler{
createAccountUC: createAccountUC, createAccountUC: createAccountUC,
@ -80,6 +81,11 @@ func (h *AccountHTTPHandler) RegisterRoutes(router *gin.RouterGroup) {
accounts.PUT("/:id", h.UpdateAccount) accounts.PUT("/:id", h.UpdateAccount)
accounts.GET("/:id/shares", h.GetAccountShares) accounts.GET("/:id/shares", h.GetAccountShares)
accounts.DELETE("/:id/shares/:shareId", h.DeactivateShare) accounts.DELETE("/:id/shares/:shareId", h.DeactivateShare)
// Signing parties configuration
accounts.POST("/:id/signing-config", h.SetSigningParties)
accounts.PUT("/:id/signing-config", h.UpdateSigningParties)
accounts.DELETE("/:id/signing-config", h.ClearSigningParties)
accounts.GET("/:id/signing-config", h.GetSigningParties)
} }
auth := router.Group("/auth") auth := router.Group("/auth")
@ -613,9 +619,9 @@ func (h *AccountHTTPHandler) CreateKeygenSession(c *gin.Context) {
// CreateSigningSessionRequest represents the request for creating a signing session // CreateSigningSessionRequest represents the request for creating a signing session
// Coordinator will automatically select parties based on account's registered shares // Coordinator will automatically select parties based on account's registered shares
type CreateSigningSessionRequest struct { type CreateSigningSessionRequest struct {
AccountID string `json:"account_id" binding:"required"` // Account to sign for AccountID string `json:"account_id" binding:"required"` // Account to sign for
MessageHash string `json:"message_hash" binding:"required"` // SHA-256 hash to sign (hex encoded) MessageHash string `json:"message_hash" binding:"required"` // SHA-256 hash to sign (hex encoded)
UserShare string `json:"user_share"` // Optional: user's encrypted share (hex) if delegate party is used UserShare string `json:"user_share"` // Required if account has delegate share: user's encrypted share (hex)
} }
// CreateSigningSession handles creating a new signing session // CreateSigningSession handles creating a new signing session
@ -655,20 +661,80 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
return return
} }
// Get the party IDs from account shares // Build a map of active shares for validation
var partyIDs []string activeSharesMap := make(map[string]*entities.AccountShare)
var allActivePartyIDs []string
var delegateShare *entities.AccountShare
for _, share := range accountOutput.Shares { for _, share := range accountOutput.Shares {
if share.IsActive { if share.IsActive {
partyIDs = append(partyIDs, share.PartyID) activeSharesMap[share.PartyID] = share
allActivePartyIDs = append(allActivePartyIDs, share.PartyID)
// Check if this is a delegate share
if share.ShareType == value_objects.ShareTypeDelegate {
delegateShare = share
}
} }
} }
// Validate we have enough active shares // Determine which parties to use for signing
var partyIDs []string
if accountOutput.Account.HasSigningPartiesConfig() {
// Use configured signing parties
configuredParties := accountOutput.Account.GetSigningParties()
// Validate all configured parties are still active
for _, partyID := range configuredParties {
if _, exists := activeSharesMap[partyID]; !exists {
c.JSON(http.StatusBadRequest, gin.H{
"error": "configured signing party is no longer active",
"party_id": partyID,
"hint": "update signing-config with active parties",
})
return
}
}
partyIDs = configuredParties
logger.Info("Using configured signing parties",
zap.String("account_id", req.AccountID),
zap.Strings("configured_parties", partyIDs))
} else {
// Use all active parties (original behavior)
partyIDs = allActivePartyIDs
logger.Info("Using all active parties for signing",
zap.String("account_id", req.AccountID),
zap.Strings("active_parties", partyIDs))
}
// Check if any of the selected parties is a delegate
// (delegate share might not be in the configured signing parties)
var selectedDelegateShare *entities.AccountShare
for _, partyID := range partyIDs {
if share := activeSharesMap[partyID]; share != nil && share.ShareType == value_objects.ShareTypeDelegate {
selectedDelegateShare = share
break
}
}
// If selected parties include delegate share, user_share is required
if selectedDelegateShare != nil && req.UserShare == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "user_share is required for accounts with delegate party",
"delegate_party_id": selectedDelegateShare.PartyID,
})
return
}
// Use the selected delegate for signing (not necessarily the account's delegate)
delegateShare = selectedDelegateShare
// Validate we have enough parties
if len(partyIDs) < accountOutput.Account.ThresholdT { if len(partyIDs) < accountOutput.Account.ThresholdT {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "insufficient active shares for signing", "error": "insufficient parties for signing",
"required": accountOutput.Account.ThresholdT, "required": accountOutput.Account.ThresholdT,
"active": len(partyIDs), "selected": len(partyIDs),
}) })
return return
} }
@ -677,10 +743,30 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
logger.Info("Calling CreateSigningSession via gRPC (auto party selection)", // Prepare delegate user share if needed
zap.String("account_id", req.AccountID), var delegateUserShare *grpcclient.DelegateUserShareInput
zap.Int("threshold_t", accountOutput.Account.ThresholdT), if delegateShare != nil {
zap.Int("available_parties", len(partyIDs))) userShareBytes, err := hex.DecodeString(req.UserShare)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user_share format (expected hex)"})
return
}
delegateUserShare = &grpcclient.DelegateUserShareInput{
DelegatePartyID: delegateShare.PartyID,
EncryptedShare: userShareBytes,
PartyIndex: int32(delegateShare.PartyIndex),
}
logger.Info("Calling CreateSigningSession with delegate user share",
zap.String("account_id", req.AccountID),
zap.String("delegate_party_id", delegateShare.PartyID),
zap.Int("threshold_t", accountOutput.Account.ThresholdT),
zap.Int("available_parties", len(partyIDs)))
} else {
logger.Info("Calling CreateSigningSession via gRPC (auto party selection)",
zap.String("account_id", req.AccountID),
zap.Int("threshold_t", accountOutput.Account.ThresholdT),
zap.Int("available_parties", len(partyIDs)))
}
resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto( resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
ctx, ctx,
@ -688,6 +774,7 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
partyIDs, partyIDs,
messageHash, messageHash,
600, // 10 minutes expiry 600, // 10 minutes expiry
delegateUserShare,
) )
if err != nil { if err != nil {
@ -700,7 +787,7 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
zap.String("session_id", resp.SessionID), zap.String("session_id", resp.SessionID),
zap.Int("num_parties", len(resp.SelectedParties))) zap.Int("num_parties", len(resp.SelectedParties)))
c.JSON(http.StatusCreated, gin.H{ response := gin.H{
"session_id": resp.SessionID, "session_id": resp.SessionID,
"session_type": "sign", "session_type": "sign",
"account_id": req.AccountID, "account_id": req.AccountID,
@ -708,7 +795,17 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
"threshold_t": accountOutput.Account.ThresholdT, "threshold_t": accountOutput.Account.ThresholdT,
"selected_parties": resp.SelectedParties, "selected_parties": resp.SelectedParties,
"status": "created", "status": "created",
}) }
// Include has_delegate for sign sessions too
if delegateShare != nil {
response["has_delegate"] = true
response["delegate_party_id"] = delegateShare.PartyID
} else {
response["has_delegate"] = false
}
c.JSON(http.StatusCreated, response)
} }
// GetSessionStatus handles getting session status // GetSessionStatus handles getting session status
@ -734,16 +831,37 @@ func (h *AccountHTTPHandler) GetSessionStatus(c *gin.Context) {
response := gin.H{ response := gin.H{
"session_id": sessionID, "session_id": sessionID,
"status": resp.Status, "status": resp.Status,
"session_type": resp.SessionType, // "keygen" or "sign"
"completed_parties": resp.CompletedParties, "completed_parties": resp.CompletedParties,
"total_parties": resp.TotalParties, "total_parties": resp.TotalParties,
} }
if len(resp.PublicKey) > 0 { // Keygen-specific fields
response["public_key"] = hex.EncodeToString(resp.PublicKey) if resp.SessionType == "keygen" {
// has_delegate only meaningful for keygen sessions
response["has_delegate"] = resp.HasDelegate
if len(resp.PublicKey) > 0 {
response["public_key"] = hex.EncodeToString(resp.PublicKey)
}
// Include delegate share if keygen session has delegate party
// has_delegate=true + delegate_share=null means share was already retrieved (one-time)
// has_delegate=false means no delegate party (pure persistent keygen)
if resp.HasDelegate && resp.DelegateShare != nil {
response["delegate_share"] = gin.H{
"encrypted_share": hex.EncodeToString(resp.DelegateShare.EncryptedShare),
"party_index": resp.DelegateShare.PartyIndex,
"party_id": resp.DelegateShare.PartyID,
}
}
} }
if len(resp.Signature) > 0 { // Sign-specific fields
response["signature"] = hex.EncodeToString(resp.Signature) if resp.SessionType == "sign" {
if len(resp.Signature) > 0 {
response["signature"] = hex.EncodeToString(resp.Signature)
}
} }
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, response)
@ -857,3 +975,256 @@ func (h *AccountHTTPHandler) CreateAccountFromKeygen(c *gin.Context) {
"public_key": hex.EncodeToString(output.Account.PublicKey), "public_key": hex.EncodeToString(output.Account.PublicKey),
}) })
} }
// ============================================
// Signing Parties Configuration Endpoints
// ============================================
// SigningPartiesRequest represents the request for setting/updating signing parties
type SigningPartiesRequest struct {
PartyIDs []string `json:"party_ids" binding:"required,min=1"`
}
// SetSigningParties handles setting signing parties for the first time
// POST /accounts/:id/signing-config
func (h *AccountHTTPHandler) SetSigningParties(c *gin.Context) {
idStr := c.Param("id")
accountID, err := value_objects.AccountIDFromString(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
return
}
var req SigningPartiesRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Get account
accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
AccountID: &accountID,
})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "account not found"})
return
}
// Check if signing parties already configured
if accountOutput.Account.HasSigningPartiesConfig() {
c.JSON(http.StatusConflict, gin.H{
"error": "signing parties already configured, use PUT to update",
"current_signing_parties": accountOutput.Account.GetSigningParties(),
})
return
}
// Validate that all party IDs are valid shares for this account
validPartyIDs := make(map[string]bool)
for _, share := range accountOutput.Shares {
if share.IsActive {
validPartyIDs[share.PartyID] = true
}
}
for _, partyID := range req.PartyIDs {
if !validPartyIDs[partyID] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid party ID - not an active share for this account",
"party_id": partyID,
})
return
}
}
// Set signing parties
if err := accountOutput.Account.SetSigningParties(req.PartyIDs); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Update account in database
_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
AccountID: accountID,
SigningParties: req.PartyIDs,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("Signing parties configured",
zap.String("account_id", idStr),
zap.Strings("signing_parties", req.PartyIDs))
c.JSON(http.StatusCreated, gin.H{
"message": "signing parties configured successfully",
"signing_parties": req.PartyIDs,
"threshold_t": accountOutput.Account.ThresholdT,
})
}
// UpdateSigningParties handles updating existing signing parties configuration
// PUT /accounts/:id/signing-config
func (h *AccountHTTPHandler) UpdateSigningParties(c *gin.Context) {
idStr := c.Param("id")
accountID, err := value_objects.AccountIDFromString(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
return
}
var req SigningPartiesRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Get account
accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
AccountID: &accountID,
})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "account not found"})
return
}
// Check if signing parties are configured (must exist to update)
if !accountOutput.Account.HasSigningPartiesConfig() {
c.JSON(http.StatusNotFound, gin.H{
"error": "signing parties not configured, use POST to set",
})
return
}
// Validate that all party IDs are valid shares for this account
validPartyIDs := make(map[string]bool)
for _, share := range accountOutput.Shares {
if share.IsActive {
validPartyIDs[share.PartyID] = true
}
}
for _, partyID := range req.PartyIDs {
if !validPartyIDs[partyID] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid party ID - not an active share for this account",
"party_id": partyID,
})
return
}
}
// Set signing parties (validates count matches threshold)
if err := accountOutput.Account.SetSigningParties(req.PartyIDs); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Update account in database
_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
AccountID: accountID,
SigningParties: req.PartyIDs,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("Signing parties updated",
zap.String("account_id", idStr),
zap.Strings("signing_parties", req.PartyIDs))
c.JSON(http.StatusOK, gin.H{
"message": "signing parties updated successfully",
"signing_parties": req.PartyIDs,
"threshold_t": accountOutput.Account.ThresholdT,
})
}
// ClearSigningParties handles clearing signing parties configuration
// DELETE /accounts/:id/signing-config
func (h *AccountHTTPHandler) ClearSigningParties(c *gin.Context) {
idStr := c.Param("id")
accountID, err := value_objects.AccountIDFromString(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
return
}
// Get account
accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
AccountID: &accountID,
})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "account not found"})
return
}
// Check if signing parties are configured
if !accountOutput.Account.HasSigningPartiesConfig() {
c.JSON(http.StatusNotFound, gin.H{
"error": "signing parties not configured",
})
return
}
// Clear signing parties - pass empty slice
_, err = h.updateAccountUC.Execute(c.Request.Context(), ports.UpdateAccountInput{
AccountID: accountID,
ClearSigningParties: true,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Info("Signing parties cleared",
zap.String("account_id", idStr))
c.JSON(http.StatusOK, gin.H{
"message": "signing parties cleared - all active parties will be used for signing",
})
}
// GetSigningParties handles getting current signing parties configuration
// GET /accounts/:id/signing-config
func (h *AccountHTTPHandler) GetSigningParties(c *gin.Context) {
idStr := c.Param("id")
accountID, err := value_objects.AccountIDFromString(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid account ID"})
return
}
// Get account
accountOutput, err := h.getAccountUC.Execute(c.Request.Context(), ports.GetAccountInput{
AccountID: &accountID,
})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "account not found"})
return
}
if accountOutput.Account.HasSigningPartiesConfig() {
c.JSON(http.StatusOK, gin.H{
"configured": true,
"signing_parties": accountOutput.Account.GetSigningParties(),
"threshold_t": accountOutput.Account.ThresholdT,
})
} else {
// Get all active parties
var activeParties []string
for _, share := range accountOutput.Shares {
if share.IsActive {
activeParties = append(activeParties, share.PartyID)
}
}
c.JSON(http.StatusOK, gin.H{
"configured": false,
"message": "no signing parties configured - all active parties will be used",
"active_parties": activeParties,
"threshold_t": accountOutput.Account.ThresholdT,
})
}
}

View File

@ -121,14 +121,23 @@ func (c *SessionCoordinatorClient) CreateKeygenSessionAuto(
}, nil }, nil
} }
// DelegateUserShareInput contains user's share for delegate party in signing
type DelegateUserShareInput struct {
DelegatePartyID string
EncryptedShare []byte
PartyIndex int32
}
// CreateSigningSessionAuto creates a new signing session with automatic party selection // CreateSigningSessionAuto creates a new signing session with automatic party selection
// Coordinator will select parties from the provided party IDs (from account shares) // Coordinator will select parties from the provided party IDs (from account shares)
// delegateUserShare is required if any of the parties is a delegate party
func (c *SessionCoordinatorClient) CreateSigningSessionAuto( func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
ctx context.Context, ctx context.Context,
thresholdT int32, thresholdT int32,
partyIDs []string, partyIDs []string,
messageHash []byte, messageHash []byte,
expiresInSeconds int64, expiresInSeconds int64,
delegateUserShare *DelegateUserShareInput,
) (*CreateSessionAutoResponse, error) { ) (*CreateSessionAutoResponse, error) {
// Convert party IDs to participant info (minimal info, coordinator will fill in details) // Convert party IDs to participant info (minimal info, coordinator will fill in details)
pbParticipants := make([]*coordinatorpb.ParticipantInfo, len(partyIDs)) pbParticipants := make([]*coordinatorpb.ParticipantInfo, len(partyIDs))
@ -147,9 +156,22 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
ExpiresInSeconds: expiresInSeconds, ExpiresInSeconds: expiresInSeconds,
} }
logger.Info("Sending CreateSigningSession gRPC request", // Add delegate user share if provided
zap.Int32("threshold_t", thresholdT), if delegateUserShare != nil {
zap.Int("num_parties", len(partyIDs))) req.DelegateUserShare = &coordinatorpb.DelegateUserShare{
DelegatePartyId: delegateUserShare.DelegatePartyID,
EncryptedShare: delegateUserShare.EncryptedShare,
PartyIndex: delegateUserShare.PartyIndex,
}
logger.Info("Sending CreateSigningSession gRPC request with delegate user share",
zap.Int32("threshold_t", thresholdT),
zap.Int("num_parties", len(partyIDs)),
zap.String("delegate_party_id", delegateUserShare.DelegatePartyID))
} else {
logger.Info("Sending CreateSigningSession gRPC request",
zap.Int32("threshold_t", thresholdT),
zap.Int("num_parties", len(partyIDs)))
}
resp, err := c.client.CreateSession(ctx, req) resp, err := c.client.CreateSession(ctx, req)
if err != nil { if err != nil {
@ -170,6 +192,7 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
return &CreateSessionAutoResponse{ return &CreateSessionAutoResponse{
SessionID: resp.SessionId, SessionID: resp.SessionId,
SelectedParties: selectedParties, SelectedParties: selectedParties,
DelegateParty: resp.DelegatePartyId,
JoinTokens: resp.JoinTokens, JoinTokens: resp.JoinTokens,
ExpiresAt: resp.ExpiresAt, ExpiresAt: resp.ExpiresAt,
}, nil }, nil
@ -189,13 +212,26 @@ func (c *SessionCoordinatorClient) GetSessionStatus(
return nil, fmt.Errorf("failed to get session status: %w", err) return nil, fmt.Errorf("failed to get session status: %w", err)
} }
return &SessionStatusResponse{ result := &SessionStatusResponse{
Status: resp.Status, Status: resp.Status,
CompletedParties: resp.CompletedParties, CompletedParties: resp.CompletedParties,
TotalParties: resp.TotalParties, TotalParties: resp.TotalParties,
SessionType: resp.SessionType,
PublicKey: resp.PublicKey, PublicKey: resp.PublicKey,
Signature: resp.Signature, Signature: resp.Signature,
}, nil HasDelegate: resp.HasDelegate,
}
// Include delegate share if present
if resp.DelegateShare != nil {
result.DelegateShare = &DelegateShareInfo{
EncryptedShare: resp.DelegateShare.EncryptedShare,
PartyIndex: resp.DelegateShare.PartyIndex,
PartyID: resp.DelegateShare.PartyId,
}
}
return result, nil
} }
// Close closes the gRPC connection // Close closes the gRPC connection
@ -236,6 +272,20 @@ type SessionStatusResponse struct {
Status string Status string
CompletedParties int32 CompletedParties int32
TotalParties int32 TotalParties int32
SessionType string // "keygen" or "sign"
PublicKey []byte PublicKey []byte
Signature []byte Signature []byte
// HasDelegate indicates whether this keygen session has a delegate party
// Only meaningful for keygen sessions. Always false for sign sessions.
HasDelegate bool
// DelegateShare is non-nil when session_type="keygen" AND has_delegate=true AND session is completed
// nil with has_delegate=true means share was already retrieved (one-time retrieval)
DelegateShare *DelegateShareInfo
}
// DelegateShareInfo contains the delegate party's share for user
type DelegateShareInfo struct {
EncryptedShare []byte
PartyIndex int32
PartyID string
} }

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/lib/pq"
"github.com/rwadurian/mpc-system/services/account/domain/entities" "github.com/rwadurian/mpc-system/services/account/domain/entities"
"github.com/rwadurian/mpc-system/services/account/domain/repositories" "github.com/rwadurian/mpc-system/services/account/domain/repositories"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects" "github.com/rwadurian/mpc-system/services/account/domain/value_objects"
@ -25,8 +26,8 @@ func NewAccountPostgresRepo(db *sql.DB) repositories.AccountRepository {
func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Account) error { func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Account) error {
query := ` query := `
INSERT INTO accounts (id, username, email, phone, public_key, keygen_session_id, INSERT INTO accounts (id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at) threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
` `
_, err := r.db.ExecContext(ctx, query, _, err := r.db.ExecContext(ctx, query,
@ -42,6 +43,7 @@ func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Acco
account.CreatedAt, account.CreatedAt,
account.UpdatedAt, account.UpdatedAt,
account.LastLoginAt, account.LastLoginAt,
pq.Array(account.SigningParties),
) )
return err return err
@ -51,7 +53,7 @@ func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Acco
func (r *AccountPostgresRepo) GetByID(ctx context.Context, id value_objects.AccountID) (*entities.Account, error) { func (r *AccountPostgresRepo) GetByID(ctx context.Context, id value_objects.AccountID) (*entities.Account, error) {
query := ` query := `
SELECT id, username, email, phone, public_key, keygen_session_id, SELECT id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
FROM accounts FROM accounts
WHERE id = $1 WHERE id = $1
` `
@ -63,7 +65,7 @@ func (r *AccountPostgresRepo) GetByID(ctx context.Context, id value_objects.Acco
func (r *AccountPostgresRepo) GetByUsername(ctx context.Context, username string) (*entities.Account, error) { func (r *AccountPostgresRepo) GetByUsername(ctx context.Context, username string) (*entities.Account, error) {
query := ` query := `
SELECT id, username, email, phone, public_key, keygen_session_id, SELECT id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
FROM accounts FROM accounts
WHERE username = $1 WHERE username = $1
` `
@ -75,7 +77,7 @@ func (r *AccountPostgresRepo) GetByUsername(ctx context.Context, username string
func (r *AccountPostgresRepo) GetByEmail(ctx context.Context, email string) (*entities.Account, error) { func (r *AccountPostgresRepo) GetByEmail(ctx context.Context, email string) (*entities.Account, error) {
query := ` query := `
SELECT id, username, email, phone, public_key, keygen_session_id, SELECT id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
FROM accounts FROM accounts
WHERE email = $1 WHERE email = $1
` `
@ -87,7 +89,7 @@ func (r *AccountPostgresRepo) GetByEmail(ctx context.Context, email string) (*en
func (r *AccountPostgresRepo) GetByPublicKey(ctx context.Context, publicKey []byte) (*entities.Account, error) { func (r *AccountPostgresRepo) GetByPublicKey(ctx context.Context, publicKey []byte) (*entities.Account, error) {
query := ` query := `
SELECT id, username, email, phone, public_key, keygen_session_id, SELECT id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
FROM accounts FROM accounts
WHERE public_key = $1 WHERE public_key = $1
` `
@ -100,7 +102,8 @@ func (r *AccountPostgresRepo) Update(ctx context.Context, account *entities.Acco
query := ` query := `
UPDATE accounts UPDATE accounts
SET username = $2, email = $3, phone = $4, public_key = $5, keygen_session_id = $6, SET username = $2, email = $3, phone = $4, public_key = $5, keygen_session_id = $6,
threshold_n = $7, threshold_t = $8, status = $9, updated_at = $10, last_login_at = $11 threshold_n = $7, threshold_t = $8, status = $9, updated_at = $10, last_login_at = $11,
signing_parties = $12
WHERE id = $1 WHERE id = $1
` `
@ -116,6 +119,7 @@ func (r *AccountPostgresRepo) Update(ctx context.Context, account *entities.Acco
account.Status.String(), account.Status.String(),
account.UpdatedAt, account.UpdatedAt,
account.LastLoginAt, account.LastLoginAt,
pq.Array(account.SigningParties),
) )
if err != nil { if err != nil {
@ -177,7 +181,7 @@ func (r *AccountPostgresRepo) ExistsByEmail(ctx context.Context, email string) (
func (r *AccountPostgresRepo) List(ctx context.Context, offset, limit int) ([]*entities.Account, error) { func (r *AccountPostgresRepo) List(ctx context.Context, offset, limit int) ([]*entities.Account, error) {
query := ` query := `
SELECT id, username, email, phone, public_key, keygen_session_id, SELECT id, username, email, phone, public_key, keygen_session_id,
threshold_n, threshold_t, status, created_at, updated_at, last_login_at threshold_n, threshold_t, status, created_at, updated_at, last_login_at, signing_parties
FROM accounts FROM accounts
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT $1 OFFSET $2 LIMIT $1 OFFSET $2
@ -222,6 +226,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
thresholdN int thresholdN int
thresholdT int thresholdT int
status string status string
signingParties pq.StringArray
account entities.Account account entities.Account
) )
@ -238,6 +243,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
&account.CreatedAt, &account.CreatedAt,
&account.UpdatedAt, &account.UpdatedAt,
&account.LastLoginAt, &account.LastLoginAt,
&signingParties,
) )
if err != nil { if err != nil {
@ -260,6 +266,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
account.ThresholdN = thresholdN account.ThresholdN = thresholdN
account.ThresholdT = thresholdT account.ThresholdT = thresholdT
account.Status = value_objects.AccountStatus(status) account.Status = value_objects.AccountStatus(status)
account.SigningParties = signingParties
return &account, nil return &account, nil
} }
@ -276,6 +283,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
thresholdN int thresholdN int
thresholdT int thresholdT int
status string status string
signingParties pq.StringArray
account entities.Account account entities.Account
) )
@ -292,6 +300,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
&account.CreatedAt, &account.CreatedAt,
&account.UpdatedAt, &account.UpdatedAt,
&account.LastLoginAt, &account.LastLoginAt,
&signingParties,
) )
if err != nil { if err != nil {
@ -311,6 +320,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
account.ThresholdN = thresholdN account.ThresholdN = thresholdN
account.ThresholdT = thresholdT account.ThresholdT = thresholdT
account.Status = value_objects.AccountStatus(status) account.Status = value_objects.AccountStatus(status)
account.SigningParties = signingParties
return &account, nil return &account, nil
} }

View File

@ -114,8 +114,10 @@ type CompleteRecoveryPort interface {
// UpdateAccountInput represents input for updating an account // UpdateAccountInput represents input for updating an account
type UpdateAccountInput struct { type UpdateAccountInput struct {
AccountID value_objects.AccountID AccountID value_objects.AccountID
Phone *string Phone *string
SigningParties []string // Set/update signing parties (nil means no change)
ClearSigningParties bool // If true, clear signing parties configuration
} }
// UpdateAccountOutput represents output from updating an account // UpdateAccountOutput represents output from updating an account

View File

@ -165,6 +165,15 @@ func (uc *UpdateAccountUseCase) Execute(ctx context.Context, input ports.UpdateA
account.SetPhone(*input.Phone) account.SetPhone(*input.Phone)
} }
// Handle signing parties update
if input.ClearSigningParties {
account.ClearSigningParties()
} else if len(input.SigningParties) > 0 {
if err := account.SetSigningParties(input.SigningParties); err != nil {
return nil, err
}
}
if err := uc.accountRepo.Update(ctx, account); err != nil { if err := uc.accountRepo.Update(ctx, account); err != nil {
return nil, err return nil, err
} }

View File

@ -21,6 +21,10 @@ type Account struct {
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
LastLoginAt *time.Time LastLoginAt *time.Time
// SigningParties is a list of party IDs designated for signing operations.
// If nil or empty, all active parties will be used for signing.
// Must contain exactly ThresholdT parties when set.
SigningParties []string
} }
// NewAccount creates a new Account // NewAccount creates a new Account
@ -135,18 +139,67 @@ func (a *Account) Validate() error {
return nil return nil
} }
// SetSigningParties sets the designated signing parties for this account
// partyIDs must contain exactly ThresholdT parties
func (a *Account) SetSigningParties(partyIDs []string) error {
if len(partyIDs) != a.ThresholdT {
return ErrInvalidSigningPartiesCount
}
// Check for duplicates
seen := make(map[string]bool)
for _, id := range partyIDs {
if id == "" {
return ErrInvalidPartyID
}
if seen[id] {
return ErrDuplicatePartyID
}
seen[id] = true
}
a.SigningParties = partyIDs
a.UpdatedAt = time.Now().UTC()
return nil
}
// ClearSigningParties removes the signing parties configuration
// After clearing, all active parties will be used for signing
func (a *Account) ClearSigningParties() {
a.SigningParties = nil
a.UpdatedAt = time.Now().UTC()
}
// HasSigningPartiesConfig returns true if signing parties are configured
func (a *Account) HasSigningPartiesConfig() bool {
return len(a.SigningParties) > 0
}
// GetSigningParties returns the configured signing parties
// Returns nil if not configured (meaning all active parties should be used)
func (a *Account) GetSigningParties() []string {
if len(a.SigningParties) == 0 {
return nil
}
// Return a copy to prevent modification
result := make([]string, len(a.SigningParties))
copy(result, a.SigningParties)
return result
}
// Account errors // Account errors
var ( var (
ErrInvalidUsername = &AccountError{Code: "INVALID_USERNAME", Message: "username is required"} ErrInvalidUsername = &AccountError{Code: "INVALID_USERNAME", Message: "username is required"}
ErrInvalidEmail = &AccountError{Code: "INVALID_EMAIL", Message: "email is required"} ErrInvalidEmail = &AccountError{Code: "INVALID_EMAIL", Message: "email is required"}
ErrInvalidPublicKey = &AccountError{Code: "INVALID_PUBLIC_KEY", Message: "public key is required"} ErrInvalidPublicKey = &AccountError{Code: "INVALID_PUBLIC_KEY", Message: "public key is required"}
ErrInvalidThreshold = &AccountError{Code: "INVALID_THRESHOLD", Message: "invalid threshold configuration"} ErrInvalidThreshold = &AccountError{Code: "INVALID_THRESHOLD", Message: "invalid threshold configuration"}
ErrAccountInRecovery = &AccountError{Code: "ACCOUNT_IN_RECOVERY", Message: "account is in recovery mode"} ErrAccountInRecovery = &AccountError{Code: "ACCOUNT_IN_RECOVERY", Message: "account is in recovery mode"}
ErrCannotInitiateRecovery = &AccountError{Code: "CANNOT_INITIATE_RECOVERY", Message: "cannot initiate recovery in current state"} ErrCannotInitiateRecovery = &AccountError{Code: "CANNOT_INITIATE_RECOVERY", Message: "cannot initiate recovery in current state"}
ErrAccountNotActive = &AccountError{Code: "ACCOUNT_NOT_ACTIVE", Message: "account is not active"} ErrAccountNotActive = &AccountError{Code: "ACCOUNT_NOT_ACTIVE", Message: "account is not active"}
ErrAccountNotFound = &AccountError{Code: "ACCOUNT_NOT_FOUND", Message: "account not found"} ErrAccountNotFound = &AccountError{Code: "ACCOUNT_NOT_FOUND", Message: "account not found"}
ErrDuplicateUsername = &AccountError{Code: "DUPLICATE_USERNAME", Message: "username already exists"} ErrDuplicateUsername = &AccountError{Code: "DUPLICATE_USERNAME", Message: "username already exists"}
ErrDuplicateEmail = &AccountError{Code: "DUPLICATE_EMAIL", Message: "email already exists"} ErrDuplicateEmail = &AccountError{Code: "DUPLICATE_EMAIL", Message: "email already exists"}
ErrInvalidSigningPartiesCount = &AccountError{Code: "INVALID_SIGNING_PARTIES_COUNT", Message: "signing parties count must equal threshold T"}
ErrInvalidPartyID = &AccountError{Code: "INVALID_PARTY_ID", Message: "party ID cannot be empty"}
ErrDuplicatePartyID = &AccountError{Code: "DUPLICATE_PARTY_ID", Message: "duplicate party ID in signing parties"}
) )
// AccountError represents an account domain error // AccountError represents an account domain error

View File

@ -77,6 +77,16 @@ func (s *AccountShare) IsRecoveryShare() bool {
return s.ShareType == value_objects.ShareTypeRecovery return s.ShareType == value_objects.ShareTypeRecovery
} }
// IsDelegateShare checks if this is a delegate share
func (s *AccountShare) IsDelegateShare() bool {
return s.ShareType == value_objects.ShareTypeDelegate
}
// RequiresUserShare checks if this share type requires user to provide share for signing
func (s *AccountShare) RequiresUserShare() bool {
return s.ShareType.RequiresUserShare()
}
// Validate validates the account share // Validate validates the account share
func (s *AccountShare) Validate() error { func (s *AccountShare) Validate() error {
if s.AccountID.IsZero() { if s.AccountID.IsZero() {

View File

@ -39,9 +39,10 @@ func (s AccountStatus) CanInitiateRecovery() bool {
type ShareType string type ShareType string
const ( const (
ShareTypeUserDevice ShareType = "user_device" ShareTypeUserDevice ShareType = "user_device" // Share stored on user's device
ShareTypeServer ShareType = "server" ShareTypeServer ShareType = "server" // Share stored by persistent server party (in DB)
ShareTypeRecovery ShareType = "recovery" ShareTypeRecovery ShareType = "recovery" // Share stored for recovery
ShareTypeDelegate ShareType = "delegate" // Share returned to user via delegate party (not stored in party DB)
) )
// String returns the string representation // String returns the string representation
@ -52,13 +53,18 @@ func (st ShareType) String() string {
// IsValid checks if the share type is valid // IsValid checks if the share type is valid
func (st ShareType) IsValid() bool { func (st ShareType) IsValid() bool {
switch st { switch st {
case ShareTypeUserDevice, ShareTypeServer, ShareTypeRecovery: case ShareTypeUserDevice, ShareTypeServer, ShareTypeRecovery, ShareTypeDelegate:
return true return true
default: default:
return false return false
} }
} }
// RequiresUserShare checks if this share type requires user to provide share for signing
func (st ShareType) RequiresUserShare() bool {
return st == ShareTypeDelegate || st == ShareTypeUserDevice
}
// RecoveryType represents the type of account recovery // RecoveryType represents the type of account recovery
type RecoveryType string type RecoveryType string

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"time" "time"
coordinator "github.com/rwadurian/mpc-system/api/grpc/coordinator/v1"
pb "github.com/rwadurian/mpc-system/api/grpc/router/v1" pb "github.com/rwadurian/mpc-system/api/grpc/router/v1"
"github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/pkg/logger"
"github.com/rwadurian/mpc-system/services/message-router/application/use_cases" "github.com/rwadurian/mpc-system/services/message-router/application/use_cases"
@ -11,6 +12,7 @@ import (
"github.com/rwadurian/mpc-system/services/message-router/domain/entities" "github.com/rwadurian/mpc-system/services/message-router/domain/entities"
"github.com/rwadurian/mpc-system/services/message-router/domain/repositories" "github.com/rwadurian/mpc-system/services/message-router/domain/repositories"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -30,6 +32,7 @@ type MessageRouterServer struct {
partyRegistry *domain.PartyRegistry partyRegistry *domain.PartyRegistry
eventBroadcaster *domain.SessionEventBroadcaster eventBroadcaster *domain.SessionEventBroadcaster
messageRepo repositories.MessageRepository messageRepo repositories.MessageRepository
coordinatorConn *grpc.ClientConn // Connection to Session Coordinator for proxying
} }
// NewMessageRouterServer creates a new gRPC server // NewMessageRouterServer creates a new gRPC server
@ -51,6 +54,13 @@ func NewMessageRouterServer(
} }
} }
// SetCoordinatorConnection sets the Session Coordinator gRPC connection for proxying
// This allows server-parties to only connect to Message Router
func (s *MessageRouterServer) SetCoordinatorConnection(conn *grpc.ClientConn) {
s.coordinatorConn = conn
logger.Info("Session Coordinator connection configured for proxying")
}
// RouteMessage routes an MPC message // RouteMessage routes an MPC message
func (s *MessageRouterServer) RouteMessage( func (s *MessageRouterServer) RouteMessage(
ctx context.Context, ctx context.Context,
@ -452,3 +462,218 @@ func toGRPCError(err error) error {
return status.Error(codes.Internal, err.Error()) return status.Error(codes.Internal, err.Error())
} }
} }
// ============================================
// Session Operations (Proxied to Coordinator)
// These methods allow server-parties to only connect to Message Router
// ============================================
// JoinSession proxies the join session request to Session Coordinator
func (s *MessageRouterServer) JoinSession(
ctx context.Context,
req *pb.JoinSessionRequest,
) (*pb.JoinSessionResponse, error) {
if s.coordinatorConn == nil {
return nil, status.Error(codes.Unavailable, "session coordinator not configured")
}
// Convert to coordinator request
coordReq := &coordinator.JoinSessionRequest{
SessionId: req.SessionId,
PartyId: req.PartyId,
JoinToken: req.JoinToken,
}
if req.DeviceInfo != nil {
coordReq.DeviceInfo = &coordinator.DeviceInfo{
DeviceType: req.DeviceInfo.DeviceType,
DeviceId: req.DeviceInfo.DeviceId,
Platform: req.DeviceInfo.Platform,
AppVersion: req.DeviceInfo.AppVersion,
}
}
// Call Session Coordinator
coordResp := &coordinator.JoinSessionResponse{}
err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/JoinSession", coordReq, coordResp)
if err != nil {
logger.Error("Failed to proxy JoinSession to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Error(err))
return nil, err
}
// Convert response
resp := &pb.JoinSessionResponse{
Success: coordResp.Success,
}
if coordResp.SessionInfo != nil {
resp.SessionInfo = &pb.SessionInfo{
SessionId: req.SessionId,
SessionType: coordResp.SessionInfo.SessionType,
ThresholdN: coordResp.SessionInfo.ThresholdN,
ThresholdT: coordResp.SessionInfo.ThresholdT,
MessageHash: coordResp.SessionInfo.MessageHash,
}
}
if len(coordResp.OtherParties) > 0 {
resp.OtherParties = make([]*pb.PartyInfo, len(coordResp.OtherParties))
for i, p := range coordResp.OtherParties {
resp.OtherParties[i] = &pb.PartyInfo{
PartyId: p.PartyId,
PartyIndex: p.PartyIndex,
}
// Find this party's index
if p.PartyId == req.PartyId {
resp.PartyIndex = p.PartyIndex
}
}
}
logger.Info("Proxied JoinSession to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Bool("success", resp.Success))
return resp, nil
}
// MarkPartyReady proxies the mark ready request to Session Coordinator
func (s *MessageRouterServer) MarkPartyReady(
ctx context.Context,
req *pb.MarkPartyReadyRequest,
) (*pb.MarkPartyReadyResponse, error) {
if s.coordinatorConn == nil {
return nil, status.Error(codes.Unavailable, "session coordinator not configured")
}
coordReq := &coordinator.MarkPartyReadyRequest{
SessionId: req.SessionId,
PartyId: req.PartyId,
}
coordResp := &coordinator.MarkPartyReadyResponse{}
err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/MarkPartyReady", coordReq, coordResp)
if err != nil {
logger.Error("Failed to proxy MarkPartyReady to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Error(err))
return nil, err
}
logger.Debug("Proxied MarkPartyReady to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Bool("all_ready", coordResp.AllReady))
return &pb.MarkPartyReadyResponse{
Success: true,
AllReady: coordResp.AllReady,
}, nil
}
// ReportCompletion proxies the completion report to Session Coordinator
func (s *MessageRouterServer) ReportCompletion(
ctx context.Context,
req *pb.ReportCompletionRequest,
) (*pb.ReportCompletionResponse, error) {
if s.coordinatorConn == nil {
return nil, status.Error(codes.Unavailable, "session coordinator not configured")
}
coordReq := &coordinator.ReportCompletionRequest{
SessionId: req.SessionId,
PartyId: req.PartyId,
PublicKey: req.PublicKey,
}
coordResp := &coordinator.ReportCompletionResponse{}
err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/ReportCompletion", coordReq, coordResp)
if err != nil {
logger.Error("Failed to proxy ReportCompletion to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Error(err))
return nil, err
}
logger.Info("Proxied ReportCompletion to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Bool("all_completed", coordResp.AllCompleted))
return &pb.ReportCompletionResponse{
Success: true,
AllCompleted: coordResp.AllCompleted,
}, nil
}
// GetSessionStatus proxies the status request to Session Coordinator
func (s *MessageRouterServer) GetSessionStatus(
ctx context.Context,
req *pb.GetSessionStatusRequest,
) (*pb.GetSessionStatusResponse, error) {
if s.coordinatorConn == nil {
return nil, status.Error(codes.Unavailable, "session coordinator not configured")
}
coordReq := &coordinator.GetSessionStatusRequest{
SessionId: req.SessionId,
}
coordResp := &coordinator.GetSessionStatusResponse{}
err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/GetSessionStatus", coordReq, coordResp)
if err != nil {
logger.Error("Failed to proxy GetSessionStatus to coordinator",
zap.String("session_id", req.SessionId),
zap.Error(err))
return nil, err
}
return &pb.GetSessionStatusResponse{
SessionId: req.SessionId,
Status: coordResp.Status,
ThresholdN: coordResp.TotalParties, // Use TotalParties as N
ThresholdT: coordResp.CompletedParties, // Return completed count in ThresholdT for info
}, nil
}
// SubmitDelegateShare proxies the delegate share submission to Session Coordinator
func (s *MessageRouterServer) SubmitDelegateShare(
ctx context.Context,
req *pb.SubmitDelegateShareRequest,
) (*pb.SubmitDelegateShareResponse, error) {
if s.coordinatorConn == nil {
return nil, status.Error(codes.Unavailable, "session coordinator not configured")
}
coordReq := &coordinator.SubmitDelegateShareRequest{
SessionId: req.SessionId,
PartyId: req.PartyId,
EncryptedShare: req.EncryptedShare,
PublicKey: req.PublicKey,
PartyIndex: req.PartyIndex,
}
coordResp := &coordinator.SubmitDelegateShareResponse{}
err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/SubmitDelegateShare", coordReq, coordResp)
if err != nil {
logger.Error("Failed to proxy SubmitDelegateShare to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Error(err))
return nil, err
}
logger.Info("Proxied SubmitDelegateShare to coordinator",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Bool("success", coordResp.Success))
return &pb.SubmitDelegateShareResponse{
Success: coordResp.Success,
}, nil
}

View File

@ -15,6 +15,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
pb "github.com/rwadurian/mpc-system/api/grpc/router/v1" pb "github.com/rwadurian/mpc-system/api/grpc/router/v1"
@ -77,6 +78,26 @@ func main() {
routeMessageUC := use_cases.NewRouteMessageUseCase(messageRepo, messageBroker) routeMessageUC := use_cases.NewRouteMessageUseCase(messageRepo, messageBroker)
getPendingMessagesUC := use_cases.NewGetPendingMessagesUseCase(messageRepo) getPendingMessagesUC := use_cases.NewGetPendingMessagesUseCase(messageRepo)
// Connect to Session Coordinator for proxying session operations
// This allows server-parties to only connect to Message Router
coordinatorAddr := os.Getenv("SESSION_COORDINATOR_ADDR")
if coordinatorAddr == "" {
coordinatorAddr = "session-coordinator:50051" // Default in docker-compose
}
var coordinatorConn *grpc.ClientConn
coordinatorConn, err = grpc.NewClient(coordinatorAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
logger.Warn("Failed to connect to Session Coordinator for proxying (session operations will be unavailable)",
zap.String("address", coordinatorAddr),
zap.Error(err))
} else {
defer coordinatorConn.Close()
logger.Info("Connected to Session Coordinator for proxying session operations",
zap.String("address", coordinatorAddr))
}
// Start message cleanup background job // Start message cleanup background job
go runMessageCleanup(messageRepo) go runMessageCleanup(messageRepo)
@ -92,7 +113,7 @@ func main() {
// Start gRPC server // Start gRPC server
go func() { go func() {
if err := startGRPCServer(cfg, routeMessageUC, getPendingMessagesUC, messageBroker, partyRegistry, eventBroadcaster, messageRepo); err != nil { if err := startGRPCServer(cfg, routeMessageUC, getPendingMessagesUC, messageBroker, partyRegistry, eventBroadcaster, messageRepo, coordinatorConn); err != nil {
errChan <- fmt.Errorf("gRPC server error: %w", err) errChan <- fmt.Errorf("gRPC server error: %w", err)
} }
}() }()
@ -189,6 +210,7 @@ func startGRPCServer(
partyRegistry *domain.PartyRegistry, partyRegistry *domain.PartyRegistry,
eventBroadcaster *domain.SessionEventBroadcaster, eventBroadcaster *domain.SessionEventBroadcaster,
messageRepo *postgres.MessagePostgresRepo, messageRepo *postgres.MessagePostgresRepo,
coordinatorConn *grpc.ClientConn,
) error { ) error {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Server.GRPCPort)) listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Server.GRPCPort))
if err != nil { if err != nil {
@ -206,6 +228,13 @@ func startGRPCServer(
eventBroadcaster, eventBroadcaster,
messageRepo, messageRepo,
) )
// Set coordinator connection for proxying session operations
// This allows server-parties to only connect to Message Router
if coordinatorConn != nil {
messageRouterServer.SetCoordinatorConnection(coordinatorConn)
}
pb.RegisterMessageRouterServer(grpcServer, messageRouterServer) pb.RegisterMessageRouterServer(grpcServer, messageRouterServer)
// Enable reflection for debugging // Enable reflection for debugging

View File

@ -14,18 +14,15 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
router "github.com/rwadurian/mpc-system/api/grpc/router/v1"
"github.com/rwadurian/mpc-system/pkg/config" "github.com/rwadurian/mpc-system/pkg/config"
"github.com/rwadurian/mpc-system/pkg/crypto" "github.com/rwadurian/mpc-system/pkg/crypto"
"github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/pkg/logger"
grpcclient "github.com/rwadurian/mpc-system/services/server-party/adapters/output/grpc" grpcclient "github.com/rwadurian/mpc-system/services/server-party/adapters/output/grpc"
"github.com/rwadurian/mpc-system/services/server-party/application/use_cases" "github.com/rwadurian/mpc-system/services/server-party/application/use_cases"
"github.com/rwadurian/mpc-system/services/server-party/infrastructure/cache"
"go.uber.org/zap" "go.uber.org/zap"
) )
// Global share cache for delegate parties
var globalShareCache *cache.ShareCache
func main() { func main() {
// Parse flags // Parse flags
configPath := flag.String("config", "", "Path to config file") configPath := flag.String("config", "", "Path to config file")
@ -48,14 +45,10 @@ func main() {
} }
defer logger.Sync() defer logger.Sync()
logger.Info("Starting Server Party API Service (Delegate Mode)", logger.Info("Starting Delegate Party Service",
zap.String("environment", cfg.Server.Environment), zap.String("environment", cfg.Server.Environment),
zap.Int("http_port", cfg.Server.HTTPPort)) zap.Int("http_port", cfg.Server.HTTPPort))
// Initialize share cache for delegate parties (15 minute TTL)
globalShareCache = cache.NewShareCache(15 * time.Minute)
logger.Info("Share cache initialized", zap.Duration("ttl", 15*time.Minute))
// Initialize crypto service with master key from environment // Initialize crypto service with master key from environment
masterKeyHex := os.Getenv("MPC_CRYPTO_MASTER_KEY") masterKeyHex := os.Getenv("MPC_CRYPTO_MASTER_KEY")
if masterKeyHex == "" { if masterKeyHex == "" {
@ -70,29 +63,14 @@ func main() {
logger.Fatal("Failed to create crypto service", zap.Error(err)) logger.Fatal("Failed to create crypto service", zap.Error(err))
} }
// Get API key for authentication // Get Message Router address from environment
apiKey := os.Getenv("MPC_API_KEY") // Delegate party (like all parties) ONLY connects to Message Router
if apiKey == "" {
logger.Warn("MPC_API_KEY not set, API will be unprotected")
}
// Get gRPC service addresses from environment
coordinatorAddr := os.Getenv("SESSION_COORDINATOR_ADDR")
if coordinatorAddr == "" {
coordinatorAddr = "session-coordinator:50051"
}
routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR") routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR")
if routerAddr == "" { if routerAddr == "" {
routerAddr = "message-router:50051" routerAddr = "message-router:50051"
} }
// Initialize gRPC clients // Initialize Message Router client (the only gRPC connection needed)
sessionClient, err := grpcclient.NewSessionCoordinatorClient(coordinatorAddr)
if err != nil {
logger.Fatal("Failed to connect to session coordinator", zap.Error(err))
}
defer sessionClient.Close()
messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr) messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr)
if err != nil { if err != nil {
logger.Fatal("Failed to connect to message router", zap.Error(err)) logger.Fatal("Failed to connect to message router", zap.Error(err))
@ -106,44 +84,74 @@ func main() {
// Get party ID from environment (or use default) // Get party ID from environment (or use default)
partyID := os.Getenv("PARTY_ID") partyID := os.Getenv("PARTY_ID")
if partyID == "" { if partyID == "" {
partyID = "server-party-api" partyID = "delegate-party"
} }
// Force PARTY_ROLE to delegate for this service // Force PARTY_ROLE to delegate for this service
os.Setenv("PARTY_ROLE", "delegate") partyRole := "delegate"
// Register this party as a delegate party with Message Router // Register this party as a delegate party with Message Router
logger.Info("Registering party with Message Router", logger.Info("Registering party with Message Router",
zap.String("party_id", partyID), zap.String("party_id", partyID),
zap.String("role", "delegate")) zap.String("role", partyRole))
if err := messageRouter.RegisterParty(ctx, partyID, "delegate", "1.0.0"); err != nil { if err := messageRouter.RegisterParty(ctx, partyID, partyRole, "1.0.0"); err != nil {
logger.Fatal("Failed to register party", zap.Error(err)) logger.Fatal("Failed to register party", zap.Error(err))
} }
logger.Info("Party registered successfully", logger.Info("Party registered successfully",
zap.String("party_id", partyID), zap.String("party_id", partyID),
zap.String("role", "delegate")) zap.String("role", partyRole))
// Start heartbeat to keep party registered
heartbeatCancel := messageRouter.StartHeartbeat(ctx, partyID, 30*time.Second, func(pendingCount int32) {
if pendingCount > 0 {
logger.Info("Pending messages detected via heartbeat",
zap.String("party_id", partyID),
zap.Int32("pending_count", pendingCount))
}
})
defer heartbeatCancel()
logger.Info("Heartbeat started", zap.String("party_id", partyID), zap.Duration("interval", 30*time.Second))
// Initialize use cases with nil keyShareRepo (delegate doesn't use DB) // Initialize use cases with nil keyShareRepo (delegate doesn't use DB)
// The use cases check PARTY_ROLE env var to determine behavior // MessageRouter handles both messaging AND session operations (proxied to coordinator)
participateKeygenUC := use_cases.NewParticipateKeygenUseCase( participateKeygenUC := use_cases.NewParticipateKeygenUseCase(
nil, // No database storage for delegate nil, // No database storage for delegate
sessionClient, messageRouter,
messageRouter, messageRouter,
cryptoService, cryptoService,
) )
participateSigningUC := use_cases.NewParticipateSigningUseCase( participateSigningUC := use_cases.NewParticipateSigningUseCase(
nil, // No database storage for delegate nil, // No database storage for delegate
sessionClient, messageRouter,
messageRouter, messageRouter,
cryptoService, cryptoService,
) )
// Start HTTP server // Subscribe to session events and handle them automatically (SAME AS SERVER-PARTY)
logger.Info("Subscribing to session events", zap.String("party_id", partyID))
eventHandler := createSessionEventHandler(
ctx,
partyID,
participateKeygenUC,
participateSigningUC,
messageRouter,
)
if err := messageRouter.SubscribeSessionEvents(ctx, partyID, eventHandler); err != nil {
logger.Fatal("Failed to subscribe to session events", zap.Error(err))
}
logger.Info("Delegate party initialized successfully (party-driven architecture)",
zap.String("party_id", partyID),
zap.String("role", partyRole))
// Start HTTP server (health check only)
errChan := make(chan error, 1) errChan := make(chan error, 1)
go func() { go func() {
if err := startHTTPServer(cfg, participateKeygenUC, participateSigningUC, cryptoService, apiKey); err != nil { if err := startHTTPServer(cfg); err != nil {
errChan <- fmt.Errorf("HTTP server error: %w", err) errChan <- fmt.Errorf("HTTP server error: %w", err)
} }
}() }()
@ -165,258 +173,162 @@ func main() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
logger.Info("Shutdown complete") logger.Info("Shutdown complete")
_ = ctx
} }
func startHTTPServer( // startHTTPServer starts HTTP server for health checks only
cfg *config.Config, func startHTTPServer(cfg *config.Config) error {
participateKeygenUC *use_cases.ParticipateKeygenUseCase,
participateSigningUC *use_cases.ParticipateSigningUseCase,
cryptoService *crypto.CryptoService,
apiKey string,
) error {
if cfg.Server.Environment == "production" { if cfg.Server.Environment == "production" {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
} }
router := gin.New() r := gin.New()
router.Use(gin.Recovery()) r.Use(gin.Recovery())
router.Use(gin.Logger())
// Health check // Health check only
router.GET("/health", func(c *gin.Context) { r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": "healthy", "status": "healthy",
"service": "server-party-api", "service": "delegate-party",
"role": "delegate", "role": "delegate",
}) })
}) })
// API routes with optional authentication logger.Info("Starting HTTP server (health check only)", zap.Int("port", cfg.Server.HTTPPort))
api := router.Group("/api/v1") return r.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort))
if apiKey != "" {
api.Use(apiKeyAuth(apiKey))
}
{
// Keygen participation - same as server-party but returns share
api.POST("/keygen/participate", func(c *gin.Context) {
var req struct {
SessionID string `json:"session_id" binding:"required"`
PartyID string `json:"party_id" binding:"required"`
JoinToken string `json:"join_token" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
sessionID, err := uuid.Parse(req.SessionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session_id format"})
return
}
logger.Info("Starting keygen participation (delegate)",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID))
// Execute keygen synchronously for delegate party
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Minute)
defer cancel()
input := use_cases.ParticipateKeygenInput{
SessionID: sessionID,
PartyID: req.PartyID,
JoinToken: req.JoinToken,
}
output, err := participateKeygenUC.Execute(ctx, input)
if err != nil {
logger.Error("Keygen participation failed",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID),
zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{
"error": "keygen failed",
"details": err.Error(),
"session_id": req.SessionID,
"party_id": req.PartyID,
})
return
}
logger.Info("Keygen participation completed (delegate)",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID),
zap.Bool("success", output.Success))
// For delegate party, ShareForUser contains the encrypted share
if len(output.ShareForUser) == 0 {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "share not generated for delegate party",
})
return
}
// Store in cache for retrieval (optional, for async pattern)
globalShareCache.Store(sessionID, req.PartyID, output.ShareForUser, output.PublicKey)
// Return share directly
c.JSON(http.StatusOK, gin.H{
"success": true,
"session_id": req.SessionID,
"party_id": req.PartyID,
"party_index": output.KeyShare.PartyIndex,
"share_data": hex.EncodeToString(output.ShareForUser),
"public_key": hex.EncodeToString(output.PublicKey),
})
})
// Signing with user-provided share
api.POST("/sign/participate", func(c *gin.Context) {
var req struct {
SessionID string `json:"session_id" binding:"required"`
PartyID string `json:"party_id" binding:"required"`
JoinToken string `json:"join_token" binding:"required"`
ShareData string `json:"share_data" binding:"required"` // User's encrypted share
MessageHash string `json:"message_hash"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
sessionID, err := uuid.Parse(req.SessionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session_id format"})
return
}
shareData, err := hex.DecodeString(req.ShareData)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid share_data format (expected hex)"})
return
}
var messageHash []byte
if req.MessageHash != "" {
messageHash, err = hex.DecodeString(req.MessageHash)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid message_hash format (expected hex)"})
return
}
}
logger.Info("Starting signing participation (delegate)",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID))
// Execute signing synchronously
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Minute)
defer cancel()
input := use_cases.ParticipateSigningInput{
SessionID: sessionID,
PartyID: req.PartyID,
JoinToken: req.JoinToken,
MessageHash: messageHash,
UserShareData: shareData, // Pass user's share
}
output, err := participateSigningUC.Execute(ctx, input)
if err != nil {
logger.Error("Signing participation failed",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID),
zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{
"error": "signing failed",
"details": err.Error(),
"session_id": req.SessionID,
"party_id": req.PartyID,
})
return
}
logger.Info("Signing participation completed (delegate)",
zap.String("session_id", req.SessionID),
zap.String("party_id", req.PartyID),
zap.Bool("success", output.Success))
// Return signature
var rHex, sHex string
if output.R != nil {
rHex = hex.EncodeToString(output.R.Bytes())
}
if output.S != nil {
sHex = hex.EncodeToString(output.S.Bytes())
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"session_id": req.SessionID,
"party_id": req.PartyID,
"signature": hex.EncodeToString(output.Signature),
"r": rHex,
"s": sHex,
})
})
// Get user share from cache (for async keygen pattern)
api.GET("/sessions/:session_id/user-share", func(c *gin.Context) {
sessionIDStr := c.Param("session_id")
sessionID, err := uuid.Parse(sessionIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid session_id format",
})
return
}
// Retrieve and delete share from cache (one-time retrieval)
entry, exists := globalShareCache.GetAndDelete(sessionID)
if !exists {
c.JSON(http.StatusNotFound, gin.H{
"error": "Share not found or already retrieved",
"note": "Shares can only be retrieved once and expire after 15 minutes",
})
return
}
logger.Info("User share retrieved successfully",
zap.String("session_id", sessionIDStr),
zap.String("party_id", entry.PartyID))
c.JSON(http.StatusOK, gin.H{
"session_id": sessionIDStr,
"party_id": entry.PartyID,
"share": hex.EncodeToString(entry.Share),
"public_key": hex.EncodeToString(entry.PublicKey),
"note": "This share has been deleted from memory and cannot be retrieved again",
})
})
}
logger.Info("Starting HTTP server", zap.Int("port", cfg.Server.HTTPPort))
return router.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort))
} }
func apiKeyAuth(expectedKey string) gin.HandlerFunc { // createSessionEventHandler creates a handler for session events (party-driven architecture)
return func(c *gin.Context) { // Delegate party automatically responds to session creation events by joining keygen or signing sessions
apiKey := c.GetHeader("X-API-Key") // After keygen, it submits the user's share to Session Coordinator (instead of saving to DB)
if apiKey == "" { func createSessionEventHandler(
apiKey = c.Query("api_key") ctx context.Context,
partyID string,
participateKeygenUC *use_cases.ParticipateKeygenUseCase,
participateSigningUC *use_cases.ParticipateSigningUseCase,
messageRouter *grpcclient.MessageRouterClient,
) func(*router.SessionEvent) {
return func(event *router.SessionEvent) {
// Check if this party is selected for the session
isSelected := false
for _, selectedParty := range event.SelectedParties {
if selectedParty == partyID {
isSelected = true
break
}
} }
if apiKey != expectedKey {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or missing API key"}) if !isSelected {
c.Abort() logger.Debug("Party not selected for this session",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID))
return return
} }
c.Next()
// Get join token for this party
joinToken, exists := event.JoinTokens[partyID]
if !exists {
logger.Error("No join token found for party",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID))
return
}
logger.Info("Delegate party selected for session, auto-participating",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID),
zap.String("event_type", event.EventType))
// Parse session ID
sessionID, err := uuid.Parse(event.SessionId)
if err != nil {
logger.Error("Invalid session ID", zap.Error(err))
return
}
// Automatically participate based on session type
go func() {
ctx := context.Background()
// Determine session type from event
if event.EventType == "session_created" {
// Check if it's keygen or sign based on message_hash
if len(event.MessageHash) == 0 {
// Keygen session
logger.Info("Auto-participating in keygen session (delegate)",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID))
input := use_cases.ParticipateKeygenInput{
SessionID: sessionID,
PartyID: partyID,
JoinToken: joinToken,
}
result, err := participateKeygenUC.Execute(ctx, input)
if err != nil {
logger.Error("Keygen participation failed",
zap.Error(err),
zap.String("session_id", event.SessionId))
return
}
logger.Info("Keygen participation completed (delegate)",
zap.String("session_id", event.SessionId),
zap.String("public_key", hex.EncodeToString(result.PublicKey)))
// Delegate party: Submit share to Session Coordinator (via Message Router)
// This is the key difference from persistent party which saves to DB
if len(result.ShareForUser) > 0 {
logger.Info("Submitting delegate share to Session Coordinator",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID),
zap.Int("share_size", len(result.ShareForUser)))
if err := messageRouter.SubmitDelegateShare(
ctx,
event.SessionId,
partyID,
result.ShareForUser,
result.PublicKey,
int32(result.KeyShare.PartyIndex),
); err != nil {
logger.Error("Failed to submit delegate share",
zap.Error(err),
zap.String("session_id", event.SessionId))
} else {
logger.Info("Delegate share submitted successfully",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID))
}
}
} else {
// Sign session
logger.Info("Auto-participating in sign session (delegate)",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID))
input := use_cases.ParticipateSigningInput{
SessionID: sessionID,
PartyID: partyID,
JoinToken: joinToken,
MessageHash: event.MessageHash,
// Note: For signing, user must provide their share via Account Service
// This will be passed through the session event or retrieved separately
}
result, err := participateSigningUC.Execute(ctx, input)
if err != nil {
logger.Error("Signing participation failed",
zap.Error(err),
zap.String("session_id", event.SessionId))
return
}
logger.Info("Signing participation completed (delegate)",
zap.String("session_id", event.SessionId),
zap.String("signature", hex.EncodeToString(result.Signature)))
}
}
}()
} }
} }

View File

@ -599,3 +599,194 @@ func (c *MessageRouterClient) StartHeartbeat(
return cancel return cancel
} }
// ============================================
// Session Operations (Proxied through Message Router)
// These methods allow server-parties to only connect to Message Router
// ============================================
// JoinSession joins an MPC session via Message Router
// Includes automatic retry with exponential backoff for transient failures
func (c *MessageRouterClient) JoinSession(
ctx context.Context,
sessionID uuid.UUID,
partyID, joinToken string,
) (*use_cases.SessionInfo, error) {
req := &router.JoinSessionRequest{
SessionId: sessionID.String(),
PartyId: partyID,
JoinToken: joinToken,
DeviceInfo: &router.DeviceInfo{
DeviceType: "server",
DeviceId: partyID,
Platform: "linux",
AppVersion: "1.0.0",
},
}
return retry.Do(ctx, c.retryCfg, "JoinSession", func() (*use_cases.SessionInfo, error) {
resp := &router.JoinSessionResponse{}
err := c.getConn().Invoke(ctx, "/mpc.router.v1.MessageRouter/JoinSession", req, resp)
if err != nil {
return nil, err
}
if !resp.Success {
logger.Error("Join session failed", zap.String("session_id", sessionID.String()))
return nil, use_cases.ErrInvalidSession
}
// Convert response to SessionInfo
participants := make([]use_cases.ParticipantInfo, len(resp.OtherParties))
for i, p := range resp.OtherParties {
logger.Debug("Received party_index from Message Router",
zap.String("party_id", p.PartyId),
zap.Int32("party_index", p.PartyIndex))
participants[i] = use_cases.ParticipantInfo{
PartyID: p.PartyId,
PartyIndex: int(p.PartyIndex),
}
}
sessionInfo := &use_cases.SessionInfo{
SessionID: sessionID,
Participants: participants,
}
if resp.SessionInfo != nil {
sessionInfo.SessionType = resp.SessionInfo.SessionType
sessionInfo.ThresholdN = int(resp.SessionInfo.ThresholdN)
sessionInfo.ThresholdT = int(resp.SessionInfo.ThresholdT)
sessionInfo.MessageHash = resp.SessionInfo.MessageHash
}
logger.Info("Joined session via Message Router",
zap.String("session_id", sessionID.String()),
zap.String("party_id", partyID),
zap.String("session_type", sessionInfo.SessionType))
return sessionInfo, nil
})
}
// ReportCompletion reports protocol completion via Message Router
// Includes automatic retry with exponential backoff for transient failures
func (c *MessageRouterClient) ReportCompletion(
ctx context.Context,
sessionID uuid.UUID,
partyID string,
resultData []byte,
) error {
req := &router.ReportCompletionRequest{
SessionId: sessionID.String(),
PartyId: partyID,
PublicKey: resultData, // For keygen: public key; for signing: signature
}
return retry.DoVoid(ctx, c.retryCfg, "ReportCompletion", func() error {
resp := &router.ReportCompletionResponse{}
err := c.getConn().Invoke(ctx, "/mpc.router.v1.MessageRouter/ReportCompletion", req, resp)
if err != nil {
return err
}
logger.Info("Reported completion via Message Router",
zap.String("session_id", sessionID.String()),
zap.String("party_id", partyID),
zap.Bool("all_completed", resp.AllCompleted))
return nil
})
}
// MarkPartyReady marks the party as ready to start the protocol via Message Router
// Includes automatic retry with exponential backoff for transient failures
func (c *MessageRouterClient) MarkPartyReady(
ctx context.Context,
sessionID uuid.UUID,
partyID string,
) (bool, error) {
req := &router.MarkPartyReadyRequest{
SessionId: sessionID.String(),
PartyId: partyID,
}
return retry.Do(ctx, c.retryCfg, "MarkPartyReady", func() (bool, error) {
resp := &router.MarkPartyReadyResponse{}
err := c.getConn().Invoke(ctx, "/mpc.router.v1.MessageRouter/MarkPartyReady", req, resp)
if err != nil {
return false, err
}
logger.Info("Marked party ready via Message Router",
zap.String("session_id", sessionID.String()),
zap.String("party_id", partyID),
zap.Bool("all_ready", resp.AllReady))
return resp.AllReady, nil
})
}
// GetSessionStatus gets the current session status via Message Router
// Includes automatic retry with exponential backoff for transient failures
func (c *MessageRouterClient) GetSessionStatus(
ctx context.Context,
sessionID uuid.UUID,
) (string, error) {
req := &router.GetSessionStatusRequest{
SessionId: sessionID.String(),
}
return retry.Do(ctx, c.retryCfg, "GetSessionStatus", func() (string, error) {
resp := &router.GetSessionStatusResponse{}
err := c.getConn().Invoke(ctx, "/mpc.router.v1.MessageRouter/GetSessionStatus", req, resp)
if err != nil {
return "", err
}
return resp.Status, nil
})
}
// SubmitDelegateShare submits user's share from delegate party to Session Coordinator
// This is called after keygen by delegate parties to return the share for user retrieval
// Includes automatic retry with exponential backoff for transient failures
func (c *MessageRouterClient) SubmitDelegateShare(
ctx context.Context,
sessionID string,
partyID string,
encryptedShare []byte,
publicKey []byte,
partyIndex int32,
) error {
req := &router.SubmitDelegateShareRequest{
SessionId: sessionID,
PartyId: partyID,
EncryptedShare: encryptedShare,
PublicKey: publicKey,
PartyIndex: partyIndex,
}
return retry.DoVoid(ctx, c.retryCfg, "SubmitDelegateShare", func() error {
resp := &router.SubmitDelegateShareResponse{}
err := c.getConn().Invoke(ctx, "/mpc.router.v1.MessageRouter/SubmitDelegateShare", req, resp)
if err != nil {
return err
}
if !resp.Success {
logger.Error("Submit delegate share failed",
zap.String("session_id", sessionID),
zap.String("party_id", partyID))
return use_cases.ErrKeygenFailed
}
logger.Info("Delegate share submitted successfully via Message Router",
zap.String("session_id", sessionID),
zap.String("party_id", partyID),
zap.Int("share_size", len(encryptedShare)))
return nil
})
}

View File

@ -81,23 +81,15 @@ func main() {
logger.Fatal("Failed to create crypto service", zap.Error(err)) logger.Fatal("Failed to create crypto service", zap.Error(err))
} }
// Get gRPC service addresses from environment // Get Message Router address from environment
coordinatorAddr := os.Getenv("SESSION_COORDINATOR_ADDR") // Server-parties ONLY connect to Message Router (not Session Coordinator)
if coordinatorAddr == "" { // Message Router proxies session operations to Session Coordinator
coordinatorAddr = "localhost:9091"
}
routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR") routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR")
if routerAddr == "" { if routerAddr == "" {
routerAddr = "localhost:9092" routerAddr = "localhost:9092"
} }
// Initialize gRPC clients // Initialize Message Router client (the only gRPC connection needed)
sessionClient, err := grpcclient.NewSessionCoordinatorClient(coordinatorAddr)
if err != nil {
logger.Fatal("Failed to connect to session coordinator", zap.Error(err))
}
defer sessionClient.Close()
messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr) messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr)
if err != nil { if err != nil {
logger.Fatal("Failed to connect to message router", zap.Error(err)) logger.Fatal("Failed to connect to message router", zap.Error(err))
@ -107,16 +99,17 @@ func main() {
// Initialize repositories // Initialize repositories
keyShareRepo := postgres.NewKeySharePostgresRepo(db) keyShareRepo := postgres.NewKeySharePostgresRepo(db)
// Initialize use cases with real gRPC clients // Initialize use cases with Message Router client
// Message Router handles both messaging AND session operations (proxied to coordinator)
participateKeygenUC := use_cases.NewParticipateKeygenUseCase( participateKeygenUC := use_cases.NewParticipateKeygenUseCase(
keyShareRepo, keyShareRepo,
sessionClient, messageRouter, // MessageRouterClient implements SessionCoordinatorClient interface
messageRouter, messageRouter,
cryptoService, cryptoService,
) )
participateSigningUC := use_cases.NewParticipateSigningUseCase( participateSigningUC := use_cases.NewParticipateSigningUseCase(
keyShareRepo, keyShareRepo,
sessionClient, messageRouter, // MessageRouterClient implements SessionCoordinatorClient interface
messageRouter, messageRouter,
cryptoService, cryptoService,
) )
@ -192,7 +185,6 @@ func main() {
partyID, partyID,
participateKeygenUC, participateKeygenUC,
participateSigningUC, participateSigningUC,
sessionClient,
) )
if err := messageRouter.SubscribeSessionEvents(ctx, partyID, eventHandler); err != nil { if err := messageRouter.SubscribeSessionEvents(ctx, partyID, eventHandler); err != nil {
@ -535,7 +527,6 @@ func createSessionEventHandler(
partyID string, partyID string,
participateKeygenUC *use_cases.ParticipateKeygenUseCase, participateKeygenUC *use_cases.ParticipateKeygenUseCase,
participateSigningUC *use_cases.ParticipateSigningUseCase, participateSigningUC *use_cases.ParticipateSigningUseCase,
sessionClient *grpcclient.SessionCoordinatorClient,
) func(*router.SessionEvent) { ) func(*router.SessionEvent) {
return func(event *router.SessionEvent) { return func(event *router.SessionEvent) {
// Check if this party is selected for the session // Check if this party is selected for the session
@ -617,6 +608,15 @@ func createSessionEventHandler(
MessageHash: event.MessageHash, MessageHash: event.MessageHash,
} }
// If this party is the delegate party and user share is provided, use it
if event.DelegateUserShare != nil && event.DelegateUserShare.DelegatePartyId == partyID {
input.UserShareData = event.DelegateUserShare.EncryptedShare
logger.Info("Using user-provided share for delegate party signing",
zap.String("session_id", event.SessionId),
zap.String("party_id", partyID),
zap.Int32("party_index", event.DelegateUserShare.PartyIndex))
}
result, err := participateSigningUC.Execute(ctx, input) result, err := participateSigningUC.Execute(ctx, input)
if err != nil { if err != nil {
logger.Error("Signing participation failed", logger.Error("Signing participation failed",

View File

@ -2,6 +2,7 @@ package grpc
import ( import (
"context" "context"
"sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -17,6 +18,79 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
// DelegateShareEntry represents a delegate party's share for user retrieval
type DelegateShareEntry struct {
PartyID string
EncryptedShare []byte
PublicKey []byte
PartyIndex int32
CreatedAt time.Time
}
// DelegateShareCache stores delegate shares temporarily for user retrieval
type DelegateShareCache struct {
mu sync.RWMutex
shares map[string]*DelegateShareEntry // sessionID -> share
ttl time.Duration
}
// NewDelegateShareCache creates a new cache with TTL
func NewDelegateShareCache(ttl time.Duration) *DelegateShareCache {
cache := &DelegateShareCache{
shares: make(map[string]*DelegateShareEntry),
ttl: ttl,
}
// Start cleanup goroutine
go cache.cleanupLoop()
return cache
}
// Store stores a delegate share
func (c *DelegateShareCache) Store(sessionID string, entry *DelegateShareEntry) {
c.mu.Lock()
defer c.mu.Unlock()
entry.CreatedAt = time.Now()
c.shares[sessionID] = entry
logger.Info("Delegate share stored",
zap.String("session_id", sessionID),
zap.String("party_id", entry.PartyID))
}
// Get retrieves and deletes a delegate share (one-time retrieval)
func (c *DelegateShareCache) Get(sessionID string) (*DelegateShareEntry, bool) {
c.mu.Lock()
defer c.mu.Unlock()
entry, exists := c.shares[sessionID]
if exists {
delete(c.shares, sessionID)
logger.Info("Delegate share retrieved and deleted",
zap.String("session_id", sessionID),
zap.String("party_id", entry.PartyID))
}
return entry, exists
}
// cleanupLoop removes expired entries
func (c *DelegateShareCache) cleanupLoop() {
ticker := time.NewTicker(c.ttl / 2)
defer ticker.Stop()
for range ticker.C {
c.mu.Lock()
now := time.Now()
for sessionID, entry := range c.shares {
if now.Sub(entry.CreatedAt) > c.ttl {
delete(c.shares, sessionID)
logger.Debug("Expired delegate share removed",
zap.String("session_id", sessionID))
}
}
c.mu.Unlock()
}
}
// Global delegate share cache (15 minute TTL)
var delegateShareCache = NewDelegateShareCache(15 * time.Minute)
// SessionCoordinatorServer implements the gRPC SessionCoordinator service // SessionCoordinatorServer implements the gRPC SessionCoordinator service
type SessionCoordinatorServer struct { type SessionCoordinatorServer struct {
pb.UnimplementedSessionCoordinatorServer pb.UnimplementedSessionCoordinatorServer
@ -86,6 +160,15 @@ func (s *SessionCoordinatorServer) CreateSession(
ExpiresIn: time.Duration(req.ExpiresInSeconds) * time.Second, ExpiresIn: time.Duration(req.ExpiresInSeconds) * time.Second,
} }
// Add delegate user share if provided (for sign sessions with delegate party)
if req.DelegateUserShare != nil {
inputData.DelegateUserShare = &input.DelegateUserShare{
DelegatePartyID: req.DelegateUserShare.DelegatePartyId,
EncryptedShare: req.DelegateUserShare.EncryptedShare,
PartyIndex: req.DelegateUserShare.PartyIndex,
}
}
// Execute use case // Execute use case
output, err := s.createSessionUC.Execute(ctx, inputData) output, err := s.createSessionUC.Execute(ctx, inputData)
if err != nil { if err != nil {
@ -194,13 +277,34 @@ func (s *SessionCoordinatorServer) GetSessionStatus(
} }
} }
return &pb.GetSessionStatusResponse{ resp := &pb.GetSessionStatusResponse{
Status: output.Status, Status: output.Status,
CompletedParties: int32(completedParties), CompletedParties: int32(completedParties),
TotalParties: int32(len(output.Participants)), TotalParties: int32(len(output.Participants)),
SessionType: output.SessionType,
PublicKey: output.PublicKey, PublicKey: output.PublicKey,
Signature: output.Signature, Signature: output.Signature,
}, nil HasDelegate: output.HasDelegate, // Only meaningful for keygen sessions
}
// Try to get delegate share from cache
// Only include if session has delegate and is completed
if output.HasDelegate && output.Status == "completed" {
entry, found := delegateShareCache.Get(req.SessionId)
if found {
resp.DelegateShare = &pb.DelegateShareInfo{
EncryptedShare: entry.EncryptedShare,
PartyIndex: entry.PartyIndex,
PartyId: entry.PartyID,
}
logger.Info("Delegate share included in GetSessionStatus response",
zap.String("session_id", req.SessionId),
zap.String("party_id", entry.PartyID))
}
// If has_delegate=true but delegate_share=nil, share was already retrieved
}
return resp, nil
} }
// ReportCompletion reports that a participant has completed // ReportCompletion reports that a participant has completed
@ -323,6 +427,40 @@ func (s *SessionCoordinatorServer) StartSession(
}, nil }, nil
} }
// SubmitDelegateShare stores a delegate party's share for user retrieval
func (s *SessionCoordinatorServer) SubmitDelegateShare(
ctx context.Context,
req *pb.SubmitDelegateShareRequest,
) (*pb.SubmitDelegateShareResponse, error) {
if req.SessionId == "" {
return nil, status.Error(codes.InvalidArgument, "session_id is required")
}
if req.PartyId == "" {
return nil, status.Error(codes.InvalidArgument, "party_id is required")
}
if len(req.EncryptedShare) == 0 {
return nil, status.Error(codes.InvalidArgument, "encrypted_share is required")
}
// Store in cache
entry := &DelegateShareEntry{
PartyID: req.PartyId,
EncryptedShare: req.EncryptedShare,
PublicKey: req.PublicKey,
PartyIndex: req.PartyIndex,
}
delegateShareCache.Store(req.SessionId, entry)
logger.Info("Delegate share submitted",
zap.String("session_id", req.SessionId),
zap.String("party_id", req.PartyId),
zap.Int("share_size", len(req.EncryptedShare)))
return &pb.SubmitDelegateShareResponse{
Success: true,
}, nil
}
// toGRPCError converts domain errors to gRPC errors // toGRPCError converts domain errors to gRPC errors
func toGRPCError(err error) error { func toGRPCError(err error) error {
switch err { switch err {

View File

@ -128,7 +128,7 @@ func (h *SessionHTTPHandler) CreateSession(c *gin.Context) {
expiresIn := time.Duration(req.ExpiresIn) * time.Second expiresIn := time.Duration(req.ExpiresIn) * time.Second
if expiresIn == 0 { if expiresIn == 0 {
expiresIn = 10 * time.Minute // Default expiresIn = 24 * time.Hour // Default: 24-hour session validity
} }
inputData := input.CreateSessionInput{ inputData := input.CreateSessionInput{

View File

@ -8,6 +8,7 @@ import (
"github.com/rwadurian/mpc-system/pkg/grpcutil" "github.com/rwadurian/mpc-system/pkg/grpcutil"
"github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/pkg/logger"
"github.com/rwadurian/mpc-system/pkg/retry" "github.com/rwadurian/mpc-system/pkg/retry"
"github.com/rwadurian/mpc-system/services/session-coordinator/application/use_cases"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -120,6 +121,7 @@ func (c *MessageRouterClient) PublishSessionCreated(
messageHash []byte, messageHash []byte,
createdAt int64, createdAt int64,
expiresAt int64, expiresAt int64,
delegateUserShare *use_cases.DelegateUserShareInfo,
) error { ) error {
event := &router.SessionEvent{ event := &router.SessionEvent{
EventId: uuid.New().String(), EventId: uuid.New().String(),
@ -134,5 +136,14 @@ func (c *MessageRouterClient) PublishSessionCreated(
ExpiresAt: expiresAt, ExpiresAt: expiresAt,
} }
// Add delegate user share if provided (for sign sessions with delegate)
if delegateUserShare != nil {
event.DelegateUserShare = &router.DelegateUserShare{
DelegatePartyId: delegateUserShare.DelegatePartyID,
EncryptedShare: delegateUserShare.EncryptedShare,
PartyIndex: delegateUserShare.PartyIndex,
}
}
return c.PublishSessionEvent(ctx, event) return c.PublishSessionEvent(ctx, event)
} }

View File

@ -36,6 +36,13 @@ type PartyComposition struct {
CustomFilters []output.PartySelectionFilter // Custom party selection filters CustomFilters []output.PartySelectionFilter // Custom party selection filters
} }
// DelegateUserShare contains user's share for delegate party to use in signing
type DelegateUserShare struct {
DelegatePartyID string // The delegate party that will use this share
EncryptedShare []byte // User's encrypted share
PartyIndex int32 // Party index for this share
}
// CreateSessionInput contains input for creating a session // CreateSessionInput contains input for creating a session
type CreateSessionInput struct { type CreateSessionInput struct {
InitiatorID string InitiatorID string
@ -43,9 +50,10 @@ type CreateSessionInput struct {
ThresholdN int ThresholdN int
ThresholdT int ThresholdT int
Participants []ParticipantInfo Participants []ParticipantInfo
PartyComposition *PartyComposition // Optional: specify party composition by role PartyComposition *PartyComposition // Optional: specify party composition by role
MessageHash []byte // For sign sessions MessageHash []byte // For sign sessions
ExpiresIn time.Duration ExpiresIn time.Duration
DelegateUserShare *DelegateUserShare // For sign sessions with delegate party
} }
// ParticipantInfo contains information about a participant // ParticipantInfo contains information about a participant
@ -96,13 +104,16 @@ type PartyInfo struct {
// SessionStatusOutput contains session status information // SessionStatusOutput contains session status information
type SessionStatusOutput struct { type SessionStatusOutput struct {
SessionID uuid.UUID SessionID uuid.UUID
Status string Status string
ThresholdT int SessionType string // "keygen" or "sign"
ThresholdN int ThresholdT int
Participants []ParticipantStatus ThresholdN int
PublicKey []byte // For completed keygen Participants []ParticipantStatus
Signature []byte // For completed sign PublicKey []byte // For completed keygen
Signature []byte // For completed sign
HasDelegate bool // True if keygen session has a delegate party (always false for sign)
DelegatePartyID string // The delegate party ID (if any, only for keygen)
} }
// ParticipantStatus contains participant status information // ParticipantStatus contains participant status information

View File

@ -16,6 +16,13 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// DelegateUserShareInfo contains user's share for delegate party
type DelegateUserShareInfo struct {
DelegatePartyID string
EncryptedShare []byte
PartyIndex int32
}
// MessageRouterClient interface for publishing session events // MessageRouterClient interface for publishing session events
type MessageRouterClient interface { type MessageRouterClient interface {
PublishSessionCreated( PublishSessionCreated(
@ -28,6 +35,7 @@ type MessageRouterClient interface {
messageHash []byte, messageHash []byte,
createdAt int64, createdAt int64,
expiresAt int64, expiresAt int64,
delegateUserShare *DelegateUserShareInfo,
) error ) error
} }
@ -286,6 +294,17 @@ func (uc *CreateSessionUseCase) Execute(
// Only publish if parties were selected from pool // Only publish if parties were selected from pool
if len(session.GetPartyIDs()) > 0 && uc.messageRouterClient != nil { if len(session.GetPartyIDs()) > 0 && uc.messageRouterClient != nil {
selectedParties := session.GetPartyIDs() selectedParties := session.GetPartyIDs()
// Convert delegate user share if provided (for sign sessions with delegate)
var delegateUserShare *DelegateUserShareInfo
if req.DelegateUserShare != nil {
delegateUserShare = &DelegateUserShareInfo{
DelegatePartyID: req.DelegateUserShare.DelegatePartyID,
EncryptedShare: req.DelegateUserShare.EncryptedShare,
PartyIndex: req.DelegateUserShare.PartyIndex,
}
}
err := uc.messageRouterClient.PublishSessionCreated( err := uc.messageRouterClient.PublishSessionCreated(
ctx, ctx,
session.ID.UUID(), session.ID.UUID(),
@ -296,6 +315,7 @@ func (uc *CreateSessionUseCase) Execute(
session.MessageHash, session.MessageHash,
session.CreatedAt.UnixMilli(), session.CreatedAt.UnixMilli(),
session.ExpiresAt.UnixMilli(), session.ExpiresAt.UnixMilli(),
delegateUserShare,
) )
if err != nil { if err != nil {
// Log error but don't fail the operation // Log error but don't fail the operation

View File

@ -46,12 +46,17 @@ func (uc *GetSessionStatusUseCase) Execute(
} }
// 3. Build response // 3. Build response
// has_delegate is only meaningful for keygen sessions
hasDelegate := session.DelegatePartyID != "" && string(session.SessionType) == "keygen"
return &input.SessionStatusOutput{ return &input.SessionStatusOutput{
SessionID: session.ID.UUID(), SessionID: session.ID.UUID(),
Status: session.Status.String(), Status: session.Status.String(),
ThresholdT: session.Threshold.T(), SessionType: string(session.SessionType),
ThresholdN: session.Threshold.N(), ThresholdT: session.Threshold.T(),
Participants: participants, ThresholdN: session.Threshold.N(),
PublicKey: session.PublicKey, Participants: participants,
PublicKey: session.PublicKey,
HasDelegate: hasDelegate,
DelegatePartyID: session.DelegatePartyID,
}, nil }, nil
} }

View File

@ -84,15 +84,11 @@ func (s *SessionCoordinatorService) ShouldExpireSession(session *entities.MPCSes
} }
// CalculateSessionTimeout calculates the timeout for a session type // CalculateSessionTimeout calculates the timeout for a session type
// Sessions are valid for 24 hours to allow users sufficient time to complete MPC operations
// Note: This is separate from reliability timeouts (party heartbeat/activity) which remain at 2 minutes
func (s *SessionCoordinatorService) CalculateSessionTimeout(sessionType entities.SessionType) time.Duration { func (s *SessionCoordinatorService) CalculateSessionTimeout(sessionType entities.SessionType) time.Duration {
switch sessionType { // All session types have a 24-hour validity period
case entities.SessionTypeKeygen: return 24 * time.Hour
return 10 * time.Minute
case entities.SessionTypeSign:
return 5 * time.Minute
default:
return 10 * time.Minute
}
} }
// ValidateMessageRouting validates if a message can be routed // ValidateMessageRouting validates if a message can be routed