tss-lib implements threshold ECDSA and EdDSA, and ships with a warning about how to use it
Threshold Signature Scheme, for ECDSA and EDDSA
At a glance
- What is it?
- tss-lib is a Go library for multi-party threshold signatures over ECDSA and EdDSA, with keygen, signing and resharing protocols and no trusted dealer. It is a building block for shared crypto wallets, not a wallet, and the documentation is unusually blunt about the parts an integrator must get right.
- Who is it for?
- tss-lib fits Go teams building a shared signing service where no single party should hold a full private key, and it does not fit a team that wants a ready multi-signature wallet or that has no plan for securing share backups and validating protocol messages.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 30 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Threshold signatures split one key across a committee that never reveals it
tss-lib is a Go implementation of multi-party {t,n}-threshold ECDSA based on the Gennaro and Goldfeder CCS 2018 work, and of EdDSA following a similar approach. The problem it solves is a custody problem: a crypto wallet where several parties must collaborate to sign a transaction, without any one of them ever holding the whole private key.
The properties the README claims are specific. One secret share per key or address is stored locally by each participant, and the shares are kept safe by the protocol, never revealed to others at any time. There is no trusted dealer of the shares, which removes the classic setup ceremony where one party generates and hands out key material.
The comparison the project draws is with multi-signature schemes. TSS transactions preserve the privacy of the signers by not revealing which t+1 participants were involved in a signing. And because the result is an ordinary signature, a blockchain node checks its validity without any extra multi-signature logic. That last point is the reason to prefer threshold signatures over multisig in a setting where the verifier is a stock node.
Three protocols, three packages, one LocalParty each
The library is organised around three protocols. Key generation creates the secret shares with no trusted dealer. Signing uses the shares to produce a signature. Resharing changes the group of participants while keeping the secret, which is the dynamic group management piece.
Each protocol has its own LocalParty type, in the keygen, signing or resharing package, and the pattern is the same for all three. You construct the party, run Start in a goroutine, and read results off two channels. The outCh collects outgoing messages destined for other parties, and the endCh receives the save data or the signature when the protocol completes.
The signing party takes the message to sign and the key data produced by keygen. The documentation is precise about the threshold requirement: t+1 signers are required to sign a message, for optimal usage no more than this should be involved, and every signer should have the same view of who those t+1 signers are. That last requirement is a real operational constraint, since a signer with a different view of the committee may end up in a protocol that never completes.
The keygen party can take pre-computed parameters as a last argument, or omit it and have them computed in round one. The README recommends pre-computing the safe primes and the Paillier secret beforehand, because that computation takes time, and shows a call that generates those parameters with a concurrency limit equal to the number of available CPU cores.
Setting up parties, parameters and a local PartyID map
Before any protocol runs, the parties have to be identified and the shared parameters built. Each participant on the network needs a *PartyID created with tss.NewPartyID, which takes an id, a moniker and a uniqueKey. The id should be a unique string representing the party in the network, the moniker can be anything or left blank, and the uniqueKey is a unique identifying key for the peer, such as its p2p public key, expressed as a big.Int.
The parties are sorted, and the context is created from them:
parties := tss.SortPartyIDs(getParticipantPartyIDs())
thisParty := tss.NewPartyID(id, moniker, uniqueKey)
ctx := tss.NewPeerContext(parties)
curve := tss.S256()
// or use EdDSA
// curve := tss.Edwards()
params := tss.NewParameters(curve, ctx, thisParty, len(parties), threshold)The curve choice selects the scheme: tss.S256() for ECDSA on the secp256k1 curve used by Bitcoin and Ethereum, or tss.Edwards() for EdDSA. The threshold argument is the t in {t,n}.
One practical detail in the setup deserves attention, because it is the kind of thing that produces a confusing failure later. The caller should keep a local mapping of id strings to *PartyID instances, so that an incoming message has its origin party's PartyID recovered when it is passed to UpdateFromBytes. Without that map, a received message cannot be attributed to a sender, and the party's state cannot advance.
Re-sharing refuses to run in place, and the panic is deliberate
The resharing protocol is the one with the sharpest constraint in the library, and the README spells it out. The old and the new committee must be disjoint: no party may appear in both. Membership is decided by the party's key, meaning two parties are the same party exactly when their keys are equal, regardless of their index in either committee.
Enforcement is in code. tss.NewReSharingParameters panics if the two committees share a member, naming the offending keys, and the documentation points at tss.CommitteeOverlapKeys(oldCtx, newCtx) as the call to make first if you would rather branch than recover from a panic. Re-sharing in place, handing the same set of parties a fresh set of shares, is not supported, because a party that is both a sender and a receiver is not a shape any round of the protocol is written for.
There is a second rule about storage. During re-sharing the key data may be modified during the rounds, so nothing saved on disk should be overwritten until the final struct has been received through the end channel. The same caution applies to keygen save data, which should be persisted to secure storage when it arrives. Writing intermediate state is how a committee ends up with shares that no longer correspond to each other.
The resharing party also needs more context than the others, which is why ReSharingParameters carries the information about the re-sharing being carried out.
Messages travel over the wire as bytes, and the library is thread-safe
The library does not transport anything itself. It gives you the messages and expects you to deliver them, which keeps the transport decision with the application. A Party has two thread-safe methods for receiving updates. UpdateFromBytes is the main entry point when updating a party's state from the wire, taking wire bytes, the sending *tss.PartyID and a flag for whether the message is a broadcast. Update takes a tss.ParsedMessage and is meant for running locally or in tests.
The distinction is worth noting. UpdateFromBytes is where deserialisation and sender attribution happen, so it is the path that needs the PartyID map described earlier. Update is the convenience path for local execution and for unit tests, where the parsed message is already in hand.
A tss.Message has two methods for converting messages to data for the wire, and the wire format is protobuf. The repository carries a protob directory and a Makefile target that regenerates the .pb.go files, with a check that refuses to regenerate unless the protoc-gen-go version matches the pinned one. The comment in that Makefile explains why the guard exists: generating with a different version silently rewrites all eight generated files, and a wrong answer there is silent, which is the problem the check exists to prevent.
The security notes are flagged because integrators get this wrong
The README carries a warning mark next to the link to its secure usage notes, telling readers not to miss them. That framing is unusual in a library README and tells you something about how the author expects the library to be used.
The safe usage pattern implied by the rest of the documentation is consistent: pre-compute parameters out of band, persist save data to secure storage rather than memory or a scratch file, treat the endCh as the only authoritative completion signal, keep the old and new committees disjoint when resharing, and make sure every signer agrees on who the t+1 signers are. Each of those is a protocol-level requirement rather than a stylistic one, and violating them produces a party that stalls, a share set that does not reconstruct, or a signature that never arrives.
The README also points out that this is a library for developers, and that a ready-to-use TSS tool that works with the Binance Chain CLI exists separately. That is worth reading as a scope signal: the library is the primitive, and the tool is the product built on it. If you need the product, use the tool rather than wiring the library yourself.
Module path, dependencies and the gap between releases
The go.mod declares the module as github.com/bnb-chain/tss-lib/v4 and requires Go 1.23. The runtime dependencies are the crypto stack rather than a framework: filippo.io/bigmod, the btcd and btcutil packages with btcec/v2 for secp256k1, the decred edwards curve packages for EdDSA, otiai10/primes for prime generation, hashicorp/go-multierror, ipfs/go-log, golang.org/x/crypto, and google.golang.org/protobuf. The ed25519 dependency is replaced with the binance-chain fork of edwards25519, and there is a testify dependency for tests.
The release history and the module path do not line up, and the Makefile comment explains why. The MODULE variable is read from go.mod rather than written out, because spelling it out let it drift to the v3 path on the v4 branch, and since no target referenced MODULE, nothing failed to say so. The listed releases are v3.0.0 on 2026-04-09, v2.0.2 on 2024-01-16 and v2.0.1 on 2023-09-20, while the last push was on 2026-09-23. There is no release tag matching the v4 module path in that list, so an integrator pinning a version should check the tag set rather than assume the module path and the release list correspond.
The licence is MIT, which is the permissive end of the scale and no obstacle for commercial use. The repository also carries a SECURITY.md file, a doc directory, and separate ecdsa and eddsa directories alongside common, crypto, tss and test.
Editorial conclusion
tss-lib fits Go teams building a shared signing service where no single party should hold a full private key, and it does not fit a team that wants a ready multi-signature wallet or that has no plan for securing share backups and validating protocol messages. Before integrating it, read the secure usage notes the README flags, persist keygen save data to secure storage as the endCh delivers it, and if you plan to reshare, verify that the old and new committees are disjoint before calling tss.NewReSharingParameters, since the call panics when they overlap.
Frequently asked questions
What does tss-lib do?
It implements multi-party {t,n}-threshold ECDSA based on the Gennaro and Goldfeder CCS 2018 work, and EdDSA following a similar approach. It provides three protocols: keygen for creating secret shares with no trusted dealer, signing, and resharing to change the group of participants while keeping the secret.
How do I run keygen and signing with tss-lib?
You build the shared parameters from the sorted party list, the curve and the threshold, then create the keygen.LocalParty with outCh, endCh and optionally pre-computed preParams, and call party.Start in a goroutine. Signing works the same way with signing.NewLocalParty, passing the message and the key data from keygen.
Can tss-lib reshare to a committee that overlaps the old one?
No. The old and the new committee must be disjoint, and tss.NewReSharingParameters panics if they share a member, naming the offending keys. You can call tss.CommitteeOverlapKeys first to check and branch instead of recovering from the panic, and re-sharing in place is not supported.
Which curves does tss-lib support?
The curve is selected when building the parameters, with tss.S256() for ECDSA on the secp256k1 curve and tss.Edwards() for EdDSA. The go.mod includes the btcec secp256k1 packages and the decred edwards curve packages.
What licence is tss-lib released under?
The library is permissively MIT licensed, with the licence text in the LICENSE file in the repository. The module path is github.com/bnb-chain/tss-lib/v4 and the go.mod requires Go 1.23.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bnb-chain-tss-lib)Was this useful?