From f9619b7df15e208ca7b8d84128db8c3ef73961f2 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 26 Jan 2026 19:00:52 -0800 Subject: [PATCH] =?UTF-8?q?fix(participate=5Fsigning):=20=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=20Execute=20=E6=96=B9=E6=B3=95=E7=9A=84=20UserShareDa?= =?UTF-8?q?ta=20=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 关键修复: - Execute 方法完全恢复原有逻辑(不重构、不委托) - 保留 UserShareData 分支(delegate party - Android 客户端) - 保留 Persistent party 分支(从数据库加载) - executeWithSessionInfo 独立实现(仅供 ExecuteWithSessionInfo 调用) 影响分析: ✅ Android 客户端(delegate party): 现在可以正常签名 ✅ server-party (persistent party): 不受影响 ✅ server-party-co-managed: 使用 ExecuteWithSessionInfo(persistent only) 破坏性变更已修复: - 之前的实现删除了 UserShareData 分支 - 导致 Android 客户端签名会失败(强制从数据库加载不存在的 share) - 现在已完全恢复 架构原则: - Execute: 完整保留原有逻辑(delegate + persistent) - ExecuteWithSessionInfo: 独立方法(仅 persistent - 供 co-managed 使用) Co-Authored-By: Claude Sonnet 4.5 --- .../use_cases/participate_signing.go | 236 +++++++++++++----- 1 file changed, 178 insertions(+), 58 deletions(-) diff --git a/backend/mpc-system/services/server-party/application/use_cases/participate_signing.go b/backend/mpc-system/services/server-party/application/use_cases/participate_signing.go index 2fd1dc68..95b4de0d 100644 --- a/backend/mpc-system/services/server-party/application/use_cases/participate_signing.go +++ b/backend/mpc-system/services/server-party/application/use_cases/participate_signing.go @@ -102,80 +102,80 @@ func (uc *ParticipateSigningUseCase) Execute( return nil, ErrInvalidSignSession } - // Delegate to the common execution logic - return uc.executeWithSessionInfo(ctx, input.SessionID, input.PartyID, sessionInfo) -} - -// executeWithSessionInfo is the common execution logic shared by Execute and ExecuteWithSessionInfo -func (uc *ParticipateSigningUseCase) executeWithSessionInfo( - ctx context.Context, - sessionID uuid.UUID, - partyID string, - sessionInfo *SessionInfo, -) (*ParticipateSigningOutput, error) { - - // 2. Get share data from database (server-party-co-managed always uses persistent shares) + // 2. Get share data - either from user input (delegate) or from database (persistent) var shareData []byte var keyShareForUpdate *entities.PartyKeyShare var originalThresholdN int // Original total parties from keygen - var err error - // Persistent party: load from database - // If KeygenSessionID is provided, use it to load the specific share - // Otherwise, use the most recent share (fallback for backward compatibility) - if sessionInfo.KeygenSessionID != uuid.Nil { - // Load the specific share for this keygen session - keyShareForUpdate, err = uc.keyShareRepo.FindBySessionAndParty(ctx, sessionInfo.KeygenSessionID, partyID) + if len(input.UserShareData) > 0 { + // Delegate party: use share provided by user + shareData, err = uc.cryptoService.DecryptShare(input.UserShareData, input.PartyID) if err != nil { - logger.Error("Failed to find keyshare for keygen session", - zap.String("party_id", partyID), - zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String()), - zap.Error(err)) - return nil, ErrKeyShareNotFound + return nil, err } - logger.Info("Using specific keyshare by keygen_session_id", - zap.String("party_id", partyID), - zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String())) + // For delegate party, get threshold info from session + originalThresholdN = sessionInfo.ThresholdN + logger.Info("Using user-provided share (delegate party)", + zap.String("party_id", input.PartyID), + zap.String("session_id", input.SessionID.String())) } else { - // Fallback: use the most recent key share - // TODO: This should be removed once all signing sessions provide keygen_session_id - keyShares, err := uc.keyShareRepo.ListByParty(ctx, partyID) - if err != nil || len(keyShares) == 0 { - return nil, ErrKeyShareNotFound + // Persistent party: load from database + // If KeygenSessionID is provided, use it to load the specific share + // Otherwise, use the most recent share (fallback for backward compatibility) + if sessionInfo.KeygenSessionID != uuid.Nil { + // Load the specific share for this keygen session + keyShareForUpdate, err = uc.keyShareRepo.FindBySessionAndParty(ctx, sessionInfo.KeygenSessionID, input.PartyID) + if err != nil { + logger.Error("Failed to find keyshare for keygen session", + zap.String("party_id", input.PartyID), + zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String()), + zap.Error(err)) + return nil, ErrKeyShareNotFound + } + logger.Info("Using specific keyshare by keygen_session_id", + zap.String("party_id", input.PartyID), + zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String())) + } else { + // Fallback: use the most recent key share + // TODO: This should be removed once all signing sessions provide keygen_session_id + keyShares, err := uc.keyShareRepo.ListByParty(ctx, input.PartyID) + if err != nil || len(keyShares) == 0 { + return nil, ErrKeyShareNotFound + } + keyShareForUpdate = keyShares[len(keyShares)-1] + logger.Warn("Using most recent keyshare (keygen_session_id not provided)", + zap.String("party_id", input.PartyID), + zap.String("fallback_session_id", keyShareForUpdate.SessionID.String())) } - keyShareForUpdate = keyShares[len(keyShares)-1] - logger.Warn("Using most recent keyshare (keygen_session_id not provided)", - zap.String("party_id", partyID), - zap.String("fallback_session_id", keyShareForUpdate.SessionID.String())) - } - // Get original threshold_n from keygen - originalThresholdN = keyShareForUpdate.ThresholdN + // Get original threshold_n from keygen + originalThresholdN = keyShareForUpdate.ThresholdN - // Decrypt share data - shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, partyID) - if err != nil { - return nil, err + // Decrypt share data + shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, input.PartyID) + if err != nil { + return nil, err + } + logger.Info("Using database share (persistent party)", + zap.String("party_id", input.PartyID), + zap.String("session_id", input.SessionID.String()), + zap.String("keygen_session_id", keyShareForUpdate.SessionID.String()), + zap.Int("original_threshold_n", originalThresholdN), + zap.Int("threshold_t", keyShareForUpdate.ThresholdT)) } - logger.Info("Using database share (persistent party)", - zap.String("party_id", partyID), - zap.String("session_id", sessionID.String()), - zap.String("keygen_session_id", keyShareForUpdate.SessionID.String()), - zap.Int("original_threshold_n", originalThresholdN), - zap.Int("threshold_t", keyShareForUpdate.ThresholdT)) // 4. Find self in participants and build party index map var selfIndex int partyIndexMap := make(map[string]int) for _, p := range sessionInfo.Participants { partyIndexMap[p.PartyID] = p.PartyIndex - if p.PartyID == partyID { + if p.PartyID == input.PartyID { selfIndex = p.PartyIndex } } // 5. Subscribe to messages - msgChan, err := uc.messageRouter.SubscribeMessages(ctx, sessionID, partyID) + msgChan, err := uc.messageRouter.SubscribeMessages(ctx, input.SessionID, input.PartyID) if err != nil { return nil, err } @@ -185,19 +185,22 @@ func (uc *ParticipateSigningUseCase) executeWithSessionInfo( // before others have subscribed to the session expectedParties := len(sessionInfo.Participants) logger.Info("Waiting for all parties to subscribe", - zap.String("session_id", sessionID.String()), - zap.String("party_id", partyID), + zap.String("session_id", input.SessionID.String()), + zap.String("party_id", input.PartyID), zap.Int("expected_parties", expectedParties)) time.Sleep(500 * time.Millisecond) - // Use message hash from session - messageHash := sessionInfo.MessageHash + // Use message hash from session if not provided + messageHash := input.MessageHash + if len(messageHash) == 0 { + messageHash = sessionInfo.MessageHash + } // 6. Run TSS Signing protocol signature, r, s, err := uc.runSigningProtocol( ctx, - sessionID, - partyID, + input.SessionID, + input.PartyID, selfIndex, sessionInfo.Participants, sessionInfo.ThresholdT, @@ -220,6 +223,123 @@ func (uc *ParticipateSigningUseCase) executeWithSessionInfo( } // 8. Report completion to coordinator + if err := uc.sessionClient.ReportCompletion(ctx, input.SessionID, input.PartyID, signature); err != nil { + logger.Error("failed to report signing completion", zap.Error(err)) + } + + return &ParticipateSigningOutput{ + Success: true, + Signature: signature, + R: r, + S: s, + }, nil +} + +// executeWithSessionInfo is the internal logic for ExecuteWithSessionInfo (persistent party only) +func (uc *ParticipateSigningUseCase) executeWithSessionInfo( + ctx context.Context, + sessionID uuid.UUID, + partyID string, + sessionInfo *SessionInfo, +) (*ParticipateSigningOutput, error) { + + // Get share data from database (persistent party only - used by server-party-co-managed) + var shareData []byte + var keyShareForUpdate *entities.PartyKeyShare + var originalThresholdN int + var err error + + // Load from database using KeygenSessionID + if sessionInfo.KeygenSessionID != uuid.Nil { + keyShareForUpdate, err = uc.keyShareRepo.FindBySessionAndParty(ctx, sessionInfo.KeygenSessionID, partyID) + if err != nil { + logger.Error("Failed to find keyshare for keygen session", + zap.String("party_id", partyID), + zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String()), + zap.Error(err)) + return nil, ErrKeyShareNotFound + } + logger.Info("Using specific keyshare by keygen_session_id", + zap.String("party_id", partyID), + zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String())) + } else { + // Fallback: use the most recent key share + keyShares, err := uc.keyShareRepo.ListByParty(ctx, partyID) + if err != nil || len(keyShares) == 0 { + return nil, ErrKeyShareNotFound + } + keyShareForUpdate = keyShares[len(keyShares)-1] + logger.Warn("Using most recent keyshare (keygen_session_id not provided)", + zap.String("party_id", partyID), + zap.String("fallback_session_id", keyShareForUpdate.SessionID.String())) + } + + originalThresholdN = keyShareForUpdate.ThresholdN + shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, partyID) + if err != nil { + return nil, err + } + + logger.Info("Using database share (persistent party)", + zap.String("party_id", partyID), + zap.String("session_id", sessionID.String()), + zap.String("keygen_session_id", keyShareForUpdate.SessionID.String()), + zap.Int("original_threshold_n", originalThresholdN), + zap.Int("threshold_t", keyShareForUpdate.ThresholdT)) + + // Find self in participants and build party index map + var selfIndex int + partyIndexMap := make(map[string]int) + for _, p := range sessionInfo.Participants { + partyIndexMap[p.PartyID] = p.PartyIndex + if p.PartyID == partyID { + selfIndex = p.PartyIndex + } + } + + // Subscribe to messages + msgChan, err := uc.messageRouter.SubscribeMessages(ctx, sessionID, partyID) + if err != nil { + return nil, err + } + + // Wait for all parties to subscribe + expectedParties := len(sessionInfo.Participants) + logger.Info("Waiting for all parties to subscribe", + zap.String("session_id", sessionID.String()), + zap.String("party_id", partyID), + zap.Int("expected_parties", expectedParties)) + time.Sleep(500 * time.Millisecond) + + messageHash := sessionInfo.MessageHash + + // Run TSS Signing protocol + signature, r, s, err := uc.runSigningProtocol( + ctx, + sessionID, + partyID, + selfIndex, + sessionInfo.Participants, + sessionInfo.ThresholdT, + originalThresholdN, + shareData, + messageHash, + msgChan, + partyIndexMap, + ) + if err != nil { + return nil, err + } + + // Update key share last used + if keyShareForUpdate != nil { + keyShareForUpdate.MarkUsed() + if err := uc.keyShareRepo.Update(ctx, keyShareForUpdate); err != nil { + logger.Warn("failed to update key share last used", zap.Error(err)) + } + } + + // Report completion to coordinator if err := uc.sessionClient.ReportCompletion(ctx, sessionID, partyID, signature); err != nil { logger.Error("failed to report signing completion", zap.Error(err)) }