fix(tss): correct threshold signing to support t-of-n properly

Previously, signing incorrectly required all n parties from keygen to participate. For 2-of-3 threshold, it required all 3 parties instead of just 2.

Root cause: tss.NewParameters was using len(currentSigners) instead of the original n from keygen.

Changes:
- Added TotalParties field to SigningConfig to store original n from keygen
- Modified participate_signing.go to read threshold_n from database
- Updated tss.NewParameters to use TotalParties instead of current signer count
- Added logging to show t, n, and current_signers

For 2-of-3: threshold_t=2, threshold_n=3, any 2 parties can now sign.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-06 07:16:24 -08:00
parent 6fdd2905b1
commit eb63b9341b
2 changed files with 22 additions and 4 deletions

View File

@ -43,8 +43,9 @@ type SigningParty struct {
// SigningConfig contains configuration for signing // SigningConfig contains configuration for signing
type SigningConfig struct { type SigningConfig struct {
Threshold int // t in t-of-n (number of signers required) Threshold int // t in t-of-n threshold value from keygen
TotalSigners int // Number of parties participating in this signing TotalParties int // n in t-of-n - total parties from keygen (NOT current signers)
TotalSigners int // Number of parties participating in this signing session
Timeout time.Duration // Signing timeout Timeout time.Duration // Signing timeout
} }
@ -112,8 +113,10 @@ func NewSigningSession(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs) sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters // Create peer context and parameters
// IMPORTANT: Use TotalParties from keygen, not len(sortedPartyIDs) which is current signers
// For 2-of-3: threshold=2, TotalParties=3, but only 2 parties might participate in signing
peerCtx := tss.NewPeerContext(sortedPartyIDs) peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), config.Threshold) params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, config.TotalParties, config.Threshold)
// Convert message hash to big.Int // Convert message hash to big.Int
msgHash := new(big.Int).SetBytes(messageHash) msgHash := new(big.Int).SetBytes(messageHash)

View File

@ -81,6 +81,7 @@ func (uc *ParticipateSigningUseCase) Execute(
// 2. Get share data - either from user input (delegate) or from database (persistent) // 2. Get share data - either from user input (delegate) or from database (persistent)
var shareData []byte var shareData []byte
var keyShareForUpdate *entities.PartyKeyShare var keyShareForUpdate *entities.PartyKeyShare
var originalThresholdN int // Original total parties from keygen
if len(input.UserShareData) > 0 { if len(input.UserShareData) > 0 {
// Delegate party: use share provided by user // Delegate party: use share provided by user
@ -88,6 +89,8 @@ func (uc *ParticipateSigningUseCase) Execute(
if err != nil { if err != nil {
return nil, err return nil, err
} }
// For delegate party, get threshold info from session
originalThresholdN = sessionInfo.ThresholdN
logger.Info("Using user-provided share (delegate party)", logger.Info("Using user-provided share (delegate party)",
zap.String("party_id", input.PartyID), zap.String("party_id", input.PartyID),
zap.String("session_id", input.SessionID.String())) zap.String("session_id", input.SessionID.String()))
@ -101,6 +104,9 @@ func (uc *ParticipateSigningUseCase) Execute(
// Use the most recent key share (in production, would match by public key or session reference) // Use the most recent key share (in production, would match by public key or session reference)
keyShareForUpdate = keyShares[len(keyShares)-1] keyShareForUpdate = keyShares[len(keyShares)-1]
// Get original threshold_n from keygen
originalThresholdN = keyShareForUpdate.ThresholdN
// Decrypt share data // Decrypt share data
shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, input.PartyID) shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, input.PartyID)
if err != nil { if err != nil {
@ -108,7 +114,9 @@ func (uc *ParticipateSigningUseCase) Execute(
} }
logger.Info("Using database share (persistent party)", logger.Info("Using database share (persistent party)",
zap.String("party_id", input.PartyID), zap.String("party_id", input.PartyID),
zap.String("session_id", input.SessionID.String())) zap.String("session_id", input.SessionID.String()),
zap.Int("original_threshold_n", originalThresholdN),
zap.Int("threshold_t", keyShareForUpdate.ThresholdT))
} }
// 4. Find self in participants and build party index map // 4. Find self in participants and build party index map
@ -141,6 +149,7 @@ func (uc *ParticipateSigningUseCase) Execute(
selfIndex, selfIndex,
sessionInfo.Participants, sessionInfo.Participants,
sessionInfo.ThresholdT, sessionInfo.ThresholdT,
originalThresholdN,
shareData, shareData,
messageHash, messageHash,
msgChan, msgChan,
@ -179,6 +188,7 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
selfIndex int, selfIndex int,
participants []ParticipantInfo, participants []ParticipantInfo,
t int, t int,
n int, // Original total parties from keygen
shareData []byte, shareData []byte,
messageHash []byte, messageHash []byte,
msgChan <-chan *MPCMessage, msgChan <-chan *MPCMessage,
@ -189,6 +199,8 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
zap.String("party_id", partyID), zap.String("party_id", partyID),
zap.Int("self_index", selfIndex), zap.Int("self_index", selfIndex),
zap.Int("t", t), zap.Int("t", t),
zap.Int("n", n),
zap.Int("current_signers", len(participants)),
zap.Int("message_hash_len", len(messageHash))) zap.Int("message_hash_len", len(messageHash)))
// Create message handler adapter // Create message handler adapter
@ -204,8 +216,11 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
go msgHandler.convertMessages(ctx, msgChan) go msgHandler.convertMessages(ctx, msgChan)
// Create signing config // Create signing config
// IMPORTANT: TotalParties must be the original n from keygen, not current signers
// For 2-of-3: t=2, n=3, but only 2 parties participate in signing
config := tss.SigningConfig{ config := tss.SigningConfig{
Threshold: t, Threshold: t,
TotalParties: n, // Original total from keygen
TotalSigners: len(participants), TotalSigners: len(participants),
Timeout: 5 * time.Minute, Timeout: 5 * time.Minute,
} }