fix(participate_signing): 恢复 Execute 方法的 UserShareData 分支

关键修复:
- 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-26 19:00:52 -08:00
parent 514722143f
commit f9619b7df1
1 changed files with 178 additions and 58 deletions

View File

@ -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))
}