Skip to content

Commit 5dca0bf

Browse files
committed
test: cover setup decoders across keygen, refresh, quorumchange and sign (F-2026-18199)
1 parent 0dfb0a6 commit 5dca0bf

1 file changed

Lines changed: 81 additions & 21 deletions

File tree

universalClient/tss/dkls/utils_test.go

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -221,45 +221,105 @@ func TestSetupThreshold(t *testing.T) {
221221
})
222222
}
223223

224-
// The threshold check also runs on quorumchange, which is built by a different
225-
// constructor than keygen. This pins that DklsQcSetupMsgNew encodes the
226-
// threshold the same way, so a library rebuild that diverges fails here rather
227-
// than rejecting every quorum change in production.
228-
func TestSetupThreshold_QuorumChange(t *testing.T) {
224+
// Cross-protocol matrix over the three setup shapes the coordinator builds:
225+
// keygen (shared with keyrefresh), quorumchange, and sign (shared with fund
226+
// migration). Pins what each decoder returns for each shape, so a rebuilt DKLS
227+
// library that changes the encoding fails here rather than in production.
228+
func TestSetupDecoders_AllProtocols(t *testing.T) {
229229
participants := []string{"party1", "party2", "party3"}
230230
ids := encodeParticipantIDs(participants)
231+
const threshold = 2
231232

232-
kgSetup, err := session.DklsKeygenSetupMsgNew(2, nil, ids)
233+
messageHash := make([]byte, 32)
234+
copy(messageHash, "outbound-signing-hash-32-bytes!!")
235+
236+
// keygen, also used verbatim for keyrefresh
237+
keygenSetup, err := session.DklsKeygenSetupMsgNew(threshold, nil, ids)
233238
if err != nil {
234239
t.Fatalf("failed to build keygen setup: %v", err)
235240
}
241+
242+
// sign, also used for fund migration
243+
signSetup, err := session.DklsSignSetupMsgNew(make([]byte, 32), nil, messageHash, ids)
244+
if err != nil {
245+
t.Fatalf("failed to build sign setup: %v", err)
246+
}
247+
248+
// quorumchange needs a real keyshare, so run a keygen to completion first
236249
sessions := map[string]Session{}
237250
for _, p := range participants {
238-
s, err := NewKeygenSession(kgSetup, "threshold-qc", p, participants, 2)
251+
sess, err := NewKeygenSession(keygenSetup, "matrix", p, participants, threshold)
239252
if err != nil {
240253
t.Fatalf("failed to create keygen session for %s: %v", p, err)
241254
}
242-
sessions[p] = s
255+
sessions[p] = sess
243256
}
244257
keyshare := runToCompletion(t, sessions)["party1"].Keyshare
245-
246258
handle, err := session.DklsKeyshareFromBytes(keyshare)
247259
if err != nil {
248260
t.Fatalf("failed to load keyshare: %v", err)
249261
}
250262
defer session.DklsKeyshareFree(handle)
251263

252-
for _, want := range []int{2, 3} {
253-
qcSetup, err := session.DklsQcSetupMsgNew(handle, want, participants, []int{0, 1, 2}, []int{0, 1, 2})
254-
if err != nil {
255-
t.Fatalf("failed to build QC setup with threshold %d: %v", want, err)
256-
}
257-
got, err := SetupThreshold(qcSetup)
258-
if err != nil {
259-
t.Fatalf("SetupThreshold(qc) error = %v", err)
260-
}
261-
if got != want {
262-
t.Errorf("SetupThreshold(qc) = %d, want %d", got, want)
263-
}
264+
qcSetup, err := session.DklsQcSetupMsgNew(handle, threshold, participants, []int{0, 1, 2}, []int{0, 1, 2})
265+
if err != nil {
266+
t.Fatalf("failed to build QC setup: %v", err)
267+
}
268+
269+
shapes := []struct {
270+
name string
271+
setup []byte
272+
wantHash []byte // nil means the shape carries no message
273+
hasThreshold bool
274+
}{
275+
{"keygen and keyrefresh", keygenSetup, nil, true},
276+
{"quorumchange", qcSetup, nil, true},
277+
{"sign and fund migration", signSetup, messageHash, false},
278+
}
279+
280+
for _, sh := range shapes {
281+
t.Run(sh.name, func(t *testing.T) {
282+
// Participants are bound for every protocol, so every shape must decode them.
283+
got, err := SetupParticipants(sh.setup)
284+
if err != nil {
285+
t.Fatalf("SetupParticipants() error = %v", err)
286+
}
287+
if len(got) != len(participants) {
288+
t.Fatalf("SetupParticipants() = %v, want %v", got, participants)
289+
}
290+
for i := range participants {
291+
if got[i] != participants[i] {
292+
t.Errorf("participant %d = %q, want %q", i, got[i], participants[i])
293+
}
294+
}
295+
296+
gotThreshold, thresholdErr := SetupThreshold(sh.setup)
297+
if sh.hasThreshold {
298+
if thresholdErr != nil {
299+
t.Fatalf("SetupThreshold() error = %v", thresholdErr)
300+
}
301+
if gotThreshold != threshold {
302+
t.Errorf("SetupThreshold() = %d, want %d", gotThreshold, threshold)
303+
}
304+
} else if thresholdErr == nil {
305+
// Sign setups carry no threshold. Erroring is what stops a bogus
306+
// value being read out of unrelated bytes.
307+
t.Errorf("SetupThreshold() on a sign setup returned %d, want an error", gotThreshold)
308+
}
309+
310+
gotHash, hashErr := SetupMessageHash(sh.setup)
311+
if hashErr != nil {
312+
t.Fatalf("SetupMessageHash() error = %v", hashErr)
313+
}
314+
if sh.wantHash == nil {
315+
// Shapes without a message report an empty hash rather than an
316+
// error, so a non-sign setup can never satisfy the hash binding.
317+
if len(gotHash) != 0 {
318+
t.Errorf("SetupMessageHash() = %x, want empty", gotHash)
319+
}
320+
} else if !bytes.Equal(gotHash, sh.wantHash) {
321+
t.Errorf("SetupMessageHash() = %x, want %x", gotHash, sh.wantHash)
322+
}
323+
})
264324
}
265325
}

0 commit comments

Comments
 (0)