Skip to content

Commit 0dd757f

Browse files
committed
catch up functionality
1 parent 8a7eaf8 commit 0dd757f

File tree

2 files changed

+211
-21
lines changed

2 files changed

+211
-21
lines changed

keygen.go

+126-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/binary"
66
"errors"
77
"fmt"
8+
"sort"
89

910
"github.com/renproject/secp256k1"
1011
)
@@ -73,13 +74,14 @@ func NewEmptyDKGState(n, t int) DKGState {
7374
return DKGState{
7475
Coefficients: coefficients,
7576

77+
Step: 1,
7678
Commitments: commitments,
7779
Shares: shares,
7880
}
7981
}
8082

8183
func (state *DKGState) Reset() {
82-
state.Coefficients = state.Coefficients[:0]
84+
state.Step = 1
8385

8486
for index := range state.Commitments {
8587
delete(state.Commitments, index)
@@ -90,14 +92,8 @@ func (state *DKGState) Reset() {
9092
}
9193
}
9294

93-
func DKGStart(state *DKGState, t int, ownIndex uint16, context [32]byte) DKGMessage {
94-
state.Step = 1
95-
for index := range state.Commitments {
96-
delete(state.Commitments, index)
97-
}
98-
for index := range state.Shares {
99-
delete(state.Shares, index)
100-
}
95+
func DKGStart(state *DKGState, info *DKGCatchUpInfo, t int, ownIndex uint16, context [32]byte) DKGMessage {
96+
state.Reset()
10197

10298
coeffs := make([]secp256k1.Fn, t)
10399
for i := range coeffs {
@@ -110,6 +106,9 @@ func DKGStart(state *DKGState, t int, ownIndex uint16, context [32]byte) DKGMess
110106
commitments[i].BaseExp(&coeffs[i])
111107
}
112108

109+
// @Performance: In the future we might want to reuse the memory of
110+
// state.Commitments between executions. This would require potential
111+
// resizing to the call to Reset.
113112
state.Commitments[ownIndex] = commitments
114113

115114
var r secp256k1.Point
@@ -124,6 +123,16 @@ func DKGStart(state *DKGState, t int, ownIndex uint16, context [32]byte) DKGMess
124123

125124
proof := Proof{R: r, Mu: mu}
126125

126+
if info != nil {
127+
info.Commitments = make([]secp256k1.Point, t)
128+
copy(info.Commitments, commitments)
129+
130+
info.Proof = proof
131+
132+
info.Coefficients = make([]secp256k1.Fn, t)
133+
copy(info.Coefficients, coeffs)
134+
}
135+
127136
data := make([]byte, ProofLenMarshalled+t*secp256k1.PointSizeMarshalled)
128137
proof.PutBytes(data[:ProofLenMarshalled])
129138
tail := data[ProofLenMarshalled:]
@@ -205,7 +214,7 @@ func DKGHandleShare(state *DKGState, ownIndex, from uint16, share secp256k1.Fn)
205214
return nil
206215
}
207216

208-
func DKGHandleMessage(state *DKGState, ownIndex uint16, indices []uint16, t int, context [32]byte, message DKGMessage, from uint16) (bool, DKGOutput, []DKGMessageTo, error) {
217+
func DKGHandleMessage(state *DKGState, info *DKGCatchUpInfo, ownIndex uint16, indices []uint16, t int, context [32]byte, message DKGMessage, from uint16) (bool, DKGOutput, []DKGMessageTo, error) {
209218
n := len(indices)
210219

211220
switch message.Type {
@@ -244,7 +253,7 @@ func DKGHandleMessage(state *DKGState, ownIndex uint16, indices []uint16, t int,
244253
}
245254

246255
if len(state.Commitments) == n {
247-
return transitionToStep2(state, ownIndex, indices)
256+
return transitionToStep2(state, info, ownIndex, indices)
248257
} else {
249258
return false, DKGOutput{}, nil, nil
250259
}
@@ -272,7 +281,7 @@ func DKGHandleMessage(state *DKGState, ownIndex uint16, indices []uint16, t int,
272281
}
273282
}
274283

275-
func DKGHandleTimeout(state *DKGState, ownIndex uint16, indices []uint16, t int) (bool, DKGOutput, []DKGMessageTo, error) {
284+
func DKGHandleTimeout(state *DKGState, info *DKGCatchUpInfo, ownIndex uint16, indices []uint16, t int) (bool, DKGOutput, []DKGMessageTo, error) {
276285
if state.Step != 1 {
277286
return false, DKGOutput{}, nil, nil
278287
}
@@ -281,10 +290,10 @@ func DKGHandleTimeout(state *DKGState, ownIndex uint16, indices []uint16, t int)
281290
return false, DKGOutput{}, nil, fmt.Errorf("timeout before obtaining sufficient commitments: needed at least %v, got %v", t, len(state.Commitments))
282291
}
283292

284-
return transitionToStep2(state, ownIndex, indices)
293+
return transitionToStep2(state, info, ownIndex, indices)
285294
}
286295

287-
func transitionToStep2(state *DKGState, ownIndex uint16, indices []uint16) (bool, DKGOutput, []DKGMessageTo, error) {
296+
func transitionToStep2(state *DKGState, info *DKGCatchUpInfo, ownIndex uint16, indices []uint16) (bool, DKGOutput, []DKGMessageTo, error) {
288297
shareMessages := make([]DKGMessageTo, 0, len(state.Commitments)-1)
289298
for index := range state.Commitments {
290299
if index == ownIndex {
@@ -311,6 +320,15 @@ func transitionToStep2(state *DKGState, ownIndex uint16, indices []uint16) (bool
311320
return true, computeOutputs(state, indices, ownIndex), nil, nil
312321
}
313322

323+
if info != nil {
324+
info.IndexSubset = make([]uint16, 0, len(state.Commitments))
325+
for index := range state.Commitments {
326+
info.IndexSubset = append(info.IndexSubset, index)
327+
}
328+
329+
sort.Slice(info.IndexSubset, func(i, j int) bool { return info.IndexSubset[i] < info.IndexSubset[j] })
330+
}
331+
314332
return false, DKGOutput{}, shareMessages, nil
315333
}
316334

@@ -388,3 +406,97 @@ func computeOutputs(state *DKGState, indices []uint16, ownIndex uint16) DKGOutpu
388406

389407
return DKGOutput{Share: share, PubKey: pubKey, PubKeyShares: pubKeyShares}
390408
}
409+
410+
type DKGCatchUpInfo struct {
411+
Commitments []secp256k1.Point
412+
Proof
413+
Coefficients []secp256k1.Fn
414+
IndexSubset []uint16
415+
}
416+
417+
func commitmentsMsgLen(t int) int {
418+
return 1 + t*secp256k1.PointSizeMarshalled + ProofLenMarshalled
419+
}
420+
421+
func shareMsgLen() int {
422+
return 1 + secp256k1.FnSizeMarshalled
423+
}
424+
425+
func (info *DKGCatchUpInfo) SerialisedMessage(index uint16) []byte {
426+
t := len(info.Coefficients)
427+
428+
l := commitmentsMsgLen(t) + shareMsgLen() + len(info.IndexSubset)*2
429+
bs := make([]byte, l)
430+
rem := bs
431+
432+
rem[0] = DKGTypeContribution
433+
rem = rem[1:]
434+
435+
info.Proof.PutBytes(rem)
436+
rem = rem[ProofLenMarshalled:]
437+
438+
for i := range info.Commitments {
439+
info.Commitments[i].PutBytes(rem)
440+
rem = rem[secp256k1.PointSizeMarshalled:]
441+
}
442+
443+
rem[0] = DKGTypeShare
444+
rem = rem[1:]
445+
share := computeShare(index, info.Coefficients)
446+
share.PutB32(rem)
447+
rem = rem[secp256k1.FnSizeMarshalled:]
448+
449+
for i := range info.IndexSubset {
450+
binary.LittleEndian.PutUint16(rem, info.IndexSubset[i])
451+
rem = rem[2:]
452+
}
453+
454+
return bs
455+
}
456+
457+
type DKGCatchUpMessage struct {
458+
IndexSubset []uint16
459+
CommitmentsMsg DKGMessage
460+
ShareMsg DKGMessage
461+
}
462+
463+
func DKGDeserialiseCatchUpMessage(bs []byte, n, t int) (DKGCatchUpMessage, error) {
464+
commitmentsMsgLen := commitmentsMsgLen(t)
465+
shareMsgLen := shareMsgLen()
466+
467+
minLen := commitmentsMsgLen + shareMsgLen + t*2
468+
maxLen := commitmentsMsgLen + shareMsgLen + n*2
469+
470+
if len(bs) < minLen {
471+
return DKGCatchUpMessage{}, fmt.Errorf("serialised message too short: expected at least %v bytes, got %v", minLen, len(bs))
472+
}
473+
474+
if len(bs) > maxLen {
475+
return DKGCatchUpMessage{}, fmt.Errorf("serialised message too long: expected at most %v bytes, got %v", maxLen, len(bs))
476+
}
477+
478+
commitmentsMsg := DKGMessage{
479+
Type: bs[0],
480+
Data: bs[1:commitmentsMsgLen],
481+
}
482+
bs = bs[commitmentsMsgLen:]
483+
484+
shareMsg := DKGMessage{
485+
Type: bs[0],
486+
Data: bs[1:shareMsgLen],
487+
}
488+
bs = bs[shareMsgLen:]
489+
490+
numIndices := len(bs) / 2
491+
indexSubset := make([]uint16, numIndices)
492+
for i := range indexSubset {
493+
indexSubset[i] = binary.LittleEndian.Uint16(bs)
494+
bs = bs[2:]
495+
}
496+
497+
return DKGCatchUpMessage{
498+
IndexSubset: indexSubset,
499+
CommitmentsMsg: commitmentsMsg,
500+
ShareMsg: shareMsg,
501+
}, nil
502+
}

keygen_test.go

+85-7
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,82 @@ var _ = Describe("DKG", func() {
7373
outputs := executeDKG(players, step1Timeout)
7474
checkOutputs(outputs, n, t, indices)
7575
})
76+
77+
FIt("should allow a node to catch up if it was offline", func() {
78+
n := 10
79+
t := 5
80+
81+
indices := sequentialIndices(n)
82+
context := [32]byte{}
83+
copy(context[:], []byte("context"))
84+
step1Timeout := time.Duration(500 * time.Millisecond)
85+
86+
players := createDKGPlayers(indices, t, context)
87+
for i := range players {
88+
players[i].enableCatchUpInfo()
89+
}
90+
for i := 0; i < t; i++ {
91+
players[i].online = false
92+
}
93+
94+
outputs := executeDKG(players, step1Timeout)
95+
96+
for i := range players {
97+
if players[i].online {
98+
continue
99+
}
100+
101+
msgsFrom := map[uint16]frost.DKGCatchUpMessage{}
102+
103+
for j := range players {
104+
if !players[j].online {
105+
continue
106+
}
107+
108+
msgBytes := players[j].info.SerialisedMessage(players[i].index)
109+
110+
msg, err := frost.DKGDeserialiseCatchUpMessage(msgBytes, n, t)
111+
Expect(err).ToNot(HaveOccurred())
112+
113+
msgsFrom[players[j].index] = msg
114+
}
115+
116+
// TODO(ross): Probably simulate picking the most commonly seen
117+
// subset.
118+
var indexSubset []uint16
119+
for _, msg := range msgsFrom {
120+
indexSubset = msg.IndexSubset
121+
break
122+
}
123+
124+
for _, index := range indexSubset {
125+
done, _, _, err := frost.DKGHandleMessage(&players[i].state, nil, players[i].index, players[i].indices, players[i].t, players[i].context, msgsFrom[index].CommitmentsMsg, index)
126+
Expect(err).ToNot(HaveOccurred())
127+
Expect(done).To(BeFalse())
128+
}
129+
130+
done, _, _, err := frost.DKGHandleTimeout(&players[i].state, nil, players[i].index, players[i].indices, players[i].t)
131+
Expect(err).ToNot(HaveOccurred())
132+
Expect(done).To(BeFalse())
133+
134+
for j, index := range indexSubset {
135+
done, output, _, err := frost.DKGHandleMessage(&players[i].state, nil, players[i].index, players[i].indices, players[i].t, players[i].context, msgsFrom[index].ShareMsg, index)
136+
Expect(err).ToNot(HaveOccurred())
137+
if j != len(indexSubset)-1 {
138+
Expect(done).To(BeFalse())
139+
} else {
140+
Expect(done).To(BeTrue())
141+
outputs = append(outputs, dkgSimOutput{
142+
index: players[i].index,
143+
output: output,
144+
err: nil,
145+
})
146+
}
147+
}
148+
}
149+
150+
checkOutputs(outputs, n, t, indices)
151+
})
76152
})
77153
})
78154

@@ -109,10 +185,6 @@ func checkOutputs(outputs []dkgSimOutput, n, t int, indices []uint16) {
109185
Expect(outputs[i].output.PubKey.Eq(&expectedPubKey)).To(BeTrue())
110186

111187
for j := range outputs[i].output.PubKeyShares {
112-
if !setContains(outputIndices, indices[j]) {
113-
continue
114-
}
115-
116188
Expect(outputs[i].output.PubKeyShares[j].Eq(&expectedPubKeyShares[j])).To(BeTrue())
117189
}
118190
}
@@ -141,6 +213,7 @@ type dkgPlayer struct {
141213
t int
142214
context [32]byte
143215
state frost.DKGState
216+
info *frost.DKGCatchUpInfo
144217

145218
online bool
146219
aborted bool
@@ -156,6 +229,7 @@ func createDKGPlayers(indices []uint16, t int, context [32]byte) []dkgPlayer {
156229
t: t,
157230
context: context,
158231
state: frost.NewEmptyDKGState(len(indices), t),
232+
info: nil,
159233

160234
online: true,
161235
aborted: false,
@@ -165,6 +239,10 @@ func createDKGPlayers(indices []uint16, t int, context [32]byte) []dkgPlayer {
165239
return players
166240
}
167241

242+
func (player *dkgPlayer) enableCatchUpInfo() {
243+
player.info = &frost.DKGCatchUpInfo{}
244+
}
245+
168246
func (player *dkgPlayer) reset() {
169247
player.state.Reset()
170248
player.online = true
@@ -184,7 +262,7 @@ func executeDKG(players []dkgPlayer, step1Timeout time.Duration) []dkgSimOutput
184262

185263
for i := range players {
186264
if players[i].online {
187-
msg := frost.DKGStart(&players[i].state, players[i].t, players[i].index, players[i].context)
265+
msg := frost.DKGStart(&players[i].state, players[i].info, players[i].t, players[i].index, players[i].context)
188266
for j := range players {
189267
if players[i].index != players[j].index {
190268
msgQueue.push(dkgMessage{
@@ -209,7 +287,7 @@ func executeDKG(players []dkgPlayer, step1Timeout time.Duration) []dkgSimOutput
209287

210288
for i := range players {
211289
if players[i].online {
212-
done, output, newMessages, err := frost.DKGHandleTimeout(&players[i].state, players[i].index, players[i].indices, players[i].t)
290+
done, output, newMessages, err := frost.DKGHandleTimeout(&players[i].state, players[i].info, players[i].index, players[i].indices, players[i].t)
213291

214292
processHandleOutput(&msgQueue, &players[i], &outputs, done, output, newMessages, err)
215293
if simulationDone(players, outputs) {
@@ -236,7 +314,7 @@ func executeDKG(players []dkgPlayer, step1Timeout time.Duration) []dkgSimOutput
236314
continue
237315
}
238316

239-
done, output, newMessages, err := frost.DKGHandleMessage(&player.state, player.index, player.indices, player.t, player.context, m.msg, m.from)
317+
done, output, newMessages, err := frost.DKGHandleMessage(&player.state, player.info, player.index, player.indices, player.t, player.context, m.msg, m.from)
240318

241319
processHandleOutput(&msgQueue, player, &outputs, done, output, newMessages, err)
242320
if simulationDone(players, outputs) {

0 commit comments

Comments
 (0)