fix(co-sign): use keygen session threshold_n for TSS signing
- Query keygen session from mpc_sessions table to get correct threshold_n - Pass keygenThresholdN to CreateSigningSessionAuto instead of len(parties) - Return parties list and correct threshold values in GetSignSessionByInviteCode - This fixes TSS signing failure "U doesn't equal T" caused by mismatched n values 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e284a46e83
commit
042212eae6
|
|
@ -833,9 +833,11 @@ func (h *AccountHTTPHandler) CreateSigningSession(c *gin.Context) {
|
|||
zap.String("keygen_session_id", accountOutput.Account.KeygenSessionID.String()))
|
||||
}
|
||||
|
||||
// CRITICAL: Pass keygenThresholdN (original n from keygen) for correct TSS math
|
||||
resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
|
||||
ctx,
|
||||
int32(accountOutput.Account.ThresholdT),
|
||||
int32(accountOutput.Account.ThresholdN),
|
||||
signingParties,
|
||||
messageHash,
|
||||
600, // 10 minutes expiry
|
||||
|
|
|
|||
|
|
@ -507,6 +507,32 @@ func (h *CoManagedHTTPHandler) CreateSignSession(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Call session coordinator via gRPC
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// CRITICAL: Query keygen session to get the original threshold_n
|
||||
// This is required for TSS signing to work correctly - the n value must match keygen
|
||||
var keygenThresholdN, keygenThresholdT int
|
||||
if h.db != nil {
|
||||
err = h.db.QueryRowContext(ctx, `
|
||||
SELECT threshold_n, threshold_t
|
||||
FROM mpc_sessions
|
||||
WHERE id = $1
|
||||
`, req.KeygenSessionID).Scan(&keygenThresholdN, &keygenThresholdT)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query keygen session for threshold values",
|
||||
zap.String("keygen_session_id", req.KeygenSessionID),
|
||||
zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup keygen session"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.Error("Database connection not available for keygen session lookup")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "service configuration error"})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate invite code for sign session
|
||||
inviteCode := generateInviteCode()
|
||||
|
||||
|
|
@ -519,22 +545,22 @@ func (h *CoManagedHTTPHandler) CreateSignSession(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// Call session coordinator via gRPC
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
logger.Info("Creating co-managed sign session",
|
||||
zap.String("keygen_session_id", req.KeygenSessionID),
|
||||
zap.String("wallet_name", req.WalletName),
|
||||
zap.Int("threshold_t", req.ThresholdT),
|
||||
zap.Int("num_parties", len(req.Parties)),
|
||||
zap.Int("keygen_threshold_n", keygenThresholdN),
|
||||
zap.Int("keygen_threshold_t", keygenThresholdT),
|
||||
zap.Int("signing_threshold_t", req.ThresholdT),
|
||||
zap.Int("num_signing_parties", len(req.Parties)),
|
||||
zap.String("invite_code", inviteCode))
|
||||
|
||||
// Create signing session
|
||||
// Note: delegateUserShare is nil for co-managed wallets (no delegate party)
|
||||
// CRITICAL: Pass keygenThresholdN (original n from keygen) for correct TSS math
|
||||
resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
|
||||
ctx,
|
||||
int32(req.ThresholdT),
|
||||
int32(keygenThresholdN),
|
||||
parties,
|
||||
messageHash,
|
||||
86400, // 24 hour expiry
|
||||
|
|
@ -682,19 +708,19 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
|||
var sessionID string
|
||||
var walletName string
|
||||
var keygenSessionID string
|
||||
var thresholdN, thresholdT int
|
||||
var status string
|
||||
var expiresAt time.Time
|
||||
var messageHash []byte
|
||||
|
||||
// Query sign session basic info
|
||||
err := h.db.QueryRowContext(ctx, `
|
||||
SELECT id, COALESCE(wallet_name, ''), COALESCE(keygen_session_id::text, ''),
|
||||
threshold_n, threshold_t, status, expires_at, COALESCE(message_hash, '')
|
||||
status, expires_at, COALESCE(message_hash, '')
|
||||
FROM mpc_sessions
|
||||
WHERE invite_code = $1 AND session_type = 'sign' AND status != 'failed'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`, inviteCode).Scan(&sessionID, &walletName, &keygenSessionID, &thresholdN, &thresholdT, &status, &expiresAt, &messageHash)
|
||||
`, inviteCode).Scan(&sessionID, &walletName, &keygenSessionID, &status, &expiresAt, &messageHash)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
@ -719,6 +745,60 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Get threshold_n and threshold_t from the KEYGEN session (the authoritative source)
|
||||
// This is critical for TSS signing to work correctly
|
||||
var keygenThresholdN, keygenThresholdT int
|
||||
if keygenSessionID != "" {
|
||||
err = h.db.QueryRowContext(ctx, `
|
||||
SELECT threshold_n, threshold_t
|
||||
FROM mpc_sessions
|
||||
WHERE id = $1
|
||||
`, keygenSessionID).Scan(&keygenThresholdN, &keygenThresholdT)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query keygen session for threshold values",
|
||||
zap.String("keygen_session_id", keygenSessionID),
|
||||
zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to lookup keygen session"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.Error("Sign session has no keygen_session_id",
|
||||
zap.String("session_id", sessionID))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "sign session missing keygen reference"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get the signing parties list from the sign session's participants table
|
||||
// These are the parties that were selected for this signing session
|
||||
var parties []gin.H
|
||||
rows, err := h.db.QueryContext(ctx, `
|
||||
SELECT party_id, party_index
|
||||
FROM participants
|
||||
WHERE session_id = $1
|
||||
ORDER BY party_index
|
||||
`, sessionID)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query sign session participants",
|
||||
zap.String("session_id", sessionID),
|
||||
zap.Error(err))
|
||||
// Continue without parties list, frontend will fallback
|
||||
} else {
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var partyID string
|
||||
var partyIndex int
|
||||
if err := rows.Scan(&partyID, &partyIndex); err != nil {
|
||||
logger.Warn("Failed to scan participant row",
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
parties = append(parties, gin.H{
|
||||
"party_id": partyID,
|
||||
"party_index": partyIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get session status from coordinator
|
||||
statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
|
||||
if err != nil {
|
||||
|
|
@ -731,11 +811,12 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
|||
"keygen_session_id": keygenSessionID,
|
||||
"wallet_name": walletName,
|
||||
"message_hash": hex.EncodeToString(messageHash),
|
||||
"threshold_n": thresholdN,
|
||||
"threshold_t": thresholdT,
|
||||
"threshold_n": keygenThresholdN,
|
||||
"threshold_t": keygenThresholdT,
|
||||
"status": status,
|
||||
"joined_count": 0,
|
||||
"expires_at": expiresAt.UnixMilli(),
|
||||
"parties": parties,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
@ -760,6 +841,10 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
|||
zap.String("invite_code", inviteCode),
|
||||
zap.String("session_id", sessionID),
|
||||
zap.String("wallet_name", walletName),
|
||||
zap.String("keygen_session_id", keygenSessionID),
|
||||
zap.Int("keygen_threshold_n", keygenThresholdN),
|
||||
zap.Int("keygen_threshold_t", keygenThresholdT),
|
||||
zap.Int("parties_count", len(parties)),
|
||||
zap.Bool("has_join_token", joinToken != ""))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
|
|
@ -767,11 +852,12 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
|||
"keygen_session_id": keygenSessionID,
|
||||
"wallet_name": walletName,
|
||||
"message_hash": hex.EncodeToString(messageHash),
|
||||
"threshold_n": thresholdN,
|
||||
"threshold_t": thresholdT,
|
||||
"threshold_n": keygenThresholdN,
|
||||
"threshold_t": keygenThresholdT,
|
||||
"status": statusResp.Status,
|
||||
"joined_count": statusResp.CompletedParties,
|
||||
"expires_at": expiresAt.UnixMilli(),
|
||||
"join_token": joinToken,
|
||||
"parties": parties,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,9 +137,11 @@ type SigningPartyInfo struct {
|
|||
// CreateSigningSessionAuto creates a new signing session with automatic party selection
|
||||
// Coordinator will select parties from the provided party info (from account shares)
|
||||
// delegateUserShare is required if any of the parties is a delegate party
|
||||
// keygenThresholdN is the original threshold_n from the keygen session (required for TSS math)
|
||||
func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
||||
ctx context.Context,
|
||||
thresholdT int32,
|
||||
keygenThresholdN int32,
|
||||
parties []SigningPartyInfo,
|
||||
messageHash []byte,
|
||||
expiresInSeconds int64,
|
||||
|
|
@ -155,9 +157,11 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
|||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Use keygenThresholdN (original n from keygen), NOT len(parties)
|
||||
// TSS signing requires the same n value used during keygen for correct mathematical operations
|
||||
req := &coordinatorpb.CreateSessionRequest{
|
||||
SessionType: "sign",
|
||||
ThresholdN: int32(len(parties)),
|
||||
ThresholdN: keygenThresholdN,
|
||||
ThresholdT: thresholdT,
|
||||
Participants: pbParticipants,
|
||||
MessageHash: messageHash,
|
||||
|
|
@ -174,12 +178,14 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
|||
}
|
||||
logger.Info("Sending CreateSigningSession gRPC request with delegate user share",
|
||||
zap.Int32("threshold_t", thresholdT),
|
||||
zap.Int("num_parties", len(parties)),
|
||||
zap.Int32("keygen_threshold_n", keygenThresholdN),
|
||||
zap.Int("num_signing_parties", len(parties)),
|
||||
zap.String("delegate_party_id", delegateUserShare.DelegatePartyID))
|
||||
} else {
|
||||
logger.Info("Sending CreateSigningSession gRPC request",
|
||||
zap.Int32("threshold_t", thresholdT),
|
||||
zap.Int("num_parties", len(parties)))
|
||||
zap.Int32("keygen_threshold_n", keygenThresholdN),
|
||||
zap.Int("num_signing_parties", len(parties)))
|
||||
}
|
||||
|
||||
resp, err := c.client.CreateSession(ctx, req)
|
||||
|
|
|
|||
Loading…
Reference in New Issue