fix(tss): remove threshold-1 in signing to match keygen exactly

The signing code was using thresholdT-1 while keygen was using thresholdT,
causing Lagrange coefficient mismatch and "U doesn't equal T" error in round 9.

Root cause: commit d0c504dc added -1 to signing threshold to "match user expectation",
but this broke the keygen/sign consistency that TSS-lib requires.

Changes:
- tss-party/main.go: Sign now uses thresholdT (same as keygen)
- pkg/tss/signing.go: Add logging, emphasize threshold must match keygen
- tss-wasm/main.go: Add comment about threshold consistency

NOTE: This fix maintains backward compatibility with existing wallets.
No wallet regeneration is needed.

ROLLBACK: If this causes issues, revert to commit before this one.
Previous signing threshold was thresholdT-1 (commit d0c504dc).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-31 12:08:40 -08:00
parent 4a00c8066a
commit 51c0f59924
3 changed files with 17 additions and 14 deletions

View File

@ -132,14 +132,15 @@ func NewSigningSession(
keygenIndexToSortedIndex, selfParty.PartyID)
// Create peer context and parameters
// IMPORTANT: Use len(sortedPartyIDs) as partyCount - this is the number of CURRENT signers
// For 2-of-3 threshold signing with only 2 parties participating:
// - partyCount = 2 (current signers)
// - threshold = 2 (minimum required from keygen)
// CRITICAL: threshold must match keygen exactly!
// Use len(sortedPartyIDs) as partyCount - this is the number of CURRENT signers
// The BuildLocalSaveDataSubset call in Start() will filter the save data to match
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), config.Threshold)
fmt.Printf("[TSS-SIGN] NewParameters: partyCount=%d, threshold=%d party_id=%s\n",
len(sortedPartyIDs), config.Threshold, selfParty.PartyID)
// Convert message hash to big.Int
msgHash := new(big.Int).SetBytes(messageHash)

View File

@ -241,8 +241,13 @@ func executeKeygen(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// NOTE: We use thresholdT directly (not thresholdT-1) to maintain backward compatibility
// with existing wallets. In our system, thresholdT=2 means "need 2 signers" but TSS-lib
// interprets threshold=2 as "need 3 signers". This is intentional to match existing keygen data.
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT)
fmt.Fprintf(os.Stderr, "[TSS-KEYGEN] NewParameters: partyCount=%d, threshold=%d\n",
len(sortedPartyIDs), thresholdT)
// Create channels
outCh := make(chan tss.Message, thresholdN*10)
@ -595,17 +600,13 @@ func executeSign(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// For signing, the first parameter to NewParameters must be the number of parties
// actually participating in the signing (len(sortedPartyIDs)), NOT the original keygen N.
// The threshold parameter is the minimum signers minus 1 (tss-lib convention: t means t+1 required)
//
// For co-managed signing with 3-of-5:
// - thresholdN = 5 (original keygen parties) - NOT used here
// - thresholdT = 3 (signers needed)
// - len(sortedPartyIDs) = 3 (actual signing participants)
// - threshold param = thresholdT - 1 = 2 (tss-lib needs 2+1=3 signers)
// CRITICAL: threshold must match keygen exactly!
// Keygen uses: NewParameters(..., len(sortedPartyIDs), thresholdT)
// Sign must use the same threshold value for Lagrange coefficients to work correctly.
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT-1)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT)
fmt.Fprintf(os.Stderr, "[TSS-SIGN] NewParameters: partyCount=%d, threshold=%d (must match keygen)\n",
len(sortedPartyIDs), thresholdT)
// CRITICAL: Build a subset of the keygen save data for the current signing parties
// This is required when signing with a subset of the original keygen participants.

View File

@ -281,6 +281,7 @@ func startSigning(this js.Value, args []js.Value) interface{} {
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// CRITICAL: threshold must match keygen exactly!
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT)