From 51c0f59924e8c5aef67c415aa64ad153c865d947 Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 31 Dec 2025 12:08:40 -0800 Subject: [PATCH] fix(tss): remove threshold-1 in signing to match keygen exactly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- backend/mpc-system/pkg/tss/signing.go | 9 ++++---- .../service-party-app/tss-party/main.go | 21 ++++++++++--------- backend/mpc-system/services/tss-wasm/main.go | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/backend/mpc-system/pkg/tss/signing.go b/backend/mpc-system/pkg/tss/signing.go index 3dc5eab9..f9d01e40 100644 --- a/backend/mpc-system/pkg/tss/signing.go +++ b/backend/mpc-system/pkg/tss/signing.go @@ -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) diff --git a/backend/mpc-system/services/service-party-app/tss-party/main.go b/backend/mpc-system/services/service-party-app/tss-party/main.go index 18e20bd4..5a2e3a57 100644 --- a/backend/mpc-system/services/service-party-app/tss-party/main.go +++ b/backend/mpc-system/services/service-party-app/tss-party/main.go @@ -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. diff --git a/backend/mpc-system/services/tss-wasm/main.go b/backend/mpc-system/services/tss-wasm/main.go index b7cd1ad1..76919b3e 100644 --- a/backend/mpc-system/services/tss-wasm/main.go +++ b/backend/mpc-system/services/tss-wasm/main.go @@ -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)