This guide explains how to use the go-cggmp-tss library to perform Distributed Key Generation (DKG) and Threshold Signing.
This library is a pure computation engine. It does not handle networking, database storage, or transport security. You are responsible for:
The protocol is implemented as a Finite State Machine (FSM).
PartyIDYou need to implement the tss.PartyID interface to identify participants.
type MyPartyID struct {
IDStr string
MonikerStr string
PubKey []byte
}
func (p *MyPartyID) ID() string { return p.IDStr }
func (p *MyPartyID) Moniker() string { return p.MonikerStr }
func (p *MyPartyID) Key() []byte { return p.PubKey }
Configure the session parameters.
import "github.com/smallyu/go-cggmp-tss/pkg/tss"
// Create party IDs
p1 := &MyPartyID{IDStr: "1", ...}
p2 := &MyPartyID{IDStr: "2", ...}
p3 := &MyPartyID{IDStr: "3", ...}
params := &tss.Parameters{
PartyID: p1, // Local party
Parties: []tss.PartyID{p1, p2, p3}, // All participants
Threshold: 1, // t (requires t+1 to sign)
Curve: "secp256k1", // Curve
SessionID: []byte("unique-session-id"),
}
The KeyGen protocol generates a distributed private key. At the end, each party receives a LocalPartySaveData object containing their share.
import "github.com/smallyu/go-cggmp-tss/internal/protocol/keygen"
state, outMsgs, err := keygen.NewStateMachine(params)
if err != nil {
panic(err)
}
// Send initial messages
network.Broadcast(outMsgs)
Run the state machine until it finishes.
for {
// 1. Receive a message from the network
msg := network.Receive()
// 2. Update the state machine
nextState, outMsgs, err := state.Update(msg)
if err != nil {
panic(err)
}
// 3. Send output messages
for _, m := range outMsgs {
if m.IsBroadcast() {
network.Broadcast(m)
} else {
network.SendTo(m.To(), m)
}
}
// 4. Check if finished
if nextState == nil {
// Protocol finished
break
}
// 5. Advance state
state = nextState
}
result := state.Result()
if result == nil {
panic("KeyGen failed")
}
keyData := result.(*keygen.LocalPartySaveData)
// Save keyData to disk securely!
Signing requires the LocalPartySaveData from KeyGen and the hash of the message to sign.
import "github.com/smallyu/go-cggmp-tss/internal/protocol/sign"
msgHash := sha256.Sum256([]byte("hello world"))
state, outMsgs, err := sign.NewStateMachine(params, keyData, msgHash[:])
if err != nil {
panic(err)
}
// Send initial messages
network.Broadcast(outMsgs)
The loop is identical to KeyGen.
result := state.Result()
if result == nil {
panic("Sign failed")
}
signature := result.(*sign.Signature)
fmt.Printf("R: %x\nS: %x\n", signature.R, signature.S)
Proactive security often involves refreshing the secret shares without changing the public key. This renders old shares useless.
The NewStateMachine for refresh takes the oldKeyData as input.
import "github.com/smallyu/go-cggmp-tss/internal/protocol/refresh"
// keyData is your result from KeyGen
state, outMsgs, err := refresh.NewStateMachine(params, keyData)
if err != nil {
panic(err)
}
// Send initial messages
network.Broadcast(outMsgs)
The loop is identical to KeyGen and Signing.
The result is a new LocalPartySaveData object.
result := state.Result()
if result == nil {
panic("Refresh failed")
}
newKeyData := result.(*keygen.LocalPartySaveData)
// IMPORTANT: The Public Key (X, Y) should match the old key data.
// But the Secret Share (Xi) and other internal values will be different.
if newKeyData.PublicKeyX.Cmp(keyData.PublicKeyX) != 0 {
panic("Public key mismatch!")
}
// Overwrite the old keyData on disk
saveToDisk(newKeyData)