From f32c6774a75394e5a0b3aad1a975171feda22cf8 Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 16:03:58 +0700 Subject: [PATCH 01/25] Add resharing functionality with event handling and session management --- examples/reshare/main.go | 67 +++++++++ pkg/client/client.go | 68 ++++++++-- pkg/event/reshare.go | 10 ++ pkg/eventconsumer/event_consumer.go | 174 ++++++++++++++++++++++-- pkg/mpc/ecdsa_resharing_session.go | 203 ++++++++++++++++++++++++++++ pkg/mpc/eddsa_resharing_session.go | 178 ++++++++++++++++++++++++ pkg/mpc/node.go | 85 +++++++++++- pkg/types/initiator_msg.go | 20 +++ 8 files changed, 780 insertions(+), 25 deletions(-) create mode 100644 examples/reshare/main.go create mode 100644 pkg/event/reshare.go diff --git a/examples/reshare/main.go b/examples/reshare/main.go new file mode 100644 index 0000000..f5c9efb --- /dev/null +++ b/examples/reshare/main.go @@ -0,0 +1,67 @@ +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/nats-io/nats.go" + "github.com/spf13/viper" +) + +func main() { + const environment = "dev" + config.InitViperConfig() + logger.Init(environment, true) + + natsURL := viper.GetString("nats.url") + natsConn, err := nats.Connect(natsURL) + if err != nil { + logger.Fatal("Failed to connect to NATS", err) + } + defer natsConn.Drain() + defer natsConn.Close() + + mpcClient := client.NewMPCClient(client.Options{ + NatsConn: natsConn, + KeyPath: "./event_initiator.key", + }) + + resharingMsg := &types.ResharingMessage{ + WalletID: "98f6a23c-e78c-445e-92d9-95ccf927ca35", + NodeIDs: []string{"0ce02715-0ead-48ef-9772-2583316cc860", "ac37e85f-caca-4bee-8a3a-49a0fe35abff"}, // new peer IDs + NewThreshold: 1, // t+1 <= len(NodeIDs) + KeyType: types.KeyTypeSecp256k1, + Signature: []byte("signature"), + } + err = mpcClient.Resharing(resharingMsg) + if err != nil { + logger.Fatal("Resharing failed", err) + } + fmt.Printf("Resharing(%q) sent, awaiting result...\n", resharingMsg.WalletID) + + // 3) Listen for signing results + err = mpcClient.OnResharingResult(func(evt event.ResharingSuccessEvent) { + logger.Info("Resharing result received", + "walletID", evt.WalletID, + "pubKey", fmt.Sprintf("%x", evt.PubKey), + "newThreshold", evt.NewThreshold, + "keyType", evt.KeyType, + ) + }) + if err != nil { + logger.Fatal("Failed to subscribe to OnResharingResult", err) + } + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + <-stop + + fmt.Println("Shutting down.") +} diff --git a/pkg/client/client.go b/pkg/client/client.go index bccd748..b18301f 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -20,7 +20,8 @@ import ( ) const ( - GenerateWalletSuccessTopic = "mpc.mpc_keygen_success.*" // wildcard to listen to all success events + GenerateWalletSuccessTopic = "mpc.mpc_keygen_success.*" // wildcard to listen to all success events + ResharingSuccessTopic = "mpc.mpc_reshare_success.*" // wildcard to listen to all success events ) type MPCClient interface { @@ -29,14 +30,18 @@ type MPCClient interface { SignTransaction(msg *types.SignTxMessage) error OnSignResult(callback func(event event.SigningResultEvent)) error + + Resharing(msg *types.ResharingMessage) error + OnResharingResult(callback func(event event.ResharingSuccessEvent)) error } type mpcClient struct { - signingStream messaging.StreamPubsub - pubsub messaging.PubSub - genKeySuccessQueue messaging.MessageQueue - signResultQueue messaging.MessageQueue - privKey ed25519.PrivateKey + signingStream messaging.StreamPubsub + pubsub messaging.PubSub + genKeySuccessQueue messaging.MessageQueue + signResultQueue messaging.MessageQueue + reshareSuccessQueue messaging.MessageQueue + privKey ed25519.PrivateKey } // Options defines configuration options for creating a new MPCClient @@ -126,13 +131,15 @@ func NewMPCClient(opts Options) MPCClient { genKeySuccessQueue := manager.NewMessageQueue("mpc_keygen_success") signResultQueue := manager.NewMessageQueue("signing_result") + reshareSuccessQueue := manager.NewMessageQueue("mpc_reshare_success") return &mpcClient{ - signingStream: signingStream, - pubsub: pubsub, - genKeySuccessQueue: genKeySuccessQueue, - signResultQueue: signResultQueue, - privKey: priv, + signingStream: signingStream, + pubsub: pubsub, + genKeySuccessQueue: genKeySuccessQueue, + signResultQueue: signResultQueue, + reshareSuccessQueue: reshareSuccessQueue, + privKey: priv, } } @@ -241,3 +248,42 @@ func (c *mpcClient) OnSignResult(callback func(event event.SigningResultEvent)) return nil } + +func (c *mpcClient) Resharing(msg *types.ResharingMessage) error { + // compute the canonical raw bytes + raw, err := msg.Raw() + if err != nil { + return fmt.Errorf("Resharing: raw payload error: %w", err) + } + // sign + msg.Signature = ed25519.Sign(c.privKey, raw) + + bytes, err := json.Marshal(msg) + if err != nil { + return fmt.Errorf("Resharing: marshal error: %w", err) + } + + if err := c.pubsub.Publish(eventconsumer.MPCReshareEvent, bytes); err != nil { + return fmt.Errorf("Resharing: publish error: %w", err) + } + return nil +} + +func (c *mpcClient) OnResharingResult(callback func(event event.ResharingSuccessEvent)) error { + + err := c.reshareSuccessQueue.Dequeue(ResharingSuccessTopic, func(msg []byte) error { + var event event.ResharingSuccessEvent + err := json.Unmarshal(msg, &event) + if err != nil { + return err + } + callback(event) + return nil + }) + + if err != nil { + return fmt.Errorf("OnResharingResult: subscribe error: %w", err) + } + + return nil +} diff --git a/pkg/event/reshare.go b/pkg/event/reshare.go new file mode 100644 index 0000000..ed13a6a --- /dev/null +++ b/pkg/event/reshare.go @@ -0,0 +1,10 @@ +package event + +import "github.com/fystack/mpcium/pkg/types" + +type ResharingSuccessEvent struct { + WalletID string `json:"wallet_id"` + NewThreshold int `json:"new_threshold"` + KeyType types.KeyType `json:"key_type"` + PubKey []byte `json:"pub_key"` +} diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index c42be22..9a4a62e 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -23,6 +23,7 @@ import ( const ( MPCGenerateEvent = "mpc:generate" MPCSignEvent = "mpc:sign" + MPCReshareEvent = "mpc:reshare" DefaultConcurrentKeygen = 2 DefaultKeyGenStartupDelayMs = 500 @@ -40,9 +41,11 @@ type eventConsumer struct { genKeySucecssQueue messaging.MessageQueue signingResultQueue messaging.MessageQueue + reshareResultQueue messaging.MessageQueue keyGenerationSub messaging.Subscription signingSub messaging.Subscription + reshareSub messaging.Subscription identityStore identity.Store msgBuffer chan *nats.Msg @@ -101,6 +104,11 @@ func (ec *eventConsumer) Run() { log.Fatal("Failed to consume tx signing event", err) } + err = ec.consumeReshareEvent() + if err != nil { + log.Fatal("Failed to consume reshare event", err) + } + logger.Info("MPC Event consumer started...!") } @@ -203,15 +211,12 @@ func (ec *eventConsumer) startKeyGenEventWorker() { // semaphore to limit concurrency semaphore := make(chan struct{}, ec.maxConcurrentKeygen) - for { - select { - case natMsg := <-ec.msgBuffer: - semaphore <- struct{}{} // acquire a slot - go func(msg *nats.Msg) { - defer func() { <-semaphore }() // release the slot when done - ec.handleKeyGenEvent(msg) - }(natMsg) - } + for natMsg := range ec.msgBuffer { + semaphore <- struct{}{} // acquire a slot + go func(msg *nats.Msg) { + defer func() { <-semaphore }() // release the slot when done + ec.handleKeyGenEvent(msg) + }(natMsg) } } @@ -398,6 +403,157 @@ func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkIntern } } +func (ec *eventConsumer) consumeReshareEvent() error { + sub, err := ec.pubsub.Subscribe(MPCReshareEvent, func(natMsg *nats.Msg) { + raw := natMsg.Data + var msg types.ResharingMessage + err := json.Unmarshal(raw, &msg) + if err != nil { + logger.Error("Failed to unmarshal signing message", err) + return + } + err = ec.identityStore.VerifyInitiatorMessage(&msg) + if err != nil { + logger.Error("Failed to verify initiator message", err) + return + } + + walletID := msg.WalletID + var ( + oldSession mpc.ReshareSession + newSession mpc.ReshareSession + ) + switch msg.KeyType { + case types.KeyTypeSecp256k1: + oldSession, err = ec.node.CreateReshareSession( + mpc.SessionTypeECDSA, + msg.WalletID, + ec.mpcThreshold, + msg.NewThreshold, + msg.NodeIDs, + false, + ec.reshareResultQueue, + ) + if err != nil { + logger.Error("Failed to create old reshare session", err, "walletID", walletID) + return + } + + newSession, err = ec.node.CreateReshareSession( + mpc.SessionTypeECDSA, + msg.WalletID, + ec.mpcThreshold, + msg.NewThreshold, + msg.NodeIDs, + true, + ec.reshareResultQueue, + ) + case types.KeyTypeEd25519: + oldSession, err = ec.node.CreateReshareSession( + mpc.SessionTypeEDDSA, + msg.WalletID, + ec.mpcThreshold, + msg.NewThreshold, + msg.NodeIDs, + false, + ec.reshareResultQueue, + ) + if err != nil { + logger.Error("Failed to create old reshare session", err, "walletID", walletID) + return + } + + newSession, err = ec.node.CreateReshareSession( + mpc.SessionTypeEDDSA, + msg.WalletID, + ec.mpcThreshold, + msg.NewThreshold, + msg.NodeIDs, + true, + ec.reshareResultQueue, + ) + } + if err != nil { + logger.Error("Failed to create reshare session", err, "walletID", walletID) + return + } + + successEvent := &event.ResharingSuccessEvent{ + WalletID: walletID, + } + + fmt.Println("oldSession", oldSession) + fmt.Println("newSession", newSession) + + ctx := context.Background() + ctxOld, doneOld := context.WithCancel(ctx) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for { + select { + case <-ctxOld.Done(): + wg.Done() + return + case err := <-oldSession.ErrChan(): + logger.Error("Keygen session error", err) + } + } + }() + if newSession != nil { + ctxNew, doneNew := context.WithCancel(ctx) + wg.Add(1) + go func() { + for { + select { + case <-ctxNew.Done(): + successEvent.PubKey = newSession.GetPubKeyResult() + wg.Done() + return + case err := <-newSession.ErrChan(): + logger.Error("Keygen session error", err) + } + } + }() + newSession.ListenToIncomingMessageAsync() + go newSession.Reshare(doneNew) + } + oldSession.ListenToIncomingMessageAsync() + + // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. + // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). + time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) + + go oldSession.Reshare(doneOld) + + wg.Wait() + logger.Info("Closing session successfully!", "event", successEvent) + + successEventBytes, err := json.Marshal(successEvent) + if err != nil { + logger.Error("Failed to marshal reshare success event", err) + return + } + + err = ec.reshareResultQueue.Enqueue(fmt.Sprintf(mpc.TypeReshareSuccess, walletID), successEventBytes, &messaging.EnqueueOptions{ + IdempotententKey: fmt.Sprintf(mpc.TypeReshareSuccess, walletID), + }) + if err != nil { + logger.Error("Failed to publish reshare success message", err) + return + } + + logger.Info("[COMPLETED RESHARE] Reshare completed successfully", "walletID", walletID) + }) + + ec.reshareSub = sub + if err != nil { + return err + } + return nil +} + // Add a cleanup routine that runs periodically func (ec *eventConsumer) sessionCleanupRoutine() { ticker := time.NewTicker(ec.cleanupInterval) diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index 1e3d1d8..7a8db32 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -1 +1,204 @@ package mpc + +import ( + "crypto/ecdsa" + "encoding/json" + "fmt" + + "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" + "github.com/bnb-chain/tss-lib/v2/ecdsa/resharing" + "github.com/bnb-chain/tss-lib/v2/tss" + "github.com/fystack/mpcium/pkg/common/errors" + "github.com/fystack/mpcium/pkg/encoding" + "github.com/fystack/mpcium/pkg/identity" + "github.com/fystack/mpcium/pkg/keyinfo" + "github.com/fystack/mpcium/pkg/kvstore" + "github.com/fystack/mpcium/pkg/logger" + "github.com/fystack/mpcium/pkg/messaging" +) + +const ( + TypeReshareSuccess = "mpc.mpc_reshare_success.%s" +) + +type ReshareSession interface { + Session + Init() + Reshare(done func()) + GetPubKeyResult() []byte +} + +type ecdsaReshareSession struct { + *session + isNewParty bool + reshareParams *tss.ReSharingParameters + endCh chan *keygen.LocalPartySaveData +} + +type ReshareSuccessEvent struct { + WalletID string `json:"wallet_id"` + ECDSAPubKey []byte `json:"ecdsa_pub_key"` + EDDSAPubKey []byte `json:"eddsa_pub_key"` +} + +func NewECDSAReshareSession( + walletID string, + pubSub messaging.PubSub, + direct messaging.DirectMessaging, + participantPeerIDs []string, + selfID *tss.PartyID, + oldPartyIDs []*tss.PartyID, + newPartyIDs []*tss.PartyID, + threshold int, + newThreshold int, + preParams *keygen.LocalPreParams, + kvstore kvstore.KVStore, + keyinfoStore keyinfo.Store, + resultQueue messaging.MessageQueue, + identityStore identity.Store, + isNewParty bool, +) *ecdsaReshareSession { + session := session{ + walletID: walletID, + pubSub: pubSub, + direct: direct, + threshold: threshold, + participantPeerIDs: participantPeerIDs, + selfPartyID: selfID, + partyIDs: newPartyIDs, + outCh: make(chan tss.Message), + ErrCh: make(chan error), + preParams: preParams, + kvstore: kvstore, + keyinfoStore: keyinfoStore, + topicComposer: &TopicComposer{ + ComposeBroadcastTopic: func() string { + return fmt.Sprintf("resharing:broadcast:ecdsa:%s", walletID) + }, + ComposeDirectTopic: func(nodeID string) string { + return fmt.Sprintf("resharing:direct:ecdsa:%s:%s", nodeID, walletID) + }, + }, + composeKey: func(walletID string) string { + return fmt.Sprintf("ecdsa:%s", walletID) + }, + getRoundFunc: GetEcdsaMsgRound, + resultQueue: resultQueue, + sessionType: SessionTypeECDSA, + identityStore: identityStore, + } + + reshareParams := tss.NewReSharingParameters( + tss.S256(), + tss.NewPeerContext(oldPartyIDs), + tss.NewPeerContext(newPartyIDs), + selfID, + len(oldPartyIDs), + threshold, + len(newPartyIDs), + newThreshold, + ) + + return &ecdsaReshareSession{ + session: &session, + reshareParams: reshareParams, + isNewParty: isNewParty, + endCh: make(chan *keygen.LocalPartySaveData), + } +} + +func (s *ecdsaReshareSession) Init() { + logger.Infof("Initializing resharing session with partyID: %s, peerIDs %s", s.selfPartyID, s.partyIDs) + var share keygen.LocalPartySaveData + if s.isNewParty { + // Initialize empty share data for new party + share = keygen.NewLocalPartySaveData(len(s.partyIDs)) + share.LocalPreParams = *s.preParams + } else { + keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) + if err != nil { + s.ErrCh <- errors.Wrap(err, "Failed to get wallet data from KVStore") + return + } + + err = json.Unmarshal(keyData, &share) + if err != nil { + s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) + return + } + } + + s.party = resharing.NewLocalParty(s.reshareParams, share, s.outCh, s.endCh) + logger.Infof("[INITIALIZED] Initialized resharing session successfully partyID: %s, peerIDs %s, walletID %s, oldThreshold = %d, newThreshold = %d", + s.selfPartyID, s.partyIDs, s.walletID, s.threshold, s.reshareParams.NewThreshold()) +} + +func (s *ecdsaReshareSession) Reshare(done func()) { + logger.Info("Starting resharing", "walletID", s.walletID, "partyID", s.selfPartyID) + go func() { + if err := s.party.Start(); err != nil { + s.ErrCh <- err + } + }() + + for { + select { + case saveData := <-s.endCh: + keyBytes, err := json.Marshal(saveData) + if err != nil { + s.ErrCh <- err + return + } + + if err := s.kvstore.Put(s.composeKey(s.walletID), keyBytes); err != nil { + s.ErrCh <- err + return + } + + keyInfo := keyinfo.KeyInfo{ + ParticipantPeerIDs: s.participantPeerIDs, + Threshold: s.reshareParams.NewThreshold(), + } + + // Save key info with resharing flag + if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { + s.ErrCh <- err + return + } + + // skip for old committee + if saveData.ECDSAPub != nil { + // Get public key + publicKey := saveData.ECDSAPub + pubKey := &ecdsa.PublicKey{ + Curve: publicKey.Curve(), + X: publicKey.X(), + Y: publicKey.Y(), + } + + pubKeyBytes, err := encoding.EncodeS256PubKey(pubKey) + if err != nil { + logger.Error("failed to encode public key", err) + s.ErrCh <- fmt.Errorf("failed to encode public key: %w", err) + return + } + + // Set the public key bytes + s.pubkeyBytes = pubKeyBytes + logger.Info("Generated public key bytes", + "walletID", s.walletID, + "pubKeyBytes", pubKeyBytes) + } + + done() + err = s.Close() + if err != nil { + logger.Error("Failed to close session", err) + } + return + case msg := <-s.outCh: + // Handle the message + s.handleTssMessage(msg) + } + } +} diff --git a/pkg/mpc/eddsa_resharing_session.go b/pkg/mpc/eddsa_resharing_session.go index 1e3d1d8..bf7bee1 100644 --- a/pkg/mpc/eddsa_resharing_session.go +++ b/pkg/mpc/eddsa_resharing_session.go @@ -1 +1,179 @@ package mpc + +import ( + "encoding/json" + "fmt" + + "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" + "github.com/bnb-chain/tss-lib/v2/eddsa/resharing" + "github.com/bnb-chain/tss-lib/v2/tss" + "github.com/decred/dcrd/dcrec/edwards/v2" + "github.com/fystack/mpcium/pkg/common/errors" + "github.com/fystack/mpcium/pkg/identity" + "github.com/fystack/mpcium/pkg/keyinfo" + "github.com/fystack/mpcium/pkg/kvstore" + "github.com/fystack/mpcium/pkg/logger" + "github.com/fystack/mpcium/pkg/messaging" +) + +type eddsaReshareSession struct { + *session + isNewParty bool + reshareParams *tss.ReSharingParameters + endCh chan *keygen.LocalPartySaveData +} + +func NewEDDSAReshareSession( + walletID string, + pubSub messaging.PubSub, + direct messaging.DirectMessaging, + participantPeerIDs []string, + selfID *tss.PartyID, + oldPartyIDs []*tss.PartyID, + newPartyIDs []*tss.PartyID, + threshold int, + newThreshold int, + kvstore kvstore.KVStore, + keyinfoStore keyinfo.Store, + resultQueue messaging.MessageQueue, + identityStore identity.Store, + isNewParty bool, +) *eddsaReshareSession { + session := session{ + walletID: walletID, + pubSub: pubSub, + direct: direct, + threshold: threshold, + participantPeerIDs: participantPeerIDs, + selfPartyID: selfID, + partyIDs: newPartyIDs, + outCh: make(chan tss.Message), + ErrCh: make(chan error), + kvstore: kvstore, + keyinfoStore: keyinfoStore, + topicComposer: &TopicComposer{ + ComposeBroadcastTopic: func() string { + return fmt.Sprintf("reshare:broadcast:eddsa:%s", walletID) + }, + ComposeDirectTopic: func(nodeID string) string { + return fmt.Sprintf("reshare:direct:eddsa:%s:%s", nodeID, walletID) + }, + }, + composeKey: func(walletID string) string { + return fmt.Sprintf("eddsa:%s", walletID) + }, + getRoundFunc: GetEddsaMsgRound, + resultQueue: resultQueue, + sessionType: SessionTypeEDDSA, + identityStore: identityStore, + } + + reshareParams := tss.NewReSharingParameters( + tss.Edwards(), + tss.NewPeerContext(oldPartyIDs), + tss.NewPeerContext(newPartyIDs), + selfID, + len(oldPartyIDs), + threshold, + len(newPartyIDs), + newThreshold, + ) + + return &eddsaReshareSession{ + session: &session, + reshareParams: reshareParams, + isNewParty: isNewParty, + endCh: make(chan *keygen.LocalPartySaveData), + } +} + +func (s *eddsaReshareSession) Init() { + logger.Infof("Initializing resharing session with partyID: %s, peerIDs %s", s.selfPartyID, s.partyIDs) + var share keygen.LocalPartySaveData + if s.isNewParty { + // Initialize empty share data for new party + share = keygen.NewLocalPartySaveData(len(s.partyIDs)) + } else { + keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) + if err != nil { + s.ErrCh <- errors.Wrap(err, "Failed to get wallet data from KVStore") + return + } + + err = json.Unmarshal(keyData, &share) + if err != nil { + s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) + return + } + } + + s.party = resharing.NewLocalParty(s.reshareParams, share, s.outCh, s.endCh) + logger.Infof("[INITIALIZED] Initialized resharing session successfully partyID: %s, peerIDs %s, walletID %s, oldThreshold = %d, newThreshold = %d", + s.selfPartyID, s.partyIDs, s.walletID, s.threshold, s.reshareParams.NewThreshold()) +} + +func (s *eddsaReshareSession) Reshare(done func()) { + logger.Info("Starting resharing", "walletID", s.walletID, "partyID", s.selfPartyID) + go func() { + if err := s.party.Start(); err != nil { + s.ErrCh <- err + } + }() + + for { + select { + case saveData := <-s.endCh: + keyBytes, err := json.Marshal(saveData) + if err != nil { + s.ErrCh <- err + return + } + + if err := s.kvstore.Put(s.composeKey(s.walletID), keyBytes); err != nil { + s.ErrCh <- err + return + } + + keyInfo := keyinfo.KeyInfo{ + ParticipantPeerIDs: s.participantPeerIDs, + Threshold: s.reshareParams.NewThreshold(), + } + + // Save key info with resharing flag + if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { + s.ErrCh <- err + return + } + + // skip for old committee + if saveData.EDDSAPub != nil { + + // Get public key + publicKey := saveData.EDDSAPub + pkX, pkY := publicKey.X(), publicKey.Y() + pk := edwards.PublicKey{ + Curve: tss.Edwards(), + X: pkX, + Y: pkY, + } + + pubKeyBytes := pk.SerializeCompressed() + s.pubkeyBytes = pubKeyBytes + + logger.Info("Generated public key bytes", + "walletID", s.walletID, + "pubKeyBytes", pubKeyBytes) + } + + done() + err = s.Close() + if err != nil { + logger.Error("Failed to close session", err) + } + return + case msg := <-s.outCh: + // Handle the message + s.handleTssMessage(msg) + } + } +} diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index cbdb5bc..02633a2 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "math/big" + "slices" "time" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" @@ -14,12 +15,12 @@ import ( "github.com/fystack/mpcium/pkg/kvstore" "github.com/fystack/mpcium/pkg/logger" "github.com/fystack/mpcium/pkg/messaging" - "github.com/google/uuid" ) const ( - PurposeKeygen string = "keygen" - PurposeSign string = "sign" + PurposeKeygen string = "keygen" + PurposeSign string = "sign" + PurposeReshare string = "reshare" ) type ID string @@ -39,9 +40,8 @@ type Node struct { } func CreatePartyID(nodeID string, label string) *tss.PartyID { - partyID := uuid.NewString() key := big.NewInt(0).SetBytes([]byte(nodeID)) - return tss.NewPartyID(partyID, label, key) + return tss.NewPartyID(nodeID+":"+label, label, key) } func PartyIDToNodeID(partyID *tss.PartyID) string { @@ -201,6 +201,81 @@ func (p *Node) CreateSigningSession( return nil, errors.New("Unknown session type") } +func (p *Node) CreateReshareSession( + sessionType SessionType, + walletID string, + oldThreshold int, + newThreshold int, + newPeerIDs []string, + isNewPeer bool, + successQueue messaging.MessageQueue, +) (ReshareSession, error) { + if !p.peerRegistry.ArePeersReady() { + return nil, fmt.Errorf("Not enough peers to create reshare session! Expected %d, got %d", newThreshold+1, p.peerRegistry.GetReadyPeersCount()) + } + readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() + for _, peerID := range newPeerIDs { + if slices.Contains(readyPeerIDs, peerID) { + continue + } + return nil, fmt.Errorf("new peer %s is not ready", peerID) + } + + // if current node is not in newPeerIDs and isNewPeer is true, return nil + if !slices.Contains(newPeerIDs, p.nodeID) && isNewPeer { + return nil, nil + } + + var selfPartyID *tss.PartyID + oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) + newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs) + if isNewPeer { + selfPartyID = newSelf + } else { + selfPartyID = oldSelf + } + + switch sessionType { + case SessionTypeECDSA: + return NewECDSAReshareSession( + walletID, + p.pubSub, + p.direct, + readyPeerIDs, + selfPartyID, + oldAllPartyIDs, + newAllPartyIDs, + oldThreshold, + newThreshold, + p.ecdsaPreParams, + p.kvstore, + p.keyinfoStore, + successQueue, + p.identityStore, + isNewPeer, + ), nil + case SessionTypeEDDSA: + return NewEDDSAReshareSession( + walletID, + p.pubSub, + p.direct, + readyPeerIDs, + selfPartyID, + oldAllPartyIDs, + newAllPartyIDs, + oldThreshold, + newThreshold, + p.kvstore, + p.keyinfoStore, + successQueue, + p.identityStore, + isNewPeer, + ), nil + default: + return nil, errors.New("Unknown session type") + } +} + func (p *Node) generatePartyIDs(purpose string, readyPeerIDs []string) (self *tss.PartyID, all []*tss.PartyID) { var selfPartyID *tss.PartyID partyIDs := make([]*tss.PartyID, len(readyPeerIDs)) diff --git a/pkg/types/initiator_msg.go b/pkg/types/initiator_msg.go index edd0bf4..70dee28 100644 --- a/pkg/types/initiator_msg.go +++ b/pkg/types/initiator_msg.go @@ -33,6 +33,14 @@ type SignTxMessage struct { Signature []byte `json:"signature"` } +type ResharingMessage struct { + NodeIDs []string `json:"node_ids"` // new peer IDs + NewThreshold int `json:"new_threshold"` + KeyType KeyType `json:"key_type"` + WalletID string `json:"wallet_id"` + Signature []byte `json:"signature"` +} + func (m *SignTxMessage) Raw() ([]byte, error) { // omit the Signature field itself when computing the signed‐over data payload := struct { @@ -70,3 +78,15 @@ func (m *GenerateKeyMessage) Sig() []byte { func (m *GenerateKeyMessage) InitiatorID() string { return m.WalletID } + +func (m *ResharingMessage) Raw() ([]byte, error) { + return []byte(m.WalletID), nil +} + +func (m *ResharingMessage) Sig() []byte { + return m.Signature +} + +func (m *ResharingMessage) InitiatorID() string { + return m.WalletID +} From 87f1e77d304fbe011767c31d3670c2b910907dc8 Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 17:32:43 +0700 Subject: [PATCH 02/25] preload preparams --- pkg/eventconsumer/event_consumer.go | 22 ++++---- pkg/mpc/node.go | 79 +++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 9a4a62e..eca0e7f 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -482,8 +482,10 @@ func (ec *eventConsumer) consumeReshareEvent() error { WalletID: walletID, } - fmt.Println("oldSession", oldSession) - fmt.Println("newSession", newSession) + oldSession.Init() + if newSession != nil { + newSession.Init() + } ctx := context.Background() ctxOld, doneOld := context.WithCancel(ctx) @@ -497,10 +499,16 @@ func (ec *eventConsumer) consumeReshareEvent() error { wg.Done() return case err := <-oldSession.ErrChan(): - logger.Error("Keygen session error", err) + logger.Error("Reshare session error", err) } } }() + // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. + // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). + time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) + + go oldSession.Reshare(doneOld) + if newSession != nil { ctxNew, doneNew := context.WithCancel(ctx) wg.Add(1) @@ -512,7 +520,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { wg.Done() return case err := <-newSession.ErrChan(): - logger.Error("Keygen session error", err) + logger.Error("Reshare session error", err) } } }() @@ -521,12 +529,6 @@ func (ec *eventConsumer) consumeReshareEvent() error { } oldSession.ListenToIncomingMessageAsync() - // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. - // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). - time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) - - go oldSession.Reshare(doneOld) - wg.Wait() logger.Info("Closing session successfully!", "event", successEvent) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 02633a2..ec9b8ea 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -2,6 +2,7 @@ package mpc import ( "bytes" + "encoding/json" "fmt" "math/big" "slices" @@ -33,7 +34,7 @@ type Node struct { direct messaging.DirectMessaging kvstore kvstore.KVStore keyinfoStore keyinfo.Store - ecdsaPreParams *keygen.LocalPreParams + ecdsaPreParams []*keygen.LocalPreParams identityStore identity.Store peerRegistry PeerRegistry @@ -67,26 +68,23 @@ func NewNode( identityStore identity.Store, ) *Node { start := time.Now() - preParams, err := keygen.GeneratePreParams(5 * time.Minute) - if err != nil { - logger.Fatal("Generate pre params failed", err) - } elapsed := time.Since(start) logger.Info("Starting new node, preparams is generated successfully!", "elapsed", elapsed.Milliseconds()) - go peerRegistry.WatchPeersReady() - - return &Node{ - nodeID: nodeID, - peerIDs: peerIDs, - pubSub: pubSub, - direct: direct, - kvstore: kvstore, - keyinfoStore: keyinfoStore, - ecdsaPreParams: preParams, - peerRegistry: peerRegistry, - identityStore: identityStore, + node := &Node{ + nodeID: nodeID, + peerIDs: peerIDs, + pubSub: pubSub, + direct: direct, + kvstore: kvstore, + keyinfoStore: keyinfoStore, + peerRegistry: peerRegistry, + identityStore: identityStore, } + node.ecdsaPreParams = node.generatePreParams() + + go peerRegistry.WatchPeersReady() + return node } func (p *Node) ID() string { @@ -124,7 +122,7 @@ func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, successQ selfPartyID, allPartyIDs, threshold, - p.ecdsaPreParams, + p.ecdsaPreParams[0], p.kvstore, p.keyinfoStore, successQueue, @@ -174,7 +172,7 @@ func (p *Node) CreateSigningSession( selfPartyID, allPartyIDs, threshold, - p.ecdsaPreParams, + p.ecdsaPreParams[0], p.kvstore, p.keyinfoStore, resultQueue, @@ -226,13 +224,18 @@ func (p *Node) CreateReshareSession( return nil, nil } - var selfPartyID *tss.PartyID + var ( + selfPartyID *tss.PartyID + preParams *keygen.LocalPreParams + ) oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs) if isNewPeer { selfPartyID = newSelf + preParams = p.ecdsaPreParams[1] } else { selfPartyID = oldSelf + preParams = p.ecdsaPreParams[0] } switch sessionType { @@ -247,7 +250,7 @@ func (p *Node) CreateReshareSession( newAllPartyIDs, oldThreshold, newThreshold, - p.ecdsaPreParams, + preParams, p.kvstore, p.keyinfoStore, successQueue, @@ -297,3 +300,37 @@ func (p *Node) Close() { logger.Error("Resign failed", err) } } + +func (p *Node) generatePreParams() []*keygen.LocalPreParams { + start := time.Now() + // Try to load from kvstore + preParams := make([]*keygen.LocalPreParams, 2) + for i := 0; i < 2; i++ { + key := fmt.Sprintf("pre_params_%d", i) + val, err := p.kvstore.Get(key) + if err == nil && val != nil { + preParams[i] = &keygen.LocalPreParams{} + err = json.Unmarshal(val, preParams[i]) + if err != nil { + logger.Fatal("Unmarshal pre params failed", err) + } + continue + } + // Not found, generate and save + params, err := keygen.GeneratePreParams(5 * time.Minute) + if err != nil { + logger.Fatal("Generate pre params failed", err) + } + bytes, err := json.Marshal(params) + if err != nil { + logger.Fatal("Marshal pre params failed", err) + } + err = p.kvstore.Put(key, bytes) + if err != nil { + logger.Fatal("Save pre params failed", err) + } + preParams[i] = params + } + logger.Info("Generate pre params successfully!", "elapsed", time.Since(start).Milliseconds()) + return preParams +} From ebe0b9af529b42eb5c2e9c8b0e24b55435be7d70 Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 17:40:44 +0700 Subject: [PATCH 03/25] Fix KeyTypeEd25519 declaration for consistency in eventconsumer and mpc packages --- pkg/eventconsumer/events.go | 2 +- pkg/mpc/key_type.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/eventconsumer/events.go b/pkg/eventconsumer/events.go index 5b9ca06..4d71714 100644 --- a/pkg/eventconsumer/events.go +++ b/pkg/eventconsumer/events.go @@ -6,7 +6,7 @@ type KeyType string const ( KeyTypeSecp256k1 KeyType = "secp256k1" - KeyTypeEd25519 = "ed25519" + KeyTypeEd25519 KeyType = "ed25519" ) // InitiatorMessage is anything that carries a payload to verify and its signature. diff --git a/pkg/mpc/key_type.go b/pkg/mpc/key_type.go index 756efa8..96add07 100644 --- a/pkg/mpc/key_type.go +++ b/pkg/mpc/key_type.go @@ -4,5 +4,5 @@ type KeyType string const ( KeyTypeSecp256k1 KeyType = "secp256k1" - KeyTypeEd25519 = "ed25519" + KeyTypeEd25519 KeyType = "ed25519" ) From 2620fcc8528d2d9ef02d7ecbd86f3aa9ea5384d8 Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 17:41:26 +0700 Subject: [PATCH 04/25] Remove go.uber.org/mock v0.3.0 dependency from go.mod and go.sum --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index fb45e42..8a4b6ed 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/spf13/viper v1.18.0 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v3 v3.3.2 - go.uber.org/mock v0.3.0 golang.org/x/term v0.31.0 ) diff --git a/go.sum b/go.sum index 3c448e3..cdd2f60 100644 --- a/go.sum +++ b/go.sum @@ -373,8 +373,6 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= From 9661ab3babaee5e3326c1ac3ef8c2e2167280f53 Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 17:55:22 +0700 Subject: [PATCH 05/25] Update reshare event handling logic and clean up ECDSA reshare session initialization --- pkg/eventconsumer/event_consumer.go | 4 ++-- pkg/mpc/ecdsa_resharing_session.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index eca0e7f..d739dfb 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -445,7 +445,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { ec.mpcThreshold, msg.NewThreshold, msg.NodeIDs, - true, + false, ec.reshareResultQueue, ) case types.KeyTypeEd25519: @@ -455,7 +455,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { ec.mpcThreshold, msg.NewThreshold, msg.NodeIDs, - false, + true, ec.reshareResultQueue, ) if err != nil { diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index 7a8db32..41a8453 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -87,7 +87,6 @@ func NewECDSAReshareSession( sessionType: SessionTypeECDSA, identityStore: identityStore, } - reshareParams := tss.NewReSharingParameters( tss.S256(), tss.NewPeerContext(oldPartyIDs), @@ -98,7 +97,6 @@ func NewECDSAReshareSession( len(newPartyIDs), newThreshold, ) - return &ecdsaReshareSession{ session: &session, reshareParams: reshareParams, From 9a19171000733e433986f193173a9611a559b50d Mon Sep 17 00:00:00 2001 From: vietddude Date: Tue, 1 Jul 2025 23:50:35 +0700 Subject: [PATCH 06/25] Enhance reshare session management by introducing versioning and updating party ID generation logic --- pkg/eventconsumer/event_consumer.go | 9 +-- pkg/keyinfo/keyinfo.go | 1 + pkg/mpc/ecdsa_resharing_session.go | 2 +- pkg/mpc/eddsa_resharing_session.go | 1 - pkg/mpc/node.go | 96 +++++++++++++++++++++-------- pkg/mpc/node_test.go | 2 +- 6 files changed, 78 insertions(+), 33 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index d739dfb..c1ab756 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -425,13 +425,14 @@ func (ec *eventConsumer) consumeReshareEvent() error { ) switch msg.KeyType { case types.KeyTypeSecp256k1: + isNewNode := true oldSession, err = ec.node.CreateReshareSession( mpc.SessionTypeECDSA, msg.WalletID, ec.mpcThreshold, msg.NewThreshold, msg.NodeIDs, - false, + !isNewNode, ec.reshareResultQueue, ) if err != nil { @@ -445,7 +446,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { ec.mpcThreshold, msg.NewThreshold, msg.NodeIDs, - false, + isNewNode, ec.reshareResultQueue, ) case types.KeyTypeEd25519: @@ -499,7 +500,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { wg.Done() return case err := <-oldSession.ErrChan(): - logger.Error("Reshare session error", err) + logger.Error("Reshare session error", err, "oldSession", true) } } }() @@ -520,7 +521,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { wg.Done() return case err := <-newSession.ErrChan(): - logger.Error("Reshare session error", err) + logger.Error("Reshare session error", err, "newSession", true) } } }() diff --git a/pkg/keyinfo/keyinfo.go b/pkg/keyinfo/keyinfo.go index 6952e7d..49d4c7f 100644 --- a/pkg/keyinfo/keyinfo.go +++ b/pkg/keyinfo/keyinfo.go @@ -11,6 +11,7 @@ import ( type KeyInfo struct { ParticipantPeerIDs []string `json:"participant_peer_ids"` Threshold int `json:"threshold"` + Version int `json:"version"` } type store struct { diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index 41a8453..412f562 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -106,7 +106,7 @@ func NewECDSAReshareSession( } func (s *ecdsaReshareSession) Init() { - logger.Infof("Initializing resharing session with partyID: %s, peerIDs %s", s.selfPartyID, s.partyIDs) + logger.Infof("Initializing resharing session with partyID: %s, newPartyIDs %s", s.selfPartyID, s.partyIDs) var share keygen.LocalPartySaveData if s.isNewParty { // Initialize empty share data for new party diff --git a/pkg/mpc/eddsa_resharing_session.go b/pkg/mpc/eddsa_resharing_session.go index bf7bee1..7f66d62 100644 --- a/pkg/mpc/eddsa_resharing_session.go +++ b/pkg/mpc/eddsa_resharing_session.go @@ -106,7 +106,6 @@ func (s *eddsaReshareSession) Init() { return } } - s.party = resharing.NewLocalParty(s.reshareParams, share, s.outCh, s.endCh) logger.Infof("[INITIALIZED] Initialized resharing session successfully partyID: %s, peerIDs %s, walletID %s, oldThreshold = %d, newThreshold = %d", s.selfPartyID, s.partyIDs, s.walletID, s.threshold, s.reshareParams.NewThreshold()) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index ec9b8ea..f09f515 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "slices" + "strconv" "time" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" @@ -16,12 +17,16 @@ import ( "github.com/fystack/mpcium/pkg/kvstore" "github.com/fystack/mpcium/pkg/logger" "github.com/fystack/mpcium/pkg/messaging" + "github.com/google/uuid" ) const ( PurposeKeygen string = "keygen" PurposeSign string = "sign" PurposeReshare string = "reshare" + + BackwardCompatibleVersion int = 0 + DefaultVersion int = 1 ) type ID string @@ -40,11 +45,6 @@ type Node struct { peerRegistry PeerRegistry } -func CreatePartyID(nodeID string, label string) *tss.PartyID { - key := big.NewInt(0).SetBytes([]byte(nodeID)) - return tss.NewPartyID(nodeID+":"+label, label, key) -} - func PartyIDToNodeID(partyID *tss.PartyID) string { return string(partyID.KeyInt().Bytes()) } @@ -103,17 +103,17 @@ func (p *Node) CreateKeyGenSession( switch sessionType { case SessionTypeECDSA: - return p.createECDSAKeyGenSession(walletID, threshold, successQueue) + return p.createECDSAKeyGenSession(walletID, threshold, DefaultVersion, successQueue) case SessionTypeEDDSA: - return p.createEDDSAKeyGenSession(walletID, threshold, successQueue) + return p.createEDDSAKeyGenSession(walletID, threshold, DefaultVersion, successQueue) default: return nil, fmt.Errorf("Unknown session type: %s", sessionType) } } -func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, successQueue messaging.MessageQueue) (KeyGenSession, error) { +func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, version int, successQueue messaging.MessageQueue) (KeyGenSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) session := newECDSAKeygenSession( walletID, p.pubSub, @@ -131,9 +131,9 @@ func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, successQ return session, nil } -func (p *Node) createEDDSAKeyGenSession(walletID string, threshold int, successQueue messaging.MessageQueue) (KeyGenSession, error) { +func (p *Node) createEDDSAKeyGenSession(walletID string, threshold int, version int, successQueue messaging.MessageQueue) (KeyGenSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) session := newEDDSAKeygenSession( walletID, p.pubSub, @@ -158,8 +158,9 @@ func (p *Node) CreateSigningSession( threshold int, resultQueue messaging.MessageQueue, ) (SigningSession, error) { + version := p.getVersion(sessionType, walletID) readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) switch sessionType { case SessionTypeECDSA: return newECDSASigningSession( @@ -224,12 +225,13 @@ func (p *Node) CreateReshareSession( return nil, nil } + version := p.getVersion(sessionType, walletID) var ( selfPartyID *tss.PartyID preParams *keygen.LocalPreParams ) - oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs) - newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs) + oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) + newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) if isNewPeer { selfPartyID = newSelf preParams = p.ecdsaPreParams[1] @@ -237,7 +239,6 @@ func (p *Node) CreateReshareSession( selfPartyID = oldSelf preParams = p.ecdsaPreParams[0] } - switch sessionType { case SessionTypeECDSA: return NewECDSAReshareSession( @@ -279,19 +280,44 @@ func (p *Node) CreateReshareSession( } } -func (p *Node) generatePartyIDs(purpose string, readyPeerIDs []string) (self *tss.PartyID, all []*tss.PartyID) { - var selfPartyID *tss.PartyID - partyIDs := make([]*tss.PartyID, len(readyPeerIDs)) - for i, peerID := range readyPeerIDs { - if peerID == p.nodeID { - selfPartyID = CreatePartyID(peerID, purpose) - partyIDs[i] = selfPartyID - } else { - partyIDs[i] = CreatePartyID(peerID, purpose) +// generatePartyIDs generates the party IDs for the given purpose and version +// It returns the self party ID and all party IDs +// It also sorts the party IDs in place +func (n *Node) generatePartyIDs( + label string, + readyPeerIDs []string, + version int, +) (self *tss.PartyID, all []*tss.PartyID) { + // Pre-allocate slice with exact size needed + partyIDs := make([]*tss.PartyID, 0, len(readyPeerIDs)) + + // Create all party IDs in one pass + for _, peerID := range readyPeerIDs { + partyID := createPartyID(peerID, label, version) + if peerID == n.nodeID { + self = partyID } + partyIDs = append(partyIDs, partyID) + } + + // Sort party IDs in place + all = tss.SortPartyIDs(partyIDs, 0) + return +} + +// createPartyID creates a new party ID for the given node ID, label and version +// It returns the party ID: random string +// Moniker: for routing messages +// Key: for mpc internal use (need persistent storage) +func createPartyID(nodeID string, label string, version int) *tss.PartyID { + partyID := uuid.NewString() + var key *big.Int + if version == BackwardCompatibleVersion { + key = big.NewInt(0).SetBytes([]byte(nodeID)) + } else { + key = big.NewInt(0).SetBytes([]byte(nodeID + ":" + strconv.Itoa(version))) } - allPartyIDs := tss.SortPartyIDs(partyIDs, 0) - return selfPartyID, allPartyIDs + return tss.NewPartyID(partyID, label, key) } func (p *Node) Close() { @@ -334,3 +360,21 @@ func (p *Node) generatePreParams() []*keygen.LocalPreParams { logger.Info("Generate pre params successfully!", "elapsed", time.Since(start).Milliseconds()) return preParams } + +func (p *Node) getVersion(sessionType SessionType, walletID string) int { + var composeKey string + switch sessionType { + case SessionTypeECDSA: + composeKey = fmt.Sprintf("ecdsa:%s", walletID) + case SessionTypeEDDSA: + composeKey = fmt.Sprintf("eddsa:%s", walletID) + default: + logger.Fatal("Unknown session type", errors.New("Unknown session type")) + } + keyinfo, err := p.keyinfoStore.Get(composeKey) + if err != nil { + logger.Error("Get keyinfo failed", err, "walletID", walletID) + return DefaultVersion + } + return keyinfo.Version +} diff --git a/pkg/mpc/node_test.go b/pkg/mpc/node_test.go index dafacda..e578d08 100644 --- a/pkg/mpc/node_test.go +++ b/pkg/mpc/node_test.go @@ -35,7 +35,7 @@ import ( // } func TestPartyIDToNodeID(t *testing.T) { - partyID := CreatePartyID("4d8cb873-dc86-4776-b6f6-cf5c668f6468", "keygen") + partyID := createPartyID("4d8cb873-dc86-4776-b6f6-cf5c668f6468", "keygen", 1) nodeID := PartyIDToNodeID(partyID) assert.Equal(t, nodeID, "4d8cb873-dc86-4776-b6f6-cf5c668f6468", "NodeID should be equal") } From 2321371fea801c5f0d11d1f2a92b35028c22a8b2 Mon Sep 17 00:00:00 2001 From: vietddude Date: Wed, 2 Jul 2025 14:24:37 +0700 Subject: [PATCH 07/25] complete reshare ecdsa supports input threshold and peer ids --- cmd/mpcium/main.go | 4 + pkg/client/client.go | 4 + pkg/eventconsumer/event_consumer.go | 195 ++++++++++++++-------------- pkg/identity/identity.go | 3 +- pkg/messaging/message_queue.go | 7 +- pkg/mpc/ecdsa_keygen_session.go | 4 +- pkg/mpc/ecdsa_resharing_session.go | 87 +++++++------ pkg/mpc/ecdsa_rounds.go | 77 ++++++++--- pkg/mpc/ecdsa_signing_session.go | 22 +++- pkg/mpc/node.go | 154 +++++++++++++++------- pkg/mpc/node_test.go | 4 +- pkg/mpc/session.go | 32 ++++- 12 files changed, 375 insertions(+), 218 deletions(-) diff --git a/cmd/mpcium/main.go b/cmd/mpcium/main.go index cff7b70..86d85a2 100644 --- a/cmd/mpcium/main.go +++ b/cmd/mpcium/main.go @@ -132,12 +132,15 @@ func runNode(ctx context.Context, c *cli.Command) error { mqManager := messaging.NewNATsMessageQueueManager("mpc", []string{ "mpc.mpc_keygen_success.*", event.SigningResultTopic, + "mpc.mpc_reshare_success.*", }, natsConn) genKeySuccessQueue := mqManager.NewMessageQueue("mpc_keygen_success") defer genKeySuccessQueue.Close() singingResultQueue := mqManager.NewMessageQueue("signing_result") defer singingResultQueue.Close() + reshareResultQueue := mqManager.NewMessageQueue("mpc_reshare_success") + defer reshareResultQueue.Close() logger.Info("Node is running", "peerID", nodeID, "name", nodeName) @@ -161,6 +164,7 @@ func runNode(ctx context.Context, c *cli.Command) error { pubsub, genKeySuccessQueue, singingResultQueue, + reshareResultQueue, identityStore, ) eventConsumer.Run() diff --git a/pkg/client/client.go b/pkg/client/client.go index b18301f..e3870de 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -127,6 +127,7 @@ func NewMPCClient(opts Options) MPCClient { manager := messaging.NewNATsMessageQueueManager("mpc", []string{ "mpc.mpc_keygen_success.*", "mpc.signing_result.*", + "mpc.mpc_reshare_success.*", }, opts.NatsConn) genKeySuccessQueue := manager.NewMessageQueue("mpc_keygen_success") @@ -272,11 +273,14 @@ func (c *mpcClient) Resharing(msg *types.ResharingMessage) error { func (c *mpcClient) OnResharingResult(callback func(event event.ResharingSuccessEvent)) error { err := c.reshareSuccessQueue.Dequeue(ResharingSuccessTopic, func(msg []byte) error { + logger.Info("Received reshare success message", "raw", string(msg)) var event event.ResharingSuccessEvent err := json.Unmarshal(msg, &event) if err != nil { + logger.Error("Failed to unmarshal reshare success event", err, "raw", string(msg)) return err } + logger.Info("Deserialized reshare success event", "event", event) callback(event) return nil }) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index c1ab756..a308864 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -64,6 +64,7 @@ func NewEventConsumer( pubsub messaging.PubSub, genKeySucecssQueue messaging.MessageQueue, signingResultQueue messaging.MessageQueue, + reshareResultQueue messaging.MessageQueue, identityStore identity.Store, ) EventConsumer { maxConcurrentKeygen := viper.GetInt("max_concurrent_keygen") @@ -76,6 +77,7 @@ func NewEventConsumer( pubsub: pubsub, genKeySucecssQueue: genKeySucecssQueue, signingResultQueue: signingResultQueue, + reshareResultQueue: reshareResultQueue, activeSessions: make(map[string]time.Time), cleanupInterval: 5 * time.Minute, // Run cleanup every 5 minutes sessionTimeout: 30 * time.Minute, // Consider sessions older than 30 minutes stale @@ -288,6 +290,12 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { ) } + // This node is not in the participantPeerIDs, so we don't need to sign + if session == nil { + natMsg.Ack() + logger.Info("This node is not in the participantPeerIDs, so we don't need to sign", "walletID", msg.WalletID, "nodeID", ec.node.ID()) + return + } if err != nil { ec.handleSigningSessionError( @@ -402,159 +410,133 @@ func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkIntern return } } - func (ec *eventConsumer) consumeReshareEvent() error { sub, err := ec.pubsub.Subscribe(MPCReshareEvent, func(natMsg *nats.Msg) { - raw := natMsg.Data var msg types.ResharingMessage - err := json.Unmarshal(raw, &msg) - if err != nil { - logger.Error("Failed to unmarshal signing message", err) + if err := json.Unmarshal(natMsg.Data, &msg); err != nil { + logger.Error("Failed to unmarshal resharing message", err) return } - err = ec.identityStore.VerifyInitiatorMessage(&msg) - if err != nil { + + if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) return } walletID := msg.WalletID - var ( - oldSession mpc.ReshareSession - newSession mpc.ReshareSession - ) - switch msg.KeyType { - case types.KeyTypeSecp256k1: - isNewNode := true - oldSession, err = ec.node.CreateReshareSession( - mpc.SessionTypeECDSA, - msg.WalletID, - ec.mpcThreshold, - msg.NewThreshold, - msg.NodeIDs, - !isNewNode, - ec.reshareResultQueue, - ) - if err != nil { - logger.Error("Failed to create old reshare session", err, "walletID", walletID) - return - } + keyType := msg.KeyType - newSession, err = ec.node.CreateReshareSession( - mpc.SessionTypeECDSA, - msg.WalletID, - ec.mpcThreshold, - msg.NewThreshold, - msg.NodeIDs, - isNewNode, - ec.reshareResultQueue, - ) - case types.KeyTypeEd25519: - oldSession, err = ec.node.CreateReshareSession( - mpc.SessionTypeEDDSA, - msg.WalletID, + // Helper: Tạo session nếu node có tham gia + createSession := func(isNewPeer bool) (mpc.ReshareSession, error) { + return ec.node.CreateReshareSession( + sessionTypeFromKeyType(keyType), + walletID, ec.mpcThreshold, msg.NewThreshold, msg.NodeIDs, - true, + isNewPeer, ec.reshareResultQueue, ) - if err != nil { - logger.Error("Failed to create old reshare session", err, "walletID", walletID) - return - } + } - newSession, err = ec.node.CreateReshareSession( - mpc.SessionTypeEDDSA, - msg.WalletID, - ec.mpcThreshold, - msg.NewThreshold, - msg.NodeIDs, - true, - ec.reshareResultQueue, - ) + oldSession, err := createSession(false) + if err != nil { + logger.Error("Failed to create old reshare session", err, "walletID", walletID) + return } + newSession, err := createSession(true) if err != nil { - logger.Error("Failed to create reshare session", err, "walletID", walletID) + logger.Error("Failed to create new reshare session", err, "walletID", walletID) return } + fmt.Println("old session", oldSession) + fmt.Println("new session", newSession) - successEvent := &event.ResharingSuccessEvent{ - WalletID: walletID, + if oldSession == nil && newSession == nil { + logger.Info("Node is not participating in this reshare (neither old nor new)", "walletID", walletID) + return } - oldSession.Init() - if newSession != nil { - newSession.Init() + successEvent := &event.ResharingSuccessEvent{ + WalletID: walletID, + NewThreshold: msg.NewThreshold, + KeyType: msg.KeyType, } + var wg sync.WaitGroup ctx := context.Background() - ctxOld, doneOld := context.WithCancel(ctx) - var wg sync.WaitGroup - wg.Add(1) - go func() { - for { - select { - case <-ctxOld.Done(): - wg.Done() - return - case err := <-oldSession.ErrChan(): - logger.Error("Reshare session error", err, "oldSession", true) - } - } - }() - // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. - // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). + // Delay để các node chuẩn bị (có thể thay bằng Consul lock sau) time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) - go oldSession.Reshare(doneOld) + if oldSession != nil { + ctxOld, doneOld := context.WithCancel(ctx) + oldSession.Init() + oldSession.ListenToIncomingMessageAsync() + go oldSession.Reshare(doneOld) + + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctxOld.Done(): + return + case err := <-oldSession.ErrChan(): + logger.Error("Old reshare session error", err) + } + } + }() + } if newSession != nil { ctxNew, doneNew := context.WithCancel(ctx) + newSession.Init() + newSession.ListenToIncomingMessageAsync() + go newSession.Reshare(doneNew) + wg.Add(1) go func() { + defer wg.Done() for { select { case <-ctxNew.Done(): successEvent.PubKey = newSession.GetPubKeyResult() - wg.Done() return case err := <-newSession.ErrChan(): - logger.Error("Reshare session error", err, "newSession", true) + logger.Error("New reshare session error", err) } } }() - newSession.ListenToIncomingMessageAsync() - go newSession.Reshare(doneNew) } - oldSession.ListenToIncomingMessageAsync() wg.Wait() - logger.Info("Closing session successfully!", "event", successEvent) - - successEventBytes, err := json.Marshal(successEvent) - if err != nil { - logger.Error("Failed to marshal reshare success event", err) - return - } + logger.Info("Reshare session finished", "walletID", walletID, "pubKey", fmt.Sprintf("%x", successEvent.PubKey)) - err = ec.reshareResultQueue.Enqueue(fmt.Sprintf(mpc.TypeReshareSuccess, walletID), successEventBytes, &messaging.EnqueueOptions{ - IdempotententKey: fmt.Sprintf(mpc.TypeReshareSuccess, walletID), - }) - if err != nil { - logger.Error("Failed to publish reshare success message", err) - return + if newSession != nil { + successBytes, err := json.Marshal(successEvent) + if err != nil { + logger.Error("Failed to marshal reshare success event", err) + return + } + err = ec.reshareResultQueue.Enqueue( + fmt.Sprintf(mpc.TypeReshareWalletSuccess, walletID), + successBytes, + &messaging.EnqueueOptions{ + IdempotententKey: fmt.Sprintf(mpc.TypeReshareWalletSuccess, walletID), + }) + if err != nil { + logger.Error("Failed to publish reshare success message", err) + return + } + logger.Info("[COMPLETED RESHARE] Successfully published", "walletID", walletID) + } else { + logger.Info("[COMPLETED RESHARE] Done (not a new party)", "walletID", walletID) } - - logger.Info("[COMPLETED RESHARE] Reshare completed successfully", "walletID", walletID) }) ec.reshareSub = sub - if err != nil { - return err - } - return nil + return err } // Add a cleanup routine that runs periodically @@ -636,3 +618,14 @@ func (ec *eventConsumer) Close() error { return nil } + +func sessionTypeFromKeyType(keyType types.KeyType) mpc.SessionType { + switch keyType { + case types.KeyTypeSecp256k1: + return mpc.SessionTypeECDSA + case types.KeyTypeEd25519: + return mpc.SessionTypeEDDSA + default: + panic(fmt.Sprintf("unsupported key type: %v", keyType)) + } +} diff --git a/pkg/identity/identity.go b/pkg/identity/identity.go index 863a696..4d281c2 100644 --- a/pkg/identity/identity.go +++ b/pkg/identity/identity.go @@ -8,6 +8,7 @@ import ( "io" "os" "path/filepath" + "strings" "sync" "syscall" @@ -264,5 +265,5 @@ func (s *fileStore) VerifyInitiatorMessage(msg types.InitiatorMessage) error { } func partyIDToNodeID(partyID *tss.PartyID) string { - return string(partyID.KeyInt().Bytes()) + return strings.Split(string(partyID.KeyInt().Bytes()), ":")[0] } diff --git a/pkg/messaging/message_queue.go b/pkg/messaging/message_queue.go index d5a1c7f..5819d9f 100644 --- a/pkg/messaging/message_queue.go +++ b/pkg/messaging/message_queue.go @@ -64,7 +64,7 @@ func NewNATsMessageQueueManager(queueName string, subjectWildCards []string, nc if err != nil { logger.Fatal("Error creating JetStream stream: ", err) } - logger.Info("Creating apex NATs Jetstream context successfully!") + logger.Info("Creating apex NATs Jetstream context successfully!", "streamName", queueName, "subjects", subjectWildCards) return &NATsMessageQueueManager{ queueName: queueName, @@ -87,7 +87,7 @@ func (m *NATsMessageQueueManager) NewMessageQueue(consumerName string) MessageQu }, MaxDeliver: 3, } - logger.Info("Creating consumer for subject", "config", cfg) + logger.Info("Creating consumer for subject", "consumerName", consumerName, "queueName", m.queueName, "filterSubject", consumerWildCard, "config", cfg) consumer, err := m.js.CreateOrUpdateConsumer(context.Background(), m.queueName, cfg) if err != nil { logger.Fatal("Error creating JetStream consumer: ", err) @@ -103,7 +103,7 @@ func (mq *msgQueue) Enqueue(topic string, message []byte, options *EnqueueOption header.Add("Nats-Msg-Id", options.IdempotententKey) } - logger.Info("Publishing message", "topic", topic) + logger.Info("Publishing message", "topic", topic, "consumerName", mq.consumerName) _, err := mq.js.PublishMsg(context.Background(), &nats.Msg{ Subject: topic, Data: message, @@ -111,6 +111,7 @@ func (mq *msgQueue) Enqueue(topic string, message []byte, options *EnqueueOption }) if err != nil { + logger.Error("Failed to publish message to JetStream", err, "topic", topic, "consumerName", mq.consumerName) return fmt.Errorf("Error enqueueing message: %w", err) } diff --git a/pkg/mpc/ecdsa_keygen_session.go b/pkg/mpc/ecdsa_keygen_session.go index c90cd79..99c953e 100644 --- a/pkg/mpc/ecdsa_keygen_session.go +++ b/pkg/mpc/ecdsa_keygen_session.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" + "strconv" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" "github.com/bnb-chain/tss-lib/v2/tss" @@ -103,7 +104,7 @@ func (s *ecdsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(s.walletID), keyBytes) + err = s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err @@ -113,6 +114,7 @@ func (s *ecdsaKeygenSession) GenerateKey(done func()) { keyInfo := keyinfo.KeyInfo{ ParticipantPeerIDs: s.participantPeerIDs, Threshold: s.threshold, + Version: s.GetVersion(), } err = s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo) diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index 412f562..1bfdfe5 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -4,11 +4,11 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" + "strconv" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" "github.com/bnb-chain/tss-lib/v2/ecdsa/resharing" "github.com/bnb-chain/tss-lib/v2/tss" - "github.com/fystack/mpcium/pkg/common/errors" "github.com/fystack/mpcium/pkg/encoding" "github.com/fystack/mpcium/pkg/identity" "github.com/fystack/mpcium/pkg/keyinfo" @@ -17,10 +17,6 @@ import ( "github.com/fystack/mpcium/pkg/messaging" ) -const ( - TypeReshareSuccess = "mpc.mpc_reshare_success.%s" -) - type ReshareSession interface { Session Init() @@ -31,16 +27,11 @@ type ReshareSession interface { type ecdsaReshareSession struct { *session isNewParty bool + newPeerIDs []string reshareParams *tss.ReSharingParameters endCh chan *keygen.LocalPartySaveData } -type ReshareSuccessEvent struct { - WalletID string `json:"wallet_id"` - ECDSAPubKey []byte `json:"ecdsa_pub_key"` - EDDSAPubKey []byte `json:"eddsa_pub_key"` -} - func NewECDSAReshareSession( walletID string, pubSub messaging.PubSub, @@ -56,6 +47,7 @@ func NewECDSAReshareSession( keyinfoStore keyinfo.Store, resultQueue messaging.MessageQueue, identityStore identity.Store, + newPeerIDs []string, isNewParty bool, ) *ecdsaReshareSession { session := session{ @@ -101,6 +93,7 @@ func NewECDSAReshareSession( session: &session, reshareParams: reshareParams, isNewParty: isNewParty, + newPeerIDs: newPeerIDs, endCh: make(chan *keygen.LocalPartySaveData), } } @@ -108,25 +101,44 @@ func NewECDSAReshareSession( func (s *ecdsaReshareSession) Init() { logger.Infof("Initializing resharing session with partyID: %s, newPartyIDs %s", s.selfPartyID, s.partyIDs) var share keygen.LocalPartySaveData + if s.isNewParty { - // Initialize empty share data for new party + // New party → generate empty share share = keygen.NewLocalPartySaveData(len(s.partyIDs)) share.LocalPreParams = *s.preParams } else { - keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) + // Old party → load from kvstore with backward compatibility + var ( + key string + keyData []byte + err error + ) + + // Try versioned key first if version > 0 + if s.GetVersion() > 0 { + key = s.composeKey(fmt.Sprintf("%s_v%d", s.walletID, s.GetVersion())) + keyData, err = s.kvstore.Get(key) + } + + // If version == 0 or previous key not found, fall back to unversioned key + if err != nil || s.GetVersion() == 0 { + key = s.composeKey(s.walletID) + keyData, err = s.kvstore.Get(key) + } + if err != nil { - s.ErrCh <- errors.Wrap(err, "Failed to get wallet data from KVStore") + s.ErrCh <- fmt.Errorf("failed to get wallet data from KVStore (key=%s): %w", key, err) return } - err = json.Unmarshal(keyData, &share) - if err != nil { + if err := json.Unmarshal(keyData, &share); err != nil { s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) return } } s.party = resharing.NewLocalParty(s.reshareParams, share, s.outCh, s.endCh) + logger.Infof("[INITIALIZED] Initialized resharing session successfully partyID: %s, peerIDs %s, walletID %s, oldThreshold = %d, newThreshold = %d", s.selfPartyID, s.partyIDs, s.walletID, s.threshold, s.reshareParams.NewThreshold()) } @@ -142,30 +154,31 @@ func (s *ecdsaReshareSession) Reshare(done func()) { for { select { case saveData := <-s.endCh: - keyBytes, err := json.Marshal(saveData) - if err != nil { - s.ErrCh <- err - return - } + // skip for old committee + if saveData.ECDSAPub != nil { - if err := s.kvstore.Put(s.composeKey(s.walletID), keyBytes); err != nil { - s.ErrCh <- err - return - } + keyBytes, err := json.Marshal(saveData) + if err != nil { + s.ErrCh <- err + return + } - keyInfo := keyinfo.KeyInfo{ - ParticipantPeerIDs: s.participantPeerIDs, - Threshold: s.reshareParams.NewThreshold(), - } + if err := s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes); err != nil { + s.ErrCh <- err + return + } - // Save key info with resharing flag - if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { - s.ErrCh <- err - return - } + keyInfo := keyinfo.KeyInfo{ + ParticipantPeerIDs: s.newPeerIDs, + Threshold: s.reshareParams.NewThreshold(), + Version: s.GetVersion(), + } - // skip for old committee - if saveData.ECDSAPub != nil { + // Save key info with resharing flag + if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { + s.ErrCh <- err + return + } // Get public key publicKey := saveData.ECDSAPub pubKey := &ecdsa.PublicKey{ @@ -189,7 +202,7 @@ func (s *ecdsaReshareSession) Reshare(done func()) { } done() - err = s.Close() + err := s.Close() if err != nil { logger.Error("Failed to close session", err) } diff --git a/pkg/mpc/ecdsa_rounds.go b/pkg/mpc/ecdsa_rounds.go index 8e70f7a..b7ddd2e 100644 --- a/pkg/mpc/ecdsa_rounds.go +++ b/pkg/mpc/ecdsa_rounds.go @@ -2,26 +2,35 @@ package mpc import ( "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" + "github.com/bnb-chain/tss-lib/v2/ecdsa/resharing" "github.com/bnb-chain/tss-lib/v2/ecdsa/signing" "github.com/bnb-chain/tss-lib/v2/tss" "github.com/fystack/mpcium/pkg/common/errors" ) const ( - KEYGEN1 = "KGRound1Message" - KEYGEN2aUnicast = "KGRound2Message1" - KEYGEN2b = "KGRound2Message2" - KEYGEN3 = "KGRound3Message" - KEYSIGN1aUnicast = "SignRound1Message1" - KEYSIGN1b = "SignRound1Message2" - KEYSIGN2Unicast = "SignRound2Message" - KEYSIGN3 = "SignRound3Message" - KEYSIGN4 = "SignRound4Message" - KEYSIGN5 = "SignRound5Message" - KEYSIGN6 = "SignRound6Message" - KEYSIGN7 = "SignRound7Message" - KEYSIGN8 = "SignRound8Message" - KEYSIGN9 = "SignRound9Message" + KEYGEN1 = "KGRound1Message" + KEYGEN2aUnicast = "KGRound2Message1" + KEYGEN2b = "KGRound2Message2" + KEYGEN3 = "KGRound3Message" + KEYSIGN1aUnicast = "SignRound1Message1" + KEYSIGN1b = "SignRound1Message2" + KEYSIGN2Unicast = "SignRound2Message" + KEYSIGN3 = "SignRound3Message" + KEYSIGN4 = "SignRound4Message" + KEYSIGN5 = "SignRound5Message" + KEYSIGN6 = "SignRound6Message" + KEYSIGN7 = "SignRound7Message" + KEYSIGN8 = "SignRound8Message" + KEYSIGN9 = "SignRound9Message" + KEYRESHARING1Unicast = "DGRound1Message" + KEYRESHARING2aUnicast = "DGRound2Message1" + KEYRESHARING2bUnicast = "DGRound2Message2" + KEYRESHARING3aUnicast = "DGRound3Message1" + KEYRESHARING3b = "DGRound3Message2" + KEYRESHARING4a = "DGRound4Message1" + KEYRESHARING4bUnicast = "DGRound4Message2" + TSSKEYGENROUNDS = 4 TSSKEYSIGNROUNDS = 10 ) @@ -113,8 +122,46 @@ func GetEcdsaMsgRound(msg []byte, partyID *tss.PartyID, isBroadcast bool) (Round Index: 9, RoundMsg: KEYSIGN9, }, nil - + case *resharing.DGRound1Message: + return RoundInfo{ + Index: 0, + RoundMsg: KEYRESHARING1Unicast, + }, nil + case *resharing.DGRound2Message1: + return RoundInfo{ + Index: 1, + RoundMsg: KEYRESHARING2aUnicast, + }, nil + case *resharing.DGRound2Message2: + return RoundInfo{ + Index: 2, + RoundMsg: KEYRESHARING2bUnicast, + }, nil + case *resharing.DGRound3Message1: + return RoundInfo{ + Index: 3, + RoundMsg: KEYRESHARING3aUnicast, + }, nil + case *resharing.DGRound3Message2: + return RoundInfo{ + Index: 4, + RoundMsg: KEYRESHARING3b, + }, nil + case *resharing.DGRound4Message1: + return RoundInfo{ + Index: 5, + RoundMsg: KEYRESHARING4a, + }, nil + case *resharing.DGRound4Message2: + return RoundInfo{ + Index: 6, + RoundMsg: KEYRESHARING4bUnicast, + }, nil default: return RoundInfo{}, errors.New("unknown round") } } + +func IsReshareRound(roundMsg string) bool { + return roundMsg == KEYRESHARING1Unicast || roundMsg == KEYRESHARING2aUnicast || roundMsg == KEYRESHARING2bUnicast || roundMsg == KEYRESHARING3aUnicast || roundMsg == KEYRESHARING3b || roundMsg == KEYRESHARING4a || roundMsg == KEYRESHARING4bUnicast +} diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 9c8af7d..f87bcdd 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/big" + "strconv" "github.com/bnb-chain/tss-lib/v2/common" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" @@ -92,12 +93,6 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { logger.Infof("Initializing signing session with partyID: %s, peerIDs %s", s.selfPartyID, s.partyIDs) ctx := tss.NewPeerContext(s.partyIDs) params := tss.NewParameters(tss.S256(), ctx, s.selfPartyID, len(s.partyIDs), s.threshold) - - keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } - keyInfo, err := s.keyinfoStore.Get(s.composeKey(s.walletID)) if err != nil { return errors.Wrap(err, "Failed to get key info data") @@ -119,6 +114,21 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { } logger.Info("Have enough participants to sign", "participants", s.participantPeerIDs) + + var keyData []byte + if keyInfo.Version == 0 { + logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID)) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") + } + } else { + logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(s.GetVersion()))) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") + } + } // Check if all the participants of the key are present var data keygen.LocalPartySaveData err = json.Unmarshal(keyData, &data) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index f09f515..1797557 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -45,7 +45,7 @@ type Node struct { peerRegistry PeerRegistry } -func PartyIDToNodeID(partyID *tss.PartyID) string { +func PartyIDToRoutingDest(partyID *tss.PartyID) string { return string(partyID.KeyInt().Bytes()) } @@ -159,17 +159,41 @@ func (p *Node) CreateSigningSession( resultQueue messaging.MessageQueue, ) (SigningSession, error) { version := p.getVersion(sessionType, walletID) - readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) switch sessionType { case SessionTypeECDSA: + keyID := fmt.Sprintf("ecdsa:%s", walletID) + keyInfo, err := p.keyinfoStore.Get(keyID) + if err != nil { + return nil, err + } + + readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() + + // Ensure all participants are ready + for _, peerID := range keyInfo.ParticipantPeerIDs { + if !slices.Contains(readyPeers, peerID) { + return nil, fmt.Errorf("participant peer %s is not ready", peerID) + } + } + + // Warn and skip if current node is not a participant + if !slices.Contains(keyInfo.ParticipantPeerIDs, p.nodeID) { + logger.Warn("This node is not in the participant list", + "walletID", walletID, + "nodeID", p.nodeID, + ) + return nil, nil + } + + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, keyInfo.ParticipantPeerIDs, version) + return newECDSASigningSession( walletID, txID, networkInternalCode, p.pubSub, p.direct, - readyPeerIDs, + keyInfo.ParticipantPeerIDs, selfPartyID, allPartyIDs, threshold, @@ -180,13 +204,39 @@ func (p *Node) CreateSigningSession( p.identityStore, ), nil case SessionTypeEDDSA: + keyID := fmt.Sprintf("eddsa:%s", walletID) + keyInfo, err := p.keyinfoStore.Get(keyID) + if err != nil { + return nil, err + } + + readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() + + // Ensure all participants are ready + for _, peerID := range keyInfo.ParticipantPeerIDs { + if !slices.Contains(readyPeers, peerID) { + return nil, fmt.Errorf("participant peer %s is not ready", peerID) + } + } + + // Warn and skip if current node is not a participant + if !slices.Contains(keyInfo.ParticipantPeerIDs, p.nodeID) { + logger.Warn("This node is not in the participant list", + "walletID", walletID, + "nodeID", p.nodeID, + ) + return nil, nil + } + + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, keyInfo.ParticipantPeerIDs, version) + return NewEDDSASigningSession( walletID, txID, networkInternalCode, p.pubSub, p.direct, - readyPeerIDs, + keyInfo.ParticipantPeerIDs, selfPartyID, allPartyIDs, threshold, @@ -199,7 +249,6 @@ func (p *Node) CreateSigningSession( return nil, errors.New("Unknown session type") } - func (p *Node) CreateReshareSession( sessionType SessionType, walletID string, @@ -209,38 +258,63 @@ func (p *Node) CreateReshareSession( isNewPeer bool, successQueue messaging.MessageQueue, ) (ReshareSession, error) { + // Ensure enough ready peers if !p.peerRegistry.ArePeersReady() { - return nil, fmt.Errorf("Not enough peers to create reshare session! Expected %d, got %d", newThreshold+1, p.peerRegistry.GetReadyPeersCount()) + return nil, fmt.Errorf( + "not enough peers to create reshare session! Expected at least %d, got %d", + newThreshold+1, + p.peerRegistry.GetReadyPeersCount(), + ) } + + // Validate all new peers are ready readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() for _, peerID := range newPeerIDs { - if slices.Contains(readyPeerIDs, peerID) { - continue + if !slices.Contains(readyPeerIDs, peerID) { + return nil, fmt.Errorf("new peer %s is not ready", peerID) } - return nil, fmt.Errorf("new peer %s is not ready", peerID) - } - - // if current node is not in newPeerIDs and isNewPeer is true, return nil - if !slices.Contains(newPeerIDs, p.nodeID) && isNewPeer { - return nil, nil } version := p.getVersion(sessionType, walletID) - var ( - selfPartyID *tss.PartyID - preParams *keygen.LocalPreParams - ) - oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) - newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) - if isNewPeer { - selfPartyID = newSelf - preParams = p.ecdsaPreParams[1] - } else { - selfPartyID = oldSelf - preParams = p.ecdsaPreParams[0] - } + switch sessionType { case SessionTypeECDSA: + oldKeyInfo, err := p.keyinfoStore.Get(fmt.Sprintf("ecdsa:%s", walletID)) + if err != nil { + return nil, fmt.Errorf("failed to get old key info: %w", err) + } + + isInOldCommittee := slices.Contains(oldKeyInfo.ParticipantPeerIDs, p.nodeID) + isInNewCommittee := slices.Contains(newPeerIDs, p.nodeID) + + // Determine whether we should run this role + if isNewPeer && !isInNewCommittee { + logger.Info("Skipping new session: this node is not in new committee", + "walletID", walletID, "nodeID", p.nodeID) + return nil, nil + } + if !isNewPeer && !isInOldCommittee { + logger.Info("Skipping old session: this node is not in old committee", + "walletID", walletID, "nodeID", p.nodeID) + return nil, nil + } + + // Generate party IDs + oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, oldKeyInfo.ParticipantPeerIDs, version) + newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) + + // Pick correct identity and params + var selfPartyID *tss.PartyID + var preParams *keygen.LocalPreParams + + if isNewPeer { + selfPartyID = newSelf + preParams = p.ecdsaPreParams[1] + } else { + selfPartyID = oldSelf + preParams = p.ecdsaPreParams[0] + } + return NewECDSAReshareSession( walletID, p.pubSub, @@ -256,27 +330,15 @@ func (p *Node) CreateReshareSession( p.keyinfoStore, successQueue, p.identityStore, + newPeerIDs, isNewPeer, ), nil - case SessionTypeEDDSA: - return NewEDDSAReshareSession( - walletID, - p.pubSub, - p.direct, - readyPeerIDs, - selfPartyID, - oldAllPartyIDs, - newAllPartyIDs, - oldThreshold, - newThreshold, - p.kvstore, - p.keyinfoStore, - successQueue, - p.identityStore, - isNewPeer, - ), nil + + // case SessionTypeEDDSA: + // return ..., nil + default: - return nil, errors.New("Unknown session type") + return nil, fmt.Errorf("unsupported session type: %v", sessionType) } } diff --git a/pkg/mpc/node_test.go b/pkg/mpc/node_test.go index e578d08..3e9f889 100644 --- a/pkg/mpc/node_test.go +++ b/pkg/mpc/node_test.go @@ -36,6 +36,6 @@ import ( func TestPartyIDToNodeID(t *testing.T) { partyID := createPartyID("4d8cb873-dc86-4776-b6f6-cf5c668f6468", "keygen", 1) - nodeID := PartyIDToNodeID(partyID) - assert.Equal(t, nodeID, "4d8cb873-dc86-4776-b6f6-cf5c668f6468", "NodeID should be equal") + nodeID := PartyIDToRoutingDest(partyID) + assert.Equal(t, nodeID, "4d8cb873-dc86-4776-b6f6-cf5c668f6468:1", "NodeID should be equal") } diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index e307c3d..55148c7 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -2,6 +2,7 @@ package mpc import ( "fmt" + "strconv" "strings" "sync" @@ -21,6 +22,7 @@ type SessionType string const ( TypeGenerateWalletSuccess = "mpc.mpc_keygen_success.%s" + TypeReshareWalletSuccess = "mpc.mpc_reshare_success.%s" SessionTypeECDSA SessionType = "session_ecdsa" SessionTypeEDDSA SessionType = "session_eddsa" ) @@ -103,7 +105,11 @@ func (s *session) handleTssMessage(keyshare tss.Message) { s.ErrCh <- fmt.Errorf("failed to marshal tss message: %w", err) return } - + toIDs := make([]string, len(routing.To)) + for i, id := range routing.To { + toIDs[i] = id.String() + } + logger.Info(fmt.Sprintf("%s Sending message", s.sessionType), "from", s.selfPartyID.String(), "to", toIDs, "isBroadcast", routing.IsBroadcast) if routing.IsBroadcast && len(routing.To) == 0 { err := s.pubSub.Publish(s.topicComposer.ComposeBroadcastTopic(), msg) if err != nil { @@ -112,7 +118,7 @@ func (s *session) handleTssMessage(keyshare tss.Message) { } } else { for _, to := range routing.To { - nodeID := PartyIDToNodeID(to) + nodeID := PartyIDToRoutingDest(to) topic := s.topicComposer.ComposeDirectTopic(nodeID) err := s.direct.Send(topic, msg) if err != nil { @@ -146,10 +152,15 @@ func (s *session) receiveTssMessage(rawMsg []byte) { s.ErrCh <- errors.Wrap(err, "Broken TSS Share") return } - - logger.Debug(fmt.Sprintf("%s Received message", s.sessionType), "from", msg.From.String(), "to", strings.Join(toIDs, ","), "isBroadcast", msg.IsBroadcast, "round", round.RoundMsg) + logger.Info("Received message", "round", round.RoundMsg, "isBroadcast", msg.IsBroadcast, "to", toIDs, "from", msg.From.String(), "self", s.selfPartyID.String()) isBroadcast := msg.IsBroadcast && len(msg.To) == 0 - isToSelf := len(msg.To) == 1 && ComparePartyIDs(msg.To[0], s.selfPartyID) + var isToSelf bool + for _, to := range msg.To { + if ComparePartyIDs(to, s.selfPartyID) { + isToSelf = true + break + } + } if isBroadcast || isToSelf { s.mu.Lock() @@ -191,7 +202,7 @@ func (s *session) ListenToIncomingMessageAsync() { s.broadcastSub = sub }() - nodeID := PartyIDToNodeID(s.selfPartyID) + nodeID := PartyIDToRoutingDest(s.selfPartyID) targetID := s.topicComposer.ComposeDirectTopic(nodeID) sub, err := s.direct.Listen(targetID, func(msg []byte) { go s.receiveTssMessage(msg) // async for avoid timeout @@ -222,3 +233,12 @@ func (s *session) GetPubKeyResult() []byte { func (s *session) ErrChan() <-chan error { return s.ErrCh } + +func (s *session) GetVersion() int { + fmt.Println("self party id", string(s.selfPartyID.GetKey())) + version, err := strconv.Atoi(strings.Split(string(s.selfPartyID.GetKey()), ":")[1]) + if err != nil { + return 0 + } + return version +} From 0723c0dd0644dc26d73575f3ac9d196da1b32147 Mon Sep 17 00:00:00 2001 From: vietddude Date: Wed, 2 Jul 2025 14:45:08 +0700 Subject: [PATCH 08/25] support eddsa reshare --- pkg/mpc/ecdsa_signing_session.go | 1 + pkg/mpc/eddsa_keygen_session.go | 4 +- pkg/mpc/eddsa_resharing_session.go | 102 +++++++++++++++++----------- pkg/mpc/eddsa_rounds.go | 50 ++++++++++++-- pkg/mpc/eddsa_signing_session.go | 22 ++++-- pkg/mpc/node.go | 103 +++++++++++++++++++---------- 6 files changed, 194 insertions(+), 88 deletions(-) diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index f87bcdd..2735823 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -93,6 +93,7 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { logger.Infof("Initializing signing session with partyID: %s, peerIDs %s", s.selfPartyID, s.partyIDs) ctx := tss.NewPeerContext(s.partyIDs) params := tss.NewParameters(tss.S256(), ctx, s.selfPartyID, len(s.partyIDs), s.threshold) + keyInfo, err := s.keyinfoStore.Get(s.composeKey(s.walletID)) if err != nil { return errors.Wrap(err, "Failed to get key info data") diff --git a/pkg/mpc/eddsa_keygen_session.go b/pkg/mpc/eddsa_keygen_session.go index d3489ac..8ada074 100644 --- a/pkg/mpc/eddsa_keygen_session.go +++ b/pkg/mpc/eddsa_keygen_session.go @@ -3,6 +3,7 @@ package mpc import ( "encoding/json" "fmt" + "strconv" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" "github.com/bnb-chain/tss-lib/v2/tss" @@ -91,7 +92,7 @@ func (s *eddsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(s.walletID), keyBytes) + err = s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err @@ -101,6 +102,7 @@ func (s *eddsaKeygenSession) GenerateKey(done func()) { keyInfo := keyinfo.KeyInfo{ ParticipantPeerIDs: s.participantPeerIDs, Threshold: s.threshold, + Version: s.GetVersion(), } err = s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo) diff --git a/pkg/mpc/eddsa_resharing_session.go b/pkg/mpc/eddsa_resharing_session.go index 7f66d62..4b2cd54 100644 --- a/pkg/mpc/eddsa_resharing_session.go +++ b/pkg/mpc/eddsa_resharing_session.go @@ -3,12 +3,12 @@ package mpc import ( "encoding/json" "fmt" + "strconv" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" "github.com/bnb-chain/tss-lib/v2/eddsa/resharing" "github.com/bnb-chain/tss-lib/v2/tss" "github.com/decred/dcrd/dcrec/edwards/v2" - "github.com/fystack/mpcium/pkg/common/errors" "github.com/fystack/mpcium/pkg/identity" "github.com/fystack/mpcium/pkg/keyinfo" "github.com/fystack/mpcium/pkg/kvstore" @@ -19,6 +19,7 @@ import ( type eddsaReshareSession struct { *session isNewParty bool + newPeerIDs []string reshareParams *tss.ReSharingParameters endCh chan *keygen.LocalPartySaveData } @@ -37,6 +38,7 @@ func NewEDDSAReshareSession( keyinfoStore keyinfo.Store, resultQueue messaging.MessageQueue, identityStore identity.Store, + newPeerIDs []string, isNewParty bool, ) *eddsaReshareSession { session := session{ @@ -83,6 +85,7 @@ func NewEDDSAReshareSession( session: &session, reshareParams: reshareParams, isNewParty: isNewParty, + newPeerIDs: newPeerIDs, endCh: make(chan *keygen.LocalPartySaveData), } } @@ -94,14 +97,31 @@ func (s *eddsaReshareSession) Init() { // Initialize empty share data for new party share = keygen.NewLocalPartySaveData(len(s.partyIDs)) } else { - keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) + // Old party → load from kvstore with backward compatibility + var ( + key string + keyData []byte + err error + ) + + // Try versioned key first if version > 0 + if s.GetVersion() > 0 { + key = s.composeKey(fmt.Sprintf("%s_v%d", s.walletID, s.GetVersion())) + keyData, err = s.kvstore.Get(key) + } + + // If version == 0 or previous key not found, fall back to unversioned key + if err != nil || s.GetVersion() == 0 { + key = s.composeKey(s.walletID) + keyData, err = s.kvstore.Get(key) + } + if err != nil { - s.ErrCh <- errors.Wrap(err, "Failed to get wallet data from KVStore") + s.ErrCh <- fmt.Errorf("failed to get wallet data from KVStore (key=%s): %w", key, err) return } - err = json.Unmarshal(keyData, &share) - if err != nil { + if err := json.Unmarshal(keyData, &share); err != nil { s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) return } @@ -122,50 +142,52 @@ func (s *eddsaReshareSession) Reshare(done func()) { for { select { case saveData := <-s.endCh: - keyBytes, err := json.Marshal(saveData) - if err != nil { - s.ErrCh <- err - return - } + if saveData.EDDSAPub != nil { + keyBytes, err := json.Marshal(saveData) + if err != nil { + s.ErrCh <- err + return + } - if err := s.kvstore.Put(s.composeKey(s.walletID), keyBytes); err != nil { - s.ErrCh <- err - return - } + if err := s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes); err != nil { + s.ErrCh <- err + return + } - keyInfo := keyinfo.KeyInfo{ - ParticipantPeerIDs: s.participantPeerIDs, - Threshold: s.reshareParams.NewThreshold(), - } + keyInfo := keyinfo.KeyInfo{ + ParticipantPeerIDs: s.newPeerIDs, + Threshold: s.reshareParams.NewThreshold(), + Version: s.GetVersion(), + } - // Save key info with resharing flag - if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { - s.ErrCh <- err - return - } + // Save key info with resharing flag + if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &keyInfo); err != nil { + s.ErrCh <- err + return + } - // skip for old committee - if saveData.EDDSAPub != nil { + // skip for old committee + if saveData.EDDSAPub != nil { - // Get public key - publicKey := saveData.EDDSAPub - pkX, pkY := publicKey.X(), publicKey.Y() - pk := edwards.PublicKey{ - Curve: tss.Edwards(), - X: pkX, - Y: pkY, - } + // Get public key + publicKey := saveData.EDDSAPub + pkX, pkY := publicKey.X(), publicKey.Y() + pk := edwards.PublicKey{ + Curve: tss.Edwards(), + X: pkX, + Y: pkY, + } - pubKeyBytes := pk.SerializeCompressed() - s.pubkeyBytes = pubKeyBytes + pubKeyBytes := pk.SerializeCompressed() + s.pubkeyBytes = pubKeyBytes - logger.Info("Generated public key bytes", - "walletID", s.walletID, - "pubKeyBytes", pubKeyBytes) + logger.Info("Generated public key bytes", + "walletID", s.walletID, + "pubKeyBytes", pubKeyBytes) + } } - done() - err = s.Close() + err := s.Close() if err != nil { logger.Error("Failed to close session", err) } diff --git a/pkg/mpc/eddsa_rounds.go b/pkg/mpc/eddsa_rounds.go index 01519d0..88864f3 100644 --- a/pkg/mpc/eddsa_rounds.go +++ b/pkg/mpc/eddsa_rounds.go @@ -2,6 +2,7 @@ package mpc import ( "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" + "github.com/bnb-chain/tss-lib/v2/eddsa/resharing" "github.com/bnb-chain/tss-lib/v2/eddsa/signing" "github.com/bnb-chain/tss-lib/v2/tss" "github.com/fystack/mpcium/pkg/common/errors" @@ -16,14 +17,21 @@ type RoundInfo struct { } const ( - EDDSA_KEYGEN1 = "KGRound1Message" - EDDSA_KEYGEN2aUnicast = "KGRound2Message1" - EDDSA_KEYGEN2b = "KGRound2Message2" - EDDSA_KEYSIGN1 = "SignRound1Message" - EDDSA_KEYSIGN2 = "SignRound2Message" - EDDSA_KEYSIGN3 = "SignRound3Message" + EDDSA_KEYGEN1 = "KGRound1Message" + EDDSA_KEYGEN2aUnicast = "KGRound2Message1" + EDDSA_KEYGEN2b = "KGRound2Message2" + EDDSA_KEYSIGN1 = "SignRound1Message" + EDDSA_KEYSIGN2 = "SignRound2Message" + EDDSA_KEYSIGN3 = "SignRound3Message" + EDDSA_RESHARING1 = "DGRound1Message" + EDDSA_RESHARING2 = "DGRound2Message" + EDDSA_RESHARING3aUnicast = "DGRound3Message1" + EDDSA_RESHARING3bUnicast = "DGRound3Message2" + EDDSA_RESHARING4 = "DGRound4Message" + EDDSA_TSSKEYGENROUNDS = 3 EDDSA_TSSKEYSIGNROUNDS = 3 + EDDSA_RESHARINGROUNDS = 4 ) func GetEddsaMsgRound(msg []byte, partyID *tss.PartyID, isBroadcast bool) (RoundInfo, error) { @@ -68,6 +76,36 @@ func GetEddsaMsgRound(msg []byte, partyID *tss.PartyID, isBroadcast bool) (Round RoundMsg: EDDSA_KEYSIGN3, }, nil + case *resharing.DGRound1Message: + return RoundInfo{ + Index: 0, + RoundMsg: EDDSA_RESHARING1, + }, nil + + case *resharing.DGRound2Message: + return RoundInfo{ + Index: 1, + RoundMsg: EDDSA_RESHARING2, + }, nil + + case *resharing.DGRound3Message1: + return RoundInfo{ + Index: 2, + RoundMsg: EDDSA_RESHARING3aUnicast, + }, nil + + case *resharing.DGRound3Message2: + return RoundInfo{ + Index: 3, + RoundMsg: EDDSA_RESHARING3bUnicast, + }, nil + + case *resharing.DGRound4Message: + return RoundInfo{ + Index: 4, + RoundMsg: EDDSA_RESHARING4, + }, nil + default: return RoundInfo{}, errors.New("unknown round") } diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 1c586da..75da920 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math/big" + "strconv" "github.com/bnb-chain/tss-lib/v2/common" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" @@ -84,11 +85,6 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { ctx := tss.NewPeerContext(s.partyIDs) params := tss.NewParameters(tss.Edwards(), ctx, s.selfPartyID, len(s.partyIDs), s.threshold) - keyData, err := s.kvstore.Get(s.composeKey(s.walletID)) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } - keyInfo, err := s.keyinfoStore.Get(s.composeKey(s.walletID)) if err != nil { return errors.Wrap(err, "Failed to get key info data") @@ -110,6 +106,22 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { } logger.Info("Have enough participants to sign", "participants", s.participantPeerIDs) + + var keyData []byte + if keyInfo.Version == 0 { + logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID)) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") + } + } else { + logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(s.GetVersion()))) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") + } + } + // Check if all the participants of the key are present var data keygen.LocalPartySaveData err = json.Unmarshal(keyData, &data) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 1797557..f47f257 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -249,6 +249,7 @@ func (p *Node) CreateSigningSession( return nil, errors.New("Unknown session type") } + func (p *Node) CreateReshareSession( sessionType SessionType, walletID string, @@ -258,7 +259,7 @@ func (p *Node) CreateReshareSession( isNewPeer bool, successQueue messaging.MessageQueue, ) (ReshareSession, error) { - // Ensure enough ready peers + // 1. Check peer readiness if !p.peerRegistry.ArePeersReady() { return nil, fmt.Errorf( "not enough peers to create reshare session! Expected at least %d, got %d", @@ -267,7 +268,7 @@ func (p *Node) CreateReshareSession( ) } - // Validate all new peers are ready + // 2. Check all new peers are ready readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() for _, peerID := range newPeerIDs { if !slices.Contains(readyPeerIDs, peerID) { @@ -275,46 +276,49 @@ func (p *Node) CreateReshareSession( } } - version := p.getVersion(sessionType, walletID) - - switch sessionType { - case SessionTypeECDSA: - oldKeyInfo, err := p.keyinfoStore.Get(fmt.Sprintf("ecdsa:%s", walletID)) - if err != nil { - return nil, fmt.Errorf("failed to get old key info: %w", err) - } + // 3. Load old key info + keyPrefix, err := sessionKeyPrefix(sessionType) + if err != nil { + return nil, fmt.Errorf("failed to get session key prefix: %w", err) + } + keyInfoKey := fmt.Sprintf("%s:%s", keyPrefix, walletID) + oldKeyInfo, err := p.keyinfoStore.Get(keyInfoKey) + if err != nil { + return nil, fmt.Errorf("failed to get old key info: %w", err) + } - isInOldCommittee := slices.Contains(oldKeyInfo.ParticipantPeerIDs, p.nodeID) - isInNewCommittee := slices.Contains(newPeerIDs, p.nodeID) + isInOldCommittee := slices.Contains(oldKeyInfo.ParticipantPeerIDs, p.nodeID) + isInNewCommittee := slices.Contains(newPeerIDs, p.nodeID) - // Determine whether we should run this role - if isNewPeer && !isInNewCommittee { - logger.Info("Skipping new session: this node is not in new committee", - "walletID", walletID, "nodeID", p.nodeID) - return nil, nil - } - if !isNewPeer && !isInOldCommittee { - logger.Info("Skipping old session: this node is not in old committee", - "walletID", walletID, "nodeID", p.nodeID) - return nil, nil - } + // 4. Skip if not relevant + if isNewPeer && !isInNewCommittee { + logger.Info("Skipping new session: node is not in new committee", "walletID", walletID, "nodeID", p.nodeID) + return nil, nil + } + if !isNewPeer && !isInOldCommittee { + logger.Info("Skipping old session: node is not in old committee", "walletID", walletID, "nodeID", p.nodeID) + return nil, nil + } - // Generate party IDs - oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, oldKeyInfo.ParticipantPeerIDs, version) - newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) + // 5. Generate party IDs + version := p.getVersion(sessionType, walletID) + oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, oldKeyInfo.ParticipantPeerIDs, version) + newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) - // Pick correct identity and params - var selfPartyID *tss.PartyID - var preParams *keygen.LocalPreParams + // 6. Pick identity and call session constructor + var selfPartyID *tss.PartyID + if isNewPeer { + selfPartyID = newSelf + } else { + selfPartyID = oldSelf + } + switch sessionType { + case SessionTypeECDSA: + preParams := p.ecdsaPreParams[0] if isNewPeer { - selfPartyID = newSelf preParams = p.ecdsaPreParams[1] - } else { - selfPartyID = oldSelf - preParams = p.ecdsaPreParams[0] } - return NewECDSAReshareSession( walletID, p.pubSub, @@ -334,8 +338,24 @@ func (p *Node) CreateReshareSession( isNewPeer, ), nil - // case SessionTypeEDDSA: - // return ..., nil + case SessionTypeEDDSA: + return NewEDDSAReshareSession( + walletID, + p.pubSub, + p.direct, + readyPeerIDs, + selfPartyID, + oldAllPartyIDs, + newAllPartyIDs, + oldThreshold, + newThreshold, + p.kvstore, + p.keyinfoStore, + successQueue, + p.identityStore, + newPeerIDs, + isNewPeer, + ), nil default: return nil, fmt.Errorf("unsupported session type: %v", sessionType) @@ -440,3 +460,14 @@ func (p *Node) getVersion(sessionType SessionType, walletID string) int { } return keyinfo.Version } + +func sessionKeyPrefix(sessionType SessionType) (string, error) { + switch sessionType { + case SessionTypeECDSA: + return "ecdsa", nil + case SessionTypeEDDSA: + return "eddsa", nil + default: + return "", fmt.Errorf("unsupported session type: %v", sessionType) + } +} From dc27b9a1bdb05f05785e689bd511e81027005d33 Mon Sep 17 00:00:00 2001 From: vietddude Date: Wed, 2 Jul 2025 14:56:03 +0700 Subject: [PATCH 09/25] remove debug log --- pkg/eventconsumer/event_consumer.go | 2 -- pkg/mpc/session.go | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index a308864..41ecae7 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -449,8 +449,6 @@ func (ec *eventConsumer) consumeReshareEvent() error { logger.Error("Failed to create new reshare session", err, "walletID", walletID) return } - fmt.Println("old session", oldSession) - fmt.Println("new session", newSession) if oldSession == nil && newSession == nil { logger.Info("Node is not participating in this reshare (neither old nor new)", "walletID", walletID) diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index 55148c7..454b7cd 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -109,7 +109,7 @@ func (s *session) handleTssMessage(keyshare tss.Message) { for i, id := range routing.To { toIDs[i] = id.String() } - logger.Info(fmt.Sprintf("%s Sending message", s.sessionType), "from", s.selfPartyID.String(), "to", toIDs, "isBroadcast", routing.IsBroadcast) + logger.Debug(fmt.Sprintf("%s Sending message", s.sessionType), "from", s.selfPartyID.String(), "to", toIDs, "isBroadcast", routing.IsBroadcast) if routing.IsBroadcast && len(routing.To) == 0 { err := s.pubSub.Publish(s.topicComposer.ComposeBroadcastTopic(), msg) if err != nil { @@ -152,7 +152,7 @@ func (s *session) receiveTssMessage(rawMsg []byte) { s.ErrCh <- errors.Wrap(err, "Broken TSS Share") return } - logger.Info("Received message", "round", round.RoundMsg, "isBroadcast", msg.IsBroadcast, "to", toIDs, "from", msg.From.String(), "self", s.selfPartyID.String()) + logger.Debug("Received message", "round", round.RoundMsg, "isBroadcast", msg.IsBroadcast, "to", toIDs, "from", msg.From.String(), "self", s.selfPartyID.String()) isBroadcast := msg.IsBroadcast && len(msg.To) == 0 var isToSelf bool for _, to := range msg.To { @@ -235,7 +235,6 @@ func (s *session) ErrChan() <-chan error { } func (s *session) GetVersion() int { - fmt.Println("self party id", string(s.selfPartyID.GetKey())) version, err := strconv.Atoi(strings.Split(string(s.selfPartyID.GetKey()), ":")[1]) if err != nil { return 0 From a1df6bf2efe2aa5525829ab00612a49f1e6a3a61 Mon Sep 17 00:00:00 2001 From: vietddude Date: Wed, 2 Jul 2025 16:26:15 +0700 Subject: [PATCH 10/25] chore: resolve PR review comments --- examples/reshare/main.go | 3 +-- pkg/client/client.go | 6 +++--- pkg/event/reshare.go | 2 +- pkg/eventconsumer/event_consumer.go | 4 ++-- pkg/mpc/ecdsa_signing_session.go | 2 +- pkg/mpc/eddsa_signing_session.go | 2 +- pkg/mpc/session.go | 12 ++++++++---- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/examples/reshare/main.go b/examples/reshare/main.go index f5c9efb..c5ef802 100644 --- a/examples/reshare/main.go +++ b/examples/reshare/main.go @@ -38,7 +38,6 @@ func main() { NodeIDs: []string{"0ce02715-0ead-48ef-9772-2583316cc860", "ac37e85f-caca-4bee-8a3a-49a0fe35abff"}, // new peer IDs NewThreshold: 1, // t+1 <= len(NodeIDs) KeyType: types.KeyTypeSecp256k1, - Signature: []byte("signature"), } err = mpcClient.Resharing(resharingMsg) if err != nil { @@ -47,7 +46,7 @@ func main() { fmt.Printf("Resharing(%q) sent, awaiting result...\n", resharingMsg.WalletID) // 3) Listen for signing results - err = mpcClient.OnResharingResult(func(evt event.ResharingSuccessEvent) { + err = mpcClient.OnResharingResult(func(evt event.ResharingResultEvent) { logger.Info("Resharing result received", "walletID", evt.WalletID, "pubKey", fmt.Sprintf("%x", evt.PubKey), diff --git a/pkg/client/client.go b/pkg/client/client.go index e3870de..faaa578 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -32,7 +32,7 @@ type MPCClient interface { OnSignResult(callback func(event event.SigningResultEvent)) error Resharing(msg *types.ResharingMessage) error - OnResharingResult(callback func(event event.ResharingSuccessEvent)) error + OnResharingResult(callback func(event event.ResharingResultEvent)) error } type mpcClient struct { @@ -270,11 +270,11 @@ func (c *mpcClient) Resharing(msg *types.ResharingMessage) error { return nil } -func (c *mpcClient) OnResharingResult(callback func(event event.ResharingSuccessEvent)) error { +func (c *mpcClient) OnResharingResult(callback func(event event.ResharingResultEvent)) error { err := c.reshareSuccessQueue.Dequeue(ResharingSuccessTopic, func(msg []byte) error { logger.Info("Received reshare success message", "raw", string(msg)) - var event event.ResharingSuccessEvent + var event event.ResharingResultEvent err := json.Unmarshal(msg, &event) if err != nil { logger.Error("Failed to unmarshal reshare success event", err, "raw", string(msg)) diff --git a/pkg/event/reshare.go b/pkg/event/reshare.go index ed13a6a..0356ed6 100644 --- a/pkg/event/reshare.go +++ b/pkg/event/reshare.go @@ -2,7 +2,7 @@ package event import "github.com/fystack/mpcium/pkg/types" -type ResharingSuccessEvent struct { +type ResharingResultEvent struct { WalletID string `json:"wallet_id"` NewThreshold int `json:"new_threshold"` KeyType types.KeyType `json:"key_type"` diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 41ecae7..c4dd938 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -281,7 +281,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { ) case types.KeyTypeEd25519: session, err = ec.node.CreateSigningSession( - mpc.SessionTypeECDSA, + mpc.SessionTypeEDDSA, msg.WalletID, msg.TxID, msg.NetworkInternalCode, @@ -455,7 +455,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return } - successEvent := &event.ResharingSuccessEvent{ + successEvent := &event.ResharingResultEvent{ WalletID: walletID, NewThreshold: msg.NewThreshold, KeyType: msg.KeyType, diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 2735823..baf38af 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -125,7 +125,7 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { } } else { logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(s.GetVersion()))) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(keyInfo.Version))) if err != nil { return errors.Wrap(err, "Failed to get wallet data from KVStore") } diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 75da920..c1a84ed 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -116,7 +116,7 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { } } else { logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(s.GetVersion()))) + keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(keyInfo.Version))) if err != nil { return errors.Wrap(err, "Failed to get wallet data from KVStore") } diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index 454b7cd..e3f2906 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -235,9 +235,13 @@ func (s *session) ErrChan() <-chan error { } func (s *session) GetVersion() int { - version, err := strconv.Atoi(strings.Split(string(s.selfPartyID.GetKey()), ":")[1]) - if err != nil { - return 0 + // For backward-compatible party IDs (version 0), the key is just nodeID (no colon) + parts := strings.Split(string(s.selfPartyID.GetKey()), ":") + if len(parts) > 1 { + version, err := strconv.Atoi(parts[1]) + if err == nil { + return version + } } - return version + return 0 // fallback for backward-compatible party IDs } From 25d89d6ed6d35758335e7b382b8add1e6ba067da Mon Sep 17 00:00:00 2001 From: vietddude Date: Wed, 2 Jul 2025 22:00:33 +0700 Subject: [PATCH 11/25] refactor: improve session type handling and key storage logic --- pkg/eventconsumer/event_consumer.go | 18 ++++++++----- pkg/mpc/ecdsa_keygen_session.go | 3 +-- pkg/mpc/ecdsa_resharing_session.go | 30 +++------------------- pkg/mpc/eddsa_keygen_session.go | 3 +-- pkg/mpc/eddsa_resharing_session.go | 30 +++------------------- pkg/mpc/session.go | 39 +++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 64 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index c4dd938..0fc25d2 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -426,10 +426,15 @@ func (ec *eventConsumer) consumeReshareEvent() error { walletID := msg.WalletID keyType := msg.KeyType - // Helper: Tạo session nếu node có tham gia + sessionType, err := sessionTypeFromKeyType(keyType) + if err != nil { + logger.Error("Failed to get session type", err) + return + } + createSession := func(isNewPeer bool) (mpc.ReshareSession, error) { return ec.node.CreateReshareSession( - sessionTypeFromKeyType(keyType), + sessionType, walletID, ec.mpcThreshold, msg.NewThreshold, @@ -617,13 +622,14 @@ func (ec *eventConsumer) Close() error { return nil } -func sessionTypeFromKeyType(keyType types.KeyType) mpc.SessionType { +func sessionTypeFromKeyType(keyType types.KeyType) (mpc.SessionType, error) { switch keyType { case types.KeyTypeSecp256k1: - return mpc.SessionTypeECDSA + return mpc.SessionTypeECDSA, nil case types.KeyTypeEd25519: - return mpc.SessionTypeEDDSA + return mpc.SessionTypeEDDSA, nil default: - panic(fmt.Sprintf("unsupported key type: %v", keyType)) + logger.Warn("Unsupported key type", "keyType", keyType) + return "", fmt.Errorf("unsupported key type: %v", keyType) } } diff --git a/pkg/mpc/ecdsa_keygen_session.go b/pkg/mpc/ecdsa_keygen_session.go index 99c953e..802f21d 100644 --- a/pkg/mpc/ecdsa_keygen_session.go +++ b/pkg/mpc/ecdsa_keygen_session.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" - "strconv" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" "github.com/bnb-chain/tss-lib/v2/tss" @@ -104,7 +103,7 @@ func (s *ecdsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes) + err = s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index 1bfdfe5..f276adc 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "encoding/json" "fmt" - "strconv" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" "github.com/bnb-chain/tss-lib/v2/ecdsa/resharing" @@ -107,32 +106,9 @@ func (s *ecdsaReshareSession) Init() { share = keygen.NewLocalPartySaveData(len(s.partyIDs)) share.LocalPreParams = *s.preParams } else { - // Old party → load from kvstore with backward compatibility - var ( - key string - keyData []byte - err error - ) - - // Try versioned key first if version > 0 - if s.GetVersion() > 0 { - key = s.composeKey(fmt.Sprintf("%s_v%d", s.walletID, s.GetVersion())) - keyData, err = s.kvstore.Get(key) - } - - // If version == 0 or previous key not found, fall back to unversioned key - if err != nil || s.GetVersion() == 0 { - key = s.composeKey(s.walletID) - keyData, err = s.kvstore.Get(key) - } - + err := s.loadOldShareDataGeneric(s.walletID, s.GetVersion(), &share) if err != nil { - s.ErrCh <- fmt.Errorf("failed to get wallet data from KVStore (key=%s): %w", key, err) - return - } - - if err := json.Unmarshal(keyData, &share); err != nil { - s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) + s.ErrCh <- err return } } @@ -163,7 +139,7 @@ func (s *ecdsaReshareSession) Reshare(done func()) { return } - if err := s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes); err != nil { + if err := s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes); err != nil { s.ErrCh <- err return } diff --git a/pkg/mpc/eddsa_keygen_session.go b/pkg/mpc/eddsa_keygen_session.go index 8ada074..f9f1847 100644 --- a/pkg/mpc/eddsa_keygen_session.go +++ b/pkg/mpc/eddsa_keygen_session.go @@ -3,7 +3,6 @@ package mpc import ( "encoding/json" "fmt" - "strconv" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" "github.com/bnb-chain/tss-lib/v2/tss" @@ -92,7 +91,7 @@ func (s *eddsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes) + err = s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err diff --git a/pkg/mpc/eddsa_resharing_session.go b/pkg/mpc/eddsa_resharing_session.go index 4b2cd54..9d20c2c 100644 --- a/pkg/mpc/eddsa_resharing_session.go +++ b/pkg/mpc/eddsa_resharing_session.go @@ -3,7 +3,6 @@ package mpc import ( "encoding/json" "fmt" - "strconv" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" "github.com/bnb-chain/tss-lib/v2/eddsa/resharing" @@ -97,32 +96,9 @@ func (s *eddsaReshareSession) Init() { // Initialize empty share data for new party share = keygen.NewLocalPartySaveData(len(s.partyIDs)) } else { - // Old party → load from kvstore with backward compatibility - var ( - key string - keyData []byte - err error - ) - - // Try versioned key first if version > 0 - if s.GetVersion() > 0 { - key = s.composeKey(fmt.Sprintf("%s_v%d", s.walletID, s.GetVersion())) - keyData, err = s.kvstore.Get(key) - } - - // If version == 0 or previous key not found, fall back to unversioned key - if err != nil || s.GetVersion() == 0 { - key = s.composeKey(s.walletID) - keyData, err = s.kvstore.Get(key) - } - + err := s.loadOldShareDataGeneric(s.walletID, s.GetVersion(), &share) if err != nil { - s.ErrCh <- fmt.Errorf("failed to get wallet data from KVStore (key=%s): %w", key, err) - return - } - - if err := json.Unmarshal(keyData, &share); err != nil { - s.ErrCh <- fmt.Errorf("failed to unmarshal wallet data: %w", err) + s.ErrCh <- err return } } @@ -149,7 +125,7 @@ func (s *eddsaReshareSession) Reshare(done func()) { return } - if err := s.kvstore.Put(s.composeKey(s.walletID+"_v"+strconv.Itoa(s.GetVersion())), keyBytes); err != nil { + if err := s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes); err != nil { s.ErrCh <- err return } diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index e3f2906..a0b325c 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -1,6 +1,7 @@ package mpc import ( + "encoding/json" "fmt" "strconv" "strings" @@ -245,3 +246,41 @@ func (s *session) GetVersion() int { } return 0 // fallback for backward-compatible party IDs } + +// loadOldShareDataGeneric loads the old share data from kvstore with backward compatibility (versioned and unversioned keys) +func (s *session) loadOldShareDataGeneric(walletID string, version int, dest interface{}) error { + var ( + key string + keyData []byte + err error + ) + + // Try versioned key first if version > 0 + if version > 0 { + key = s.composeKey(toKVKey(walletID, version)) + keyData, err = s.kvstore.Get(key) + } + + // If version == 0 or previous key not found, fall back to unversioned key + if err != nil || version == 0 { + key = s.composeKey(walletID) + keyData, err = s.kvstore.Get(key) + } + + if err != nil { + return fmt.Errorf("failed to get wallet data from KVStore (key=%s): %w", key, err) + } + + if err := json.Unmarshal(keyData, dest); err != nil { + return fmt.Errorf("failed to unmarshal wallet data: %w", err) + } + return nil +} + +// toKVKey is used to compose the key for the kvstore +func toKVKey(walletID string, version int) string { + if version > 0 { + return fmt.Sprintf("%s%d", walletID, version) + } + return walletID +} From c48abd81cc6cd4517ec9cb7518abb669d813857f Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 08:51:48 +0700 Subject: [PATCH 12/25] Fix issue key not found after resharing --- pkg/mpc/ecdsa_keygen_session.go | 2 +- pkg/mpc/ecdsa_resharing_session.go | 8 ++++++-- pkg/mpc/ecdsa_signing_session.go | 17 +++-------------- pkg/mpc/eddsa_keygen_session.go | 2 +- pkg/mpc/eddsa_resharing_session.go | 8 ++++++-- pkg/mpc/eddsa_signing_session.go | 17 +++-------------- pkg/mpc/node.go | 8 +++++++- pkg/mpc/session.go | 21 ++++++--------------- 8 files changed, 33 insertions(+), 50 deletions(-) diff --git a/pkg/mpc/ecdsa_keygen_session.go b/pkg/mpc/ecdsa_keygen_session.go index 802f21d..e78bbe3 100644 --- a/pkg/mpc/ecdsa_keygen_session.go +++ b/pkg/mpc/ecdsa_keygen_session.go @@ -103,7 +103,7 @@ func (s *ecdsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes) + err = s.kvstore.Put(s.composeKey(walletIDWithVersion(s.walletID, s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err diff --git a/pkg/mpc/ecdsa_resharing_session.go b/pkg/mpc/ecdsa_resharing_session.go index f276adc..0b19be7 100644 --- a/pkg/mpc/ecdsa_resharing_session.go +++ b/pkg/mpc/ecdsa_resharing_session.go @@ -48,6 +48,7 @@ func NewECDSAReshareSession( identityStore identity.Store, newPeerIDs []string, isNewParty bool, + version int, ) *ecdsaReshareSession { session := session{ walletID: walletID, @@ -62,6 +63,7 @@ func NewECDSAReshareSession( preParams: preParams, kvstore: kvstore, keyinfoStore: keyinfoStore, + version: version, topicComposer: &TopicComposer{ ComposeBroadcastTopic: func() string { return fmt.Sprintf("resharing:broadcast:ecdsa:%s", walletID) @@ -139,7 +141,9 @@ func (s *ecdsaReshareSession) Reshare(done func()) { return } - if err := s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes); err != nil { + newVersion := s.GetVersion() + 1 + key := s.composeKey(walletIDWithVersion(s.walletID, newVersion)) + if err := s.kvstore.Put(key, keyBytes); err != nil { s.ErrCh <- err return } @@ -147,7 +151,7 @@ func (s *ecdsaReshareSession) Reshare(done func()) { keyInfo := keyinfo.KeyInfo{ ParticipantPeerIDs: s.newPeerIDs, Threshold: s.reshareParams.NewThreshold(), - Version: s.GetVersion(), + Version: newVersion, } // Save key info with resharing flag diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index baf38af..0d66cd1 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "math/big" - "strconv" "github.com/bnb-chain/tss-lib/v2/common" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" @@ -116,19 +115,9 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { logger.Info("Have enough participants to sign", "participants", s.participantPeerIDs) - var keyData []byte - if keyInfo.Version == 0 { - logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID)) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } - } else { - logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(keyInfo.Version))) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } + keyData, err := s.kvstore.Get(s.composeKey(walletIDWithVersion(s.walletID, keyInfo.Version))) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") } // Check if all the participants of the key are present var data keygen.LocalPartySaveData diff --git a/pkg/mpc/eddsa_keygen_session.go b/pkg/mpc/eddsa_keygen_session.go index f9f1847..d91c50e 100644 --- a/pkg/mpc/eddsa_keygen_session.go +++ b/pkg/mpc/eddsa_keygen_session.go @@ -91,7 +91,7 @@ func (s *eddsaKeygenSession) GenerateKey(done func()) { return } - err = s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes) + err = s.kvstore.Put(s.composeKey(walletIDWithVersion(s.walletID, s.GetVersion())), keyBytes) if err != nil { logger.Error("Failed to save key", err, "walletID", s.walletID) s.ErrCh <- err diff --git a/pkg/mpc/eddsa_resharing_session.go b/pkg/mpc/eddsa_resharing_session.go index 9d20c2c..9135fbc 100644 --- a/pkg/mpc/eddsa_resharing_session.go +++ b/pkg/mpc/eddsa_resharing_session.go @@ -39,12 +39,14 @@ func NewEDDSAReshareSession( identityStore identity.Store, newPeerIDs []string, isNewParty bool, + version int, ) *eddsaReshareSession { session := session{ walletID: walletID, pubSub: pubSub, direct: direct, threshold: threshold, + version: version, participantPeerIDs: participantPeerIDs, selfPartyID: selfID, partyIDs: newPartyIDs, @@ -125,7 +127,9 @@ func (s *eddsaReshareSession) Reshare(done func()) { return } - if err := s.kvstore.Put(s.composeKey(toKVKey(s.walletID, s.GetVersion())), keyBytes); err != nil { + newVersion := s.GetVersion() + 1 + key := s.composeKey(walletIDWithVersion(s.walletID, newVersion)) + if err := s.kvstore.Put(key, keyBytes); err != nil { s.ErrCh <- err return } @@ -133,7 +137,7 @@ func (s *eddsaReshareSession) Reshare(done func()) { keyInfo := keyinfo.KeyInfo{ ParticipantPeerIDs: s.newPeerIDs, Threshold: s.reshareParams.NewThreshold(), - Version: s.GetVersion(), + Version: newVersion, } // Save key info with resharing flag diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index c1a84ed..151a7bb 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "math/big" - "strconv" "github.com/bnb-chain/tss-lib/v2/common" "github.com/bnb-chain/tss-lib/v2/eddsa/keygen" @@ -107,19 +106,9 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { logger.Info("Have enough participants to sign", "participants", s.participantPeerIDs) - var keyData []byte - if keyInfo.Version == 0 { - logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID)) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } - } else { - logger.Info("Getting key data from KVStore", "walletID", s.walletID, "version", keyInfo.Version) - keyData, err = s.kvstore.Get(s.composeKey(s.walletID + "_v" + strconv.Itoa(keyInfo.Version))) - if err != nil { - return errors.Wrap(err, "Failed to get wallet data from KVStore") - } + keyData, err := s.kvstore.Get(s.composeKey(walletIDWithVersion(s.walletID, keyInfo.Version))) + if err != nil { + return errors.Wrap(err, "Failed to get wallet data from KVStore") } // Check if all the participants of the key are present diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index f47f257..d6ad357 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -98,7 +98,11 @@ func (p *Node) CreateKeyGenSession( successQueue messaging.MessageQueue, ) (KeyGenSession, error) { if !p.peerRegistry.ArePeersReady() { - return nil, fmt.Errorf("Not enough peers to create gen session! Expected %d, got %d", threshold+1, p.peerRegistry.GetReadyPeersCount()) + return nil, fmt.Errorf( + "Not enough peers to create gen session! Expected %d, got %d", + p.peerRegistry.GetTotalPeersCount(), + p.peerRegistry.GetReadyPeersCount(), + ) } switch sessionType { @@ -336,6 +340,7 @@ func (p *Node) CreateReshareSession( p.identityStore, newPeerIDs, isNewPeer, + oldKeyInfo.Version, ), nil case SessionTypeEDDSA: @@ -355,6 +360,7 @@ func (p *Node) CreateReshareSession( p.identityStore, newPeerIDs, isNewPeer, + oldKeyInfo.Version, ), nil default: diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index a0b325c..ef9ce67 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -3,8 +3,6 @@ package mpc import ( "encoding/json" "fmt" - "strconv" - "strings" "sync" "github.com/bnb-chain/tss-lib/v2/ecdsa/keygen" @@ -56,6 +54,7 @@ type session struct { outCh chan tss.Message ErrCh chan error party tss.Party + version int // preParams is nil for EDDSA session preParams *keygen.LocalPreParams @@ -236,15 +235,7 @@ func (s *session) ErrChan() <-chan error { } func (s *session) GetVersion() int { - // For backward-compatible party IDs (version 0), the key is just nodeID (no colon) - parts := strings.Split(string(s.selfPartyID.GetKey()), ":") - if len(parts) > 1 { - version, err := strconv.Atoi(parts[1]) - if err == nil { - return version - } - } - return 0 // fallback for backward-compatible party IDs + return s.version } // loadOldShareDataGeneric loads the old share data from kvstore with backward compatibility (versioned and unversioned keys) @@ -257,7 +248,7 @@ func (s *session) loadOldShareDataGeneric(walletID string, version int, dest int // Try versioned key first if version > 0 if version > 0 { - key = s.composeKey(toKVKey(walletID, version)) + key = s.composeKey(walletIDWithVersion(walletID, version)) keyData, err = s.kvstore.Get(key) } @@ -277,10 +268,10 @@ func (s *session) loadOldShareDataGeneric(walletID string, version int, dest int return nil } -// toKVKey is used to compose the key for the kvstore -func toKVKey(walletID string, version int) string { +// walletIDWithVersion is used to compose the key for the kvstore +func walletIDWithVersion(walletID string, version int) string { if version > 0 { - return fmt.Sprintf("%s%d", walletID, version) + return fmt.Sprintf("%s_v%d", walletID, version) } return walletID } From 6a83226252a99b78116d9a5c8c910f937abc9deb Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 09:01:07 +0700 Subject: [PATCH 13/25] Populate version in signing session --- pkg/mpc/ecdsa_signing_session.go | 1 + pkg/mpc/eddsa_signing_session.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 0d66cd1..38cecec 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -128,6 +128,7 @@ func (s *ecdsaSigningSession) Init(tx *big.Int) error { s.party = signing.NewLocalParty(tx, params, data, s.outCh, s.endCh) s.data = &data + s.version = keyInfo.Version s.tx = tx logger.Info("Initialized sigining session successfully!") return nil diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 151a7bb..3ed152b 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -120,6 +120,7 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { s.party = signing.NewLocalParty(tx, params, data, s.outCh, s.endCh) s.data = &data + s.version = keyInfo.Version s.tx = tx logger.Info("Initialized sigining session successfully!") return nil From 2a3d21aa8f0b3df634090ec6217e52cb6570a3a4 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 10:31:07 +0700 Subject: [PATCH 14/25] Adjust signing logic, only require t+1 nodes to participate --- pkg/eventconsumer/event_consumer.go | 13 ++- pkg/mpc/node.go | 121 +++++++++++++++------------- 2 files changed, 71 insertions(+), 63 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 0fc25d2..98e7c3a 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -25,8 +25,8 @@ const ( MPCSignEvent = "mpc:sign" MPCReshareEvent = "mpc:reshare" - DefaultConcurrentKeygen = 2 - DefaultKeyGenStartupDelayMs = 500 + DefaultConcurrentKeygen = 2 + DefaultSessionStartupDelay = 500 ) type EventConsumer interface { @@ -184,7 +184,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). - time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) + time.Sleep(DefaultSessionStartupDelay * time.Millisecond) go ecdsaSession.GenerateKey(doneEcdsa) go eddsaSession.GenerateKey(doneEddsa) @@ -276,7 +276,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.WalletID, msg.TxID, msg.NetworkInternalCode, - ec.mpcThreshold, ec.signingResultQueue, ) case types.KeyTypeEd25519: @@ -285,7 +284,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.WalletID, msg.TxID, msg.NetworkInternalCode, - ec.mpcThreshold, ec.signingResultQueue, ) @@ -361,7 +359,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { // One solution: // The messaging includes mechanisms for direct point-to-point communication (in point2point.go). // The nodes could explicitly coordinate through request-response patterns before starting signing - time.Sleep(1 * time.Second) + time.Sleep(DefaultSessionStartupDelay * time.Millisecond) onSuccess := func(data []byte) { done() @@ -469,8 +467,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { var wg sync.WaitGroup ctx := context.Background() - // Delay để các node chuẩn bị (có thể thay bằng Consul lock sau) - time.Sleep(DefaultKeyGenStartupDelayMs * time.Millisecond) + time.Sleep(DefaultSessionStartupDelay * time.Millisecond) if oldSession != nil { ctxOld, doneOld := context.WithCancel(ctx) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index d6ad357..7e1f8e8 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -159,91 +159,69 @@ func (p *Node) CreateSigningSession( walletID string, txID string, networkInternalCode string, - threshold int, resultQueue messaging.MessageQueue, ) (SigningSession, error) { version := p.getVersion(sessionType, walletID) - switch sessionType { - case SessionTypeECDSA: - keyID := fmt.Sprintf("ecdsa:%s", walletID) - keyInfo, err := p.keyinfoStore.Get(keyID) - if err != nil { - return nil, err - } + keyInfo, err := p.getKeyInfo(sessionType, walletID) + if err != nil { + return nil, err + } - readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() + readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() + readyParticipantIDs := p.getReadyPeersForSigning(keyInfo, readyPeers) - // Ensure all participants are ready - for _, peerID := range keyInfo.ParticipantPeerIDs { - if !slices.Contains(readyPeers, peerID) { - return nil, fmt.Errorf("participant peer %s is not ready", peerID) - } - } + logger.Info("Creating signing session", + "type", sessionType, + "readyPeers", readyPeers, + "participantPeerIDs", keyInfo.ParticipantPeerIDs, + "readyCount", len(readyParticipantIDs), + "threshold", keyInfo.Threshold+1, + ) - // Warn and skip if current node is not a participant - if !slices.Contains(keyInfo.ParticipantPeerIDs, p.nodeID) { - logger.Warn("This node is not in the participant list", - "walletID", walletID, - "nodeID", p.nodeID, - ) - return nil, nil - } + if len(readyParticipantIDs) < keyInfo.Threshold+1 { + return nil, fmt.Errorf("not enough peers to create signing session! expected %d, got %d", keyInfo.Threshold+1, len(readyParticipantIDs)) + } + + if err := p.ensureNodeIsParticipant(keyInfo); err != nil { + return nil, err + } - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, keyInfo.ParticipantPeerIDs, version) + selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyParticipantIDs, version) + switch sessionType { + case SessionTypeECDSA: return newECDSASigningSession( walletID, txID, networkInternalCode, p.pubSub, p.direct, - keyInfo.ParticipantPeerIDs, + readyParticipantIDs, selfPartyID, allPartyIDs, - threshold, + keyInfo.Threshold, p.ecdsaPreParams[0], p.kvstore, p.keyinfoStore, resultQueue, p.identityStore, ), nil - case SessionTypeEDDSA: - keyID := fmt.Sprintf("eddsa:%s", walletID) - keyInfo, err := p.keyinfoStore.Get(keyID) - if err != nil { - return nil, err - } - - readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() - - // Ensure all participants are ready - for _, peerID := range keyInfo.ParticipantPeerIDs { - if !slices.Contains(readyPeers, peerID) { - return nil, fmt.Errorf("participant peer %s is not ready", peerID) - } - } - // Warn and skip if current node is not a participant - if !slices.Contains(keyInfo.ParticipantPeerIDs, p.nodeID) { - logger.Warn("This node is not in the participant list", - "walletID", walletID, - "nodeID", p.nodeID, - ) - return nil, nil + case SessionTypeEDDSA: + if len(readyParticipantIDs) != len(keyInfo.ParticipantPeerIDs) { + return nil, fmt.Errorf("not all participants are ready") } - selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, keyInfo.ParticipantPeerIDs, version) - return NewEDDSASigningSession( walletID, txID, networkInternalCode, p.pubSub, p.direct, - keyInfo.ParticipantPeerIDs, + readyParticipantIDs, selfPartyID, allPartyIDs, - threshold, + keyInfo.Threshold, p.kvstore, p.keyinfoStore, resultQueue, @@ -251,7 +229,39 @@ func (p *Node) CreateSigningSession( ), nil } - return nil, errors.New("Unknown session type") + return nil, errors.New("unknown session type") +} + +func (p *Node) getKeyInfo(sessionType SessionType, walletID string) (*keyinfo.KeyInfo, error) { + var keyID string + switch sessionType { + case SessionTypeECDSA: + keyID = fmt.Sprintf("ecdsa:%s", walletID) + case SessionTypeEDDSA: + keyID = fmt.Sprintf("eddsa:%s", walletID) + default: + return nil, errors.New("unsupported session type") + } + return p.keyinfoStore.Get(keyID) +} + +func (p *Node) getReadyPeersForSigning(keyInfo *keyinfo.KeyInfo, readyPeers []string) []string { + // Ensure all participants are ready + readyParticipantIDs := make([]string, 0, len(keyInfo.ParticipantPeerIDs)) + for _, peerID := range keyInfo.ParticipantPeerIDs { + if slices.Contains(readyPeers, peerID) { + readyParticipantIDs = append(readyParticipantIDs, peerID) + } + } + + return readyParticipantIDs +} + +func (p *Node) ensureNodeIsParticipant(keyInfo *keyinfo.KeyInfo) error { + if !slices.Contains(keyInfo.ParticipantPeerIDs, p.nodeID) { + return fmt.Errorf("this node %s is not in the participant list", p.nodeID) + } + return nil } func (p *Node) CreateReshareSession( @@ -264,11 +274,12 @@ func (p *Node) CreateReshareSession( successQueue messaging.MessageQueue, ) (ReshareSession, error) { // 1. Check peer readiness - if !p.peerRegistry.ArePeersReady() { + count := p.peerRegistry.GetReadyPeersCount() + if count < int64(newThreshold)+1 { return nil, fmt.Errorf( "not enough peers to create reshare session! Expected at least %d, got %d", newThreshold+1, - p.peerRegistry.GetReadyPeersCount(), + count, ) } From 350b48d8e010fd73a864ff3cb588181811027d66 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 12:03:54 +0700 Subject: [PATCH 15/25] Better error handling --- pkg/eventconsumer/event_consumer.go | 13 +++++-------- pkg/mpc/node.go | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 98e7c3a..fd72d19 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -264,7 +264,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { // Check for duplicate session and track if new if ec.checkDuplicateSession(msg.WalletID, msg.TxID) { - natMsg.Term() return } @@ -288,14 +287,8 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { ) } - // This node is not in the participantPeerIDs, so we don't need to sign - if session == nil { - natMsg.Ack() - logger.Info("This node is not in the participantPeerIDs, so we don't need to sign", "walletID", msg.WalletID, "nodeID", ec.node.ID()) - return - } - if err != nil { + logger.Error("Failed to create signing session", err) ec.handleSigningSessionError( msg.WalletID, msg.TxID, @@ -615,6 +608,10 @@ func (ec *eventConsumer) Close() error { if err != nil { return err } + err = ec.reshareSub.Unsubscribe() + if err != nil { + return err + } return nil } diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 7e1f8e8..7d850e1 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -105,6 +105,11 @@ func (p *Node) CreateKeyGenSession( ) } + keyInfo, _ := p.getKeyInfo(sessionType, walletID) + if keyInfo != nil { + return nil, fmt.Errorf("Key already exists: %s", walletID) + } + switch sessionType { case SessionTypeECDSA: return p.createECDSAKeyGenSession(walletID, threshold, DefaultVersion, successQueue) @@ -174,8 +179,9 @@ func (p *Node) CreateSigningSession( "type", sessionType, "readyPeers", readyPeers, "participantPeerIDs", keyInfo.ParticipantPeerIDs, - "readyCount", len(readyParticipantIDs), - "threshold", keyInfo.Threshold+1, + "ready count", len(readyParticipantIDs), + "min ready", keyInfo.Threshold+1, + "version", version, ) if len(readyParticipantIDs) < keyInfo.Threshold+1 { @@ -283,6 +289,10 @@ func (p *Node) CreateReshareSession( ) } + if len(newPeerIDs) < newThreshold+1 { + return nil, fmt.Errorf("new peer list is smaller than required t+1") + } + // 2. Check all new peers are ready readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() for _, peerID := range newPeerIDs { From d2129f0ad48f56659cfe870ce0e8768d6eec848d Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 12:40:50 +0700 Subject: [PATCH 16/25] Enrich signing session message --- go.mod | 2 +- go.sum | 2 ++ pkg/eventconsumer/event_consumer.go | 29 +++++++++++++++++++++-------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8a4b6ed..2d96a3e 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect diff --git a/go.sum b/go.sum index cdd2f60..df1895b 100644 --- a/go.sum +++ b/go.sum @@ -310,6 +310,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index fd72d19..fe86516 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -376,29 +376,42 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return nil } -func (ec *eventConsumer) handleSigningSessionError(walletID, txID, NetworkInternalCode string, err error, errMsg string, natMsg *nats.Msg) { - logger.Error("Signing session error", err, "walletID", walletID, "txID", txID, "error", errMsg) +func (ec *eventConsumer) handleSigningSessionError(walletID, txID, networkInternalCode string, err error, contextMsg string, natMsg *nats.Msg) { + fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) + logger.Warn("Signing session error", + "walletID", walletID, + "txID", txID, + "networkInternalCode", networkInternalCode, + "error", err.Error(), + "context", contextMsg, + ) + signingResult := event.SigningResultEvent{ ResultType: event.SigningResultTypeError, - NetworkInternalCode: NetworkInternalCode, + NetworkInternalCode: networkInternalCode, WalletID: walletID, TxID: txID, - ErrorReason: errMsg, + ErrorReason: fullErrMsg, } signingResultBytes, err := json.Marshal(signingResult) if err != nil { - logger.Error("Failed to marshal signing result event", err) + logger.Error("Failed to marshal signing result event", err, + "walletID", walletID, + "txID", txID, + ) return } - natMsg.Ack() err = ec.signingResultQueue.Enqueue(event.SigningResultCompleteTopic, signingResultBytes, &messaging.EnqueueOptions{ IdempotententKey: txID, }) if err != nil { - logger.Error("Failed to publish signing result event", err) - return + logger.Error("Failed to enqueue signing result event", err, + "walletID", walletID, + "txID", txID, + "payload", string(signingResultBytes), + ) } } func (ec *eventConsumer) consumeReshareEvent() error { From 0d991507c0a586185c7d5c1107f96c014005bbb8 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 13:01:16 +0700 Subject: [PATCH 17/25] Improve nats configuration --- cmd/mpcium/main.go | 39 ++++++++++++++++++++++++---------- pkg/messaging/message_queue.go | 8 +++++-- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cmd/mpcium/main.go b/cmd/mpcium/main.go index 86d85a2..08047be 100644 --- a/cmd/mpcium/main.go +++ b/cmd/mpcium/main.go @@ -7,6 +7,7 @@ import ( "os/signal" "path/filepath" "syscall" + "time" "github.com/fystack/mpcium/pkg/config" "github.com/fystack/mpcium/pkg/constant" @@ -344,16 +345,32 @@ func NewBadgerKV(nodeName string) *kvstore.BadgerKVStore { } func GetNATSConnection(environment string) (*nats.Conn, error) { - if environment != constant.EnvProduction { - return nats.Connect(viper.GetString("nats.url")) + url := viper.GetString("nats.url") + opts := []nats.Option{ + nats.MaxReconnects(-1), // retry forever + nats.ReconnectWait(2 * time.Second), + nats.DisconnectHandler(func(nc *nats.Conn) { + logger.Warn("Disconnected from NATS") + }), + nats.ReconnectHandler(func(nc *nats.Conn) { + logger.Info("Reconnected to NATS", "url", nc.ConnectedUrl()) + }), + nats.ClosedHandler(func(nc *nats.Conn) { + logger.Info("NATS connection closed!") + }), } - clientCert := filepath.Join(".", "certs", "client-cert.pem") - clientKey := filepath.Join(".", "certs", "client-key.pem") - caCert := filepath.Join(".", "certs", "rootCA.pem") - - return nats.Connect(viper.GetString("nats.url"), - nats.ClientCert(clientCert, clientKey), - nats.RootCAs(caCert), - nats.UserInfo(viper.GetString("nats.username"), viper.GetString("nats.password")), - ) + + if environment == constant.EnvProduction { + clientCert := filepath.Join(".", "certs", "client-cert.pem") + clientKey := filepath.Join(".", "certs", "client-key.pem") + caCert := filepath.Join(".", "certs", "rootCA.pem") + + opts = append(opts, + nats.ClientCert(clientCert, clientKey), + nats.RootCAs(caCert), + nats.UserInfo(viper.GetString("nats.username"), viper.GetString("nats.password")), + ) + } + + return nats.Connect(url, opts...) } diff --git a/pkg/messaging/message_queue.go b/pkg/messaging/message_queue.go index 5819d9f..8aa2f73 100644 --- a/pkg/messaging/message_queue.go +++ b/pkg/messaging/message_queue.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/fystack/mpcium/pkg/logger" "github.com/nats-io/nats.go" @@ -57,7 +58,7 @@ func NewNATsMessageQueueManager(queueName string, subjectWildCards []string, nc Name: queueName, Description: "Stream for " + queueName, Subjects: subjectWildCards, - MaxBytes: 1024, + MaxBytes: 10_485_760, // Light Production (Low Traffic) (10 MB) Storage: jetstream.FileStorage, Retention: jetstream.WorkQueuePolicy, }) @@ -81,7 +82,10 @@ func (m *NATsMessageQueueManager) NewMessageQueue(consumerName string) MessageQu cfg := jetstream.ConsumerConfig{ Name: consumerName, Durable: consumerName, - MaxAckPending: 4, + MaxAckPending: 1000, + // If a message isn't acked within AckWait, it will be redelivered up to MaxDelive + AckWait: 30 * time.Second, + AckPolicy: jetstream.AckExplicitPolicy, FilterSubjects: []string{ consumerWildCard, }, From b7a6185fe4788329642cb471579e76e36c58c1cb Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 14:57:43 +0700 Subject: [PATCH 18/25] Fix signing eddsa crash on key v1 --- pkg/eventconsumer/event_consumer.go | 6 +----- pkg/mpc/ecdsa_keygen_session.go | 1 + pkg/mpc/eddsa_keygen_session.go | 1 + pkg/mpc/eddsa_signing_session.go | 7 +++---- pkg/mpc/node.go | 6 +----- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index fe86516..7ee2865 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -295,7 +295,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.NetworkInternalCode, err, "Failed to create signing session", - natMsg, ) return } @@ -314,7 +313,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.NetworkInternalCode, err, "Failed to init signing session", - natMsg, ) return } @@ -336,7 +334,6 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { msg.NetworkInternalCode, err, "Failed to sign tx", - natMsg, ) return } @@ -375,8 +372,7 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { return nil } - -func (ec *eventConsumer) handleSigningSessionError(walletID, txID, networkInternalCode string, err error, contextMsg string, natMsg *nats.Msg) { +func (ec *eventConsumer) handleSigningSessionError(walletID, txID, networkInternalCode string, err error, contextMsg string) { fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) logger.Warn("Signing session error", "walletID", walletID, diff --git a/pkg/mpc/ecdsa_keygen_session.go b/pkg/mpc/ecdsa_keygen_session.go index e78bbe3..e9ea00a 100644 --- a/pkg/mpc/ecdsa_keygen_session.go +++ b/pkg/mpc/ecdsa_keygen_session.go @@ -48,6 +48,7 @@ func newECDSAKeygenSession( pubSub: pubSub, direct: direct, threshold: threshold, + version: DefaultVersion, participantPeerIDs: participantPeerIDs, selfPartyID: selfID, partyIDs: partyIDs, diff --git a/pkg/mpc/eddsa_keygen_session.go b/pkg/mpc/eddsa_keygen_session.go index d91c50e..a4fe030 100644 --- a/pkg/mpc/eddsa_keygen_session.go +++ b/pkg/mpc/eddsa_keygen_session.go @@ -37,6 +37,7 @@ func newEDDSAKeygenSession( pubSub: pubSub, direct: direct, threshold: threshold, + version: DefaultVersion, participantPeerIDs: participantPeerIDs, selfPartyID: selfID, partyIDs: partyIDs, diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 3ed152b..02c1ec7 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -29,7 +29,7 @@ type eddsaSigningSession struct { networkInternalCode string } -func NewEDDSASigningSession( +func newEDDSASigningSession( walletID string, txID string, networkInternalCode string, @@ -105,12 +105,11 @@ func (s *eddsaSigningSession) Init(tx *big.Int) error { } logger.Info("Have enough participants to sign", "participants", s.participantPeerIDs) - - keyData, err := s.kvstore.Get(s.composeKey(walletIDWithVersion(s.walletID, keyInfo.Version))) + key := s.composeKey(walletIDWithVersion(s.walletID, keyInfo.Version)) + keyData, err := s.kvstore.Get(key) if err != nil { return errors.Wrap(err, "Failed to get wallet data from KVStore") } - // Check if all the participants of the key are present var data keygen.LocalPartySaveData err = json.Unmarshal(keyData, &data) diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index 7d850e1..a2d7485 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -214,11 +214,7 @@ func (p *Node) CreateSigningSession( ), nil case SessionTypeEDDSA: - if len(readyParticipantIDs) != len(keyInfo.ParticipantPeerIDs) { - return nil, fmt.Errorf("not all participants are ready") - } - - return NewEDDSASigningSession( + return newEDDSASigningSession( walletID, txID, networkInternalCode, From 70544af85bd1cf83ce2e22c723a9ec09bd9acbef Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 15:11:07 +0700 Subject: [PATCH 19/25] Implement coordination, waiting for enough peers before consuming sign message --- cmd/mpcium/main.go | 2 +- pkg/eventconsumer/sign_consumer.go | 71 ++++++++++++++++++++++++--- pkg/eventconsumer/timeout_consumer.go | 3 +- pkg/messaging/pubsub.go | 11 +++-- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/cmd/mpcium/main.go b/cmd/mpcium/main.go index 08047be..5e658f2 100644 --- a/cmd/mpcium/main.go +++ b/cmd/mpcium/main.go @@ -178,7 +178,7 @@ func runNode(ctx context.Context, c *cli.Command) error { timeoutConsumer.Run() defer timeoutConsumer.Close() - signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub) + signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingStream, pubsub, peerRegistry) // Make the node ready before starting the signing consumer peerRegistry.Ready() diff --git a/pkg/eventconsumer/sign_consumer.go b/pkg/eventconsumer/sign_consumer.go index 83387a6..6722fa1 100644 --- a/pkg/eventconsumer/sign_consumer.go +++ b/pkg/eventconsumer/sign_consumer.go @@ -8,8 +8,10 @@ import ( "github.com/fystack/mpcium/pkg/event" "github.com/fystack/mpcium/pkg/logger" "github.com/fystack/mpcium/pkg/messaging" + "github.com/fystack/mpcium/pkg/mpc" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "github.com/spf13/viper" ) const ( @@ -17,6 +19,8 @@ const ( signingResponseTimeout = 30 * time.Second // How often to poll for the reply message. signingPollingInterval = 500 * time.Millisecond + // How often to check if enough peers are ready + readinessCheckInterval = 2 * time.Second ) // SigningConsumer represents a consumer that processes signing events. @@ -29,25 +33,65 @@ type SigningConsumer interface { // signingConsumer implements SigningConsumer. type signingConsumer struct { - natsConn *nats.Conn - pubsub messaging.PubSub - jsPubsub messaging.StreamPubsub + natsConn *nats.Conn + pubsub messaging.PubSub + jsPubsub messaging.StreamPubsub + peerRegistry mpc.PeerRegistry + mpcThreshold int // jsSub holds the JetStream subscription, so it can be cleaned up during Close(). jsSub messaging.Subscription } // NewSigningConsumer returns a new instance of SigningConsumer. -func NewSigningConsumer(natsConn *nats.Conn, jsPubsub messaging.StreamPubsub, pubsub messaging.PubSub) SigningConsumer { +func NewSigningConsumer(natsConn *nats.Conn, jsPubsub messaging.StreamPubsub, pubsub messaging.PubSub, peerRegistry mpc.PeerRegistry) SigningConsumer { + mpcThreshold := viper.GetInt("mpc_threshold") return &signingConsumer{ - natsConn: natsConn, - pubsub: pubsub, - jsPubsub: jsPubsub, + natsConn: natsConn, + pubsub: pubsub, + jsPubsub: jsPubsub, + peerRegistry: peerRegistry, + mpcThreshold: mpcThreshold, + } +} + +// waitForSufficientPeers waits until enough peers are ready to handle signing requests +func (sc *signingConsumer) waitForSufficientPeers(ctx context.Context) error { + requiredPeers := int64(sc.mpcThreshold + 1) // t+1 peers needed for signing + + logger.Info("SigningConsumer: Waiting for sufficient peers before consuming messages", + "required", requiredPeers, + "threshold", sc.mpcThreshold) + + ticker := time.NewTicker(readinessCheckInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + readyPeers := sc.peerRegistry.GetReadyPeersCount() + if readyPeers >= requiredPeers { + logger.Info("SigningConsumer: Sufficient peers ready, starting message consumption", + "ready", readyPeers, + "t+1", requiredPeers) + return nil + } + logger.Info("SigningConsumer: Waiting for more peers to be ready", + "ready", readyPeers, + "t+1", requiredPeers) + } } } // Run subscribes to signing events and processes them until the context is canceled. func (sc *signingConsumer) Run(ctx context.Context) error { + // Wait for sufficient peers before starting to consume messages + if err := sc.waitForSufficientPeers(ctx); err != nil { + return fmt.Errorf("failed to wait for sufficient peers: %w", err) + } + sub, err := sc.jsPubsub.Subscribe( event.SigningConsumerStream, event.SigningRequestEventTopic, @@ -83,6 +127,17 @@ func (sc *signingConsumer) Run(ctx context.Context) error { // When signing completes, the session publishes the result to a queue and calls the onSuccess callback, which sends a reply to the inbox that the SigningConsumer is monitoring. // The reply signals completion, allowing the SigningConsumer to acknowledge the original message. func (sc *signingConsumer) handleSigningEvent(msg jetstream.Msg) { + // Check if we still have enough peers before processing the message + requiredPeers := int64(sc.mpcThreshold + 1) + readyPeers := sc.peerRegistry.GetReadyPeersCount() + + if readyPeers < requiredPeers { + logger.Warn("SigningConsumer: Not enough peers to process signing request, rejecting message", + "ready", readyPeers, + "required", requiredPeers) + return + } + // Create a reply inbox to receive the signing event response. replyInbox := nats.NewInbox() @@ -95,7 +150,7 @@ func (sc *signingConsumer) handleSigningEvent(msg jetstream.Msg) { } defer func() { if err := replySub.Unsubscribe(); err != nil { - logger.Warn("SigningConsumer: Failed to unsubscribe from reply inbox", err) + logger.Warn("SigningConsumer: Failed to unsubscribe from reply inbox", "error", err) } }() diff --git a/pkg/eventconsumer/timeout_consumer.go b/pkg/eventconsumer/timeout_consumer.go index 4c5c31c..5c71787 100644 --- a/pkg/eventconsumer/timeout_consumer.go +++ b/pkg/eventconsumer/timeout_consumer.go @@ -2,7 +2,6 @@ package eventconsumer import ( "encoding/json" - "fmt" "github.com/fystack/mpcium/pkg/event" "github.com/fystack/mpcium/pkg/logger" @@ -63,7 +62,7 @@ func (tc *timeOutConsumer) Run() { signErrorResult.ResultType = event.SigningResultTypeError signErrorResult.IsTimeout = true - signErrorResult.ErrorReason = fmt.Sprintf("Message delivery exceeded for stream %s", advisory.Stream) + signErrorResult.ErrorReason = "Signing failed: maximum delivery attempts exceeded" signErrorResultBytes, err := json.Marshal(signErrorResult) if err != nil { diff --git a/pkg/messaging/pubsub.go b/pkg/messaging/pubsub.go index 9860e02..27750fa 100644 --- a/pkg/messaging/pubsub.go +++ b/pkg/messaging/pubsub.go @@ -223,10 +223,11 @@ func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg logger.Info("Subscribing to topic", sanitizeConsumerName(name), topic) consumerConfig := jetstream.ConsumerConfig{ - Name: sanitizeConsumerName(name), - Durable: sanitizeConsumerName(name), - AckPolicy: jetstream.AckExplicitPolicy, - MaxDeliver: 4, + Name: sanitizeConsumerName(name), + Durable: sanitizeConsumerName(name), + AckPolicy: jetstream.AckExplicitPolicy, + MaxDeliver: 4, + // backoff is NOT applied to naked messages. BackOff: []time.Duration{30 * time.Second, 30 * time.Second, 30 * time.Second}, DeliverPolicy: jetstream.DeliverAllPolicy, // Deliver all messages FilterSubject: topic, @@ -245,7 +246,7 @@ func (j *jetStreamPubSub) Subscribe(name string, topic string, handler func(msg } _, err = consumer.Consume(func(msg jetstream.Msg) { - logger.Info("Received jetStreamPubSub message", "subject", msg.Data()) + logger.Info("Received jetStreamPubSub message") handler(msg) }) From ce8a6427d0aa358d0edd2dd151ab69a896b9e3c0 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 18:08:20 +0700 Subject: [PATCH 20/25] Rename to improve consistent naming --- cmd/mpcium/main.go | 14 ++++++------- pkg/client/client.go | 16 +++++++------- pkg/event/sign.go | 30 ++++++++++----------------- pkg/eventconsumer/event_consumer.go | 23 +++++++++++--------- pkg/eventconsumer/timeout_consumer.go | 2 +- pkg/mpc/ecdsa_signing_session.go | 2 +- pkg/mpc/eddsa_signing_session.go | 2 +- pkg/mpc/node.go | 20 +++++++++--------- pkg/mpc/session.go | 9 ++++---- 9 files changed, 57 insertions(+), 61 deletions(-) diff --git a/cmd/mpcium/main.go b/cmd/mpcium/main.go index 5e658f2..11808e2 100644 --- a/cmd/mpcium/main.go +++ b/cmd/mpcium/main.go @@ -131,16 +131,16 @@ func runNode(ctx context.Context, c *cli.Command) error { directMessaging := messaging.NewNatsDirectMessaging(natsConn) mqManager := messaging.NewNATsMessageQueueManager("mpc", []string{ - "mpc.mpc_keygen_success.*", + "mpc.mpc_keygen_result.*", event.SigningResultTopic, - "mpc.mpc_reshare_success.*", + "mpc.mpc_reshare_result.*", }, natsConn) - genKeySuccessQueue := mqManager.NewMessageQueue("mpc_keygen_success") - defer genKeySuccessQueue.Close() - singingResultQueue := mqManager.NewMessageQueue("signing_result") + genKeyResultQueue := mqManager.NewMessageQueue("mpc_keygen_result") + defer genKeyResultQueue.Close() + singingResultQueue := mqManager.NewMessageQueue("mpc_signing_result") defer singingResultQueue.Close() - reshareResultQueue := mqManager.NewMessageQueue("mpc_reshare_success") + reshareResultQueue := mqManager.NewMessageQueue("mpc_reshare_result") defer reshareResultQueue.Close() logger.Info("Node is running", "peerID", nodeID, "name", nodeName) @@ -163,7 +163,7 @@ func runNode(ctx context.Context, c *cli.Command) error { eventConsumer := eventconsumer.NewEventConsumer( mpcNode, pubsub, - genKeySuccessQueue, + genKeyResultQueue, singingResultQueue, reshareResultQueue, identityStore, diff --git a/pkg/client/client.go b/pkg/client/client.go index faaa578..a130a4c 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -20,8 +20,8 @@ import ( ) const ( - GenerateWalletSuccessTopic = "mpc.mpc_keygen_success.*" // wildcard to listen to all success events - ResharingSuccessTopic = "mpc.mpc_reshare_success.*" // wildcard to listen to all success events + GenerateWalletSuccessTopic = "mpc.mpc_keygen_result.*" // wildcard to listen to all success events + ResharingSuccessTopic = "mpc.mpc_reshare_result.*" // wildcard to listen to all success events ) type MPCClient interface { @@ -125,14 +125,14 @@ func NewMPCClient(opts Options) MPCClient { pubsub := messaging.NewNATSPubSub(opts.NatsConn) manager := messaging.NewNATsMessageQueueManager("mpc", []string{ - "mpc.mpc_keygen_success.*", - "mpc.signing_result.*", - "mpc.mpc_reshare_success.*", + "mpc.mpc_keygen_result.*", + "mpc.mpc_signing_result.*", + "mpc.mpc_reshare_result.*", }, opts.NatsConn) - genKeySuccessQueue := manager.NewMessageQueue("mpc_keygen_success") - signResultQueue := manager.NewMessageQueue("signing_result") - reshareSuccessQueue := manager.NewMessageQueue("mpc_reshare_success") + genKeySuccessQueue := manager.NewMessageQueue("mpc_keygen_result") + signResultQueue := manager.NewMessageQueue("mpc_signing_result") + reshareSuccessQueue := manager.NewMessageQueue("mpc_reshare_result") return &mpcClient{ signingStream: signingStream, diff --git a/pkg/event/sign.go b/pkg/event/sign.go index cb8d53d..09059ca 100644 --- a/pkg/event/sign.go +++ b/pkg/event/sign.go @@ -4,30 +4,22 @@ const ( SigningPublisherStream = "mpc-signing" SigningConsumerStream = "mpc-signing-consumer" SigningRequestTopic = "mpc.signing_request.*" - SigningResultTopic = "mpc.signing_result.*" - SigningResultCompleteTopic = "mpc.signing_result.complete" + SigningResultTopic = "mpc.mpc_signing_result.*" + SigningResultCompleteTopic = "mpc.mpc_signing_result.complete" MPCSigningEventTopic = "mpc:sign" SigningRequestEventTopic = "mpc.signing_request.event" ) -type SigningResultType int - -const ( - SigningResultTypeUnknown SigningResultType = iota - SigningResultTypeSuccess - SigningResultTypeError -) - type SigningResultEvent struct { - ResultType SigningResultType `json:"result_type"` - ErrorReason string `json:"error_reason"` - IsTimeout bool `json:"is_timeout"` - NetworkInternalCode string `json:"network_internal_code"` - WalletID string `json:"wallet_id"` - TxID string `json:"tx_id"` - R []byte `json:"r"` - S []byte `json:"s"` - SignatureRecovery []byte `json:"signature_recovery"` + ResultType ResultType `json:"result_type"` + ErrorReason string `json:"error_reason"` + IsTimeout bool `json:"is_timeout"` + NetworkInternalCode string `json:"network_internal_code"` + WalletID string `json:"wallet_id"` + TxID string `json:"tx_id"` + R []byte `json:"r"` + S []byte `json:"s"` + SignatureRecovery []byte `json:"signature_recovery"` // TODO: define two separate events for eddsa and ecdsa Signature []byte `json:"signature"` diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 7ee2865..864234b 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -39,7 +39,7 @@ type eventConsumer struct { pubsub messaging.PubSub mpcThreshold int - genKeySucecssQueue messaging.MessageQueue + genKeyResultQueue messaging.MessageQueue signingResultQueue messaging.MessageQueue reshareResultQueue messaging.MessageQueue @@ -62,7 +62,7 @@ type eventConsumer struct { func NewEventConsumer( node *mpc.Node, pubsub messaging.PubSub, - genKeySucecssQueue messaging.MessageQueue, + genKeyResultQueue messaging.MessageQueue, signingResultQueue messaging.MessageQueue, reshareResultQueue messaging.MessageQueue, identityStore identity.Store, @@ -75,7 +75,7 @@ func NewEventConsumer( ec := &eventConsumer{ node: node, pubsub: pubsub, - genKeySucecssQueue: genKeySucecssQueue, + genKeyResultQueue: genKeyResultQueue, signingResultQueue: signingResultQueue, reshareResultQueue: reshareResultQueue, activeSessions: make(map[string]time.Time), @@ -129,12 +129,12 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { } walletID := msg.WalletID - ecdsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeECDSA, walletID, ec.mpcThreshold, ec.genKeySucecssQueue) + ecdsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeECDSA, walletID, ec.mpcThreshold, ec.genKeyResultQueue) if err != nil { logger.Error("Failed to create key generation session", err, "walletID", walletID) return } - eddsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeEDDSA, walletID, ec.mpcThreshold, ec.genKeySucecssQueue) + eddsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeEDDSA, walletID, ec.mpcThreshold, ec.genKeyResultQueue) if err != nil { logger.Error("Failed to create key generation session", err, "walletID", walletID) return @@ -198,8 +198,9 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { return } - err = ec.genKeySucecssQueue.Enqueue(fmt.Sprintf(mpc.TypeGenerateWalletSuccess, walletID), successEventBytes, &messaging.EnqueueOptions{ - IdempotententKey: fmt.Sprintf(mpc.TypeGenerateWalletSuccess, walletID), + key := fmt.Sprintf(mpc.TypeGenerateWalletResultFmt, walletID) + err = ec.genKeyResultQueue.Enqueue(key, successEventBytes, &messaging.EnqueueOptions{ + IdempotententKey: key, }) if err != nil { logger.Error("Failed to publish key generation success message", err) @@ -383,7 +384,7 @@ func (ec *eventConsumer) handleSigningSessionError(walletID, txID, networkIntern ) signingResult := event.SigningResultEvent{ - ResultType: event.SigningResultTypeError, + ResultType: event.ResultTypeError, NetworkInternalCode: networkInternalCode, WalletID: walletID, TxID: txID, @@ -521,11 +522,13 @@ func (ec *eventConsumer) consumeReshareEvent() error { logger.Error("Failed to marshal reshare success event", err) return } + + key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, walletID) err = ec.reshareResultQueue.Enqueue( - fmt.Sprintf(mpc.TypeReshareWalletSuccess, walletID), + key, successBytes, &messaging.EnqueueOptions{ - IdempotententKey: fmt.Sprintf(mpc.TypeReshareWalletSuccess, walletID), + IdempotententKey: key, }) if err != nil { logger.Error("Failed to publish reshare success message", err) diff --git a/pkg/eventconsumer/timeout_consumer.go b/pkg/eventconsumer/timeout_consumer.go index 5c71787..93a532e 100644 --- a/pkg/eventconsumer/timeout_consumer.go +++ b/pkg/eventconsumer/timeout_consumer.go @@ -60,7 +60,7 @@ func (tc *timeOutConsumer) Run() { return } - signErrorResult.ResultType = event.SigningResultTypeError + signErrorResult.ResultType = event.ResultTypeError signErrorResult.IsTimeout = true signErrorResult.ErrorReason = "Signing failed: maximum delivery attempts exceeded" diff --git a/pkg/mpc/ecdsa_signing_session.go b/pkg/mpc/ecdsa_signing_session.go index 38cecec..76cba2c 100644 --- a/pkg/mpc/ecdsa_signing_session.go +++ b/pkg/mpc/ecdsa_signing_session.go @@ -162,7 +162,7 @@ func (s *ecdsaSigningSession) Sign(onSuccess func(data []byte)) { } r := event.SigningResultEvent{ - ResultType: event.SigningResultTypeSuccess, + ResultType: event.ResultTypeSuccess, NetworkInternalCode: s.networkInternalCode, WalletID: s.walletID, TxID: s.txID, diff --git a/pkg/mpc/eddsa_signing_session.go b/pkg/mpc/eddsa_signing_session.go index 02c1ec7..4538730 100644 --- a/pkg/mpc/eddsa_signing_session.go +++ b/pkg/mpc/eddsa_signing_session.go @@ -153,7 +153,7 @@ func (s *eddsaSigningSession) Sign(onSuccess func(data []byte)) { } r := event.SigningResultEvent{ - ResultType: event.SigningResultTypeSuccess, + ResultType: event.ResultTypeSuccess, NetworkInternalCode: s.networkInternalCode, WalletID: s.walletID, TxID: s.txID, diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index a2d7485..e6e089e 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -95,7 +95,7 @@ func (p *Node) CreateKeyGenSession( sessionType SessionType, walletID string, threshold int, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) (KeyGenSession, error) { if !p.peerRegistry.ArePeersReady() { return nil, fmt.Errorf( @@ -112,15 +112,15 @@ func (p *Node) CreateKeyGenSession( switch sessionType { case SessionTypeECDSA: - return p.createECDSAKeyGenSession(walletID, threshold, DefaultVersion, successQueue) + return p.createECDSAKeyGenSession(walletID, threshold, DefaultVersion, resultQueue) case SessionTypeEDDSA: - return p.createEDDSAKeyGenSession(walletID, threshold, DefaultVersion, successQueue) + return p.createEDDSAKeyGenSession(walletID, threshold, DefaultVersion, resultQueue) default: return nil, fmt.Errorf("Unknown session type: %s", sessionType) } } -func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, version int, successQueue messaging.MessageQueue) (KeyGenSession, error) { +func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, version int, resultQueue messaging.MessageQueue) (KeyGenSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) session := newECDSAKeygenSession( @@ -134,13 +134,13 @@ func (p *Node) createECDSAKeyGenSession(walletID string, threshold int, version p.ecdsaPreParams[0], p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, p.identityStore, ) return session, nil } -func (p *Node) createEDDSAKeyGenSession(walletID string, threshold int, version int, successQueue messaging.MessageQueue) (KeyGenSession, error) { +func (p *Node) createEDDSAKeyGenSession(walletID string, threshold int, version int, resultQueue messaging.MessageQueue) (KeyGenSession, error) { readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() selfPartyID, allPartyIDs := p.generatePartyIDs(PurposeKeygen, readyPeerIDs, version) session := newEDDSAKeygenSession( @@ -153,7 +153,7 @@ func (p *Node) createEDDSAKeyGenSession(walletID string, threshold int, version threshold, p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, p.identityStore, ) return session, nil @@ -273,7 +273,7 @@ func (p *Node) CreateReshareSession( newThreshold int, newPeerIDs []string, isNewPeer bool, - successQueue messaging.MessageQueue, + resultQueue messaging.MessageQueue, ) (ReshareSession, error) { // 1. Check peer readiness count := p.peerRegistry.GetReadyPeersCount() @@ -353,7 +353,7 @@ func (p *Node) CreateReshareSession( preParams, p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, p.identityStore, newPeerIDs, isNewPeer, @@ -373,7 +373,7 @@ func (p *Node) CreateReshareSession( newThreshold, p.kvstore, p.keyinfoStore, - successQueue, + resultQueue, p.identityStore, newPeerIDs, isNewPeer, diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index ef9ce67..0f3fe2d 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -20,10 +20,11 @@ import ( type SessionType string const ( - TypeGenerateWalletSuccess = "mpc.mpc_keygen_success.%s" - TypeReshareWalletSuccess = "mpc.mpc_reshare_success.%s" - SessionTypeECDSA SessionType = "session_ecdsa" - SessionTypeEDDSA SessionType = "session_eddsa" + TypeGenerateWalletResultFmt = "mpc.mpc_keygen_result.%s" + TypeReshareWalletResultFmt = "mpc.mpc_reshare_result.%s" + + SessionTypeECDSA SessionType = "session_ecdsa" + SessionTypeEDDSA SessionType = "session_eddsa" ) var ( From bcc8019f6613b769cefcb666291f9db1c262d2eb Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 19:56:31 +0700 Subject: [PATCH 21/25] Include error in keygen and reshare event --- pkg/event/{generate.go => keygen.go} | 4 + pkg/event/reshare.go | 4 + pkg/event/sign.go | 12 +- pkg/event/types.go | 148 +++++++++++++++++++ pkg/eventconsumer/event_consumer.go | 203 ++++++++++++++++++++------ pkg/eventconsumer/timeout_consumer.go | 1 + pkg/messaging/message_queue.go | 2 +- pkg/mpc/session.go | 3 +- 8 files changed, 324 insertions(+), 53 deletions(-) rename pkg/event/{generate.go => keygen.go} (56%) create mode 100644 pkg/event/types.go diff --git a/pkg/event/generate.go b/pkg/event/keygen.go similarity index 56% rename from pkg/event/generate.go rename to pkg/event/keygen.go index eba2bcc..8b607e9 100644 --- a/pkg/event/generate.go +++ b/pkg/event/keygen.go @@ -4,4 +4,8 @@ type KeygenSuccessEvent struct { WalletID string `json:"wallet_id"` ECDSAPubKey []byte `json:"ecdsa_pub_key"` EDDSAPubKey []byte `json:"eddsa_pub_key"` + + ResultType ResultType `json:"result_type"` + ErrorReason string `json:"error_reason"` + ErrorCode string `json:"error_code"` } diff --git a/pkg/event/reshare.go b/pkg/event/reshare.go index 0356ed6..3615388 100644 --- a/pkg/event/reshare.go +++ b/pkg/event/reshare.go @@ -7,4 +7,8 @@ type ResharingResultEvent struct { NewThreshold int `json:"new_threshold"` KeyType types.KeyType `json:"key_type"` PubKey []byte `json:"pub_key"` + + ResultType ResultType `json:"result_type"` + ErrorReason string `json:"error_reason"` + ErrorCode string `json:"error_code"` } diff --git a/pkg/event/sign.go b/pkg/event/sign.go index 09059ca..4a376c4 100644 --- a/pkg/event/sign.go +++ b/pkg/event/sign.go @@ -12,6 +12,7 @@ const ( type SigningResultEvent struct { ResultType ResultType `json:"result_type"` + ErrorCode ErrorCode `json:"error_code"` ErrorReason string `json:"error_reason"` IsTimeout bool `json:"is_timeout"` NetworkInternalCode string `json:"network_internal_code"` @@ -38,9 +39,10 @@ type SigningResultSuccessEvent struct { } type SigningResultErrorEvent struct { - NetworkInternalCode string `json:"network_internal_code"` - WalletID string `json:"wallet_id"` - TxID string `json:"tx_id"` - ErrorReason string `json:"error_reason"` - IsTimeout bool `json:"is_timeout"` + NetworkInternalCode string `json:"network_internal_code"` + WalletID string `json:"wallet_id"` + TxID string `json:"tx_id"` + ErrorCode ErrorCode `json:"error_code"` + ErrorReason string `json:"error_reason"` + IsTimeout bool `json:"is_timeout"` } diff --git a/pkg/event/types.go b/pkg/event/types.go new file mode 100644 index 0000000..8cd0a04 --- /dev/null +++ b/pkg/event/types.go @@ -0,0 +1,148 @@ +package event + +import "strings" + +type ResultType string + +const ( + ResultTypeSuccess ResultType = "success" + ResultTypeError ResultType = "error" +) + +// ErrorCode defines specific error types that can occur in MPC operations +type ErrorCode string + +const ( + // Generic/Unknown errors + ErrorCodeUnknown ErrorCode = "ERROR_UNKNOWN" + + // Network and connectivity errors + ErrorCodeNetworkTimeout ErrorCode = "ERROR_NETWORK_TIMEOUT" + ErrorCodeNetworkConnection ErrorCode = "ERROR_NETWORK_CONNECTION" + ErrorCodeNetworkSubscription ErrorCode = "ERROR_NETWORK_SUBSCRIPTION" + ErrorCodeMessageRouting ErrorCode = "ERROR_MESSAGE_ROUTING" + ErrorCodeDirectMessaging ErrorCode = "ERROR_DIRECT_MESSAGING" + + // Session errors + ErrorCodeSessionTimeout ErrorCode = "ERROR_SESSION_TIMEOUT" + ErrorCodeSessionCreation ErrorCode = "ERROR_SESSION_CREATION" + ErrorCodeSessionInitialization ErrorCode = "ERROR_SESSION_INITIALIZATION" + ErrorCodeSessionCleanup ErrorCode = "ERROR_SESSION_CLEANUP" + ErrorCodeSessionDuplicate ErrorCode = "ERROR_SESSION_DUPLICATE" + ErrorCodeSessionStale ErrorCode = "ERROR_SESSION_STALE" + + // Participant and peer errors + ErrorCodeInsufficientParticipants ErrorCode = "ERROR_INSUFFICIENT_PARTICIPANTS" + ErrorCodeIncompatiblePeerIDs ErrorCode = "ERROR_INCOMPATIBLE_PEER_IDS" + ErrorCodePeerNotReady ErrorCode = "ERROR_PEER_NOT_READY" + ErrorCodePeerUnavailable ErrorCode = "ERROR_PEER_UNAVAILABLE" + ErrorCodeParticipantNotFound ErrorCode = "ERROR_PARTICIPANT_NOT_FOUND" + + // Key management errors + ErrorCodeKeyNotFound ErrorCode = "ERROR_KEY_NOT_FOUND" + ErrorCodeKeyAlreadyExists ErrorCode = "ERROR_KEY_ALREADY_EXISTS" + ErrorCodeKeyGeneration ErrorCode = "ERROR_KEY_GENERATION" + ErrorCodeKeySave ErrorCode = "ERROR_KEY_SAVE" + ErrorCodeKeyLoad ErrorCode = "ERROR_KEY_LOAD" + ErrorCodeKeyInfoSave ErrorCode = "ERROR_KEY_INFO_SAVE" + ErrorCodeKeyInfoLoad ErrorCode = "ERROR_KEY_INFO_LOAD" + ErrorCodeKeyEncoding ErrorCode = "ERROR_KEY_ENCODING" + ErrorCodeKeyDecoding ErrorCode = "ERROR_KEY_DECODING" + + // Cryptographic operation errors + ErrorCodeSignatureGeneration ErrorCode = "ERROR_SIGNATURE_GENERATION" + ErrorCodeSignatureVerification ErrorCode = "ERROR_SIGNATURE_VERIFICATION" + ErrorCodePreParamsGeneration ErrorCode = "ERROR_PRE_PARAMS_GENERATION" + ErrorCodeTSSPartyCreation ErrorCode = "ERROR_TSS_PARTY_CREATION" + + // Data serialization errors + ErrorCodeMarshalFailure ErrorCode = "ERROR_MARSHAL_FAILURE" + ErrorCodeUnmarshalFailure ErrorCode = "ERROR_UNMARSHAL_FAILURE" + ErrorCodeDataCorruption ErrorCode = "ERROR_DATA_CORRUPTION" + + // Storage errors + ErrorCodeStorageRead ErrorCode = "ERROR_STORAGE_READ" + ErrorCodeStorageWrite ErrorCode = "ERROR_STORAGE_WRITE" + ErrorCodeStorageInit ErrorCode = "ERROR_STORAGE_INIT" + + // Message and verification errors + ErrorCodeMessageVerification ErrorCode = "ERROR_MESSAGE_VERIFICATION" + ErrorCodeMessageFormat ErrorCode = "ERROR_MESSAGE_FORMAT" + ErrorCodeMessageDelivery ErrorCode = "ERROR_MESSAGE_DELIVERY" + ErrorCodeMaxDeliveryAttempts ErrorCode = "ERROR_MAX_DELIVERY_ATTEMPTS" + + // Configuration errors + ErrorCodeInvalidConfiguration ErrorCode = "ERROR_INVALID_CONFIGURATION" + ErrorCodeInvalidThreshold ErrorCode = "ERROR_INVALID_THRESHOLD" + ErrorCodeInvalidSessionType ErrorCode = "ERROR_INVALID_SESSION_TYPE" + + // Resource errors + ErrorCodeResourceExhausted ErrorCode = "ERROR_RESOURCE_EXHAUSTED" + ErrorCodeMemoryAllocation ErrorCode = "ERROR_MEMORY_ALLOCATION" + ErrorCodeConcurrencyLimit ErrorCode = "ERROR_CONCURRENCY_LIMIT" + + // Operation-specific errors + ErrorCodeKeygenFailure ErrorCode = "ERROR_KEYGEN_FAILURE" + ErrorCodeSigningFailure ErrorCode = "ERROR_SIGNING_FAILURE" + ErrorCodeReshareFailure ErrorCode = "ERROR_RESHARE_FAILURE" + + // Context and cancellation errors + ErrorCodeContextCancelled ErrorCode = "ERROR_CONTEXT_CANCELLED" + ErrorCodeOperationAborted ErrorCode = "ERROR_OPERATION_ABORTED" +) + +// GetErrorCodeFromError attempts to categorize a generic error into a specific error code +func GetErrorCodeFromError(err error) ErrorCode { + if err == nil { + return "" + } + + errStr := err.Error() + + // Check for specific error patterns + switch { + case contains(errStr, "timeout", "timed out"): + return ErrorCodeNetworkTimeout + case contains(errStr, "connection", "connect"): + return ErrorCodeNetworkConnection + case contains(errStr, "send"): + return ErrorCodePeerUnavailable + case contains(errStr, "not enough", "insufficient"): + return ErrorCodeInsufficientParticipants + case contains(errStr, "incompatible"): + return ErrorCodeIncompatiblePeerIDs + case contains(errStr, "key not found", "no such key"): + return ErrorCodeKeyNotFound + case contains(errStr, "exists"): + return ErrorCodeKeyAlreadyExists + case contains(errStr, "marshal"): + return ErrorCodeMarshalFailure + case contains(errStr, "unmarshal"): + return ErrorCodeUnmarshalFailure + case contains(errStr, "storage", "kvstore"): + return ErrorCodeStorageRead + case contains(errStr, "save", "put"): + return ErrorCodeStorageWrite + case contains(errStr, "session"): + return ErrorCodeSessionCreation + case contains(errStr, "verify", "verification"): + return ErrorCodeMessageVerification + case contains(errStr, "delivery", "deliver"): + return ErrorCodeMessageDelivery + case contains(errStr, "context", "cancelled"): + return ErrorCodeContextCancelled + default: + return ErrorCodeUnknown + } +} + +// Helper function for case-insensitive string matching +func contains(str string, patterns ...string) bool { + str = strings.ToLower(str) + for _, pattern := range patterns { + if strings.Contains(str, strings.ToLower(pattern)) { + return true + } + } + return false +} diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 864234b..602c7e6 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -27,6 +27,8 @@ const ( DefaultConcurrentKeygen = 2 DefaultSessionStartupDelay = 500 + + KeyGenTimeOut = 30 * time.Second ) type EventConsumer interface { @@ -115,101 +117,149 @@ func (ec *eventConsumer) Run() { } func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { + baseCtx, baseCancel := context.WithTimeout(context.Background(), KeyGenTimeOut) + defer baseCancel() + raw := natMsg.Data var msg types.GenerateKeyMessage - err := json.Unmarshal(raw, &msg) - if err != nil { - logger.Error("Failed to unmarshal signing message", err) + if err := json.Unmarshal(raw, &msg); err != nil { + logger.Error("Failed to unmarshal keygen message", err) + ec.handleKeygenSessionError(msg.WalletID, err, "Failed to unmarshal keygen message") return } - err = ec.identityStore.VerifyInitiatorMessage(&msg) - if err != nil { + if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) + ec.handleKeygenSessionError(msg.WalletID, err, "Failed to verify initiator message") return } walletID := msg.WalletID ecdsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeECDSA, walletID, ec.mpcThreshold, ec.genKeyResultQueue) if err != nil { - logger.Error("Failed to create key generation session", err, "walletID", walletID) + logger.Error("Failed to create ECDSA key generation session", err, "walletID", walletID) + ec.handleKeygenSessionError(walletID, err, "Failed to create ECDSA key generation session") return } eddsaSession, err := ec.node.CreateKeyGenSession(mpc.SessionTypeEDDSA, walletID, ec.mpcThreshold, ec.genKeyResultQueue) if err != nil { - logger.Error("Failed to create key generation session", err, "walletID", walletID) + logger.Error("Failed to create EdDSA key generation session", err, "walletID", walletID) + ec.handleKeygenSessionError(walletID, err, "Failed to create EdDSA key generation session") return } - ecdsaSession.Init() eddsaSession.Init() - ctx := context.Background() - ctxEcdsa, doneEcdsa := context.WithCancel(ctx) - ctxEddsa, doneEddsa := context.WithCancel(ctx) - - successEvent := &event.KeygenSuccessEvent{ - WalletID: walletID, - } + ctxEcdsa, doneEcdsa := context.WithCancel(baseCtx) + ctxEddsa, doneEddsa := context.WithCancel(baseCtx) + successEvent := &event.KeygenSuccessEvent{WalletID: walletID, ResultType: event.ResultTypeSuccess} var wg sync.WaitGroup wg.Add(2) + go func() { - for { - select { - case <-ctxEcdsa.Done(): - successEvent.ECDSAPubKey = ecdsaSession.GetPubKeyResult() - wg.Done() - return - case err := <-ecdsaSession.ErrChan(): - logger.Error("Keygen session error", err) - } + defer wg.Done() + select { + case <-ctxEcdsa.Done(): + successEvent.ECDSAPubKey = ecdsaSession.GetPubKeyResult() + case err := <-ecdsaSession.ErrChan(): + logger.Error("ECDSA keygen session error", err) + ec.handleKeygenSessionError(walletID, err, "ECDSA keygen session error") + doneEcdsa() } }() - go func() { - for { - select { - case <-ctxEddsa.Done(): - successEvent.EDDSAPubKey = eddsaSession.GetPubKeyResult() - wg.Done() - return - case err := <-eddsaSession.ErrChan(): - logger.Error("Keygen session error", err) - } + defer wg.Done() + select { + case <-ctxEddsa.Done(): + successEvent.EDDSAPubKey = eddsaSession.GetPubKeyResult() + case err := <-eddsaSession.ErrChan(): + logger.Error("EdDSA keygen session error", err) + ec.handleKeygenSessionError(walletID, err, "EdDSA keygen session error") + doneEddsa() } }() ecdsaSession.ListenToIncomingMessageAsync() eddsaSession.ListenToIncomingMessageAsync() - // Temporary delay to allow peer nodes to subscribe and prepare before starting key generation. - // This should be replaced with a proper distributed coordination mechanism later (e.g., Consul lock). + // Temporary delay for peer setup time.Sleep(DefaultSessionStartupDelay * time.Millisecond) - go ecdsaSession.GenerateKey(doneEcdsa) go eddsaSession.GenerateKey(doneEddsa) - wg.Wait() - logger.Info("Closing session successfully!", "event", successEvent) + // Wait for completion or timeout + doneAll := make(chan struct{}) + go func() { + wg.Wait() + close(doneAll) + }() - successEventBytes, err := json.Marshal(successEvent) + select { + case <-doneAll: + // all sessions finished before timeout + case <-baseCtx.Done(): + // timeout occurred + logger.Warn("Key generation timed out", "walletID", walletID) + ec.handleKeygenSessionError(walletID, fmt.Errorf("keygen session timed out after"), "Key generation timed out") + return + } + + logger.Info("Closing session successfully!", "event", successEvent) + payload, err := json.Marshal(successEvent) if err != nil { logger.Error("Failed to marshal keygen success event", err) + ec.handleKeygenSessionError(walletID, err, "Failed to marshal keygen success event") return } key := fmt.Sprintf(mpc.TypeGenerateWalletResultFmt, walletID) - err = ec.genKeyResultQueue.Enqueue(key, successEventBytes, &messaging.EnqueueOptions{ - IdempotententKey: key, - }) - if err != nil { + if err := ec.genKeyResultQueue.Enqueue(key, payload, &messaging.EnqueueOptions{IdempotententKey: key}); err != nil { logger.Error("Failed to publish key generation success message", err) + ec.handleKeygenSessionError(walletID, err, "Failed to publish key generation success message") return } - logger.Info("[COMPLETED KEY GEN] Key generation completed successfully", "walletID", walletID) } +// handleKeygenSessionError handles errors that occur during key generation +func (ec *eventConsumer) handleKeygenSessionError(walletID string, err error, contextMsg string) { + fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) + errorCode := event.GetErrorCodeFromError(err) + + logger.Warn("Keygen session error", + "walletID", walletID, + "error", err.Error(), + "errorCode", errorCode, + "context", contextMsg, + ) + + keygenResult := event.KeygenSuccessEvent{ + ResultType: event.ResultTypeError, + ErrorCode: string(errorCode), + WalletID: walletID, + ErrorReason: fullErrMsg, + } + + keygenResultBytes, err := json.Marshal(keygenResult) + if err != nil { + logger.Error("Failed to marshal keygen result event", err, + "walletID", walletID, + ) + return + } + + key := fmt.Sprintf(mpc.TypeGenerateWalletResultFmt, walletID) + err = ec.genKeyResultQueue.Enqueue(key, keygenResultBytes, &messaging.EnqueueOptions{ + IdempotententKey: key, + }) + if err != nil { + logger.Error("Failed to enqueue keygen result event", err, + "walletID", walletID, + "payload", string(keygenResultBytes), + ) + } +} + func (ec *eventConsumer) startKeyGenEventWorker() { // semaphore to limit concurrency semaphore := make(chan struct{}, ec.maxConcurrentKeygen) @@ -375,16 +425,20 @@ func (ec *eventConsumer) consumeTxSigningEvent() error { } func (ec *eventConsumer) handleSigningSessionError(walletID, txID, networkInternalCode string, err error, contextMsg string) { fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) + errorCode := event.GetErrorCodeFromError(err) + logger.Warn("Signing session error", "walletID", walletID, "txID", txID, "networkInternalCode", networkInternalCode, "error", err.Error(), + "errorCode", errorCode, "context", contextMsg, ) signingResult := event.SigningResultEvent{ ResultType: event.ResultTypeError, + ErrorCode: errorCode, NetworkInternalCode: networkInternalCode, WalletID: walletID, TxID: txID, @@ -416,11 +470,13 @@ func (ec *eventConsumer) consumeReshareEvent() error { var msg types.ResharingMessage if err := json.Unmarshal(natMsg.Data, &msg); err != nil { logger.Error("Failed to unmarshal resharing message", err) + ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to unmarshal resharing message") return } if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) + ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to verify initiator message") return } @@ -430,6 +486,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { sessionType, err := sessionTypeFromKeyType(keyType) if err != nil { logger.Error("Failed to get session type", err) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to get session type") return } @@ -448,11 +505,13 @@ func (ec *eventConsumer) consumeReshareEvent() error { oldSession, err := createSession(false) if err != nil { logger.Error("Failed to create old reshare session", err, "walletID", walletID) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create old reshare session") return } newSession, err := createSession(true) if err != nil { logger.Error("Failed to create new reshare session", err, "walletID", walletID) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create new reshare session") return } @@ -465,6 +524,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { WalletID: walletID, NewThreshold: msg.NewThreshold, KeyType: msg.KeyType, + ResultType: event.ResultTypeSuccess, } var wg sync.WaitGroup @@ -487,6 +547,9 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-oldSession.ErrChan(): logger.Error("Old reshare session error", err) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Old reshare session error") + doneOld() // Cancel the context to stop this session + return } } }() @@ -508,18 +571,23 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-newSession.ErrChan(): logger.Error("New reshare session error", err) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "New reshare session error") + doneNew() // Cancel the context to stop this session + return } } }() } wg.Wait() + logger.Info("Reshare session finished", "walletID", walletID, "pubKey", fmt.Sprintf("%x", successEvent.PubKey)) if newSession != nil { successBytes, err := json.Marshal(successEvent) if err != nil { logger.Error("Failed to marshal reshare success event", err) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to marshal reshare success event") return } @@ -532,6 +600,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { }) if err != nil { logger.Error("Failed to publish reshare success message", err) + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to publish reshare success message") return } logger.Info("[COMPLETED RESHARE] Successfully published", "walletID", walletID) @@ -544,6 +613,49 @@ func (ec *eventConsumer) consumeReshareEvent() error { return err } +// handleReshareSessionError handles errors that occur during reshare operations +func (ec *eventConsumer) handleReshareSessionError(walletID string, keyType types.KeyType, newThreshold int, err error, contextMsg string) { + fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) + errorCode := event.GetErrorCodeFromError(err) + + logger.Warn("Reshare session error", + "walletID", walletID, + "keyType", keyType, + "newThreshold", newThreshold, + "error", err.Error(), + "errorCode", errorCode, + "context", contextMsg, + ) + + reshareResult := event.ResharingResultEvent{ + ResultType: event.ResultTypeError, + ErrorCode: string(errorCode), + WalletID: walletID, + KeyType: keyType, + NewThreshold: newThreshold, + ErrorReason: fullErrMsg, + } + + reshareResultBytes, err := json.Marshal(reshareResult) + if err != nil { + logger.Error("Failed to marshal reshare result event", err, + "walletID", walletID, + ) + return + } + + key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, walletID) + err = ec.reshareResultQueue.Enqueue(key, reshareResultBytes, &messaging.EnqueueOptions{ + IdempotententKey: key, + }) + if err != nil { + logger.Error("Failed to enqueue reshare result event", err, + "walletID", walletID, + "payload", string(reshareResultBytes), + ) + } +} + // Add a cleanup routine that runs periodically func (ec *eventConsumer) sessionCleanupRoutine() { ticker := time.NewTicker(ec.cleanupInterval) @@ -567,7 +679,6 @@ func (ec *eventConsumer) cleanupStaleSessions() { for sessionID, creationTime := range ec.activeSessions { if now.Sub(creationTime) > ec.sessionTimeout { - logger.Info("Cleaning up stale session", "sessionID", sessionID, "age", now.Sub(creationTime)) delete(ec.activeSessions, sessionID) } } diff --git a/pkg/eventconsumer/timeout_consumer.go b/pkg/eventconsumer/timeout_consumer.go index 93a532e..bd91170 100644 --- a/pkg/eventconsumer/timeout_consumer.go +++ b/pkg/eventconsumer/timeout_consumer.go @@ -61,6 +61,7 @@ func (tc *timeOutConsumer) Run() { } signErrorResult.ResultType = event.ResultTypeError + signErrorResult.ErrorCode = event.ErrorCodeMaxDeliveryAttempts signErrorResult.IsTimeout = true signErrorResult.ErrorReason = "Signing failed: maximum delivery attempts exceeded" diff --git a/pkg/messaging/message_queue.go b/pkg/messaging/message_queue.go index 8aa2f73..c2aeef9 100644 --- a/pkg/messaging/message_queue.go +++ b/pkg/messaging/message_queue.go @@ -116,7 +116,7 @@ func (mq *msgQueue) Enqueue(topic string, message []byte, options *EnqueueOption if err != nil { logger.Error("Failed to publish message to JetStream", err, "topic", topic, "consumerName", mq.consumerName) - return fmt.Errorf("Error enqueueing message: %w", err) + return fmt.Errorf("error enqueueing message: %w", err) } return nil diff --git a/pkg/mpc/session.go b/pkg/mpc/session.go index 0f3fe2d..9b73e71 100644 --- a/pkg/mpc/session.go +++ b/pkg/mpc/session.go @@ -123,7 +123,8 @@ func (s *session) handleTssMessage(keyshare tss.Message) { topic := s.topicComposer.ComposeDirectTopic(nodeID) err := s.direct.Send(topic, msg) if err != nil { - s.ErrCh <- fmt.Errorf("Failed to send direct message to %s: %w", topic, err) + logger.Error("Failed to send direct message to", err, "topic", topic) + s.ErrCh <- fmt.Errorf("Failed to send direct message to %s", topic) } } From 21867b09dd587686aa8981fe546d210d9bd20b54 Mon Sep 17 00:00:00 2001 From: anhthii Date: Thu, 3 Jul 2025 21:59:46 +0700 Subject: [PATCH 22/25] Rename success event -> result event --- examples/generate/main.go | 2 +- pkg/client/client.go | 6 +++--- pkg/event/keygen.go | 2 +- pkg/eventconsumer/event_consumer.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/generate/main.go b/examples/generate/main.go index 9511418..343a418 100644 --- a/examples/generate/main.go +++ b/examples/generate/main.go @@ -60,7 +60,7 @@ func main() { } // STEP 2: Register the result handler AFTER all walletIDs are stored - err = mpcClient.OnWalletCreationResult(func(event event.KeygenSuccessEvent) { + err = mpcClient.OnWalletCreationResult(func(event event.KeygenResultEvent) { now := time.Now() startTimeAny, ok := walletStartTimes.Load(event.WalletID) if ok { diff --git a/pkg/client/client.go b/pkg/client/client.go index a130a4c..fe61bb4 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -26,7 +26,7 @@ const ( type MPCClient interface { CreateWallet(walletID string) error - OnWalletCreationResult(callback func(event event.KeygenSuccessEvent)) error + OnWalletCreationResult(callback func(event event.KeygenResultEvent)) error SignTransaction(msg *types.SignTxMessage) error OnSignResult(callback func(event event.SigningResultEvent)) error @@ -193,9 +193,9 @@ func (c *mpcClient) CreateWallet(walletID string) error { } // The callback will be invoked whenever a wallet creation result is received. -func (c *mpcClient) OnWalletCreationResult(callback func(event event.KeygenSuccessEvent)) error { +func (c *mpcClient) OnWalletCreationResult(callback func(event event.KeygenResultEvent)) error { err := c.genKeySuccessQueue.Dequeue(GenerateWalletSuccessTopic, func(msg []byte) error { - var event event.KeygenSuccessEvent + var event event.KeygenResultEvent err := json.Unmarshal(msg, &event) if err != nil { return err diff --git a/pkg/event/keygen.go b/pkg/event/keygen.go index 8b607e9..9aa86da 100644 --- a/pkg/event/keygen.go +++ b/pkg/event/keygen.go @@ -1,6 +1,6 @@ package event -type KeygenSuccessEvent struct { +type KeygenResultEvent struct { WalletID string `json:"wallet_id"` ECDSAPubKey []byte `json:"ecdsa_pub_key"` EDDSAPubKey []byte `json:"eddsa_pub_key"` diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 602c7e6..fa88147 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -152,7 +152,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { ctxEcdsa, doneEcdsa := context.WithCancel(baseCtx) ctxEddsa, doneEddsa := context.WithCancel(baseCtx) - successEvent := &event.KeygenSuccessEvent{WalletID: walletID, ResultType: event.ResultTypeSuccess} + successEvent := &event.KeygenResultEvent{WalletID: walletID, ResultType: event.ResultTypeSuccess} var wg sync.WaitGroup wg.Add(2) @@ -233,7 +233,7 @@ func (ec *eventConsumer) handleKeygenSessionError(walletID string, err error, co "context", contextMsg, ) - keygenResult := event.KeygenSuccessEvent{ + keygenResult := event.KeygenResultEvent{ ResultType: event.ResultTypeError, ErrorCode: string(errorCode), WalletID: walletID, From 721c8170daf8a09ee37c2ce1d882618bac99d12d Mon Sep 17 00:00:00 2001 From: anhthii Date: Fri, 4 Jul 2025 14:13:17 +0700 Subject: [PATCH 23/25] Fix resharing, only require t+1 participants, add session_id to remove idempotempt issue --- examples/reshare/main.go | 27 ++++++++++-------- pkg/event/types.go | 3 ++ pkg/eventconsumer/event_consumer.go | 13 ++++++++- pkg/mpc/node.go | 43 +++++++++++++++++++++++------ pkg/types/initiator_msg.go | 7 +++-- 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/examples/reshare/main.go b/examples/reshare/main.go index c5ef802..880868e 100644 --- a/examples/reshare/main.go +++ b/examples/reshare/main.go @@ -11,6 +11,7 @@ import ( "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" ) @@ -33,18 +34,6 @@ func main() { KeyPath: "./event_initiator.key", }) - resharingMsg := &types.ResharingMessage{ - WalletID: "98f6a23c-e78c-445e-92d9-95ccf927ca35", - NodeIDs: []string{"0ce02715-0ead-48ef-9772-2583316cc860", "ac37e85f-caca-4bee-8a3a-49a0fe35abff"}, // new peer IDs - NewThreshold: 1, // t+1 <= len(NodeIDs) - KeyType: types.KeyTypeSecp256k1, - } - err = mpcClient.Resharing(resharingMsg) - if err != nil { - logger.Fatal("Resharing failed", err) - } - fmt.Printf("Resharing(%q) sent, awaiting result...\n", resharingMsg.WalletID) - // 3) Listen for signing results err = mpcClient.OnResharingResult(func(evt event.ResharingResultEvent) { logger.Info("Resharing result received", @@ -58,6 +47,20 @@ func main() { logger.Fatal("Failed to subscribe to OnResharingResult", err) } + resharingMsg := &types.ResharingMessage{ + SessionID: uuid.NewString(), + WalletID: "bf2cc849-8e55-47e4-ab73-e17fb1eb690c", + NodeIDs: []string{"d926fa75-72c7-4538-9052-4a064a84981d", "7b1090cd-ffe3-46ff-8375-594dd3204169"}, // new peer IDs + + NewThreshold: 2, // t+1 <= len(NodeIDs) + KeyType: types.KeyTypeEd25519, + } + err = mpcClient.Resharing(resharingMsg) + if err != nil { + logger.Fatal("Resharing failed", err) + } + fmt.Printf("Resharing(%q) sent, awaiting result...\n", resharingMsg.WalletID) + stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) <-stop diff --git a/pkg/event/types.go b/pkg/event/types.go index 8cd0a04..c24c28a 100644 --- a/pkg/event/types.go +++ b/pkg/event/types.go @@ -48,6 +48,7 @@ const ( ErrorCodeKeyInfoLoad ErrorCode = "ERROR_KEY_INFO_LOAD" ErrorCodeKeyEncoding ErrorCode = "ERROR_KEY_ENCODING" ErrorCodeKeyDecoding ErrorCode = "ERROR_KEY_DECODING" + ErrorCodeMsgValidation ErrorCode = "ERROR_MSG_VALIDATION" // Cryptographic operation errors ErrorCodeSignatureGeneration ErrorCode = "ERROR_SIGNATURE_GENERATION" @@ -101,6 +102,8 @@ func GetErrorCodeFromError(err error) ErrorCode { // Check for specific error patterns switch { + case contains(errStr, "validation"): + return ErrorCodeMsgValidation case contains(errStr, "timeout", "timed out"): return ErrorCodeNetworkTimeout case contains(errStr, "connection", "connect"): diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index fa88147..89323b8 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -474,6 +474,17 @@ func (ec *eventConsumer) consumeReshareEvent() error { return } + if msg.SessionID == "" { + ec.handleReshareSessionError( + msg.WalletID, + msg.KeyType, + msg.NewThreshold, + errors.New("validation: session ID is empty"), + "Session ID is empty", + ) + return + } + if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to verify initiator message") @@ -591,7 +602,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return } - key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, walletID) + key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, msg.SessionID) err = ec.reshareResultQueue.Enqueue( key, successBytes, diff --git a/pkg/mpc/node.go b/pkg/mpc/node.go index e6e089e..720741d 100644 --- a/pkg/mpc/node.go +++ b/pkg/mpc/node.go @@ -173,7 +173,7 @@ func (p *Node) CreateSigningSession( } readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() - readyParticipantIDs := p.getReadyPeersForSigning(keyInfo, readyPeers) + readyParticipantIDs := p.getReadyPeersForSession(keyInfo, readyPeers) logger.Info("Creating signing session", "type", sessionType, @@ -247,7 +247,7 @@ func (p *Node) getKeyInfo(sessionType SessionType, walletID string) (*keyinfo.Ke return p.keyinfoStore.Get(keyID) } -func (p *Node) getReadyPeersForSigning(keyInfo *keyinfo.KeyInfo, readyPeers []string) []string { +func (p *Node) getReadyPeersForSession(keyInfo *keyinfo.KeyInfo, readyPeers []string) []string { // Ensure all participants are ready readyParticipantIDs := make([]string, 0, len(keyInfo.ParticipantPeerIDs)) for _, peerID := range keyInfo.ParticipantPeerIDs { @@ -289,10 +289,10 @@ func (p *Node) CreateReshareSession( return nil, fmt.Errorf("new peer list is smaller than required t+1") } - // 2. Check all new peers are ready - readyPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() + // 2. Make sure all new peers are ready + readyNewPeerIDs := p.peerRegistry.GetReadyPeersIncludeSelf() for _, peerID := range newPeerIDs { - if !slices.Contains(readyPeerIDs, peerID) { + if !slices.Contains(readyNewPeerIDs, peerID) { return nil, fmt.Errorf("new peer %s is not ready", peerID) } } @@ -308,6 +308,9 @@ func (p *Node) CreateReshareSession( return nil, fmt.Errorf("failed to get old key info: %w", err) } + readyPeers := p.peerRegistry.GetReadyPeersIncludeSelf() + readyOldParticipantIDs := p.getReadyPeersForSession(oldKeyInfo, readyPeers) + isInOldCommittee := slices.Contains(oldKeyInfo.ParticipantPeerIDs, p.nodeID) isInNewCommittee := slices.Contains(newPeerIDs, p.nodeID) @@ -321,17 +324,37 @@ func (p *Node) CreateReshareSession( return nil, nil } + logger.Info("Creating resharing session", + "type", sessionType, + "readyPeers", readyPeers, + "participantPeerIDs", oldKeyInfo.ParticipantPeerIDs, + "ready count", len(readyOldParticipantIDs), + "min ready", oldKeyInfo.Threshold+1, + "version", oldKeyInfo.Version, + ) + + if len(readyOldParticipantIDs) < oldKeyInfo.Threshold+1 { + return nil, fmt.Errorf("not enough peers to create resharing session! expected %d, got %d", oldKeyInfo.Threshold+1, len(readyOldParticipantIDs)) + } + + if err := p.ensureNodeIsParticipant(oldKeyInfo); err != nil { + return nil, err + } + // 5. Generate party IDs version := p.getVersion(sessionType, walletID) - oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, oldKeyInfo.ParticipantPeerIDs, version) + oldSelf, oldAllPartyIDs := p.generatePartyIDs(PurposeKeygen, readyOldParticipantIDs, version) newSelf, newAllPartyIDs := p.generatePartyIDs(PurposeReshare, newPeerIDs, version+1) // 6. Pick identity and call session constructor var selfPartyID *tss.PartyID + var participantPeerIDs []string if isNewPeer { selfPartyID = newSelf + participantPeerIDs = newPeerIDs } else { selfPartyID = oldSelf + participantPeerIDs = readyOldParticipantIDs } switch sessionType { @@ -339,12 +362,16 @@ func (p *Node) CreateReshareSession( preParams := p.ecdsaPreParams[0] if isNewPeer { preParams = p.ecdsaPreParams[1] + participantPeerIDs = newPeerIDs + } else { + participantPeerIDs = oldKeyInfo.ParticipantPeerIDs } + return NewECDSAReshareSession( walletID, p.pubSub, p.direct, - readyPeerIDs, + participantPeerIDs, selfPartyID, oldAllPartyIDs, newAllPartyIDs, @@ -365,7 +392,7 @@ func (p *Node) CreateReshareSession( walletID, p.pubSub, p.direct, - readyPeerIDs, + participantPeerIDs, selfPartyID, oldAllPartyIDs, newAllPartyIDs, diff --git a/pkg/types/initiator_msg.go b/pkg/types/initiator_msg.go index 70dee28..6684058 100644 --- a/pkg/types/initiator_msg.go +++ b/pkg/types/initiator_msg.go @@ -34,11 +34,12 @@ type SignTxMessage struct { } type ResharingMessage struct { + SessionID string `json:"session_id"` NodeIDs []string `json:"node_ids"` // new peer IDs NewThreshold int `json:"new_threshold"` KeyType KeyType `json:"key_type"` WalletID string `json:"wallet_id"` - Signature []byte `json:"signature"` + Signature []byte `json:"signature,omitempty"` } func (m *SignTxMessage) Raw() ([]byte, error) { @@ -80,7 +81,9 @@ func (m *GenerateKeyMessage) InitiatorID() string { } func (m *ResharingMessage) Raw() ([]byte, error) { - return []byte(m.WalletID), nil + copy := *m // create a shallow copy + copy.Signature = nil // modify only the copy + return json.Marshal(©) } func (m *ResharingMessage) Sig() []byte { From 1849bb881fee1e438abb90672d2cc91aace4562d Mon Sep 17 00:00:00 2001 From: anhthii Date: Fri, 4 Jul 2025 16:25:37 +0700 Subject: [PATCH 24/25] Refactor handle resharing error --- pkg/eventconsumer/event_consumer.go | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 89323b8..89130a3 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -470,24 +470,18 @@ func (ec *eventConsumer) consumeReshareEvent() error { var msg types.ResharingMessage if err := json.Unmarshal(natMsg.Data, &msg); err != nil { logger.Error("Failed to unmarshal resharing message", err) - ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to unmarshal resharing message") + ec.handleReshareSessionError(msg, err, "Failed to unmarshal resharing message") return } if msg.SessionID == "" { - ec.handleReshareSessionError( - msg.WalletID, - msg.KeyType, - msg.NewThreshold, - errors.New("validation: session ID is empty"), - "Session ID is empty", - ) + ec.handleReshareSessionError(msg, errors.New("validation: session ID is empty"), "Session ID is empty") return } if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) - ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to verify initiator message") + ec.handleReshareSessionError(msg, err, "Failed to verify initiator message") return } @@ -497,7 +491,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { sessionType, err := sessionTypeFromKeyType(keyType) if err != nil { logger.Error("Failed to get session type", err) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to get session type") + ec.handleReshareSessionError(msg, err, "Failed to get session type") return } @@ -516,13 +510,13 @@ func (ec *eventConsumer) consumeReshareEvent() error { oldSession, err := createSession(false) if err != nil { logger.Error("Failed to create old reshare session", err, "walletID", walletID) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create old reshare session") + ec.handleReshareSessionError(msg, err, "Failed to create old reshare session") return } newSession, err := createSession(true) if err != nil { logger.Error("Failed to create new reshare session", err, "walletID", walletID) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create new reshare session") + ec.handleReshareSessionError(msg, err, "Failed to create new reshare session") return } @@ -558,7 +552,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-oldSession.ErrChan(): logger.Error("Old reshare session error", err) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Old reshare session error") + ec.handleReshareSessionError(msg, err, "Old reshare session error") doneOld() // Cancel the context to stop this session return } @@ -582,7 +576,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-newSession.ErrChan(): logger.Error("New reshare session error", err) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "New reshare session error") + ec.handleReshareSessionError(msg, err, "New reshare session error") doneNew() // Cancel the context to stop this session return } @@ -598,7 +592,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { successBytes, err := json.Marshal(successEvent) if err != nil { logger.Error("Failed to marshal reshare success event", err) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to marshal reshare success event") + ec.handleReshareSessionError(msg, err, "Failed to marshal reshare success event") return } @@ -611,7 +605,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { }) if err != nil { logger.Error("Failed to publish reshare success message", err) - ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to publish reshare success message") + ec.handleReshareSessionError(msg, err, "Failed to publish reshare success message") return } logger.Info("[COMPLETED RESHARE] Successfully published", "walletID", walletID) @@ -625,14 +619,19 @@ func (ec *eventConsumer) consumeReshareEvent() error { } // handleReshareSessionError handles errors that occur during reshare operations -func (ec *eventConsumer) handleReshareSessionError(walletID string, keyType types.KeyType, newThreshold int, err error, contextMsg string) { +func (ec *eventConsumer) handleReshareSessionError( + msg types.ResharingMessage, + err error, + contextMsg string, +) { fullErrMsg := fmt.Sprintf("%s: %v", contextMsg, err) errorCode := event.GetErrorCodeFromError(err) logger.Warn("Reshare session error", - "walletID", walletID, - "keyType", keyType, - "newThreshold", newThreshold, + "walletID", msg.WalletID, + "keyType", msg.KeyType, + "newThreshold", msg.NewThreshold, + "sessionID", msg.SessionID, "error", err.Error(), "errorCode", errorCode, "context", contextMsg, @@ -641,27 +640,28 @@ func (ec *eventConsumer) handleReshareSessionError(walletID string, keyType type reshareResult := event.ResharingResultEvent{ ResultType: event.ResultTypeError, ErrorCode: string(errorCode), - WalletID: walletID, - KeyType: keyType, - NewThreshold: newThreshold, + WalletID: msg.WalletID, + KeyType: msg.KeyType, + NewThreshold: msg.NewThreshold, ErrorReason: fullErrMsg, } reshareResultBytes, err := json.Marshal(reshareResult) if err != nil { logger.Error("Failed to marshal reshare result event", err, - "walletID", walletID, + "walletID", msg.WalletID, ) return } - key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, walletID) + key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, msg.SessionID) err = ec.reshareResultQueue.Enqueue(key, reshareResultBytes, &messaging.EnqueueOptions{ IdempotententKey: key, }) if err != nil { logger.Error("Failed to enqueue reshare result event", err, - "walletID", walletID, + "walletID", msg.WalletID, + "sessionID", msg.SessionID, "payload", string(reshareResultBytes), ) } From 14d6952dbf33de452f395cf4b19391e4862693fa Mon Sep 17 00:00:00 2001 From: anhthii Date: Fri, 4 Jul 2025 16:48:18 +0700 Subject: [PATCH 25/25] Consistent reshare result topic and fix Inconsistent Success Event --- pkg/eventconsumer/event_consumer.go | 68 ++++++++++++++++++----------- pkg/eventconsumer/sign_consumer.go | 1 + 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/pkg/eventconsumer/event_consumer.go b/pkg/eventconsumer/event_consumer.go index 89130a3..085699e 100644 --- a/pkg/eventconsumer/event_consumer.go +++ b/pkg/eventconsumer/event_consumer.go @@ -156,6 +156,9 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { var wg sync.WaitGroup wg.Add(2) + // Channel to communicate errors from goroutines to main function + errorChan := make(chan error, 2) + go func() { defer wg.Done() select { @@ -164,6 +167,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { case err := <-ecdsaSession.ErrChan(): logger.Error("ECDSA keygen session error", err) ec.handleKeygenSessionError(walletID, err, "ECDSA keygen session error") + errorChan <- err doneEcdsa() } }() @@ -175,6 +179,7 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { case err := <-eddsaSession.ErrChan(): logger.Error("EdDSA keygen session error", err) ec.handleKeygenSessionError(walletID, err, "EdDSA keygen session error") + errorChan <- err doneEddsa() } }() @@ -196,11 +201,18 @@ func (ec *eventConsumer) handleKeyGenEvent(natMsg *nats.Msg) { select { case <-doneAll: - // all sessions finished before timeout + // Check if any errors occurred during execution + select { + case <-errorChan: + // Error already handled by the goroutine, just return early + return + default: + // No errors, continue with success + } case <-baseCtx.Done(): // timeout occurred - logger.Warn("Key generation timed out", "walletID", walletID) - ec.handleKeygenSessionError(walletID, fmt.Errorf("keygen session timed out after"), "Key generation timed out") + logger.Warn("Key generation timed out", "walletID", walletID, "timeout", KeyGenTimeOut) + ec.handleKeygenSessionError(walletID, fmt.Errorf("keygen session timed out after %v", KeyGenTimeOut), "Key generation timed out") return } @@ -470,18 +482,24 @@ func (ec *eventConsumer) consumeReshareEvent() error { var msg types.ResharingMessage if err := json.Unmarshal(natMsg.Data, &msg); err != nil { logger.Error("Failed to unmarshal resharing message", err) - ec.handleReshareSessionError(msg, err, "Failed to unmarshal resharing message") + ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to unmarshal resharing message") return } if msg.SessionID == "" { - ec.handleReshareSessionError(msg, errors.New("validation: session ID is empty"), "Session ID is empty") + ec.handleReshareSessionError( + msg.WalletID, + msg.KeyType, + msg.NewThreshold, + errors.New("validation: session ID is empty"), + "Session ID is empty", + ) return } if err := ec.identityStore.VerifyInitiatorMessage(&msg); err != nil { logger.Error("Failed to verify initiator message", err) - ec.handleReshareSessionError(msg, err, "Failed to verify initiator message") + ec.handleReshareSessionError(msg.WalletID, msg.KeyType, msg.NewThreshold, err, "Failed to verify initiator message") return } @@ -491,7 +509,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { sessionType, err := sessionTypeFromKeyType(keyType) if err != nil { logger.Error("Failed to get session type", err) - ec.handleReshareSessionError(msg, err, "Failed to get session type") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to get session type") return } @@ -510,13 +528,13 @@ func (ec *eventConsumer) consumeReshareEvent() error { oldSession, err := createSession(false) if err != nil { logger.Error("Failed to create old reshare session", err, "walletID", walletID) - ec.handleReshareSessionError(msg, err, "Failed to create old reshare session") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create old reshare session") return } newSession, err := createSession(true) if err != nil { logger.Error("Failed to create new reshare session", err, "walletID", walletID) - ec.handleReshareSessionError(msg, err, "Failed to create new reshare session") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to create new reshare session") return } @@ -552,7 +570,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-oldSession.ErrChan(): logger.Error("Old reshare session error", err) - ec.handleReshareSessionError(msg, err, "Old reshare session error") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Old reshare session error") doneOld() // Cancel the context to stop this session return } @@ -576,7 +594,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { return case err := <-newSession.ErrChan(): logger.Error("New reshare session error", err) - ec.handleReshareSessionError(msg, err, "New reshare session error") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "New reshare session error") doneNew() // Cancel the context to stop this session return } @@ -592,7 +610,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { successBytes, err := json.Marshal(successEvent) if err != nil { logger.Error("Failed to marshal reshare success event", err) - ec.handleReshareSessionError(msg, err, "Failed to marshal reshare success event") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to marshal reshare success event") return } @@ -605,7 +623,7 @@ func (ec *eventConsumer) consumeReshareEvent() error { }) if err != nil { logger.Error("Failed to publish reshare success message", err) - ec.handleReshareSessionError(msg, err, "Failed to publish reshare success message") + ec.handleReshareSessionError(walletID, keyType, msg.NewThreshold, err, "Failed to publish reshare success message") return } logger.Info("[COMPLETED RESHARE] Successfully published", "walletID", walletID) @@ -620,7 +638,9 @@ func (ec *eventConsumer) consumeReshareEvent() error { // handleReshareSessionError handles errors that occur during reshare operations func (ec *eventConsumer) handleReshareSessionError( - msg types.ResharingMessage, + walletID string, + keyType types.KeyType, + newThreshold int, err error, contextMsg string, ) { @@ -628,10 +648,9 @@ func (ec *eventConsumer) handleReshareSessionError( errorCode := event.GetErrorCodeFromError(err) logger.Warn("Reshare session error", - "walletID", msg.WalletID, - "keyType", msg.KeyType, - "newThreshold", msg.NewThreshold, - "sessionID", msg.SessionID, + "walletID", walletID, + "keyType", keyType, + "newThreshold", newThreshold, "error", err.Error(), "errorCode", errorCode, "context", contextMsg, @@ -640,28 +659,27 @@ func (ec *eventConsumer) handleReshareSessionError( reshareResult := event.ResharingResultEvent{ ResultType: event.ResultTypeError, ErrorCode: string(errorCode), - WalletID: msg.WalletID, - KeyType: msg.KeyType, - NewThreshold: msg.NewThreshold, + WalletID: walletID, + KeyType: keyType, + NewThreshold: newThreshold, ErrorReason: fullErrMsg, } reshareResultBytes, err := json.Marshal(reshareResult) if err != nil { logger.Error("Failed to marshal reshare result event", err, - "walletID", msg.WalletID, + "walletID", walletID, ) return } - key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, msg.SessionID) + key := fmt.Sprintf(mpc.TypeReshareWalletResultFmt, walletID) err = ec.reshareResultQueue.Enqueue(key, reshareResultBytes, &messaging.EnqueueOptions{ IdempotententKey: key, }) if err != nil { logger.Error("Failed to enqueue reshare result event", err, - "walletID", msg.WalletID, - "sessionID", msg.SessionID, + "walletID", walletID, "payload", string(reshareResultBytes), ) } diff --git a/pkg/eventconsumer/sign_consumer.go b/pkg/eventconsumer/sign_consumer.go index 6722fa1..deff686 100644 --- a/pkg/eventconsumer/sign_consumer.go +++ b/pkg/eventconsumer/sign_consumer.go @@ -135,6 +135,7 @@ func (sc *signingConsumer) handleSigningEvent(msg jetstream.Msg) { logger.Warn("SigningConsumer: Not enough peers to process signing request, rejecting message", "ready", readyPeers, "required", requiredPeers) + // Immediately return and let nats redeliver the message with backoff return }