package http import ( "context" "encoding/hex" "net/http" "time" "github.com/gin-gonic/gin" grpcadapter "github.com/rwadurian/mpc-system/services/account/adapters/output/grpc" ) // MPCHandler handles MPC-related HTTP requests type MPCHandler struct { sessionCoordinatorClient *grpcadapter.SessionCoordinatorClient } // NewMPCHandler creates a new MPCHandler func NewMPCHandler(sessionCoordinatorClient *grpcadapter.SessionCoordinatorClient) *MPCHandler { return &MPCHandler{ sessionCoordinatorClient: sessionCoordinatorClient, } } // RegisterRoutes registers MPC routes func (h *MPCHandler) RegisterRoutes(router *gin.RouterGroup) { mpc := router.Group("/mpc") { mpc.POST("/keygen", h.CreateKeygenSession) mpc.POST("/sign", h.CreateSigningSession) mpc.GET("/sessions/:id", h.GetSessionStatus) } } // CreateKeygenSessionRequest represents a keygen session creation request type CreateKeygenSessionRequest struct { ThresholdN int json:"threshold_n" binding:"required,min=2,max=10" ThresholdT int json:"threshold_t" binding:"required,min=1" Participants []ParticipantRequest json:"participants" binding:"required,min=2" } // ParticipantRequest represents a participant in a request type ParticipantRequest struct { PartyID string json:"party_id" binding:"required" DeviceType string json:"device_type" binding:"required" DeviceID string json:"device_id,omitempty" } // CreateKeygenSession handles creating a new keygen session func (h *MPCHandler) CreateKeygenSession(c *gin.Context) { var req CreateKeygenSessionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate threshold if req.ThresholdT > req.ThresholdN { c.JSON(http.StatusBadRequest, gin.H{"error": "threshold_t cannot be greater than threshold_n"}) return } if len(req.Participants) != req.ThresholdN { c.JSON(http.StatusBadRequest, gin.H{"error": "number of participants must equal threshold_n"}) return } // Convert participants participants := make([]grpcadapter.ParticipantInfo, len(req.Participants)) for i, p := range req.Participants { participants[i] = grpcadapter.ParticipantInfo{ PartyID: p.PartyID, DeviceType: p.DeviceType, DeviceID: p.DeviceID, } } // Call gRPC service ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() resp, err := h.sessionCoordinatorClient.CreateKeygenSession( ctx, int32(req.ThresholdN), int32(req.ThresholdT), participants, 600, // 10 minutes expiry ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "session_id": resp.SessionID, "session_type": "keygen", "threshold_n": req.ThresholdN, "threshold_t": req.ThresholdT, "join_tokens": resp.JoinTokens, "status": "created", }) } // CreateSigningSessionRequest represents a signing session creation request type CreateSigningSessionRequest struct { AccountID string json:"account_id" binding:"required" MessageHash string json:"message_hash" binding:"required" Participants []ParticipantRequest json:"participants" binding:"required,min=2" } // CreateSigningSession handles creating a new signing session func (h *MPCHandler) CreateSigningSession(c *gin.Context) { var req CreateSigningSessionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Decode message hash messageHash, err := hex.DecodeString(req.MessageHash) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid message hash format"}) return } // Convert participants participants := make([]grpcadapter.ParticipantInfo, len(req.Participants)) for i, p := range req.Participants { participants[i] = grpcadapter.ParticipantInfo{ PartyID: p.PartyID, DeviceType: p.DeviceType, DeviceID: p.DeviceID, } } // Determine threshold (should come from account configuration) // For now, use len(participants) as threshold thresholdT := int32(len(req.Participants)) // Call gRPC service ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() resp, err := h.sessionCoordinatorClient.CreateSigningSession( ctx, thresholdT, participants, messageHash, 600, // 10 minutes expiry ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "session_id": resp.SessionID, "session_type": "sign", "account_id": req.AccountID, "message_hash": req.MessageHash, "threshold_t": thresholdT, "join_tokens": resp.JoinTokens, "status": "created", }) } // GetSessionStatus handles querying session status func (h *MPCHandler) GetSessionStatus(c *gin.Context) { sessionID := c.Param("id") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } response := gin.H{ "session_id": sessionID, "status": resp.Status, "completed_parties": resp.CompletedParties, "total_parties": resp.TotalParties, } if len(resp.PublicKey) > 0 { response["public_key"] = hex.EncodeToString(resp.PublicKey) } if len(resp.Signature) > 0 { response["signature"] = hex.EncodeToString(resp.Signature) } c.JSON(http.StatusOK, response) }