# How to Migrate Existing Wallets to a New Threshold Configuration via Resharing in mpcium

> Learn how to migrate existing wallets to a new threshold configuration using resharing in mpcium. Follow simple steps to update your wallet security settings effortlessly.

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

---

**Migrate existing wallets to a new threshold configuration via resharing by constructing a `ResharingMessage` with the new committee and threshold, invoking `MPCClient.Resharing` to publish the signed request, and subscribing to `OnResharingResult` to receive the updated public key and key metadata.**

The mpcium library provides a secure Multi-Party Computation (MPC) infrastructure for threshold signature schemes. When you need to migrate existing wallets to a new threshold configuration via resharing, the library coordinates a distributed protocol that transfers signing authority from an old committee to a new committee without ever reconstructing the private key.

## Prepare the ResharingMessage

Begin by constructing a `ResharingMessage` that defines the migration parameters. This struct is defined in [[`pkg/types/initiator_msg.go`](https://github.com/fystack/mpcium/blob/main/pkg/types/initiator_msg.go)](https://github.com/fystack/mpcium/blob/master/pkg/types/initiator_msg.go#L54-L62) and requires the following fields:

- `SessionID`: A unique UUID for this resharing run.
- `WalletID`: The identifier of the existing wallet being migrated.
- `NodeIDs`: The complete list of peer IDs forming the **new** committee.
- `NewThreshold`: The desired threshold value (must satisfy `NewThreshold + 1 ≤ len(NodeIDs)`).
- `KeyType`: The signature algorithm, either `types.KeyTypeEd25519` or `types.KeyTypeSecp256k1`.

```go
resharingMsg := &types.ResharingMessage{
    SessionID:    uuid.NewString(),
    WalletID:     "c2a5c9d4-...",
    NodeIDs:      []string{"node-A-uuid", "node-B-uuid", "node-C-uuid"},
    NewThreshold: 2,
    KeyType:      types.KeyTypeEd25519,
}

```

Before transmission, the message must be canonically serialized for signing. The `Raw()` method (lines 106-110 in [`initiator_msg.go`](https://github.com/fystack/mpcium/blob/main/initiator_msg.go)) strips the `Signature` and `AuthorizerSignatures` fields to produce a deterministic payload. The `MPCClient.Resharing` method automatically invokes `Raw()` and signs the result using the configured `Signer` instance.

## Invoke the Resharing Protocol

Publish the resharing request through the `MPCClient.Resharing` API located in [[`pkg/client/client.go`](https://github.com/fystack/mpcium/blob/main/pkg/client/client.go)](https://github.com/fystack/mpcium/blob/master/pkg/client/client.go#L200-L212). This method serializes the message, attaches the initiator's signature, and publishes it to the **`mpc.mpc_reshare`** NATS topic.

```go
if err := mpcClient.Resharing(resharingMsg); err != nil {
    logger.Fatal("Resharing failed", err)
}

```

Upon receiving the event in [[`pkg/eventconsumer/event_consumer.go`](https://github.com/fystack/mpcium/blob/main/pkg/eventconsumer/event_consumer.go)](https://github.com/fystack/mpcium/blob/master/pkg/eventconsumer/event_consumer.go#L582), the server unmarshals the message and initiates the appropriate TSS resharing session:

- **Ed25519**: Uses `NewEDDSAReshareSession` in [[`pkg/mpc/eddsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/eddsa_resharing_session.go)](https://github.com/fystack/mpcium/blob/master/pkg/mpc/eddsa_resharing_session.go).
- **Secp256k1**: Uses `NewECDSAReshareSession` in [[`pkg/mpc/ecdsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ecdsa_resharing_session.go)](https://github.com/fystack/mpcium/blob/master/pkg/mpc/ecdsa_resharing_session.go).

Both constructors build a `tss.ReSharingParameters` object containing the old committee (current share holders), the new committee (from `NodeIDs`), and both the old and new thresholds. The session determines whether the local node is part of the old committee, the new committee, or both, and initializes share data accordingly—existing shares are loaded from the KV store for old parties, while new parties begin with empty shares.

## Handle the ResharingResultEvent

Subscribe to the completion event using `OnResharingResult`, which listens on the **`mpc.mpc_reshare_result.*`** queue (see lines 224-246 in [`pkg/client/client.go`](https://github.com/fystack/mpcium/blob/main/pkg/client/client.go)). The server publishes the result via `handleReshareResult` (lines 630-665 in [[`pkg/eventconsumer/event_consumer.go`](https://github.com/fystack/mpcium/blob/main/pkg/eventconsumer/event_consumer.go)](https://github.com/fystack/mpcium/blob/master/pkg/eventconsumer/event_consumer.go#L630-L665)) after successfully storing the new share and updating the key metadata.

```go
err := mpcClient.OnResharingResult(func(evt event.ResharingResultEvent) {
    logger.Info("Resharing completed",
        "walletID", evt.WalletID,
        "pubKey", fmt.Sprintf("%x", evt.PubKey),
        "newThreshold", evt.NewThreshold,
        "version", evt.Version)
})

```

The `ResharingResultEvent` (defined in [`pkg/event/reshare.go`](https://github.com/fystack/mpcium/blob/main/pkg/event/reshare.go)) contains:

- `WalletID`: The migrated wallet identifier.
- `PubKey`: The new public key in compressed byte format.
- `NewThreshold`: The updated signing threshold.
- `Version`: The incremented key-info version (atomic counter).
- `ParticipantPeerIDs`: The definitive list of new committee members.

After the resharing session completes, the server persists the new share in the KV store under a versioned key (`walletID:version`) and updates the `KeyInfo` record via `keyinfo.Store.Save`, ensuring subsequent signing operations use the refreshed committee and threshold.

## Complete Migration Example

The following example demonstrates the full client-side flow, from configuration to result handling. This mirrors the official [[`examples/reshare/main.go`](https://github.com/fystack/mpcium/blob/main/examples/reshare/main.go)](https://github.com/fystack/mpcium/blob/master/examples/reshare/main.go):

```go
package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"

    "github.com/fystack/mpcium/pkg/client"
    "github.com/fystack/mpcium/pkg/config"
    "github.com/fystack/mpcium/pkg/event"
    "github.com/fystack/mpcium/pkg/logger"
    "github.com/fystack/mpcium/pkg/types"
    "github.com/google/uuid"
    "github.com/nats-io/nats.go"
    "github.com/spf13/viper"
)

func main() {
    // Initialise configuration and logger
    config.InitViperConfig("")
    logger.Init("dev", true)

    // Connect to NATS
    natsURL := viper.GetString("nats.url")
    nc, err := nats.Connect(natsURL)
    if err != nil {
        logger.Fatal("NATS connection failed", err)
    }
    defer nc.Drain()

    // Create local signer for the event initiator
    localSigner, err := client.NewLocalSigner(
        types.EventInitiatorKeyTypeEd25519,
        client.LocalSignerOptions{KeyPath: "./event_initiator.key"},
    )
    if err != nil {
        logger.Fatal("Failed to initialise signer", err)
    }

    // Build MPC client
    mpc := client.NewMPCClient(client.Options{
        NatsConn: nc,
        Signer:   localSigner,
    })

    // Subscribe to resharing results
    if err := mpc.OnResharingResult(func(evt event.ResharingResultEvent) {
        logger.Info("Resharing completed",
            "walletID", evt.WalletID,
            "pubKey", fmt.Sprintf("%x", evt.PubKey),
            "newThreshold", evt.NewThreshold,
            "version", evt.Version)
    }); err != nil {
        logger.Fatal("Failed to subscribe to resharing result", err)
    }

    // Create and send resharing request
    resharingMsg := &types.ResharingMessage{
        SessionID:    uuid.NewString(),
        WalletID:     "c2a5c9d4-...",
        NodeIDs:      []string{"node-A-uuid", "node-B-uuid", "node-C-uuid"},
        NewThreshold: 2,
        KeyType:      types.KeyTypeEd25519,
    }

    if err := mpc.Resharing(resharingMsg); err != nil {
        logger.Fatal("Resharing failed", err)
    }
    fmt.Printf("Resharing request sent for wallet %s\n", resharingMsg.WalletID)

    // Keep alive until interrupted
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
    <-stop
}

```

## Summary

- **Construct** a `ResharingMessage` with `SessionID`, `WalletID`, `NodeIDs`, `NewThreshold`, and `KeyType` to define the migration target.
- **Sign and publish** via `MPCClient.Resharing`, which serializes the canonical payload and broadcasts to the `mpc.mpc_reshare` NATS topic.
- **Listen** for `ResharingResultEvent` through `OnResharingResult` to obtain the new public key, updated threshold, and incremented key version.
- The server automatically manages TSS resharing sessions, versioning the new shares in the KV store and updating `KeyInfo` metadata atomically.

## Frequently Asked Questions

### What happens to the old shares after resharing completes?

The old shares remain in the KV store under their previous version keys but are no longer used for signing operations. The `KeyInfo` record is atomically updated to point to the new version, and the server begins using the new committee for all subsequent signature requests. Old shares can be garbage collected based on your retention policy.

### Can a node be part of both the old and new committees during resharing?

Yes. The resharing sessions in [`eddsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/eddsa_resharing_session.go) and [`ecdsa_resharing_session.go`](https://github.com/fystack/mpcium/blob/main/ecdsa_resharing_session.go) determine committee membership using the `isNewParty` and `isOldParty` flags. A node can hold an existing share (old committee) while simultaneously receiving a new share (new committee), ensuring continuity of service during the migration.

### How is the initiator signature validated during the resharing request?

The `ResharingMessage` includes a `Signature` field populated by `MPCClient.Resharing` using the configured `Signer`. The server validates this signature against the canonical payload produced by `msg.Raw()` to ensure the request originated from an authorized event initiator and was not tampered with during transit.

### What is the relationship between threshold and the number of nodes?

The threshold `t` (stored as `NewThreshold`) defines the minimum number of shares required to produce a signature, where `t+1` parties must cooperate. This value must satisfy `t+1 ≤ len(NodeIDs)` for the new committee. The server enforces this constraint when constructing `tss.ReSharingParameters` in the session constructors.