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
type SigningConfig struct {
Threshold int // t in t-of-n (number of signers required)
TotalSigners int // Number of parties participating in this signing
Threshold int // t in t-of-n threshold value from keygen
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
}
@ -112,8 +113,10 @@ func NewSigningSession(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// 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)
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
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)
var shareData []byte
var keyShareForUpdate *entities.PartyKeyShare
var originalThresholdN int // Original total parties from keygen
if len(input.UserShareData) > 0 {
// Delegate party: use share provided by user
@ -88,6 +89,8 @@ func (uc *ParticipateSigningUseCase) Execute(
if err != nil {
return nil, err
}
// For delegate party, get threshold info from session
originalThresholdN = sessionInfo.ThresholdN
logger.Info("Using user-provided share (delegate party)",
zap.String("party_id", input.PartyID),
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)
keyShareForUpdate = keyShares[len(keyShares)-1]
// Get original threshold_n from keygen
originalThresholdN = keyShareForUpdate.ThresholdN
// Decrypt share data
shareData, err = uc.cryptoService.DecryptShare(keyShareForUpdate.ShareData, input.PartyID)
if err != nil {
@ -108,7 +114,9 @@ func (uc *ParticipateSigningUseCase) Execute(
}
logger.Info("Using database share (persistent party)",
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
@ -141,6 +149,7 @@ func (uc *ParticipateSigningUseCase) Execute(
selfIndex,
sessionInfo.Participants,
sessionInfo.ThresholdT,
originalThresholdN,
shareData,
messageHash,
msgChan,
@ -179,6 +188,7 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
selfIndex int,
participants []ParticipantInfo,
t int,
n int, // Original total parties from keygen
shareData []byte,
messageHash []byte,
msgChan <-chan *MPCMessage,
@ -189,6 +199,8 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
zap.String("party_id", partyID),
zap.Int("self_index", selfIndex),
zap.Int("t", t),
zap.Int("n", n),
zap.Int("current_signers", len(participants)),
zap.Int("message_hash_len", len(messageHash)))
// Create message handler adapter
@ -204,8 +216,11 @@ func (uc *ParticipateSigningUseCase) runSigningProtocol(
go msgHandler.convertMessages(ctx, msgChan)
// 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{
Threshold: t,
TotalParties: n, // Original total from keygen
TotalSigners: len(participants),
Timeout: 5 * time.Minute,
}