diff --git a/backend/mpc-system/services/session-coordinator/application/use_cases/create_session.go b/backend/mpc-system/services/session-coordinator/application/use_cases/create_session.go index cadb8bf4..b11d5c7f 100644 --- a/backend/mpc-system/services/session-coordinator/application/use_cases/create_session.go +++ b/backend/mpc-system/services/session-coordinator/application/use_cases/create_session.go @@ -160,8 +160,14 @@ func (uc *CreateSessionUseCase) Execute( // Check if party composition is specified if req.PartyComposition != nil { - // Select parties based on composition requirements - selectedParties, err = uc.selectPartiesByComposition(req.PartyComposition) + // For co_managed_keygen, TemporaryCount represents external participants + // who will join dynamically via invite code - don't select from pool + if sessionType == entities.SessionTypeCoManagedKeygen { + selectedParties, err = uc.selectPartiesByCompositionForCoManaged(req.PartyComposition) + } else { + // Select parties based on composition requirements + selectedParties, err = uc.selectPartiesByComposition(req.PartyComposition) + } if err != nil { logger.Warn("failed to select parties by composition, falling back to simple selection", zap.Error(err)) @@ -359,6 +365,62 @@ func (uc *CreateSessionUseCase) Execute( }, nil } +// selectPartiesByCompositionForCoManaged selects parties for co_managed_keygen sessions +// For co_managed_keygen, TemporaryCount represents external participants who will join +// dynamically via invite code - we don't select them from pool, only select persistent parties +func (uc *CreateSessionUseCase) selectPartiesByCompositionForCoManaged(composition *input.PartyComposition) ([]output.PartyEndpoint, error) { + if uc.partyPool == nil { + return nil, fmt.Errorf("party pool not configured") + } + + var allSelected []output.PartyEndpoint + + // Select persistent parties (server-side parties) + if composition.PersistentCount > 0 { + persistent, err := uc.partyPool.SelectPartiesWithFilter(output.PartySelectionFilter{ + Count: composition.PersistentCount, + Role: output.PartyRolePersistent, + }) + if err != nil { + return nil, fmt.Errorf("failed to select persistent parties: %w", err) + } + allSelected = append(allSelected, persistent...) + } + + // Select delegate parties if any + if composition.DelegateCount > 0 { + delegate, err := uc.partyPool.SelectPartiesWithFilter(output.PartySelectionFilter{ + Count: composition.DelegateCount, + Role: output.PartyRoleDelegate, + }) + if err != nil { + return nil, fmt.Errorf("failed to select delegate parties: %w", err) + } + allSelected = append(allSelected, delegate...) + } + + // NOTE: TemporaryCount is intentionally NOT selected from pool for co_managed_keygen + // External participants will join dynamically via invite code with wildcard token + if composition.TemporaryCount > 0 { + logger.Info("co_managed_keygen: TemporaryCount represents external participants, not selecting from pool", + zap.Int("temporary_count", composition.TemporaryCount), + zap.Int("persistent_selected", len(allSelected))) + } + + // Apply custom filters if provided + for _, filter := range composition.CustomFilters { + customParties, err := uc.partyPool.SelectPartiesWithFilter(filter) + if err != nil { + return nil, fmt.Errorf("failed to select parties with custom filter: %w", err) + } + allSelected = append(allSelected, customParties...) + } + + // For co_managed_keygen, it's OK to have no selected parties if all are external + // The session will use wildcard tokens for all participants + return allSelected, nil +} + // selectPartiesByComposition selects parties based on composition requirements func (uc *CreateSessionUseCase) selectPartiesByComposition(composition *input.PartyComposition) ([]output.PartyEndpoint, error) { if uc.partyPool == nil { diff --git a/backend/mpc-system/services/session-coordinator/application/use_cases/join_session.go b/backend/mpc-system/services/session-coordinator/application/use_cases/join_session.go index f32cd21c..2de4e416 100644 --- a/backend/mpc-system/services/session-coordinator/application/use_cases/join_session.go +++ b/backend/mpc-system/services/session-coordinator/application/use_cases/join_session.go @@ -81,14 +81,55 @@ func (uc *JoinSessionUseCase) Execute( } // 5. Get participant (must already exist from CreateSession) + // For co_managed_keygen with wildcard token, allow dynamic participant addition participant, err := session.GetParticipant(partyID) if err != nil { - // Participant doesn't exist - this party was not invited to this session - logger.Warn("party not found in session participants", - zap.String("session_id", session.ID.String()), - zap.String("party_id", inputData.PartyID), - zap.Int("existing_participant_count", len(session.Participants))) - return nil, entities.ErrPartyNotInvited + // Check if this is a co_managed_keygen session with wildcard token + // Wildcard tokens allow any party to join dynamically + if session.SessionType == entities.SessionTypeCoManagedKeygen && claims.PartyID == "*" { + // Dynamic participant addition for co_managed_keygen + if len(session.Participants) >= session.Threshold.N() { + logger.Warn("session is full, cannot add more participants", + zap.String("session_id", session.ID.String()), + zap.String("party_id", inputData.PartyID), + zap.Int("current_participants", len(session.Participants)), + zap.Int("threshold_n", session.Threshold.N())) + return nil, entities.ErrSessionFull + } + + // Create new participant with the next available index + newIndex := len(session.Participants) + participant, err = entities.NewParticipant(partyID, newIndex, inputData.DeviceInfo) + if err != nil { + logger.Error("failed to create new participant", + zap.String("session_id", session.ID.String()), + zap.String("party_id", inputData.PartyID), + zap.Error(err)) + return nil, err + } + + // Add participant to session + if err := session.AddParticipant(participant); err != nil { + logger.Error("failed to add participant to session", + zap.String("session_id", session.ID.String()), + zap.String("party_id", inputData.PartyID), + zap.Error(err)) + return nil, err + } + + logger.Info("dynamically added participant to co_managed_keygen session", + zap.String("session_id", session.ID.String()), + zap.String("party_id", inputData.PartyID), + zap.Int("party_index", newIndex), + zap.Int("total_participants", len(session.Participants))) + } else { + // Participant doesn't exist - this party was not invited to this session + logger.Warn("party not found in session participants", + zap.String("session_id", session.ID.String()), + zap.String("party_id", inputData.PartyID), + zap.Int("existing_participant_count", len(session.Participants))) + return nil, entities.ErrPartyNotInvited + } } logger.Debug("participant found in session", diff --git a/backend/mpc-system/services/session-coordinator/application/use_cases/report_completion.go b/backend/mpc-system/services/session-coordinator/application/use_cases/report_completion.go index c01f53b3..f845736d 100644 --- a/backend/mpc-system/services/session-coordinator/application/use_cases/report_completion.go +++ b/backend/mpc-system/services/session-coordinator/application/use_cases/report_completion.go @@ -191,8 +191,8 @@ func (uc *ReportCompletionUseCase) executeWithRetry( zap.Error(err)) } - // For keygen sessions, automatically create account record - if session.SessionType == entities.SessionTypeKeygen && uc.accountService != nil { + // For keygen sessions (including co_managed_keygen), automatically create account record + if session.SessionType.IsKeygen() && uc.accountService != nil { uc.createAccountFromKeygen(ctx, session) } }