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()))
|
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(
|
resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
|
||||||
ctx,
|
ctx,
|
||||||
int32(accountOutput.Account.ThresholdT),
|
int32(accountOutput.Account.ThresholdT),
|
||||||
|
int32(accountOutput.Account.ThresholdN),
|
||||||
signingParties,
|
signingParties,
|
||||||
messageHash,
|
messageHash,
|
||||||
600, // 10 minutes expiry
|
600, // 10 minutes expiry
|
||||||
|
|
|
||||||
|
|
@ -507,6 +507,32 @@ func (h *CoManagedHTTPHandler) CreateSignSession(c *gin.Context) {
|
||||||
return
|
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
|
// Generate invite code for sign session
|
||||||
inviteCode := generateInviteCode()
|
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",
|
logger.Info("Creating co-managed sign session",
|
||||||
zap.String("keygen_session_id", req.KeygenSessionID),
|
zap.String("keygen_session_id", req.KeygenSessionID),
|
||||||
zap.String("wallet_name", req.WalletName),
|
zap.String("wallet_name", req.WalletName),
|
||||||
zap.Int("threshold_t", req.ThresholdT),
|
zap.Int("keygen_threshold_n", keygenThresholdN),
|
||||||
zap.Int("num_parties", len(req.Parties)),
|
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))
|
zap.String("invite_code", inviteCode))
|
||||||
|
|
||||||
// Create signing session
|
// Create signing session
|
||||||
// Note: delegateUserShare is nil for co-managed wallets (no delegate party)
|
// 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(
|
resp, err := h.sessionCoordinatorClient.CreateSigningSessionAuto(
|
||||||
ctx,
|
ctx,
|
||||||
int32(req.ThresholdT),
|
int32(req.ThresholdT),
|
||||||
|
int32(keygenThresholdN),
|
||||||
parties,
|
parties,
|
||||||
messageHash,
|
messageHash,
|
||||||
86400, // 24 hour expiry
|
86400, // 24 hour expiry
|
||||||
|
|
@ -682,19 +708,19 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
||||||
var sessionID string
|
var sessionID string
|
||||||
var walletName string
|
var walletName string
|
||||||
var keygenSessionID string
|
var keygenSessionID string
|
||||||
var thresholdN, thresholdT int
|
|
||||||
var status string
|
var status string
|
||||||
var expiresAt time.Time
|
var expiresAt time.Time
|
||||||
var messageHash []byte
|
var messageHash []byte
|
||||||
|
|
||||||
|
// Query sign session basic info
|
||||||
err := h.db.QueryRowContext(ctx, `
|
err := h.db.QueryRowContext(ctx, `
|
||||||
SELECT id, COALESCE(wallet_name, ''), COALESCE(keygen_session_id::text, ''),
|
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
|
FROM mpc_sessions
|
||||||
WHERE invite_code = $1 AND session_type = 'sign' AND status != 'failed'
|
WHERE invite_code = $1 AND session_type = 'sign' AND status != 'failed'
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 1
|
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 != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
|
@ -719,6 +745,60 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
||||||
return
|
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
|
// Get session status from coordinator
|
||||||
statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
|
statusResp, err := h.sessionCoordinatorClient.GetSessionStatus(ctx, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -731,11 +811,12 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
||||||
"keygen_session_id": keygenSessionID,
|
"keygen_session_id": keygenSessionID,
|
||||||
"wallet_name": walletName,
|
"wallet_name": walletName,
|
||||||
"message_hash": hex.EncodeToString(messageHash),
|
"message_hash": hex.EncodeToString(messageHash),
|
||||||
"threshold_n": thresholdN,
|
"threshold_n": keygenThresholdN,
|
||||||
"threshold_t": thresholdT,
|
"threshold_t": keygenThresholdT,
|
||||||
"status": status,
|
"status": status,
|
||||||
"joined_count": 0,
|
"joined_count": 0,
|
||||||
"expires_at": expiresAt.UnixMilli(),
|
"expires_at": expiresAt.UnixMilli(),
|
||||||
|
"parties": parties,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -760,6 +841,10 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
||||||
zap.String("invite_code", inviteCode),
|
zap.String("invite_code", inviteCode),
|
||||||
zap.String("session_id", sessionID),
|
zap.String("session_id", sessionID),
|
||||||
zap.String("wallet_name", walletName),
|
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 != ""))
|
zap.Bool("has_join_token", joinToken != ""))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|
@ -767,11 +852,12 @@ func (h *CoManagedHTTPHandler) GetSignSessionByInviteCode(c *gin.Context) {
|
||||||
"keygen_session_id": keygenSessionID,
|
"keygen_session_id": keygenSessionID,
|
||||||
"wallet_name": walletName,
|
"wallet_name": walletName,
|
||||||
"message_hash": hex.EncodeToString(messageHash),
|
"message_hash": hex.EncodeToString(messageHash),
|
||||||
"threshold_n": thresholdN,
|
"threshold_n": keygenThresholdN,
|
||||||
"threshold_t": thresholdT,
|
"threshold_t": keygenThresholdT,
|
||||||
"status": statusResp.Status,
|
"status": statusResp.Status,
|
||||||
"joined_count": statusResp.CompletedParties,
|
"joined_count": statusResp.CompletedParties,
|
||||||
"expires_at": expiresAt.UnixMilli(),
|
"expires_at": expiresAt.UnixMilli(),
|
||||||
"join_token": joinToken,
|
"join_token": joinToken,
|
||||||
|
"parties": parties,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,11 @@ type SigningPartyInfo struct {
|
||||||
// CreateSigningSessionAuto creates a new signing session with automatic party selection
|
// CreateSigningSessionAuto creates a new signing session with automatic party selection
|
||||||
// Coordinator will select parties from the provided party info (from account shares)
|
// Coordinator will select parties from the provided party info (from account shares)
|
||||||
// delegateUserShare is required if any of the parties is a delegate party
|
// 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(
|
func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
thresholdT int32,
|
thresholdT int32,
|
||||||
|
keygenThresholdN int32,
|
||||||
parties []SigningPartyInfo,
|
parties []SigningPartyInfo,
|
||||||
messageHash []byte,
|
messageHash []byte,
|
||||||
expiresInSeconds int64,
|
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{
|
req := &coordinatorpb.CreateSessionRequest{
|
||||||
SessionType: "sign",
|
SessionType: "sign",
|
||||||
ThresholdN: int32(len(parties)),
|
ThresholdN: keygenThresholdN,
|
||||||
ThresholdT: thresholdT,
|
ThresholdT: thresholdT,
|
||||||
Participants: pbParticipants,
|
Participants: pbParticipants,
|
||||||
MessageHash: messageHash,
|
MessageHash: messageHash,
|
||||||
|
|
@ -174,12 +178,14 @@ func (c *SessionCoordinatorClient) CreateSigningSessionAuto(
|
||||||
}
|
}
|
||||||
logger.Info("Sending CreateSigningSession gRPC request with delegate user share",
|
logger.Info("Sending CreateSigningSession gRPC request with delegate user share",
|
||||||
zap.Int32("threshold_t", thresholdT),
|
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))
|
zap.String("delegate_party_id", delegateUserShare.DelegatePartyID))
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Sending CreateSigningSession gRPC request",
|
logger.Info("Sending CreateSigningSession gRPC request",
|
||||||
zap.Int32("threshold_t", thresholdT),
|
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)
|
resp, err := c.client.CreateSession(ctx, req)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue