rwadurian/backend/mpc-system/services/account/adapters/input/http/mpc_handler.go

199 lines
5.5 KiB
Go

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