fix(tss): use BuildLocalSaveDataSubset for threshold signing with party subsets

When signing with fewer parties than keygen (e.g., 2-of-3 signing with only 2 parties),
the TSS-lib requires filtered save data containing only the participating parties.

Without this fix, signing fails with "U doesn't equal T" error because:
- Keygen creates save data for all N parties (e.g., 3 parties with indices 0, 1, 2)
- Sign uses only T parties (e.g., 2 parties with indices 1, 2)
- TSS-lib internal index validation fails due to mismatch

Changes:
- pkg/tss/signing.go: Use len(sortedPartyIDs) for partyCount and call BuildLocalSaveDataSubset
- tss-party/main.go: Add BuildLocalSaveDataSubset call for Electron app
- tss-wasm/main.go: Add BuildLocalSaveDataSubset call for WASM builds

This fix is backward compatible - when all parties participate, the subset equals the original data.

🤖 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 11:25:22 -08:00
parent 24ff1409d0
commit 7ab28dced0
3 changed files with 31 additions and 9 deletions

View File

@ -132,10 +132,13 @@ func NewSigningSession(
keygenIndexToSortedIndex, selfParty.PartyID)
// 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
// 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)
// The BuildLocalSaveDataSubset call in Start() will filter the save data to match
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, config.TotalParties, config.Threshold)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), config.Threshold)
// Convert message hash to big.Int
msgHash := new(big.Int).SetBytes(messageHash)
@ -167,8 +170,17 @@ func (s *SigningSession) Start(ctx context.Context) (*SigningResult, error) {
s.started = true
s.mu.Unlock()
// Create local party for signing
s.localParty = signing.NewLocalParty(s.messageHash, s.params, *s.saveData, s.outCh, s.endCh)
// CRITICAL: Build a subset of the save data for the current signing parties
// When signing with fewer parties than keygen (e.g., 2-of-3 signing with only 2 parties),
// we must filter the save data to only include the participating parties' data.
// This ensures TSS-lib's internal indices match the actual signers.
subsetSaveData := keygen.BuildLocalSaveDataSubset(*s.saveData, s.tssPartyIDs)
fmt.Printf("[TSS-SIGN] Built save data subset for %d signing parties (original keygen had %d parties) party_id=%s\n",
len(s.tssPartyIDs), len(s.saveData.Ks), s.selfParty.PartyID)
// Create local party for signing with the SUBSET save data
s.localParty = signing.NewLocalParty(s.messageHash, s.params, subsetSaveData, s.outCh, s.endCh)
// Start the local party
go func() {

View File

@ -607,13 +607,19 @@ func executeSign(
peerCtx := tss.NewPeerContext(sortedPartyIDs)
params := tss.NewParameters(tss.S256(), peerCtx, selfTSSID, len(sortedPartyIDs), thresholdT-1)
// 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.
// BuildLocalSaveDataSubset filters the Ks, BigXj, NTildej, H1j, H2j, and PaillierPKs
// arrays to only include data for the participating signers.
subsetKeygenData := keygen.BuildLocalSaveDataSubset(keygenData, sortedPartyIDs)
// Create channels
outCh := make(chan tss.Message, thresholdT*10)
endCh := make(chan *common.SignatureData, 1)
errCh := make(chan error, 1)
// Create local party for signing
localParty := signing.NewLocalParty(msgBigInt, params, keygenData, outCh, endCh)
// Create local party for signing with the SUBSET keygen data
localParty := signing.NewLocalParty(msgBigInt, params, subsetKeygenData, outCh, endCh)
// Build party index map for incoming messages
partyIndexMap := make(map[int]*tss.PartyID)

View File

@ -298,8 +298,12 @@ func startSigning(this js.Value, args []js.Value) interface{} {
// Create message hash as big.Int
msgHashBig := new(big.Int).SetBytes(messageHash)
// Create local signing party
session.LocalParty = signing.NewLocalParty(msgHashBig, params, saveData, session.OutCh, session.EndChSign)
// 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.
subsetSaveData := keygen.BuildLocalSaveDataSubset(saveData, sortedPartyIDs)
// Create local signing party with the SUBSET save data
session.LocalParty = signing.NewLocalParty(msgHashBig, params, subsetSaveData, session.OutCh, session.EndChSign)
// Store session
sessionMutex.Lock()