# How the t-of-n Threshold Signature Scheme Works in Mpcium: A Technical Deep Dive

> Explore the t-of-n threshold signature scheme in Mpcium. Learn how t+1 parties secure ECDSA and EdDSA keys with distributed key generation and dynamic reshare using tss-lib.

- Repository: [Fystack Labs/mpcium](https://github.com/fystack/mpcium)
- Tags: deep-dive
- Published: 2026-03-02

---

**Mpcium implements a t-of-n threshold signature scheme using the tss-lib library, requiring t+1 parties to collaborate on signing while supporting distributed key generation and dynamic resharing for both ECDSA and EdDSA keys.**

Mpcium is an open-source threshold signature service that provides secure, distributed key management for blockchain applications. The repository `fystack/mpcium` delivers a complete implementation of the **t-of-n threshold signature scheme**, wrapping the `tss-lib` cryptographic library to enable fault-tolerant signing where no single entity controls the private key.

## Core Phases of the t-of-n Threshold Signature Scheme

The implementation organizes threshold cryptography into three distinct phases: distributed key generation, collaborative signing, and dynamic resharing. Each phase utilizes specific session handlers in the `pkg/mpc/` directory.

### Distributed Key Generation

During key generation, `n` parties execute a distributed protocol with a configurable threshold `t`. The private key is mathematically split into `n` shares, requiring `t+1` shares for any cryptographic operation. The resulting public key is stored for external verification.

In [`pkg/mpc/ecdsa_keygen_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_keygen_session.go), the `newECDSAKeygenSession` function initializes the protocol:

```go
// From pkg/mpc/ecdsa_keygen_session.go
s.party = keygen.NewLocalParty(params, outCh, endCh, *preParams)

```

The `keygen.NewLocalParty` call originates from the **tss-lib** library and manages the multi-round key generation protocol. Upon completion, each node stores its share locally while the system persists the `KeyInfo` record containing the threshold and participant list.

### Collaborative Signing with t+1 Parties

When signing a transaction, exactly **t+1** parties must participate. The `ecdsaSigningSession` enforces this requirement before invoking the cryptographic protocol.

The signing logic resides in [`pkg/mpc/ecdsa_signing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_signing_session.go):

```go
// Threshold validation before signing
if len(s.participantPeerIDs) < keyInfo.Threshold+1 {
    return ErrNotEnoughParticipants
}
result := lo.Intersect(s.participantPeerIDs, keyInfo.ParticipantPeerIDs)
if len(result) < keyInfo.Threshold+1 {
    return fmt.Errorf("Incompatible peerIDs …")
}

```

With validation passed, the session instantiates the signing party:

```go
s.party = signing.NewLocalParty(tx, params, data, s.outCh, s.endCh)

```

The `signing.NewLocalParty` function from **tss-lib** executes the threshold signing rounds. The resulting signature is verified locally using `ecdsa.Verify` before transmission to the client.

### Dynamic Resharing and Threshold Updates

Mpcium supports changing the committee composition and threshold without regenerating the underlying key. The resharing protocol redistributes shares to a new set of parties while preserving the original secret.

Implemented in [`pkg/mpc/ecdsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_resharing_session.go):

```go
reshareParams := tss.NewReSharingParameters(
    tss.S256(),
    tss.NewPeerContext(oldPartyIDs),
    tss.NewPeerContext(newPartyIDs),
    selfID,
    len(oldPartyIDs),
    oldThreshold,
    len(newPartyIDs),
    newThreshold,
)
s.party = resharing.NewLocalParty(reshareParams, share, s.outCh, s.endCh)

```

Upon completion, the system persists a new `KeyInfo` record with the updated `Threshold` and `ParticipantPeerIDs`, incrementing the version number to track the committee evolution.

## Threshold Enforcement and Key Metadata

The threshold value `t` is not merely a runtime parameter; it is persisted and validated at every critical operation to maintain the security guarantees of the t-of-n scheme.

### Storing the Threshold in KeyInfo

The `KeyInfo` structure in [`pkg/keyinfo/keyinfo.go`](https://github.com/fystack/mpcium/blob/main/pkg/keyinfo/keyinfo.go) stores the threshold alongside participant metadata:

```go
type KeyInfo struct {
    ParticipantPeerIDs []string `json:"participant_peer_ids"`
    Threshold          int      `json:"threshold"`   // the "t" value
    Version            int      `json:"version"`
}

```

During wallet creation, `ecdsaKeygenSession.GenerateKey` writes this record to Consul KV with the user-specified threshold. This persistence ensures that all subsequent operations reference the same consensus parameters.

### Validating Participant Count Before Signing

Before initiating the TSS protocol, the signing session performs strict validation in [`pkg/mpc/ecdsa_signing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_signing_session.go):

1. **Count Check**: Ensures at least `threshold+1` participants are present.
2. **Identity Check**: Verifies that all participants are authorized members of the original key generation committee by intersecting the supplied peer list with the stored `KeyInfo.ParticipantPeerIDs`.

Only after both checks pass does the session invoke `signing.NewLocalParty`, ensuring that the cryptographic operation adheres to the t-of-n security model.

## Message Transport and Round Coordination

Mpcium abstracts the network layer from the cryptographic state machine using a message wrapper and round mapping utilities, enabling secure communication over NATS.

### TssMessage Structure

All protocol messages flow through the `TssMessage` type defined in [`pkg/types/tss.go`](https://github.com/fystack/mpcium/blob/main/pkg/types/tss.go):

```go
type TssMessage struct {
    WalletID    string
    MsgBytes    []byte
    IsBroadcast bool
    From        *tss.PartyID
    To          *tss.PartyID
}

```

The session's `handleTssMessage` method signs broadcast messages with the node's identity (`s.identityStore.SignMessage`) and transmits them over NATS Pub/Sub. Direct messages are encrypted per-recipient using `s.identityStore.EncryptMessage`, ensuring confidentiality during the multi-round protocol.

### Mapping TSS Rounds for Debugging

To aid observability, [`pkg/mpc/ecdsa_rounds.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_rounds.go) maps raw wire messages to human-readable round names:

```go
func GetEcdsaMsgRound(msg []byte, partyID *tss.PartyID, isBroadcast bool) (RoundInfo, error) {
    parsedMsg, err := tss.ParseWireMessage(msg, partyID, isBroadcast)
    // ...
    switch parsedMsg.Content().(type) {
    case *signing.SignRound1Message1:
        return RoundInfo{Index: 0, RoundMsg: KEYSIGN1aUnicast}, nil
    // additional cases...
    }
}

```

This mapping is used exclusively for logging and debugging; the underlying state machine is fully driven by the `tss-lib` party object.

## Practical Implementation Examples

The following examples demonstrate complete workflows for wallet creation, signing, and resharing using the Mpcium client API.

### Creating a 2-of-5 Wallet

To initialize a wallet with a 2-of-5 threshold, the client constructs a `GenerateKeyMessage` and publishes it:

```go
// Client-side initiation (from cmd/mpcium-cli/generate-initiator.go)
walletID := "wallet-123"
threshold := 2  // t = 2, requires 3 parties to sign

initiatorMsg := &types.GenerateKeyMessage{WalletID: walletID}
raw, _ := initiatorMsg.Raw()
sig, _ := localSigner.SignMessage(raw)
initiatorMsg.Signature = sig

// Publish to trigger distributed key generation
mpcClient.CreateWallet(walletID)

```

Server-side, nodes invoke `newECDSAKeygenSession`, which calls `keygen.NewLocalParty` from `tss-lib`. Upon completion, each node stores its share locally while the system persists the `KeyInfo` record with `Threshold: 2`.

### Signing with Three Parties

Given a 2-of-5 wallet, signing requires exactly three participants (t+1). The client initiates the process:

```go
txID := "tx-abc"
msg := &types.SignTxMessage{
    KeyType:    types.KeyTypeSecp256k1,
    WalletID:   walletID,
    TxID:       txID,
    Tx:         rawTxBytes,
    NetworkInternalCode: "mainnet",
}
raw, _ := msg.Raw()
sig, _ := localSigner.SignMessage(raw)
msg.Signature = sig

// Publish to initiate signing session

```

Nodes running `ecdsaSigningSession` validate the participant count:

```go
if len(s.participantPeerIDs) < keyInfo.Threshold+1 {
    return ErrNotEnoughParticipants
}
result := lo.Intersect(s.participantPeerIDs, keyInfo.ParticipantPeerIDs)
if len(result) < keyInfo.Threshold+1 {
    return fmt.Errorf("Incompatible peerIDs …")
}

```

With validation passed, `signing.NewLocalParty` executes the threshold signing protocol. The resulting signature is verified locally using `ecdsa.Verify` before transmission to the client.

### Resharing to a New Committee

To migrate from a 2-of-5 configuration to a 3-of-7 configuration without changing the underlying key:

```go
reshareMsg := &types.ResharingMessage{
    SessionID:    walletID,
    NodeIDs:      []string{"nodeA", "nodeB", "nodeC", "nodeD", "nodeE", "nodeF", "nodeG"},
    NewThreshold: 3,  // new t = 3, requires 4 parties to sign
    KeyType:      types.KeyTypeSecp256k1,
    WalletID:     walletID,
}
raw, _ := reshareMsg.Raw()
sig, _ := localSigner.SignMessage(raw)
reshareMsg.Signature = sig

```

Each node creates a resharing session via `NewECDSAReshareSession`, which constructs `resharing.NewLocalParty` with parameters for both the old and new committees. The protocol redistributes shares to the seven new parties while preserving the original secret. Upon completion, the system persists a new `KeyInfo` record with `Threshold: 3` and the updated participant list.

## Summary

- Mpcium implements **t-of-n threshold signatures** for ECDSA and EdDSA using the `tss-lib` cryptographic library.
- The **threshold** `t` is stored in the `KeyInfo` structure and enforced during signing operations.
- **Key generation** utilizes `keygen.NewLocalParty` to create distributed shares across `n` parties.
- **Signing** requires exactly `t+1` participants, validated in [`ecdsa_signing_session.go`](https://github.com/fystack/mpcium/blob/main/ecdsa_signing_session.go) before invoking `signing.NewLocalParty`.
- **Resharing** enables dynamic committee changes and threshold updates via `resharing.NewLocalParty` without key regeneration.
- All TSS messages flow through the `TssMessage` structure and are transported over NATS with identity-based encryption.

## Frequently Asked Questions

### What is a t-of-n threshold signature scheme?

A **t-of-n threshold signature scheme** distributes cryptographic signing authority across `n` parties such that any subset of `t+1` participants can generate a valid signature, while fewer than `t+1` parties cannot reconstruct the private key or produce signatures. This construction ensures that no single entity controls the key material, providing fault tolerance against the compromise of up to `t` nodes.

### How does Mpcium enforce the threshold during signing?

Mpcium enforces the threshold in [`pkg/mpc/ecdsa_signing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_signing_session.go) by performing two validations before invoking the TSS protocol. First, it checks that the number of participating peer IDs is at least `threshold+1`. Second, it verifies that the participants are authorized by intersecting the supplied peer list with the `KeyInfo.ParticipantPeerIDs` stored during key generation. Only after both checks pass does the session call `signing.NewLocalParty`.

### Can the threshold be changed after key generation?

Yes, Mpcium supports **dynamic resharing** to modify the threshold and committee composition without regenerating the underlying key. The [`pkg/mpc/ecdsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_resharing_session.go) file implements this using `resharing.NewLocalParty`, which redistributes shares to a new set of parties while preserving the original secret. Upon successful completion, the system persists a new `KeyInfo` record with the updated `Threshold` and `ParticipantPeerIDs`.

### What cryptographic library does Mpcium use for threshold signatures?

Mpcium builds upon **tss-lib**, an open-source Go implementation of threshold signature schemes. The library provides the underlying state machines for key generation (`keygen.NewLocalParty`), signing (`signing.NewLocalParty`), and resharing (`resharing.NewLocalParty`), which Mpcium wraps with network transport, persistent storage, and identity-based authentication layers.