feat(co-managed): 支持 2-of-3 服务器参与签名功能
修改内容: 1. participate_signing.go: 添加 ExecuteWithSessionInfo 方法 - 新增方法供 server-party-co-managed 调用 - 跳过 JoinSession 步骤(已在 session_created 阶段完成) - 将核心逻辑提取到 executeWithSessionInfo 共享方法 2. server-party-co-managed/main.go: 完整实现 co-sign 支持 - 初始化 participateSigningUC - session_created: 移除签名会话拒绝逻辑,添加 2-of-3 安全检查 - session_started: 根据 messageHash 判断 keygen/sign 并调用对应 use case 功能特性: - ✅ 仅支持 2-of-3 配置的签名会话 - ✅ 100% 寄生 server-party 的 use_cases(与 co-keygen 架构一致) - ✅ 不影响现有 server-party 功能 - ✅ 完整的两阶段事件处理(session_created + session_started) 安全限制: - 仅当 threshold_t=2 且 threshold_n=3 时参与签名 - 其他配置(3-of-5, 4-of-7等)会被拒绝 测试: - ✅ server-party-co-managed 编译成功 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dbeef9f415
commit
ad4549e767
|
|
@ -149,6 +149,14 @@ func main() {
|
|||
cryptoService,
|
||||
)
|
||||
|
||||
// Initialize signing use case (for co-managed sign sessions)
|
||||
participateSigningUC := use_cases.NewParticipateSigningUseCase(
|
||||
keyShareRepo,
|
||||
messageRouter,
|
||||
messageRouter,
|
||||
cryptoService,
|
||||
)
|
||||
|
||||
// Create shutdown context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
|
@ -186,14 +194,15 @@ func main() {
|
|||
defer heartbeatCancel()
|
||||
logger.Info("Heartbeat started", zap.String("party_id", partyID), zap.Duration("interval", 30*time.Second))
|
||||
|
||||
// Subscribe to session events with two-phase handling for co_managed_keygen
|
||||
logger.Info("Subscribing to session events (co_managed_keygen only)", zap.String("party_id", partyID))
|
||||
// Subscribe to session events with two-phase handling for co_managed_keygen and co_managed_sign
|
||||
logger.Info("Subscribing to session events (co_managed_keygen and co_managed_sign)", zap.String("party_id", partyID))
|
||||
|
||||
eventHandler := createCoManagedSessionEventHandler(
|
||||
ctx,
|
||||
partyID,
|
||||
messageRouter,
|
||||
participateKeygenUC,
|
||||
participateSigningUC,
|
||||
)
|
||||
|
||||
if err := messageRouter.SubscribeSessionEvents(ctx, partyID, eventHandler); err != nil {
|
||||
|
|
@ -306,15 +315,17 @@ func startHTTPServer(cfg *config.Config) error {
|
|||
return r.Run(fmt.Sprintf(":%d", cfg.Server.HTTPPort))
|
||||
}
|
||||
|
||||
// createCoManagedSessionEventHandler creates a handler specifically for co_managed_keygen sessions
|
||||
// createCoManagedSessionEventHandler creates a handler for co_managed_keygen and co_managed_sign sessions
|
||||
// Two-phase event handling:
|
||||
// Phase 1 (session_created): JoinSession immediately + store session info
|
||||
// Phase 2 (session_started): Execute TSS protocol (same timing as user clients receiving all_joined)
|
||||
// Supports both keygen (no message_hash) and sign (with message_hash) sessions
|
||||
func createCoManagedSessionEventHandler(
|
||||
ctx context.Context,
|
||||
partyID string,
|
||||
messageRouter *grpcclient.MessageRouterClient,
|
||||
participateKeygenUC *use_cases.ParticipateKeygenUseCase,
|
||||
participateSigningUC *use_cases.ParticipateSigningUseCase,
|
||||
) func(*router.SessionEvent) {
|
||||
return func(event *router.SessionEvent) {
|
||||
// Check if this party is selected for the session
|
||||
|
|
@ -348,11 +359,26 @@ func createCoManagedSessionEventHandler(
|
|||
// Handle different event types
|
||||
switch event.EventType {
|
||||
case "session_created":
|
||||
// Only handle keygen sessions (no message_hash)
|
||||
// Handle both keygen (no message_hash) and sign (with message_hash) sessions
|
||||
// For sign sessions: only support 2-of-3 configuration
|
||||
if len(event.MessageHash) > 0 {
|
||||
logger.Debug("Ignoring sign session (co-managed only handles keygen)",
|
||||
zap.String("session_id", event.SessionId))
|
||||
return
|
||||
// This is a sign session
|
||||
// Security check: only support 2-of-3 configuration
|
||||
if event.ThresholdT != 2 || event.ThresholdN != 3 {
|
||||
logger.Warn("Ignoring sign session: only 2-of-3 configuration is supported",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.Int32("threshold_t", event.ThresholdT),
|
||||
zap.Int32("threshold_n", event.ThresholdN))
|
||||
return
|
||||
}
|
||||
logger.Info("Sign session detected (2-of-3), proceeding with participation",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
} else {
|
||||
// This is a keygen session
|
||||
logger.Info("Keygen session detected, proceeding with participation",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
}
|
||||
|
||||
// Phase 1: Get join token
|
||||
|
|
@ -401,21 +427,26 @@ func createCoManagedSessionEventHandler(
|
|||
return
|
||||
}
|
||||
|
||||
logger.Info("Session started event received, beginning TSS keygen protocol",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
// Determine session type based on message_hash
|
||||
isSignSession := len(pendingSession.MessageHash) > 0
|
||||
|
||||
// Execute TSS keygen protocol in goroutine
|
||||
if isSignSession {
|
||||
logger.Info("Session started event received, beginning TSS signing protocol",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
} else {
|
||||
logger.Info("Session started event received, beginning TSS keygen protocol",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
}
|
||||
|
||||
// Execute TSS protocol in goroutine
|
||||
// Timeout starts NOW (when session_started is received), not at session_created
|
||||
go func() {
|
||||
// 10 minute timeout for TSS protocol execution
|
||||
participateCtx, cancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
logger.Info("Auto-participating in co_managed_keygen session",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
|
||||
// Build SessionInfo from session_started event (NOT from pendingSession cache)
|
||||
// session_started event contains ALL participants who have joined,
|
||||
// including external parties that joined dynamically after session_created
|
||||
|
|
@ -429,29 +460,67 @@ func createCoManagedSessionEventHandler(
|
|||
}
|
||||
}
|
||||
|
||||
sessionInfo := &use_cases.SessionInfo{
|
||||
SessionID: pendingSession.SessionID,
|
||||
SessionType: "co_managed_keygen",
|
||||
ThresholdN: int(event.ThresholdN),
|
||||
ThresholdT: int(event.ThresholdT),
|
||||
MessageHash: pendingSession.MessageHash,
|
||||
Participants: participants,
|
||||
}
|
||||
|
||||
result, err := participateKeygenUC.ExecuteWithSessionInfo(
|
||||
participateCtx,
|
||||
pendingSession.SessionID,
|
||||
partyID,
|
||||
sessionInfo,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Co-managed keygen participation failed",
|
||||
zap.Error(err),
|
||||
zap.String("session_id", event.SessionId))
|
||||
} else {
|
||||
logger.Info("Co-managed keygen participation completed",
|
||||
if isSignSession {
|
||||
// Execute signing protocol
|
||||
logger.Info("Auto-participating in co_managed_sign session",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("public_key", hex.EncodeToString(result.PublicKey)))
|
||||
zap.String("party_id", partyID))
|
||||
|
||||
sessionInfo := &use_cases.SessionInfo{
|
||||
SessionID: pendingSession.SessionID,
|
||||
SessionType: "co_managed_sign",
|
||||
ThresholdN: int(event.ThresholdN),
|
||||
ThresholdT: int(event.ThresholdT),
|
||||
MessageHash: pendingSession.MessageHash,
|
||||
KeygenSessionID: uuid.Nil, // Use nil to trigger fallback logic (load most recent share)
|
||||
Participants: participants,
|
||||
}
|
||||
|
||||
result, err := participateSigningUC.ExecuteWithSessionInfo(
|
||||
participateCtx,
|
||||
pendingSession.SessionID,
|
||||
partyID,
|
||||
sessionInfo,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Co-managed signing participation failed",
|
||||
zap.Error(err),
|
||||
zap.String("session_id", event.SessionId))
|
||||
} else {
|
||||
logger.Info("Co-managed signing participation completed",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("signature", hex.EncodeToString(result.Signature)))
|
||||
}
|
||||
} else {
|
||||
// Execute keygen protocol
|
||||
logger.Info("Auto-participating in co_managed_keygen session",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("party_id", partyID))
|
||||
|
||||
sessionInfo := &use_cases.SessionInfo{
|
||||
SessionID: pendingSession.SessionID,
|
||||
SessionType: "co_managed_keygen",
|
||||
ThresholdN: int(event.ThresholdN),
|
||||
ThresholdT: int(event.ThresholdT),
|
||||
MessageHash: pendingSession.MessageHash,
|
||||
Participants: participants,
|
||||
}
|
||||
|
||||
result, err := participateKeygenUC.ExecuteWithSessionInfo(
|
||||
participateCtx,
|
||||
pendingSession.SessionID,
|
||||
partyID,
|
||||
sessionInfo,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Co-managed keygen participation failed",
|
||||
zap.Error(err),
|
||||
zap.String("session_id", event.SessionId))
|
||||
} else {
|
||||
logger.Info("Co-managed keygen participation completed",
|
||||
zap.String("session_id", event.SessionId),
|
||||
zap.String("public_key", hex.EncodeToString(result.PublicKey)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,30 @@ func NewParticipateSigningUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
// ExecuteWithSessionInfo participates in a signing session with pre-obtained SessionInfo
|
||||
// This is used by server-party-co-managed which has already called JoinSession in session_created phase
|
||||
// and receives session_started event when all participants have joined
|
||||
func (uc *ParticipateSigningUseCase) ExecuteWithSessionInfo(
|
||||
ctx context.Context,
|
||||
sessionID uuid.UUID,
|
||||
partyID string,
|
||||
sessionInfo *SessionInfo,
|
||||
) (*ParticipateSigningOutput, error) {
|
||||
// Validate session type
|
||||
if sessionInfo.SessionType != "sign" && sessionInfo.SessionType != "co_managed_sign" {
|
||||
return nil, ErrInvalidSignSession
|
||||
}
|
||||
|
||||
logger.Info("ExecuteWithSessionInfo: starting signing with pre-obtained session info",
|
||||
zap.String("session_id", sessionID.String()),
|
||||
zap.String("party_id", partyID),
|
||||
zap.String("session_type", sessionInfo.SessionType),
|
||||
zap.Int("participants", len(sessionInfo.Participants)))
|
||||
|
||||
// Delegate to the common execution logic (skipping JoinSession)
|
||||
return uc.executeWithSessionInfo(ctx, sessionID, partyID, sessionInfo)
|
||||
}
|
||||
|
||||
// Execute participates in a signing session using real TSS protocol
|
||||
func (uc *ParticipateSigningUseCase) Execute(
|
||||
ctx context.Context,
|
||||
|
|
@ -78,80 +102,80 @@ func (uc *ParticipateSigningUseCase) Execute(
|
|||
return nil, ErrInvalidSignSession
|
||||
}
|
||||
|
||||
// 2. Get share data - either from user input (delegate) or from database (persistent)
|
||||
// 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)
|
||||
var shareData []byte
|
||||
var keyShareForUpdate *entities.PartyKeyShare
|
||||
var originalThresholdN int // Original total parties from keygen
|
||||
var err error
|
||||
|
||||
if len(input.UserShareData) > 0 {
|
||||
// Delegate party: use share provided by user
|
||||
shareData, err = uc.cryptoService.DecryptShare(input.UserShareData, input.PartyID)
|
||||
// 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 err != nil {
|
||||
return nil, err
|
||||
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
|
||||
}
|
||||
// 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()))
|
||||
logger.Info("Using specific keyshare by keygen_session_id",
|
||||
zap.String("party_id", partyID),
|
||||
zap.String("keygen_session_id", sessionInfo.KeygenSessionID.String()))
|
||||
} else {
|
||||
// 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()))
|
||||
// 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
|
||||
}
|
||||
|
||||
// Get original threshold_n from keygen
|
||||
originalThresholdN = keyShareForUpdate.ThresholdN
|
||||
|
||||
// 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))
|
||||
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
|
||||
|
||||
// Decrypt share data
|
||||
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))
|
||||
|
||||
// 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 == input.PartyID {
|
||||
if p.PartyID == partyID {
|
||||
selfIndex = p.PartyIndex
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Subscribe to messages
|
||||
msgChan, err := uc.messageRouter.SubscribeMessages(ctx, input.SessionID, input.PartyID)
|
||||
msgChan, err := uc.messageRouter.SubscribeMessages(ctx, sessionID, partyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -161,22 +185,19 @@ func (uc *ParticipateSigningUseCase) Execute(
|
|||
// before others have subscribed to the session
|
||||
expectedParties := len(sessionInfo.Participants)
|
||||
logger.Info("Waiting for all parties to subscribe",
|
||||
zap.String("session_id", input.SessionID.String()),
|
||||
zap.String("party_id", input.PartyID),
|
||||
zap.String("session_id", sessionID.String()),
|
||||
zap.String("party_id", partyID),
|
||||
zap.Int("expected_parties", expectedParties))
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Use message hash from session if not provided
|
||||
messageHash := input.MessageHash
|
||||
if len(messageHash) == 0 {
|
||||
messageHash = sessionInfo.MessageHash
|
||||
}
|
||||
// Use message hash from session
|
||||
messageHash := sessionInfo.MessageHash
|
||||
|
||||
// 6. Run TSS Signing protocol
|
||||
signature, r, s, err := uc.runSigningProtocol(
|
||||
ctx,
|
||||
input.SessionID,
|
||||
input.PartyID,
|
||||
sessionID,
|
||||
partyID,
|
||||
selfIndex,
|
||||
sessionInfo.Participants,
|
||||
sessionInfo.ThresholdT,
|
||||
|
|
@ -199,7 +220,7 @@ func (uc *ParticipateSigningUseCase) Execute(
|
|||
}
|
||||
|
||||
// 8. Report completion to coordinator
|
||||
if err := uc.sessionClient.ReportCompletion(ctx, input.SessionID, input.PartyID, signature); err != nil {
|
||||
if err := uc.sessionClient.ReportCompletion(ctx, sessionID, partyID, signature); err != nil {
|
||||
logger.Error("failed to report signing completion", zap.Error(err))
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue