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:
parent
55f5ec49f2
commit
aa74e2b2e2
|
|
@ -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
|
||||
|
|
@ -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=
|
||||
|
|
@ -31,6 +31,8 @@ type CreateSessionRequest struct {
|
|||
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
|
||||
PartyComposition *PartyComposition `protobuf:"bytes,7,opt,name=party_composition,json=partyComposition,proto3" json:"party_composition,omitempty"` // Optional: party composition requirements for auto-selection
|
||||
// For sign sessions with delegate party: user must provide their encrypted share
|
||||
DelegateUserShare *DelegateUserShare `protobuf:"bytes,8,opt,name=delegate_user_share,json=delegateUserShare,proto3" json:"delegate_user_share,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -114,6 +116,74 @@ func (x *CreateSessionRequest) GetPartyComposition() *PartyComposition {
|
|||
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
|
||||
type PartyComposition struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
|
|
@ -126,7 +196,7 @@ type PartyComposition struct {
|
|||
|
||||
func (x *PartyComposition) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -138,7 +208,7 @@ func (x *PartyComposition) String() string {
|
|||
func (*PartyComposition) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -151,7 +221,7 @@ func (x *PartyComposition) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use PartyComposition.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -186,7 +256,7 @@ type ParticipantInfo struct {
|
|||
|
||||
func (x *ParticipantInfo) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -198,7 +268,7 @@ func (x *ParticipantInfo) String() string {
|
|||
func (*ParticipantInfo) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -211,7 +281,7 @@ func (x *ParticipantInfo) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ParticipantInfo.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -241,7 +311,7 @@ type DeviceInfo struct {
|
|||
|
||||
func (x *DeviceInfo) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -253,7 +323,7 @@ func (x *DeviceInfo) String() string {
|
|||
func (*DeviceInfo) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -266,7 +336,7 @@ func (x *DeviceInfo) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use DeviceInfo.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -311,7 +381,7 @@ type CreateSessionResponse struct {
|
|||
|
||||
func (x *CreateSessionResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -323,7 +393,7 @@ func (x *CreateSessionResponse) String() string {
|
|||
func (*CreateSessionResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -336,7 +406,7 @@ func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use CreateSessionResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -387,7 +457,7 @@ type JoinSessionRequest struct {
|
|||
|
||||
func (x *JoinSessionRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -399,7 +469,7 @@ func (x *JoinSessionRequest) String() string {
|
|||
func (*JoinSessionRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -412,7 +482,7 @@ func (x *JoinSessionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use JoinSessionRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -455,7 +525,7 @@ type JoinSessionResponse struct {
|
|||
|
||||
func (x *JoinSessionResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -467,7 +537,7 @@ func (x *JoinSessionResponse) String() string {
|
|||
func (*JoinSessionResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -480,7 +550,7 @@ func (x *JoinSessionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use JoinSessionResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -519,7 +589,7 @@ type SessionInfo struct {
|
|||
|
||||
func (x *SessionInfo) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -531,7 +601,7 @@ func (x *SessionInfo) String() string {
|
|||
func (*SessionInfo) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -544,7 +614,7 @@ func (x *SessionInfo) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use SessionInfo.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -601,7 +671,7 @@ type PartyInfo struct {
|
|||
|
||||
func (x *PartyInfo) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -613,7 +683,7 @@ func (x *PartyInfo) String() string {
|
|||
func (*PartyInfo) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -626,7 +696,7 @@ func (x *PartyInfo) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use PartyInfo.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -660,7 +730,7 @@ type GetSessionStatusRequest struct {
|
|||
|
||||
func (x *GetSessionStatusRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -672,7 +742,7 @@ func (x *GetSessionStatusRequest) String() string {
|
|||
func (*GetSessionStatusRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -685,7 +755,7 @@ func (x *GetSessionStatusRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetSessionStatusRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -701,15 +771,22 @@ type GetSessionStatusResponse struct {
|
|||
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"`
|
||||
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
|
||||
Signature []byte `protobuf:"bytes,5,opt,name=signature,proto3" json:"signature,omitempty"` // For completed sign
|
||||
SessionType string `protobuf:"bytes,4,opt,name=session_type,json=sessionType,proto3" json:"session_type,omitempty"` // "keygen" or "sign"
|
||||
PublicKey []byte `protobuf:"bytes,5,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // For completed keygen
|
||||
Signature []byte `protobuf:"bytes,6,opt,name=signature,proto3" json:"signature,omitempty"` // For completed sign
|
||||
// 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() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -721,7 +798,7 @@ func (x *GetSessionStatusResponse) String() string {
|
|||
func (*GetSessionStatusResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -734,7 +811,7 @@ func (x *GetSessionStatusResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetSessionStatusResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -758,6 +835,13 @@ func (x *GetSessionStatusResponse) GetTotalParties() int32 {
|
|||
return 0
|
||||
}
|
||||
|
||||
func (x *GetSessionStatusResponse) GetSessionType() string {
|
||||
if x != nil {
|
||||
return x.SessionType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetSessionStatusResponse) GetPublicKey() []byte {
|
||||
if x != nil {
|
||||
return x.PublicKey
|
||||
|
|
@ -772,6 +856,81 @@ func (x *GetSessionStatusResponse) GetSignature() []byte {
|
|||
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
|
||||
type ReportCompletionRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
|
|
@ -785,7 +944,7 @@ type ReportCompletionRequest struct {
|
|||
|
||||
func (x *ReportCompletionRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -797,7 +956,7 @@ func (x *ReportCompletionRequest) String() string {
|
|||
func (*ReportCompletionRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -810,7 +969,7 @@ func (x *ReportCompletionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ReportCompletionRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -852,7 +1011,7 @@ type ReportCompletionResponse struct {
|
|||
|
||||
func (x *ReportCompletionResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -864,7 +1023,7 @@ func (x *ReportCompletionResponse) String() string {
|
|||
func (*ReportCompletionResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -877,7 +1036,7 @@ func (x *ReportCompletionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ReportCompletionResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -904,7 +1063,7 @@ type CloseSessionRequest struct {
|
|||
|
||||
func (x *CloseSessionRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -916,7 +1075,7 @@ func (x *CloseSessionRequest) String() string {
|
|||
func (*CloseSessionRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -929,7 +1088,7 @@ func (x *CloseSessionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use CloseSessionRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -949,7 +1108,7 @@ type CloseSessionResponse struct {
|
|||
|
||||
func (x *CloseSessionResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -961,7 +1120,7 @@ func (x *CloseSessionResponse) String() string {
|
|||
func (*CloseSessionResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -974,7 +1133,7 @@ func (x *CloseSessionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use CloseSessionResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -995,7 +1154,7 @@ type MarkPartyReadyRequest struct {
|
|||
|
||||
func (x *MarkPartyReadyRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -1007,7 +1166,7 @@ func (x *MarkPartyReadyRequest) String() string {
|
|||
func (*MarkPartyReadyRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -1020,7 +1179,7 @@ func (x *MarkPartyReadyRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use MarkPartyReadyRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -1050,7 +1209,7 @@ type MarkPartyReadyResponse struct {
|
|||
|
||||
func (x *MarkPartyReadyResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -1062,7 +1221,7 @@ func (x *MarkPartyReadyResponse) String() string {
|
|||
func (*MarkPartyReadyResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -1075,7 +1234,7 @@ func (x *MarkPartyReadyResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use MarkPartyReadyResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -1116,7 +1275,7 @@ type StartSessionRequest struct {
|
|||
|
||||
func (x *StartSessionRequest) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -1128,7 +1287,7 @@ func (x *StartSessionRequest) String() string {
|
|||
func (*StartSessionRequest) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -1141,7 +1300,7 @@ func (x *StartSessionRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use StartSessionRequest.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -1162,7 +1321,7 @@ type StartSessionResponse struct {
|
|||
|
||||
func (x *StartSessionResponse) Reset() {
|
||||
*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.StoreMessageInfo(mi)
|
||||
}
|
||||
|
|
@ -1174,7 +1333,7 @@ func (x *StartSessionResponse) String() string {
|
|||
func (*StartSessionResponse) ProtoMessage() {}
|
||||
|
||||
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 {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
|
@ -1187,7 +1346,7 @@ func (x *StartSessionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use StartSessionResponse.ProtoReflect.Descriptor instead.
|
||||
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 {
|
||||
|
|
@ -1204,11 +1363,133 @@ func (x *StartSessionResponse) GetStatus() string {
|
|||
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
|
||||
|
||||
const file_api_proto_session_coordinator_proto_rawDesc = "" +
|
||||
"\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" +
|
||||
"\fsession_type\x18\x01 \x01(\tR\vsessionType\x12\x1f\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" +
|
||||
"\fmessage_hash\x18\x05 \x01(\fR\vmessageHash\x12,\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" +
|
||||
"\x10persistent_count\x18\x01 \x01(\x05R\x0fpersistentCount\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" +
|
||||
"\x17GetSessionStatusRequest\x12\x1d\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" +
|
||||
"\x06status\x18\x01 \x01(\tR\x06status\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" +
|
||||
"public_key\x18\x04 \x01(\fR\tpublicKey\x12\x1c\n" +
|
||||
"\tsignature\x18\x05 \x01(\fR\tsignature\"\x90\x01\n" +
|
||||
"public_key\x18\x05 \x01(\fR\tpublicKey\x12\x1c\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" +
|
||||
"\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" +
|
||||
"\x14StartSessionResponse\x12\x18\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" +
|
||||
"\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" +
|
||||
|
|
@ -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" +
|
||||
"\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" +
|
||||
"\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 (
|
||||
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
|
||||
}
|
||||
|
||||
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{
|
||||
(*CreateSessionRequest)(nil), // 0: mpc.coordinator.v1.CreateSessionRequest
|
||||
(*PartyComposition)(nil), // 1: mpc.coordinator.v1.PartyComposition
|
||||
(*ParticipantInfo)(nil), // 2: mpc.coordinator.v1.ParticipantInfo
|
||||
(*DeviceInfo)(nil), // 3: mpc.coordinator.v1.DeviceInfo
|
||||
(*CreateSessionResponse)(nil), // 4: mpc.coordinator.v1.CreateSessionResponse
|
||||
(*JoinSessionRequest)(nil), // 5: mpc.coordinator.v1.JoinSessionRequest
|
||||
(*JoinSessionResponse)(nil), // 6: mpc.coordinator.v1.JoinSessionResponse
|
||||
(*SessionInfo)(nil), // 7: mpc.coordinator.v1.SessionInfo
|
||||
(*PartyInfo)(nil), // 8: mpc.coordinator.v1.PartyInfo
|
||||
(*GetSessionStatusRequest)(nil), // 9: mpc.coordinator.v1.GetSessionStatusRequest
|
||||
(*GetSessionStatusResponse)(nil), // 10: mpc.coordinator.v1.GetSessionStatusResponse
|
||||
(*ReportCompletionRequest)(nil), // 11: mpc.coordinator.v1.ReportCompletionRequest
|
||||
(*ReportCompletionResponse)(nil), // 12: mpc.coordinator.v1.ReportCompletionResponse
|
||||
(*CloseSessionRequest)(nil), // 13: mpc.coordinator.v1.CloseSessionRequest
|
||||
(*CloseSessionResponse)(nil), // 14: mpc.coordinator.v1.CloseSessionResponse
|
||||
(*MarkPartyReadyRequest)(nil), // 15: mpc.coordinator.v1.MarkPartyReadyRequest
|
||||
(*MarkPartyReadyResponse)(nil), // 16: mpc.coordinator.v1.MarkPartyReadyResponse
|
||||
(*StartSessionRequest)(nil), // 17: mpc.coordinator.v1.StartSessionRequest
|
||||
(*StartSessionResponse)(nil), // 18: mpc.coordinator.v1.StartSessionResponse
|
||||
nil, // 19: mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry
|
||||
(*DelegateUserShare)(nil), // 1: mpc.coordinator.v1.DelegateUserShare
|
||||
(*PartyComposition)(nil), // 2: mpc.coordinator.v1.PartyComposition
|
||||
(*ParticipantInfo)(nil), // 3: mpc.coordinator.v1.ParticipantInfo
|
||||
(*DeviceInfo)(nil), // 4: mpc.coordinator.v1.DeviceInfo
|
||||
(*CreateSessionResponse)(nil), // 5: mpc.coordinator.v1.CreateSessionResponse
|
||||
(*JoinSessionRequest)(nil), // 6: mpc.coordinator.v1.JoinSessionRequest
|
||||
(*JoinSessionResponse)(nil), // 7: mpc.coordinator.v1.JoinSessionResponse
|
||||
(*SessionInfo)(nil), // 8: mpc.coordinator.v1.SessionInfo
|
||||
(*PartyInfo)(nil), // 9: mpc.coordinator.v1.PartyInfo
|
||||
(*GetSessionStatusRequest)(nil), // 10: mpc.coordinator.v1.GetSessionStatusRequest
|
||||
(*GetSessionStatusResponse)(nil), // 11: mpc.coordinator.v1.GetSessionStatusResponse
|
||||
(*DelegateShareInfo)(nil), // 12: mpc.coordinator.v1.DelegateShareInfo
|
||||
(*ReportCompletionRequest)(nil), // 13: mpc.coordinator.v1.ReportCompletionRequest
|
||||
(*ReportCompletionResponse)(nil), // 14: mpc.coordinator.v1.ReportCompletionResponse
|
||||
(*CloseSessionRequest)(nil), // 15: mpc.coordinator.v1.CloseSessionRequest
|
||||
(*CloseSessionResponse)(nil), // 16: mpc.coordinator.v1.CloseSessionResponse
|
||||
(*MarkPartyReadyRequest)(nil), // 17: mpc.coordinator.v1.MarkPartyReadyRequest
|
||||
(*MarkPartyReadyResponse)(nil), // 18: mpc.coordinator.v1.MarkPartyReadyResponse
|
||||
(*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{
|
||||
2, // 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
|
||||
3, // 2: mpc.coordinator.v1.ParticipantInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
19, // 3: mpc.coordinator.v1.CreateSessionResponse.join_tokens:type_name -> mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry
|
||||
3, // 4: mpc.coordinator.v1.JoinSessionRequest.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
7, // 5: mpc.coordinator.v1.JoinSessionResponse.session_info:type_name -> mpc.coordinator.v1.SessionInfo
|
||||
8, // 6: mpc.coordinator.v1.JoinSessionResponse.other_parties:type_name -> mpc.coordinator.v1.PartyInfo
|
||||
3, // 7: mpc.coordinator.v1.PartyInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
0, // 8: mpc.coordinator.v1.SessionCoordinator.CreateSession:input_type -> mpc.coordinator.v1.CreateSessionRequest
|
||||
5, // 9: mpc.coordinator.v1.SessionCoordinator.JoinSession:input_type -> mpc.coordinator.v1.JoinSessionRequest
|
||||
9, // 10: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:input_type -> mpc.coordinator.v1.GetSessionStatusRequest
|
||||
15, // 11: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:input_type -> mpc.coordinator.v1.MarkPartyReadyRequest
|
||||
17, // 12: mpc.coordinator.v1.SessionCoordinator.StartSession:input_type -> mpc.coordinator.v1.StartSessionRequest
|
||||
11, // 13: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:input_type -> mpc.coordinator.v1.ReportCompletionRequest
|
||||
13, // 14: mpc.coordinator.v1.SessionCoordinator.CloseSession:input_type -> mpc.coordinator.v1.CloseSessionRequest
|
||||
4, // 15: mpc.coordinator.v1.SessionCoordinator.CreateSession:output_type -> mpc.coordinator.v1.CreateSessionResponse
|
||||
6, // 16: mpc.coordinator.v1.SessionCoordinator.JoinSession:output_type -> mpc.coordinator.v1.JoinSessionResponse
|
||||
10, // 17: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:output_type -> mpc.coordinator.v1.GetSessionStatusResponse
|
||||
16, // 18: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:output_type -> mpc.coordinator.v1.MarkPartyReadyResponse
|
||||
18, // 19: mpc.coordinator.v1.SessionCoordinator.StartSession:output_type -> mpc.coordinator.v1.StartSessionResponse
|
||||
12, // 20: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:output_type -> mpc.coordinator.v1.ReportCompletionResponse
|
||||
14, // 21: mpc.coordinator.v1.SessionCoordinator.CloseSession:output_type -> mpc.coordinator.v1.CloseSessionResponse
|
||||
15, // [15:22] is the sub-list for method output_type
|
||||
8, // [8:15] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
3, // 0: mpc.coordinator.v1.CreateSessionRequest.participants:type_name -> mpc.coordinator.v1.ParticipantInfo
|
||||
2, // 1: mpc.coordinator.v1.CreateSessionRequest.party_composition:type_name -> mpc.coordinator.v1.PartyComposition
|
||||
1, // 2: mpc.coordinator.v1.CreateSessionRequest.delegate_user_share:type_name -> mpc.coordinator.v1.DelegateUserShare
|
||||
4, // 3: mpc.coordinator.v1.ParticipantInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
23, // 4: mpc.coordinator.v1.CreateSessionResponse.join_tokens:type_name -> mpc.coordinator.v1.CreateSessionResponse.JoinTokensEntry
|
||||
4, // 5: mpc.coordinator.v1.JoinSessionRequest.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
8, // 6: mpc.coordinator.v1.JoinSessionResponse.session_info:type_name -> mpc.coordinator.v1.SessionInfo
|
||||
9, // 7: mpc.coordinator.v1.JoinSessionResponse.other_parties:type_name -> mpc.coordinator.v1.PartyInfo
|
||||
4, // 8: mpc.coordinator.v1.PartyInfo.device_info:type_name -> mpc.coordinator.v1.DeviceInfo
|
||||
12, // 9: mpc.coordinator.v1.GetSessionStatusResponse.delegate_share:type_name -> mpc.coordinator.v1.DelegateShareInfo
|
||||
0, // 10: mpc.coordinator.v1.SessionCoordinator.CreateSession:input_type -> mpc.coordinator.v1.CreateSessionRequest
|
||||
6, // 11: mpc.coordinator.v1.SessionCoordinator.JoinSession:input_type -> mpc.coordinator.v1.JoinSessionRequest
|
||||
10, // 12: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:input_type -> mpc.coordinator.v1.GetSessionStatusRequest
|
||||
17, // 13: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:input_type -> mpc.coordinator.v1.MarkPartyReadyRequest
|
||||
19, // 14: mpc.coordinator.v1.SessionCoordinator.StartSession:input_type -> mpc.coordinator.v1.StartSessionRequest
|
||||
13, // 15: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:input_type -> mpc.coordinator.v1.ReportCompletionRequest
|
||||
15, // 16: mpc.coordinator.v1.SessionCoordinator.CloseSession:input_type -> mpc.coordinator.v1.CloseSessionRequest
|
||||
21, // 17: mpc.coordinator.v1.SessionCoordinator.SubmitDelegateShare:input_type -> mpc.coordinator.v1.SubmitDelegateShareRequest
|
||||
5, // 18: mpc.coordinator.v1.SessionCoordinator.CreateSession:output_type -> mpc.coordinator.v1.CreateSessionResponse
|
||||
7, // 19: mpc.coordinator.v1.SessionCoordinator.JoinSession:output_type -> mpc.coordinator.v1.JoinSessionResponse
|
||||
11, // 20: mpc.coordinator.v1.SessionCoordinator.GetSessionStatus:output_type -> mpc.coordinator.v1.GetSessionStatusResponse
|
||||
18, // 21: mpc.coordinator.v1.SessionCoordinator.MarkPartyReady:output_type -> mpc.coordinator.v1.MarkPartyReadyResponse
|
||||
20, // 22: mpc.coordinator.v1.SessionCoordinator.StartSession:output_type -> mpc.coordinator.v1.StartSessionResponse
|
||||
14, // 23: mpc.coordinator.v1.SessionCoordinator.ReportCompletion:output_type -> mpc.coordinator.v1.ReportCompletionResponse
|
||||
16, // 24: mpc.coordinator.v1.SessionCoordinator.CloseSession:output_type -> mpc.coordinator.v1.CloseSessionResponse
|
||||
22, // 25: mpc.coordinator.v1.SessionCoordinator.SubmitDelegateShare:output_type -> mpc.coordinator.v1.SubmitDelegateShareResponse
|
||||
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() }
|
||||
|
|
@ -1401,7 +1716,7 @@ func file_api_proto_session_coordinator_proto_init() {
|
|||
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)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 20,
|
||||
NumMessages: 24,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const (
|
|||
SessionCoordinator_StartSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/StartSession"
|
||||
SessionCoordinator_ReportCompletion_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/ReportCompletion"
|
||||
SessionCoordinator_CloseSession_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/CloseSession"
|
||||
SessionCoordinator_SubmitDelegateShare_FullMethodName = "/mpc.coordinator.v1.SessionCoordinator/SubmitDelegateShare"
|
||||
)
|
||||
|
||||
// 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)
|
||||
ReportCompletion(ctx context.Context, in *ReportCompletionRequest, opts ...grpc.CallOption) (*ReportCompletionResponse, 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 {
|
||||
|
|
@ -122,6 +125,16 @@ func (c *sessionCoordinatorClient) CloseSession(ctx context.Context, in *CloseSe
|
|||
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.
|
||||
// All implementations must embed UnimplementedSessionCoordinatorServer
|
||||
// for forward compatibility.
|
||||
|
|
@ -136,6 +149,8 @@ type SessionCoordinatorServer interface {
|
|||
StartSession(context.Context, *StartSessionRequest) (*StartSessionResponse, error)
|
||||
ReportCompletion(context.Context, *ReportCompletionRequest) (*ReportCompletionResponse, 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()
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +182,9 @@ func (UnimplementedSessionCoordinatorServer) ReportCompletion(context.Context, *
|
|||
func (UnimplementedSessionCoordinatorServer) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) {
|
||||
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) testEmbeddedByValue() {}
|
||||
|
||||
|
|
@ -314,6 +332,24 @@ func _SessionCoordinator_CloseSession_Handler(srv interface{}, ctx context.Conte
|
|||
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.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
|
|
@ -349,6 +385,10 @@ var SessionCoordinator_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "CloseSession",
|
||||
Handler: _SessionCoordinator_CloseSession_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SubmitDelegateShare",
|
||||
Handler: _SessionCoordinator_SubmitDelegateShare_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "api/proto/session_coordinator.proto",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -29,6 +29,11 @@ const (
|
|||
MessageRouter_SubscribeSessionEvents_FullMethodName = "/mpc.router.v1.MessageRouter/SubscribeSessionEvents"
|
||||
MessageRouter_PublishSessionEvent_FullMethodName = "/mpc.router.v1.MessageRouter/PublishSessionEvent"
|
||||
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.
|
||||
|
|
@ -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.
|
||||
//
|
||||
// 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 {
|
||||
// RouteMessage routes a message from one party to others
|
||||
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)
|
||||
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
|
||||
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 {
|
||||
|
|
@ -186,11 +203,63 @@ func (c *messageRouterClient) GetRegisteredParties(ctx context.Context, in *GetR
|
|||
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.
|
||||
// All implementations must embed UnimplementedMessageRouterServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// 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 {
|
||||
// RouteMessage routes a message from one party to others
|
||||
RouteMessage(context.Context, *RouteMessageRequest) (*RouteMessageResponse, error)
|
||||
|
|
@ -213,6 +282,16 @@ type MessageRouterServer interface {
|
|||
PublishSessionEvent(context.Context, *PublishSessionEventRequest) (*PublishSessionEventResponse, error)
|
||||
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
@ -253,6 +332,21 @@ func (UnimplementedMessageRouterServer) PublishSessionEvent(context.Context, *Pu
|
|||
func (UnimplementedMessageRouterServer) GetRegisteredParties(context.Context, *GetRegisteredPartiesRequest) (*GetRegisteredPartiesResponse, error) {
|
||||
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) testEmbeddedByValue() {}
|
||||
|
||||
|
|
@ -440,6 +534,96 @@ func _MessageRouter_GetRegisteredParties_Handler(srv interface{}, ctx context.Co
|
|||
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.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
|
|
@ -479,6 +663,26 @@ var MessageRouter_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "GetRegisteredParties",
|
||||
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{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@ package mpc.router.v1;
|
|||
option go_package = "github.com/rwadurian/mpc-system/api/grpc/router/v1;router";
|
||||
|
||||
// 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 {
|
||||
// ============================================
|
||||
// Message Routing
|
||||
// ============================================
|
||||
|
||||
// RouteMessage routes a message from one party to others
|
||||
rpc RouteMessage(RouteMessageRequest) returns (RouteMessageResponse);
|
||||
|
||||
|
|
@ -22,20 +28,52 @@ service MessageRouter {
|
|||
// GetMessageStatus gets the delivery status of a message
|
||||
rpc GetMessageStatus(GetMessageStatusRequest) returns (GetMessageStatusResponse);
|
||||
|
||||
// ============================================
|
||||
// Party Registration & Heartbeat
|
||||
// ============================================
|
||||
|
||||
// RegisterParty registers a party with the message router (party actively connects)
|
||||
rpc RegisterParty(RegisterPartyRequest) returns (RegisterPartyResponse);
|
||||
|
||||
// Heartbeat sends a heartbeat to keep the party alive
|
||||
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
|
||||
|
||||
// ============================================
|
||||
// Session Events (Push from Coordinator)
|
||||
// ============================================
|
||||
|
||||
// SubscribeSessionEvents subscribes to session lifecycle events (session start, etc.)
|
||||
rpc SubscribeSessionEvents(SubscribeSessionEventsRequest) returns (stream SessionEvent);
|
||||
|
||||
// PublishSessionEvent publishes a session event (called by Session Coordinator)
|
||||
rpc PublishSessionEvent(PublishSessionEventRequest) returns (PublishSessionEventResponse);
|
||||
|
||||
// ============================================
|
||||
// Party Discovery (for Session Coordinator)
|
||||
// ============================================
|
||||
|
||||
// GetRegisteredParties returns all registered parties (for Session Coordinator party discovery)
|
||||
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
|
||||
|
|
@ -126,6 +164,15 @@ message SessionEvent {
|
|||
bytes message_hash = 8; // For sign sessions
|
||||
int64 created_at = 9; // 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
|
||||
|
|
@ -211,3 +258,102 @@ message HeartbeatResponse {
|
|||
int64 server_timestamp = 2; // Server timestamp for clock sync
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ service SessionCoordinator {
|
|||
rpc StartSession(StartSessionRequest) returns (StartSessionResponse);
|
||||
rpc ReportCompletion(ReportCompletionRequest) returns (ReportCompletionResponse);
|
||||
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
|
||||
|
|
@ -25,6 +28,15 @@ message CreateSessionRequest {
|
|||
bytes message_hash = 5; // Required for sign sessions
|
||||
int64 expires_in_seconds = 6; // Session expiration time
|
||||
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
|
||||
|
|
@ -99,8 +111,22 @@ message GetSessionStatusResponse {
|
|||
string status = 1;
|
||||
int32 completed_parties = 2;
|
||||
int32 total_parties = 3;
|
||||
bytes public_key = 4; // For completed keygen
|
||||
bytes signature = 5; // For completed sign
|
||||
string session_type = 4; // "keygen" or "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
|
||||
|
|
@ -151,3 +177,17 @@ message StartSessionResponse {
|
|||
bool success = 1;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,23 @@
|
|||
# =============================================================================
|
||||
# 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
|
||||
# 8081 - Session Coordinator API
|
||||
# 8082 - Message Router WebSocket
|
||||
# 8083 - Server Party API (user share generation)
|
||||
# 8082 - Message Router HTTP
|
||||
# 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
|
||||
|
|
@ -18,34 +30,70 @@ RED='\033[0;31m'
|
|||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${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)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Load environment
|
||||
if [ -f ".env" ]; then
|
||||
log_info "Loading environment from .env file"
|
||||
# Determine which environment file to load
|
||||
load_env() {
|
||||
local env_file="$1"
|
||||
if [ -f "$env_file" ]; then
|
||||
log_info "Loading environment from $env_file"
|
||||
set -a
|
||||
source .env
|
||||
source "$env_file"
|
||||
set +a
|
||||
elif [ ! -f ".env" ] && [ -f ".env.example" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Load environment based on mode
|
||||
load_environment() {
|
||||
local mode="$1"
|
||||
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"
|
||||
log_warn "Please edit .env and configure for your environment!"
|
||||
cp .env.example .env
|
||||
log_error "Please configure .env file and run again"
|
||||
exit 1
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Core services list
|
||||
CORE_SERVICES="postgres redis rabbitmq"
|
||||
MPC_SERVICES="session-coordinator message-router server-party-1 server-party-2 server-party-3 server-party-api account-service"
|
||||
ALL_SERVICES="$CORE_SERVICES $MPC_SERVICES"
|
||||
# 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)
|
||||
|
|
@ -61,7 +109,7 @@ case "$1" in
|
|||
;;
|
||||
|
||||
up|start)
|
||||
log_info "Starting MPC System..."
|
||||
log_info "Starting MPC System (Development)..."
|
||||
docker compose up -d
|
||||
log_success "MPC System started"
|
||||
echo ""
|
||||
|
|
@ -105,10 +153,8 @@ case "$1" in
|
|||
|
||||
health)
|
||||
log_info "Checking MPC System health..."
|
||||
|
||||
# Check infrastructure
|
||||
echo ""
|
||||
echo "=== Infrastructure ==="
|
||||
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"
|
||||
|
|
@ -117,10 +163,9 @@ case "$1" in
|
|||
fi
|
||||
done
|
||||
|
||||
# Check MPC services
|
||||
echo ""
|
||||
echo "=== MPC Services ==="
|
||||
for svc in $MPC_SERVICES; do
|
||||
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
|
||||
|
|
@ -128,9 +173,8 @@ case "$1" in
|
|||
fi
|
||||
done
|
||||
|
||||
# Check external API
|
||||
echo ""
|
||||
echo "=== External API ==="
|
||||
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
|
||||
|
|
@ -138,50 +182,6 @@ case "$1" in
|
|||
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
|
||||
|
|
@ -213,31 +213,305 @@ case "$1" in
|
|||
;;
|
||||
|
||||
*)
|
||||
echo "MPC System Deployment Script"
|
||||
echo ""
|
||||
echo "Usage: $0 <command> [options]"
|
||||
echo ""
|
||||
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
|
||||
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
|
||||
prod)
|
||||
shift
|
||||
prod_commands "$@" || {
|
||||
echo "Usage: $0 prod {build|up|down|restart|logs|status|health|clean}"
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
|
||||
party)
|
||||
shift
|
||||
party_commands "$@" || {
|
||||
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
|
||||
}
|
||||
;;
|
||||
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
|
||||
"")
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
|
||||
*)
|
||||
# Default to development mode
|
||||
dev_commands "$@" || {
|
||||
show_help
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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.';
|
||||
|
|
@ -9,9 +9,10 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"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/use_cases"
|
||||
"github.com/rwadurian/mpc-system/services/account/domain/entities"
|
||||
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
|
@ -31,7 +32,7 @@ type AccountHTTPHandler struct {
|
|||
completeRecoveryUC *use_cases.CompleteRecoveryUseCase
|
||||
getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase
|
||||
cancelRecoveryUC *use_cases.CancelRecoveryUseCase
|
||||
sessionCoordinatorClient *grpc.SessionCoordinatorClient
|
||||
sessionCoordinatorClient *grpcclient.SessionCoordinatorClient
|
||||
}
|
||||
|
||||
// NewAccountHTTPHandler creates a new AccountHTTPHandler
|
||||
|
|
@ -49,7 +50,7 @@ func NewAccountHTTPHandler(
|
|||
completeRecoveryUC *use_cases.CompleteRecoveryUseCase,
|
||||
getRecoveryStatusUC *use_cases.GetRecoveryStatusUseCase,
|
||||
cancelRecoveryUC *use_cases.CancelRecoveryUseCase,
|
||||
sessionCoordinatorClient *grpc.SessionCoordinatorClient,
|
||||
sessionCoordinatorClient *grpcclient.SessionCoordinatorClient,
|
||||
) *AccountHTTPHandler {
|
||||
return &AccountHTTPHandler{
|
||||
createAccountUC: createAccountUC,
|
||||
|
|
@ -80,6 +81,11 @@ func (h *AccountHTTPHandler) RegisterRoutes(router *gin.RouterGroup) {
|
|||
accounts.PUT("/:id", h.UpdateAccount)
|
||||
accounts.GET("/:id/shares", h.GetAccountShares)
|
||||
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")
|
||||
|
|
@ -615,7 +621,7 @@ func (h *AccountHTTPHandler) CreateKeygenSession(c *gin.Context) {
|
|||
type CreateSigningSessionRequest struct {
|
||||
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)
|
||||
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
|
||||
|
|
@ -655,20 +661,80 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Get the party IDs from account shares
|
||||
var partyIDs []string
|
||||
// Build a map of active shares for validation
|
||||
activeSharesMap := make(map[string]*entities.AccountShare)
|
||||
var allActivePartyIDs []string
|
||||
var delegateShare *entities.AccountShare
|
||||
for _, share := range accountOutput.Shares {
|
||||
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 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "insufficient active shares for signing",
|
||||
"error": "insufficient parties for signing",
|
||||
"required": accountOutput.Account.ThresholdT,
|
||||
"active": len(partyIDs),
|
||||
"selected": len(partyIDs),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -677,10 +743,30 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Prepare delegate user share if needed
|
||||
var delegateUserShare *grpcclient.DelegateUserShareInput
|
||||
if delegateShare != nil {
|
||||
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(
|
||||
ctx,
|
||||
|
|
@ -688,6 +774,7 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
partyIDs,
|
||||
messageHash,
|
||||
600, // 10 minutes expiry
|
||||
delegateUserShare,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -700,7 +787,7 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
zap.String("session_id", resp.SessionID),
|
||||
zap.Int("num_parties", len(resp.SelectedParties)))
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
response := gin.H{
|
||||
"session_id": resp.SessionID,
|
||||
"session_type": "sign",
|
||||
"account_id": req.AccountID,
|
||||
|
|
@ -708,7 +795,17 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
"threshold_t": accountOutput.Account.ThresholdT,
|
||||
"selected_parties": resp.SelectedParties,
|
||||
"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
|
||||
|
|
@ -734,17 +831,38 @@ func (h *AccountHTTPHandler) GetSessionStatus(c *gin.Context) {
|
|||
response := gin.H{
|
||||
"session_id": sessionID,
|
||||
"status": resp.Status,
|
||||
"session_type": resp.SessionType, // "keygen" or "sign"
|
||||
"completed_parties": resp.CompletedParties,
|
||||
"total_parties": resp.TotalParties,
|
||||
}
|
||||
|
||||
// Keygen-specific fields
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sign-specific fields
|
||||
if resp.SessionType == "sign" {
|
||||
if len(resp.Signature) > 0 {
|
||||
response["signature"] = hex.EncodeToString(resp.Signature)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
|
@ -857,3 +975,256 @@ func (h *AccountHTTPHandler) CreateAccountFromKeygen(c *gin.Context) {
|
|||
"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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,14 +121,23 @@ func (c *SessionCoordinatorClient) CreateKeygenSessionAuto(
|
|||
}, 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
|
||||
// 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(
|
||||
ctx context.Context,
|
||||
thresholdT int32,
|
||||
partyIDs []string,
|
||||
messageHash []byte,
|
||||
expiresInSeconds int64,
|
||||
delegateUserShare *DelegateUserShareInput,
|
||||
) (*CreateSessionAutoResponse, error) {
|
||||
// Convert party IDs to participant info (minimal info, coordinator will fill in details)
|
||||
pbParticipants := make([]*coordinatorpb.ParticipantInfo, len(partyIDs))
|
||||
|
|
@ -147,9 +156,22 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
|||
ExpiresInSeconds: expiresInSeconds,
|
||||
}
|
||||
|
||||
// Add delegate user share if provided
|
||||
if delegateUserShare != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
@ -170,6 +192,7 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
|||
return &CreateSessionAutoResponse{
|
||||
SessionID: resp.SessionId,
|
||||
SelectedParties: selectedParties,
|
||||
DelegateParty: resp.DelegatePartyId,
|
||||
JoinTokens: resp.JoinTokens,
|
||||
ExpiresAt: resp.ExpiresAt,
|
||||
}, nil
|
||||
|
|
@ -189,13 +212,26 @@ func (c *SessionCoordinatorClient) GetSessionStatus(
|
|||
return nil, fmt.Errorf("failed to get session status: %w", err)
|
||||
}
|
||||
|
||||
return &SessionStatusResponse{
|
||||
result := &SessionStatusResponse{
|
||||
Status: resp.Status,
|
||||
CompletedParties: resp.CompletedParties,
|
||||
TotalParties: resp.TotalParties,
|
||||
SessionType: resp.SessionType,
|
||||
PublicKey: resp.PublicKey,
|
||||
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
|
||||
|
|
@ -236,6 +272,20 @@ type SessionStatusResponse struct {
|
|||
Status string
|
||||
CompletedParties int32
|
||||
TotalParties int32
|
||||
SessionType string // "keygen" or "sign"
|
||||
PublicKey []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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"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/repositories"
|
||||
"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 {
|
||||
query := `
|
||||
INSERT INTO accounts (id, username, email, phone, public_key, keygen_session_id,
|
||||
threshold_n, threshold_t, status, created_at, updated_at, last_login_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
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, $13)
|
||||
`
|
||||
|
||||
_, err := r.db.ExecContext(ctx, query,
|
||||
|
|
@ -42,6 +43,7 @@ func (r *AccountPostgresRepo) Create(ctx context.Context, account *entities.Acco
|
|||
account.CreatedAt,
|
||||
account.UpdatedAt,
|
||||
account.LastLoginAt,
|
||||
pq.Array(account.SigningParties),
|
||||
)
|
||||
|
||||
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) {
|
||||
query := `
|
||||
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
|
||||
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) {
|
||||
query := `
|
||||
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
|
||||
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) {
|
||||
query := `
|
||||
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
|
||||
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) {
|
||||
query := `
|
||||
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
|
||||
WHERE public_key = $1
|
||||
`
|
||||
|
|
@ -100,7 +102,8 @@ func (r *AccountPostgresRepo) Update(ctx context.Context, account *entities.Acco
|
|||
query := `
|
||||
UPDATE accounts
|
||||
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
|
||||
`
|
||||
|
||||
|
|
@ -116,6 +119,7 @@ func (r *AccountPostgresRepo) Update(ctx context.Context, account *entities.Acco
|
|||
account.Status.String(),
|
||||
account.UpdatedAt,
|
||||
account.LastLoginAt,
|
||||
pq.Array(account.SigningParties),
|
||||
)
|
||||
|
||||
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) {
|
||||
query := `
|
||||
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
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
|
|
@ -222,6 +226,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
|
|||
thresholdN int
|
||||
thresholdT int
|
||||
status string
|
||||
signingParties pq.StringArray
|
||||
account entities.Account
|
||||
)
|
||||
|
||||
|
|
@ -238,6 +243,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
|
|||
&account.CreatedAt,
|
||||
&account.UpdatedAt,
|
||||
&account.LastLoginAt,
|
||||
&signingParties,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -260,6 +266,7 @@ func (r *AccountPostgresRepo) scanAccount(row *sql.Row) (*entities.Account, erro
|
|||
account.ThresholdN = thresholdN
|
||||
account.ThresholdT = thresholdT
|
||||
account.Status = value_objects.AccountStatus(status)
|
||||
account.SigningParties = signingParties
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
|
@ -276,6 +283,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
|
|||
thresholdN int
|
||||
thresholdT int
|
||||
status string
|
||||
signingParties pq.StringArray
|
||||
account entities.Account
|
||||
)
|
||||
|
||||
|
|
@ -292,6 +300,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
|
|||
&account.CreatedAt,
|
||||
&account.UpdatedAt,
|
||||
&account.LastLoginAt,
|
||||
&signingParties,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -311,6 +320,7 @@ func (r *AccountPostgresRepo) scanAccountFromRows(rows *sql.Rows) (*entities.Acc
|
|||
account.ThresholdN = thresholdN
|
||||
account.ThresholdT = thresholdT
|
||||
account.Status = value_objects.AccountStatus(status)
|
||||
account.SigningParties = signingParties
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ type CompleteRecoveryPort interface {
|
|||
type UpdateAccountInput struct {
|
||||
AccountID value_objects.AccountID
|
||||
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
|
||||
|
|
|
|||
|
|
@ -165,6 +165,15 @@ func (uc *UpdateAccountUseCase) Execute(ctx context.Context, input ports.UpdateA
|
|||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ type Account struct {
|
|||
CreatedAt time.Time
|
||||
UpdatedAt 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
|
||||
|
|
@ -135,6 +139,52 @@ func (a *Account) Validate() error {
|
|||
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
|
||||
var (
|
||||
ErrInvalidUsername = &AccountError{Code: "INVALID_USERNAME", Message: "username is required"}
|
||||
|
|
@ -147,6 +197,9 @@ var (
|
|||
ErrAccountNotFound = &AccountError{Code: "ACCOUNT_NOT_FOUND", Message: "account not found"}
|
||||
ErrDuplicateUsername = &AccountError{Code: "DUPLICATE_USERNAME", Message: "username 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
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ func (s *AccountShare) IsRecoveryShare() bool {
|
|||
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
|
||||
func (s *AccountShare) Validate() error {
|
||||
if s.AccountID.IsZero() {
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@ func (s AccountStatus) CanInitiateRecovery() bool {
|
|||
type ShareType string
|
||||
|
||||
const (
|
||||
ShareTypeUserDevice ShareType = "user_device"
|
||||
ShareTypeServer ShareType = "server"
|
||||
ShareTypeRecovery ShareType = "recovery"
|
||||
ShareTypeUserDevice ShareType = "user_device" // Share stored on user's device
|
||||
ShareTypeServer ShareType = "server" // Share stored by persistent server party (in DB)
|
||||
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
|
||||
|
|
@ -52,13 +53,18 @@ func (st ShareType) String() string {
|
|||
// IsValid checks if the share type is valid
|
||||
func (st ShareType) IsValid() bool {
|
||||
switch st {
|
||||
case ShareTypeUserDevice, ShareTypeServer, ShareTypeRecovery:
|
||||
case ShareTypeUserDevice, ShareTypeServer, ShareTypeRecovery, ShareTypeDelegate:
|
||||
return true
|
||||
default:
|
||||
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
|
||||
type RecoveryType string
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
coordinator "github.com/rwadurian/mpc-system/api/grpc/coordinator/v1"
|
||||
pb "github.com/rwadurian/mpc-system/api/grpc/router/v1"
|
||||
"github.com/rwadurian/mpc-system/pkg/logger"
|
||||
"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/repositories"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
|
@ -30,6 +32,7 @@ type MessageRouterServer struct {
|
|||
partyRegistry *domain.PartyRegistry
|
||||
eventBroadcaster *domain.SessionEventBroadcaster
|
||||
messageRepo repositories.MessageRepository
|
||||
coordinatorConn *grpc.ClientConn // Connection to Session Coordinator for proxying
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s *MessageRouterServer) RouteMessage(
|
||||
ctx context.Context,
|
||||
|
|
@ -452,3 +462,218 @@ func toGRPCError(err error) 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/lib/pq"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
pb "github.com/rwadurian/mpc-system/api/grpc/router/v1"
|
||||
|
|
@ -77,6 +78,26 @@ func main() {
|
|||
routeMessageUC := use_cases.NewRouteMessageUseCase(messageRepo, messageBroker)
|
||||
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
|
||||
go runMessageCleanup(messageRepo)
|
||||
|
||||
|
|
@ -92,7 +113,7 @@ func main() {
|
|||
|
||||
// Start gRPC server
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
|
@ -189,6 +210,7 @@ func startGRPCServer(
|
|||
partyRegistry *domain.PartyRegistry,
|
||||
eventBroadcaster *domain.SessionEventBroadcaster,
|
||||
messageRepo *postgres.MessagePostgresRepo,
|
||||
coordinatorConn *grpc.ClientConn,
|
||||
) error {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.Server.GRPCPort))
|
||||
if err != nil {
|
||||
|
|
@ -206,6 +228,13 @@ func startGRPCServer(
|
|||
eventBroadcaster,
|
||||
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)
|
||||
|
||||
// Enable reflection for debugging
|
||||
|
|
|
|||
|
|
@ -14,18 +14,15 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"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/crypto"
|
||||
"github.com/rwadurian/mpc-system/pkg/logger"
|
||||
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/infrastructure/cache"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Global share cache for delegate parties
|
||||
var globalShareCache *cache.ShareCache
|
||||
|
||||
func main() {
|
||||
// Parse flags
|
||||
configPath := flag.String("config", "", "Path to config file")
|
||||
|
|
@ -48,14 +45,10 @@ func main() {
|
|||
}
|
||||
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.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
|
||||
masterKeyHex := os.Getenv("MPC_CRYPTO_MASTER_KEY")
|
||||
if masterKeyHex == "" {
|
||||
|
|
@ -70,29 +63,14 @@ func main() {
|
|||
logger.Fatal("Failed to create crypto service", zap.Error(err))
|
||||
}
|
||||
|
||||
// Get API key for authentication
|
||||
apiKey := os.Getenv("MPC_API_KEY")
|
||||
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"
|
||||
}
|
||||
// Get Message Router address from environment
|
||||
// Delegate party (like all parties) ONLY connects to Message Router
|
||||
routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR")
|
||||
if routerAddr == "" {
|
||||
routerAddr = "message-router:50051"
|
||||
}
|
||||
|
||||
// Initialize gRPC clients
|
||||
sessionClient, err := grpcclient.NewSessionCoordinatorClient(coordinatorAddr)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to session coordinator", zap.Error(err))
|
||||
}
|
||||
defer sessionClient.Close()
|
||||
|
||||
// Initialize Message Router client (the only gRPC connection needed)
|
||||
messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr)
|
||||
if err != nil {
|
||||
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)
|
||||
partyID := os.Getenv("PARTY_ID")
|
||||
if partyID == "" {
|
||||
partyID = "server-party-api"
|
||||
partyID = "delegate-party"
|
||||
}
|
||||
|
||||
// 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
|
||||
logger.Info("Registering party with Message Router",
|
||||
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.Info("Party registered successfully",
|
||||
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)
|
||||
// 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(
|
||||
nil, // No database storage for delegate
|
||||
sessionClient,
|
||||
messageRouter,
|
||||
messageRouter,
|
||||
cryptoService,
|
||||
)
|
||||
participateSigningUC := use_cases.NewParticipateSigningUseCase(
|
||||
nil, // No database storage for delegate
|
||||
sessionClient,
|
||||
messageRouter,
|
||||
messageRouter,
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
|
@ -165,258 +173,162 @@ func main() {
|
|||
|
||||
time.Sleep(5 * time.Second)
|
||||
logger.Info("Shutdown complete")
|
||||
|
||||
_ = ctx
|
||||
}
|
||||
|
||||
func startHTTPServer(
|
||||
cfg *config.Config,
|
||||
participateKeygenUC *use_cases.ParticipateKeygenUseCase,
|
||||
participateSigningUC *use_cases.ParticipateSigningUseCase,
|
||||
cryptoService *crypto.CryptoService,
|
||||
apiKey string,
|
||||
) error {
|
||||
// startHTTPServer starts HTTP server for health checks only
|
||||
func startHTTPServer(cfg *config.Config) error {
|
||||
if cfg.Server.Environment == "production" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
router := gin.New()
|
||||
router.Use(gin.Recovery())
|
||||
router.Use(gin.Logger())
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// Health check
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
// Health check only
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "server-party-api",
|
||||
"service": "delegate-party",
|
||||
"role": "delegate",
|
||||
})
|
||||
})
|
||||
|
||||
// API routes with optional authentication
|
||||
api := router.Group("/api/v1")
|
||||
if apiKey != "" {
|
||||
api.Use(apiKeyAuth(apiKey))
|
||||
logger.Info("Starting HTTP server (health check only)", zap.Int("port", cfg.Server.HTTPPort))
|
||||
return r.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort))
|
||||
}
|
||||
|
||||
{
|
||||
// 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"`
|
||||
// createSessionEventHandler creates a handler for session events (party-driven architecture)
|
||||
// Delegate party automatically responds to session creation events by joining keygen or signing sessions
|
||||
// After keygen, it submits the user's share to Session Coordinator (instead of saving to DB)
|
||||
func createSessionEventHandler(
|
||||
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 err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
if !isSelected {
|
||||
logger.Debug("Party not selected for this session",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
return
|
||||
}
|
||||
|
||||
sessionID, err := uuid.Parse(req.SessionID)
|
||||
// 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 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session_id format"})
|
||||
logger.Error("Invalid session ID", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Starting keygen participation (delegate)",
|
||||
zap.String("session_id", req.SessionID),
|
||||
zap.String("party_id", req.PartyID))
|
||||
// Automatically participate based on session type
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Execute keygen synchronously for delegate party
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Minute)
|
||||
defer cancel()
|
||||
// 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: req.PartyID,
|
||||
JoinToken: req.JoinToken,
|
||||
PartyID: partyID,
|
||||
JoinToken: joinToken,
|
||||
}
|
||||
|
||||
output, err := participateKeygenUC.Execute(ctx, input)
|
||||
result, 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,
|
||||
})
|
||||
zap.Error(err),
|
||||
zap.String("session_id", event.SessionId))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Keygen participation completed (delegate)",
|
||||
zap.String("session_id", req.SessionID),
|
||||
zap.String("party_id", req.PartyID),
|
||||
zap.Bool("success", output.Success))
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("public_key", hex.EncodeToString(result.PublicKey)))
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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)))
|
||||
|
||||
// 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
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
} 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: req.PartyID,
|
||||
JoinToken: req.JoinToken,
|
||||
MessageHash: messageHash,
|
||||
UserShareData: shareData, // Pass user's share
|
||||
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
|
||||
}
|
||||
|
||||
output, err := participateSigningUC.Execute(ctx, input)
|
||||
result, 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,
|
||||
})
|
||||
zap.Error(err),
|
||||
zap.String("session_id", event.SessionId))
|
||||
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 {
|
||||
return func(c *gin.Context) {
|
||||
apiKey := c.GetHeader("X-API-Key")
|
||||
if apiKey == "" {
|
||||
apiKey = c.Query("api_key")
|
||||
}
|
||||
if apiKey != expectedKey {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or missing API key"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("signature", hex.EncodeToString(result.Signature)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -599,3 +599,194 @@ func (c *MessageRouterClient) StartHeartbeat(
|
|||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,23 +81,15 @@ func main() {
|
|||
logger.Fatal("Failed to create crypto service", zap.Error(err))
|
||||
}
|
||||
|
||||
// Get gRPC service addresses from environment
|
||||
coordinatorAddr := os.Getenv("SESSION_COORDINATOR_ADDR")
|
||||
if coordinatorAddr == "" {
|
||||
coordinatorAddr = "localhost:9091"
|
||||
}
|
||||
// Get Message Router address from environment
|
||||
// Server-parties ONLY connect to Message Router (not Session Coordinator)
|
||||
// Message Router proxies session operations to Session Coordinator
|
||||
routerAddr := os.Getenv("MESSAGE_ROUTER_ADDR")
|
||||
if routerAddr == "" {
|
||||
routerAddr = "localhost:9092"
|
||||
}
|
||||
|
||||
// Initialize gRPC clients
|
||||
sessionClient, err := grpcclient.NewSessionCoordinatorClient(coordinatorAddr)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to session coordinator", zap.Error(err))
|
||||
}
|
||||
defer sessionClient.Close()
|
||||
|
||||
// Initialize Message Router client (the only gRPC connection needed)
|
||||
messageRouter, err := grpcclient.NewMessageRouterClient(routerAddr)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to message router", zap.Error(err))
|
||||
|
|
@ -107,16 +99,17 @@ func main() {
|
|||
// Initialize repositories
|
||||
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(
|
||||
keyShareRepo,
|
||||
sessionClient,
|
||||
messageRouter, // MessageRouterClient implements SessionCoordinatorClient interface
|
||||
messageRouter,
|
||||
cryptoService,
|
||||
)
|
||||
participateSigningUC := use_cases.NewParticipateSigningUseCase(
|
||||
keyShareRepo,
|
||||
sessionClient,
|
||||
messageRouter, // MessageRouterClient implements SessionCoordinatorClient interface
|
||||
messageRouter,
|
||||
cryptoService,
|
||||
)
|
||||
|
|
@ -192,7 +185,6 @@ func main() {
|
|||
partyID,
|
||||
participateKeygenUC,
|
||||
participateSigningUC,
|
||||
sessionClient,
|
||||
)
|
||||
|
||||
if err := messageRouter.SubscribeSessionEvents(ctx, partyID, eventHandler); err != nil {
|
||||
|
|
@ -535,7 +527,6 @@ func createSessionEventHandler(
|
|||
partyID string,
|
||||
participateKeygenUC *use_cases.ParticipateKeygenUseCase,
|
||||
participateSigningUC *use_cases.ParticipateSigningUseCase,
|
||||
sessionClient *grpcclient.SessionCoordinatorClient,
|
||||
) func(*router.SessionEvent) {
|
||||
return func(event *router.SessionEvent) {
|
||||
// Check if this party is selected for the session
|
||||
|
|
@ -617,6 +608,15 @@ func createSessionEventHandler(
|
|||
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)
|
||||
if err != nil {
|
||||
logger.Error("Signing participation failed",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package grpc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -17,6 +18,79 @@ import (
|
|||
"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
|
||||
type SessionCoordinatorServer struct {
|
||||
pb.UnimplementedSessionCoordinatorServer
|
||||
|
|
@ -86,6 +160,15 @@ func (s *SessionCoordinatorServer) CreateSession(
|
|||
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
|
||||
output, err := s.createSessionUC.Execute(ctx, inputData)
|
||||
if err != nil {
|
||||
|
|
@ -194,13 +277,34 @@ func (s *SessionCoordinatorServer) GetSessionStatus(
|
|||
}
|
||||
}
|
||||
|
||||
return &pb.GetSessionStatusResponse{
|
||||
resp := &pb.GetSessionStatusResponse{
|
||||
Status: output.Status,
|
||||
CompletedParties: int32(completedParties),
|
||||
TotalParties: int32(len(output.Participants)),
|
||||
SessionType: output.SessionType,
|
||||
PublicKey: output.PublicKey,
|
||||
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
|
||||
|
|
@ -323,6 +427,40 @@ func (s *SessionCoordinatorServer) StartSession(
|
|||
}, 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
|
||||
func toGRPCError(err error) error {
|
||||
switch err {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (h *SessionHTTPHandler) CreateSession(c *gin.Context) {
|
|||
|
||||
expiresIn := time.Duration(req.ExpiresIn) * time.Second
|
||||
if expiresIn == 0 {
|
||||
expiresIn = 10 * time.Minute // Default
|
||||
expiresIn = 24 * time.Hour // Default: 24-hour session validity
|
||||
}
|
||||
|
||||
inputData := input.CreateSessionInput{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/rwadurian/mpc-system/pkg/grpcutil"
|
||||
"github.com/rwadurian/mpc-system/pkg/logger"
|
||||
"github.com/rwadurian/mpc-system/pkg/retry"
|
||||
"github.com/rwadurian/mpc-system/services/session-coordinator/application/use_cases"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
@ -120,6 +121,7 @@ func (c *MessageRouterClient) PublishSessionCreated(
|
|||
messageHash []byte,
|
||||
createdAt int64,
|
||||
expiresAt int64,
|
||||
delegateUserShare *use_cases.DelegateUserShareInfo,
|
||||
) error {
|
||||
event := &router.SessionEvent{
|
||||
EventId: uuid.New().String(),
|
||||
|
|
@ -134,5 +136,14 @@ func (c *MessageRouterClient) PublishSessionCreated(
|
|||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ type PartyComposition struct {
|
|||
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
|
||||
type CreateSessionInput struct {
|
||||
InitiatorID string
|
||||
|
|
@ -46,6 +53,7 @@ type CreateSessionInput struct {
|
|||
PartyComposition *PartyComposition // Optional: specify party composition by role
|
||||
MessageHash []byte // For sign sessions
|
||||
ExpiresIn time.Duration
|
||||
DelegateUserShare *DelegateUserShare // For sign sessions with delegate party
|
||||
}
|
||||
|
||||
// ParticipantInfo contains information about a participant
|
||||
|
|
@ -98,11 +106,14 @@ type PartyInfo struct {
|
|||
type SessionStatusOutput struct {
|
||||
SessionID uuid.UUID
|
||||
Status string
|
||||
SessionType string // "keygen" or "sign"
|
||||
ThresholdT int
|
||||
ThresholdN int
|
||||
Participants []ParticipantStatus
|
||||
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
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ import (
|
|||
"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
|
||||
type MessageRouterClient interface {
|
||||
PublishSessionCreated(
|
||||
|
|
@ -28,6 +35,7 @@ type MessageRouterClient interface {
|
|||
messageHash []byte,
|
||||
createdAt int64,
|
||||
expiresAt int64,
|
||||
delegateUserShare *DelegateUserShareInfo,
|
||||
) error
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +294,17 @@ func (uc *CreateSessionUseCase) Execute(
|
|||
// Only publish if parties were selected from pool
|
||||
if len(session.GetPartyIDs()) > 0 && uc.messageRouterClient != nil {
|
||||
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(
|
||||
ctx,
|
||||
session.ID.UUID(),
|
||||
|
|
@ -296,6 +315,7 @@ func (uc *CreateSessionUseCase) Execute(
|
|||
session.MessageHash,
|
||||
session.CreatedAt.UnixMilli(),
|
||||
session.ExpiresAt.UnixMilli(),
|
||||
delegateUserShare,
|
||||
)
|
||||
if err != nil {
|
||||
// Log error but don't fail the operation
|
||||
|
|
|
|||
|
|
@ -46,12 +46,17 @@ func (uc *GetSessionStatusUseCase) Execute(
|
|||
}
|
||||
|
||||
// 3. Build response
|
||||
// has_delegate is only meaningful for keygen sessions
|
||||
hasDelegate := session.DelegatePartyID != "" && string(session.SessionType) == "keygen"
|
||||
return &input.SessionStatusOutput{
|
||||
SessionID: session.ID.UUID(),
|
||||
Status: session.Status.String(),
|
||||
SessionType: string(session.SessionType),
|
||||
ThresholdT: session.Threshold.T(),
|
||||
ThresholdN: session.Threshold.N(),
|
||||
Participants: participants,
|
||||
PublicKey: session.PublicKey,
|
||||
HasDelegate: hasDelegate,
|
||||
DelegatePartyID: session.DelegatePartyID,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,15 +84,11 @@ func (s *SessionCoordinatorService) ShouldExpireSession(session *entities.MPCSes
|
|||
}
|
||||
|
||||
// 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 {
|
||||
switch sessionType {
|
||||
case entities.SessionTypeKeygen:
|
||||
return 10 * time.Minute
|
||||
case entities.SessionTypeSign:
|
||||
return 5 * time.Minute
|
||||
default:
|
||||
return 10 * time.Minute
|
||||
}
|
||||
// All session types have a 24-hour validity period
|
||||
return 24 * time.Hour
|
||||
}
|
||||
|
||||
// ValidateMessageRouting validates if a message can be routed
|
||||
|
|
|
|||
Loading…
Reference in New Issue