fix(tss): convert threshold to tss-lib format (threshold-1) in all keygen and signing

TSS-lib convention: threshold=t means (t+1) signers required.
User expectation: "2-of-3" means 2 signers needed.

Before this fix:
- Keygen used thresholdT directly (e.g., 2)
- TSS-lib interpreted as needing 3 signers (2+1)
- 2-of-3 wallet was actually 3-of-3!

After this fix:
- Both keygen and signing use (thresholdT-1)
- For 2-of-3: tss-lib threshold=1, needs 1+1=2 signers ✓

Files changed:
- tss-party/main.go: keygen and signing both use thresholdT-1
- tss-wasm/main.go: keygen and signing both use thresholdT-1
- pkg/tss/keygen.go: uses config.Threshold-1
- pkg/tss/signing.go: uses config.Threshold-1

BREAKING CHANGE: Existing wallets created before this fix used wrong
threshold and need to be regenerated. New wallets will work correctly.

🤖 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:19:58 -08:00
parent 51c0f59924
commit b5512d421c
4 changed files with 33 additions and 21 deletions

View File

@ -117,8 +117,11 @@ func NewKeygenSession(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// User says "2-of-3" meaning 2 signers needed, so we pass (Threshold-1) to TSS-lib
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), config.Threshold)
tssThreshold := config.Threshold - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
return &KeygenSession{
config: config,

View File

@ -132,14 +132,15 @@ func NewSigningSession(
keygenIndexToSortedIndex, selfParty.PartyID)
// Create peer context and parameters
// CRITICAL: threshold must match keygen exactly!
// Use len(sortedPartyIDs) as partyCount - this is the number of CURRENT signers
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// This MUST match keygen exactly! Both use (Threshold-1)
// 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)
tssThreshold := config.Threshold - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
fmt.Printf("[TSS-SIGN] NewParameters: partyCount=%d, threshold=%d party_id=%s\n",
len(sortedPartyIDs), config.Threshold, selfParty.PartyID)
fmt.Printf("[TSS-SIGN] NewParameters: partyCount=%d, tssThreshold=%d (from config.Threshold=%d) party_id=%s\n",
len(sortedPartyIDs), tssThreshold, config.Threshold, selfParty.PartyID)
// Convert message hash to big.Int
msgHash := new(big.Int).SetBytes(messageHash)

View File

@ -241,13 +241,14 @@ 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.
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// User says "2-of-3" meaning 2 signers needed, so we pass (thresholdT-1) to TSS-lib
// For 2-of-3: thresholdT=2, tss-lib threshold=1, signers_needed=1+1=2 ✓
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)
tssThreshold := thresholdT - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
fmt.Fprintf(os.Stderr, "[TSS-KEYGEN] NewParameters: partyCount=%d, tssThreshold=%d (from thresholdT=%d, means %d signers needed)\n",
len(sortedPartyIDs), tssThreshold, thresholdT, thresholdT)
// Create channels
outCh := make(chan tss.Message, thresholdN*10)
@ -600,13 +601,15 @@ func executeSign(
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// 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.
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// User says "2-of-3" meaning 2 signers needed, so we pass (thresholdT-1) to TSS-lib
// This MUST match keygen exactly!
// For 2-of-3: thresholdT=2, tss-lib threshold=1, signers_needed=1+1=2 ✓
peerCtx := tss.NewPeerContext(sortedPartyIDs)
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)
tssThreshold := thresholdT - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
fmt.Fprintf(os.Stderr, "[TSS-SIGN] NewParameters: partyCount=%d, tssThreshold=%d (from thresholdT=%d, means %d signers needed)\n",
len(sortedPartyIDs), tssThreshold, thresholdT, 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

@ -160,8 +160,11 @@ func startKeygen(this js.Value, args []js.Value) interface{} {
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// User says "2-of-3" meaning 2 signers needed, so we pass (thresholdT-1) to TSS-lib
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT)
tssThreshold := thresholdT - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
// Build party index map
session.PartyIndexMap = make(map[int]*tss.PartyID)
@ -281,9 +284,11 @@ func startSigning(this js.Value, args []js.Value) interface{} {
sortedPartyIDs := tss.SortPartyIDs(tssPartyIDs)
// Create peer context and parameters
// CRITICAL: threshold must match keygen exactly!
// IMPORTANT: TSS-lib threshold convention: threshold=t means (t+1) signers required
// This MUST match keygen exactly! Both use (thresholdT-1)
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT)
tssThreshold := thresholdT - 1
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), tssThreshold)
// Build party index map
session.PartyIndexMap = make(map[int]*tss.PartyID)