package use_cases import ( "context" "errors" "math/big" "time" "github.com/google/uuid" "github.com/rwadurian/mpc-system/pkg/crypto" "github.com/rwadurian/mpc-system/pkg/logger" "github.com/rwadurian/mpc-system/services/server-party/domain/repositories" "go.uber.org/zap" ) var ( ErrSigningFailed = errors.New("signing failed") ErrSigningTimeout = errors.New("signing timeout") ErrKeyShareNotFound = errors.New("key share not found") ErrInvalidSignSession = errors.New("invalid sign session") ) // ParticipateSigningInput contains input for signing participation type ParticipateSigningInput struct { SessionID uuid.UUID PartyID string JoinToken string MessageHash []byte } // ParticipateSigningOutput contains output from signing participation type ParticipateSigningOutput struct { Success bool Signature []byte R *big.Int S *big.Int } // ParticipateSigningUseCase handles signing participation type ParticipateSigningUseCase struct { keyShareRepo repositories.KeyShareRepository sessionClient SessionCoordinatorClient messageRouter MessageRouterClient cryptoService *crypto.CryptoService } // NewParticipateSigningUseCase creates a new participate signing use case func NewParticipateSigningUseCase( keyShareRepo repositories.KeyShareRepository, sessionClient SessionCoordinatorClient, messageRouter MessageRouterClient, cryptoService *crypto.CryptoService, ) *ParticipateSigningUseCase { return &ParticipateSigningUseCase{ keyShareRepo: keyShareRepo, sessionClient: sessionClient, messageRouter: messageRouter, cryptoService: cryptoService, } } // Execute participates in a signing session func (uc *ParticipateSigningUseCase) Execute( ctx context.Context, input ParticipateSigningInput, ) (*ParticipateSigningOutput, error) { // 1. Join session via coordinator sessionInfo, err := uc.sessionClient.JoinSession(ctx, input.SessionID, input.PartyID, input.JoinToken) if err != nil { return nil, err } if sessionInfo.SessionType != "sign" { return nil, ErrInvalidSignSession } // 2. Load key share for this party // In a real implementation, we'd need to identify which keygen session this signing session relates to keyShares, err := uc.keyShareRepo.ListByParty(ctx, input.PartyID) if err != nil || len(keyShares) == 0 { return nil, ErrKeyShareNotFound } // Use the most recent key share (in production, would match by public key or session reference) keyShare := keyShares[len(keyShares)-1] // 3. Decrypt share data shareData, err := uc.cryptoService.DecryptShare(keyShare.ShareData, input.PartyID) if err != nil { return nil, err } // 4. Find self in participants var selfIndex int for _, p := range sessionInfo.Participants { if p.PartyID == input.PartyID { selfIndex = p.PartyIndex break } } // 5. Subscribe to messages msgChan, err := uc.messageRouter.SubscribeMessages(ctx, input.SessionID, input.PartyID) if err != nil { return nil, err } // 6. Run TSS Signing protocol signature, r, s, err := uc.runSigningProtocol( ctx, input.SessionID, input.PartyID, selfIndex, sessionInfo.Participants, sessionInfo.ThresholdT, shareData, input.MessageHash, msgChan, ) if err != nil { return nil, err } // 7. Update key share last used keyShare.MarkUsed() if err := uc.keyShareRepo.Update(ctx, keyShare); err != nil { logger.Warn("failed to update key share last used", zap.Error(err)) } // 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 } // runSigningProtocol runs the TSS signing protocol // This is a placeholder implementation func (uc *ParticipateSigningUseCase) runSigningProtocol( ctx context.Context, sessionID uuid.UUID, partyID string, selfIndex int, participants []ParticipantInfo, t int, shareData []byte, messageHash []byte, msgChan <-chan *MPCMessage, ) ([]byte, *big.Int, *big.Int, error) { /* Real implementation would: 1. Deserialize LocalPartySaveData from shareData 2. Create tss.PartyID list 3. Create tss.Parameters 4. Create signing.LocalParty with message hash 5. Handle outgoing messages via messageRouter 6. Handle incoming messages from msgChan 7. Wait for signing completion 8. Return signature (R, S) Example with tss-lib: var saveData keygen.LocalPartySaveData saveData.UnmarshalBinary(shareData) parties := make([]*tss.PartyID, len(participants)) for i, p := range participants { parties[i] = tss.NewPartyID(p.PartyID, p.PartyID, big.NewInt(int64(p.PartyIndex))) } selfPartyID := parties[selfIndex] tssCtx := tss.NewPeerContext(parties) params := tss.NewParameters(tss.S256(), tssCtx, selfPartyID, len(participants), t) outCh := make(chan tss.Message, len(participants)*10) endCh := make(chan *common.SignatureData, 1) msgHash := new(big.Int).SetBytes(messageHash) party := signing.NewLocalParty(msgHash, params, saveData, outCh, endCh) go handleOutgoingMessages(ctx, sessionID, partyID, outCh) go handleIncomingMessages(ctx, party, msgChan) party.Start() select { case signData := <-endCh: signature := append(signData.R, signData.S...) return signature, signData.R, signData.S, nil case <-time.After(5*time.Minute): return nil, nil, nil, ErrSigningTimeout } */ // Placeholder: Generate mock signature for demonstration logger.Info("Running signing protocol (placeholder)", zap.String("session_id", sessionID.String()), zap.String("party_id", partyID), zap.Int("self_index", selfIndex), zap.Int("t", t), zap.Int("message_hash_len", len(messageHash))) // Simulate signing delay select { case <-ctx.Done(): return nil, nil, nil, ctx.Err() case <-time.After(1 * time.Second): } // Generate placeholder signature (R || S, each 32 bytes) r := new(big.Int).SetBytes(messageHash[:16]) s := new(big.Int).SetBytes(messageHash[16:]) signature := make([]byte, 64) rBytes := r.Bytes() sBytes := s.Bytes() // Pad to 32 bytes each copy(signature[32-len(rBytes):32], rBytes) copy(signature[64-len(sBytes):64], sBytes) return signature, r, s, nil }