ECDH Interface Extension
First, extend the identity store interface to support ECDH operations alongside the existing Ed25519 functionality:
// pkg/identity/store.go
type ECDHStore interface {
GenerateECDHKeyPair() (*ecdh.PrivateKey, *ecdh.PublicKey, error)
PerformECDH(privateKey *ecdh.PrivateKey, peerPublicKey *ecdh.PublicKey) ([]byte, error)
DeriveSymmetricKey(sharedSecret []byte, info []byte) ([]byte, error)
}
type Store interface {
// Existing methods...
SignMessage(message *types.TssMessage) ([]byte, error)
VerifyInitiatorMessage(message *types.GenerateKeyMessage) error
// New ECDH methods
ECDHStore
}
ECDH Key Exchange Session
Create a dedicated ECDH key exchange session that follows the same pattern as the existing MPC sessions 1 :
// pkg/mpc/ecdh_session.go
package mpc
import (
"crypto/ecdh"
"crypto/rand"
"fmt"
"time"
"github.com/fystack/mpcium/pkg/identity"
"github.com/fystack/mpcium/pkg/messaging"
"github.com/fystack/mpcium/pkg/types"
)
type ECDHSession struct {
nodeID string
peerIDs []string
pubSub messaging.PubSub
direct messaging.DirectMessaging
identityStore identity.Store
symmetricKeys map[string][]byte // peerID -> symmetric key
privateKey *ecdh.PrivateKey
publicKey *ecdh.PublicKey
exchangeComplete chan struct{}
errCh chan error
}
type ECDHMessage struct {
From string `json:"from"`
To string `json:"to"`
PublicKey []byte `json:"public_key"`
Timestamp time.Time `json:"timestamp"`
Signature []byte `json:"signature"`
}
func NewECDHSession(
nodeID string,
peerIDs []string,
pubSub messaging.PubSub,
direct messaging.DirectMessaging,
identityStore identity.Store,
) *ECDHSession {
return &ECDHSession{
nodeID: nodeID,
peerIDs: peerIDs,
pubSub: pubSub,
direct: direct,
identityStore: identityStore,
symmetricKeys: make(map[string][]byte),
exchangeComplete: make(chan struct{}),
errCh: make(chan error),
}
}
func (e *ECDHSession) StartKeyExchange() error {
// Generate ECDH key pair
privateKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return fmt.Errorf("failed to generate ECDH key pair: %w", err)
}
e.privateKey = privateKey
e.publicKey = privateKey.PublicKey()
// Subscribe to ECDH messages
sub, err := e.pubSub.Subscribe(fmt.Sprintf("ecdh:exchange:%s", e.nodeID))
if err != nil {
return fmt.Errorf("failed to subscribe to ECDH topic: %w", err)
}
go e.handleIncomingMessages(sub)
// Broadcast public key to all peers
return e.broadcastPublicKey()
}
func (e *ECDHSession) broadcastPublicKey() error {
publicKeyBytes := e.publicKey.Bytes()
for _, peerID := range e.peerIDs {
msg := ECDHMessage{
From: e.nodeID,
To: peerID,
PublicKey: publicKeyBytes,
Timestamp: time.Now(),
}
// Sign the message using existing identity store
msgBytes, _ := json.Marshal(msg)
signature, err := e.identityStore.SignMessage(&types.TssMessage{
WalletID: "ecdh-exchange",
Data: msgBytes,
})
if err != nil {
return fmt.Errorf("failed to sign ECDH message: %w", err)
}
msg.Signature = signature
signedMsgBytes, _ := json.Marshal(msg)
topic := fmt.Sprintf("ecdh:exchange:%s", peerID)
if err := e.direct.Send(topic, signedMsgBytes); err != nil {
return fmt.Errorf("failed to send ECDH message to %s: %w", peerID, err)
}
}
return nil
}
func (e *ECDHSession) handleIncomingMessages(sub messaging.Subscription) {
for {
msg, err := sub.NextMessage()
if err != nil {
e.errCh <- err
return
}
var ecdhMsg ECDHMessage
if err := json.Unmarshal(msg.Data, &ecdhMsg); err != nil {
continue
}
// Verify message signature
if !e.verifyECDHMessage(&ecdhMsg) {
continue
}
// Perform ECDH key exchange
if err := e.processECDHMessage(&ecdhMsg); err != nil {
e.errCh <- err
return
}
// Check if exchange is complete
if len(e.symmetricKeys) == len(e.peerIDs) {
close(e.exchangeComplete)
return
}
}
}
func (e *ECDHSession) processECDHMessage(msg *ECDHMessage) error {
peerPublicKey, err := ecdh.P256().NewPublicKey(msg.PublicKey)
if err != nil {
return fmt.Errorf("invalid peer public key: %w", err)
}
// Perform ECDH
sharedSecret, err := e.privateKey.ECDH(peerPublicKey)
if err != nil {
return fmt.Errorf("ECDH failed: %w", err)
}
// Derive symmetric key using HKDF
symmetricKey := e.deriveSymmetricKey(sharedSecret, msg.From)
e.symmetricKeys[msg.From] = symmetricKey
return nil
}
func (e *ECDHSession) GetSymmetricKey(peerID string) ([]byte, bool) {
key, exists := e.symmetricKeys[peerID]
return key, exists
}
func (e *ECDHSession) WaitForCompletion() error {
select {
case <-e.exchangeComplete:
return nil
case err := <-e.errCh:
return err
case <-time.After(30 * time.Second):
return fmt.Errorf("ECDH key exchange timeout")
}
}
Integration with Node Initialization
Integrate the ECDH session into the node startup process in cmd/mpcium/main.go 2 :
// cmd/mpcium/main.go - after mpcNode creation
mpcNode := mpc.NewNode(
nodeID,
peerNodeIDs,
pubsub,
directMessaging,
badgerKV,
keyinfoStore,
peerRegistry,
identityStore,
)
// Perform ECDH key exchange before starting event consumer
ecdhSession := mpc.NewECDHSession(nodeID, peerNodeIDs, pubsub, directMessaging, identityStore)
if err := ecdhSession.StartKeyExchange(); err != nil {
logger.Fatal("Failed to start ECDH key exchange", err)
}
if err := ecdhSession.WaitForCompletion(); err != nil {
logger.Fatal("ECDH key exchange failed", err)
}
logger.Info("ECDH key exchange completed successfully")
Enhanced Message Encryption
Modify the session's message handling to use symmetric encryption 3 :
// pkg/mpc/session.go - enhanced handleTssMessage
func (s *session) handleTssMessage(keyshare tss.Message) {
data, routing, err := keyshare.WireBytes()
if err != nil {
s.ErrCh <- err
return
}
tssMsg := types.NewTssMessage(s.walletID, data, routing.IsBroadcast, routing.From, routing.To)
// Encrypt message data if symmetric keys are available
if s.ecdhSession != nil && !routing.IsBroadcast {
for _, to := range routing.To {
nodeID := PartyIDToRoutingDest(to)
if symmetricKey, exists := s.ecdhSession.GetSymmetricKey(nodeID); exists {
encryptedData, err := s.encryptWithSymmetricKey(data, symmetricKey)
if err != nil {
s.ErrCh <- fmt.Errorf("failed to encrypt message: %w", err)
return
}
tssMsg.Data = encryptedData
tssMsg.Encrypted = true
}
}
}
// Sign the message as before
signature, err := s.identityStore.SignMessage(&tssMsg)
if err != nil {
s.ErrCh <- fmt.Errorf("failed to sign message: %w", err)
return
}
tssMsg.Signature = signature
// Continue with existing message sending logic...
}
Notes
This implementation provides:
- ECDH key exchange during node startup before MPC operations begin
- Symmetric encryption for direct messages between nodes using derived keys
- Integration with the existing Ed25519 authentication system for message signing
- Compatibility with the current session architecture 4
The ECDH session follows the same patterns as existing MPC sessions and integrates cleanly with the current messaging infrastructure. The symmetric keys derived from ECDH can be used to encrypt TSS protocol messages for confidentiality while maintaining the existing authentication guarantees.
Wiki pages you might want to explore:
ECDH Interface Extension
First, extend the identity store interface to support ECDH operations alongside the existing Ed25519 functionality:
ECDH Key Exchange Session
Create a dedicated ECDH key exchange session that follows the same pattern as the existing MPC sessions 1 :
Integration with Node Initialization
Integrate the ECDH session into the node startup process in
cmd/mpcium/main.go2 :Enhanced Message Encryption
Modify the session's message handling to use symmetric encryption 3 :
Notes
This implementation provides:
The ECDH session follows the same patterns as existing MPC sessions and integrates cleanly with the current messaging infrastructure. The symmetric keys derived from ECDH can be used to encrypt TSS protocol messages for confidentiality while maintaining the existing authentication guarantees.
Wiki pages you might want to explore: