@@ -27,6 +27,15 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
2727 return nil , errors .New ("reshare config is nil" )
2828 }
2929
30+ // Validate that AddedPeers and RemovedPeers are disjoint sets
31+ for _ , addedPeer := range config .Reshare .AddedPeers {
32+ for _ , removedPeer := range config .Reshare .RemovedPeers {
33+ if addedPeer == removedPeer {
34+ return nil , errors .New ("peer cannot be both added and removed" , z .Any ("peer_id" , addedPeer ))
35+ }
36+ }
37+ }
38+
3039 thisNodeIndex , err := config .ThisNodeIndex ()
3140 if err != nil {
3241 return nil , err
@@ -91,24 +100,77 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
91100 distKeyShares = append (distKeyShares , dks )
92101 }
93102
94- // The new nodes are always appended to the old nodes slice
95- // The old nodes can be indices within the existing nodes slice
96- oldN := len (nodes ) - len (config .Reshare .NewPeers )
97- oldNodes := nodes [:oldN ]
98- newNodes := make ([]kdkg.Node , len (nodes ))
99- copy (newNodes , nodes )
103+ // Classify nodes for reshare operation.
104+ // In KDKG terminology:
105+ // - oldNodes: nodes with existing shares (participating in the old cluster)
106+ // - newNodes: nodes that will have shares in the new cluster
107+ //
108+ // Supported scenarios:
109+ // 1. Pure reshare (no adds/removes): oldNodes = newNodes = all participating nodes
110+ // 2. Add-only: oldNodes = existing nodes; newNodes = existing + added nodes
111+ // 3. Remove-only: oldNodes = all participating nodes; newNodes = participating - removed nodes
112+
113+ // Determine if this node has existing shares to contribute
114+ thisIsOldNode := len (distKeyShares ) > 0
115+
116+ oldNodes := make ([]kdkg.Node , 0 , len (nodes ))
117+ newNodes := make ([]kdkg.Node , 0 , len (nodes ))
118+ thisIsRemovedNode := false
119+ thisIsAddedNode := false
120+
121+ for _ , node := range nodes {
122+ isRemoving := false
123+ isNewlyAdded := false
124+
125+ // Check if this node is being removed
126+ for _ , removedPeerID := range config .Reshare .RemovedPeers {
127+ if idx , ok := config .PeerMap [removedPeerID ]; ok && idx .PeerIdx == int (node .Index ) {
128+ isRemoving = true
129+ if idx .PeerIdx == thisNodeIndex {
130+ thisIsRemovedNode = true
131+ }
132+
133+ break
134+ }
135+ }
136+
137+ // Check if this node is newly added
138+ for _ , addedPeerID := range config .Reshare .AddedPeers {
139+ if idx , ok := config .PeerMap [addedPeerID ]; ok && idx .PeerIdx == int (node .Index ) {
140+ isNewlyAdded = true
141+ if idx .PeerIdx == thisNodeIndex {
142+ thisIsAddedNode = true
143+ }
144+
145+ break
146+ }
147+ }
100148
101- thisIsOldNode := false
149+ // Classify nodes:
150+ // - oldNodes: nodes that are not newly added (have existing shares)
151+ // - newNodes: nodes that are not being removed (will be in new cluster)
152+ if ! isNewlyAdded {
153+ oldNodes = append (oldNodes , node )
154+ }
102155
103- for _ , oid := range config .Reshare .OldPeers {
104- idx := config .PeerMap [oid ]
105- if idx .PeerIdx == thisNodeIndex {
106- thisIsOldNode = true
156+ if ! isRemoving {
157+ newNodes = append (newNodes , node )
107158 }
159+ }
160+
161+ // Validate node classification
162+ if len (config .Reshare .RemovedPeers ) > 0 && len (oldNodes ) == 0 {
163+ return nil , errors .New ("remove operation requires at least one node with existing shares to participate" )
164+ }
108165
109- newNodes = slices .DeleteFunc (newNodes , func (n kdkg.Node ) bool {
110- return n .Index == kdkg .Index (idx .PeerIdx )
111- })
166+ if len (config .Reshare .AddedPeers ) > 0 && len (newNodes ) <= len (oldNodes ) {
167+ return nil , errors .New ("add operation requires new nodes to join, but all nodes already exist in the cluster" )
168+ }
169+
170+ // In add operations, old nodes must have shares to contribute
171+ // (new nodes being added won't have shares, which is expected)
172+ if len (config .Reshare .AddedPeers ) > 0 && ! thisIsAddedNode && len (distKeyShares ) == 0 {
173+ return nil , errors .New ("existing node in add operation must have shares to contribute" )
112174 }
113175
114176 nonce , err := generateNonce (nodes )
@@ -132,19 +194,23 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
132194 log .Info (ctx , "Starting pedersen reshare..." ,
133195 z .Int ("oldNodes" , len (oldNodes )), z .Int ("newNodes" , len (newNodes )),
134196 z .Int ("oldThreshold" , config .Threshold ), z .Int ("newThreshold" , config .Reshare .NewThreshold ),
135- z .Bool ("removed " , thisIsOldNode ))
197+ z .Bool ("thisIsOldNode " , thisIsOldNode ), z . Bool ( "thisIsRemovedNode" , thisIsRemovedNode ))
136198
137199 newShares := make ([]share.Share , 0 , config .Reshare .TotalShares )
138200
139201 for shareNum := range config .Reshare .TotalShares {
140202 phaser := kdkg .NewTimePhaser (config .PhaseDuration )
141203
142- if len (distKeyShares ) > 0 {
143- // Share is to be set by old nodes only
204+ // Nodes with existing shares provide their share to the reshare protocol.
205+ // New nodes without shares provide public coefficients instead.
206+ isNodeWithExistingShares := len (distKeyShares ) > 0
207+
208+ if isNodeWithExistingShares {
209+ // This node has existing shares to contribute to the reshare
144210 reshareConfig .Share = distKeyShares [shareNum ]
145211 reshareConfig .PublicCoeffs = nil
146212 } else {
147- // PublicCoeffs is to be set by new nodes only, but not the Share
213+ // This is a new node - restore public coefficients from exchanged public key shares
148214 commits , err := restoreCommits (pubKeyShares , shareNum , config .Threshold )
149215 if err != nil {
150216 return nil , errors .Wrap (err , "restore commits" )
@@ -170,11 +236,13 @@ func RunReshareDKG(ctx context.Context, config *Config, board *Board, shares []s
170236 case <- ctx .Done ():
171237 return nil , errors .New ("pedersen reshare context done, protocol aborted" )
172238 case kdkgResult := <- protocol .WaitEnd ():
173- if thisIsOldNode {
239+ if thisIsRemovedNode {
240+ // This node is being removed and will not receive new shares
174241 if err := broadcastNoneKey (ctx , config , board ); err != nil {
175242 return nil , err
176243 }
177244 } else {
245+ // This node will be part of the new cluster and receives new shares
178246 if kdkgResult .Error != nil {
179247 return nil , errors .Wrap (kdkgResult .Error , "pedersen reshare protocol failed" , z .Bool ("thisIsOldNode" , thisIsOldNode ))
180248 }
0 commit comments