github.com/onflow/flow-go/crypto@v0.24.8/bls_thresholdsign_test.go (about) 1 //go:build relic 2 // +build relic 3 4 package crypto 5 6 import ( 7 crand "crypto/rand" 8 "fmt" 9 "sync" 10 "testing" 11 "time" 12 13 log "github.com/sirupsen/logrus" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestBLSThresholdSignature(t *testing.T) { 19 // stateless API 20 t.Run("centralized_stateless_keygen", testCentralizedStatelessAPI) 21 // stateful API 22 t.Run("centralized_stateful_keygen", testCentralizedStatefulAPI) 23 t.Run("distributed_stateful_feldmanVSS_keygen", testDistributedStatefulAPI_FeldmanVSS) 24 t.Run("distributed_stateful_jointFeldman_keygen", testDistributedStatefulAPI_JointFeldman) // Flow Random beacon case 25 } 26 27 const thresholdSignatureTag = "random tag" 28 29 var thresholdSignatureMessage = []byte("random message") 30 31 // centralized test of the stateful threshold signature using the threshold key generation. 32 func testCentralizedStatefulAPI(t *testing.T) { 33 n := 10 34 for threshold := MinimumThreshold; threshold < n; threshold++ { 35 // generate threshold keys 36 rand := getPRG(t) 37 seed := make([]byte, SeedMinLenDKG) 38 _, err := rand.Read(seed) 39 require.NoError(t, err) 40 skShares, pkShares, pkGroup, err := BLSThresholdKeyGen(n, threshold, seed) 41 require.NoError(t, err) 42 // generate signature shares 43 signers := make([]int, 0, n) 44 // hasher 45 kmac := NewExpandMsgXOFKMAC128(thresholdSignatureTag) 46 // fill the signers list and shuffle it 47 for i := 0; i < n; i++ { 48 signers = append(signers, i) 49 } 50 rand.Shuffle(n, func(i, j int) { 51 signers[i], signers[j] = signers[j], signers[i] 52 }) 53 54 t.Run("happy path", func(t *testing.T) { 55 // create the stateful threshold signer 56 ts, err := NewBLSThresholdSignatureInspector(pkGroup, pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 57 require.NoError(t, err) 58 59 // check EnoughShares 60 enough := ts.EnoughShares() 61 assert.False(t, enough) 62 var wg sync.WaitGroup 63 // create (t) signatures of the first randomly chosen signers 64 // ( 1 signature short of the threshold) 65 for j := 0; j < threshold; j++ { 66 wg.Add(1) 67 // test thread safety 68 go func(j int) { 69 defer wg.Done() 70 i := signers[j] 71 share, err := skShares[i].Sign(thresholdSignatureMessage, kmac) 72 require.NoError(t, err) 73 // VerifyShare 74 verif, err := ts.VerifyShare(i, share) 75 assert.NoError(t, err) 76 assert.True(t, verif, "signature should be valid") 77 // check HasSignature is false 78 ok, err := ts.HasShare(i) 79 assert.NoError(t, err) 80 assert.False(t, ok) 81 // TrustedAdd 82 enough, err := ts.TrustedAdd(i, share) 83 assert.NoError(t, err) 84 assert.False(t, enough) 85 // check HasShare is true 86 ok, err = ts.HasShare(i) 87 assert.NoError(t, err) 88 assert.True(t, ok) 89 // check EnoughSignature 90 assert.False(t, ts.EnoughShares(), "threshold shouldn't be reached") 91 // check ThresholdSignature 92 sig, err := ts.ThresholdSignature() 93 assert.Error(t, err) 94 assert.True(t, IsNotEnoughSharesError(err)) 95 assert.Nil(t, sig) 96 }(j) 97 } 98 wg.Wait() 99 // add the last required signature to get (t+1) shares 100 i := signers[threshold] 101 share, err := skShares[i].Sign(thresholdSignatureMessage, kmac) 102 require.NoError(t, err) 103 verif, enough, err := ts.VerifyAndAdd(i, share) 104 assert.NoError(t, err) 105 assert.True(t, verif) 106 assert.True(t, enough) 107 // check EnoughSignature 108 assert.True(t, ts.EnoughShares()) 109 110 // add a share when threshold is reached 111 if threshold+1 < n { 112 i := signers[threshold+1] 113 share, err := skShares[i].Sign(thresholdSignatureMessage, kmac) 114 require.NoError(t, err) 115 // Trusted Add 116 enough, err := ts.TrustedAdd(i, share) 117 assert.NoError(t, err) 118 assert.True(t, enough) 119 // VerifyAndAdd 120 verif, enough, err := ts.VerifyAndAdd(i, share) 121 assert.NoError(t, err) 122 assert.True(t, verif) 123 assert.True(t, enough) 124 } 125 // reconstruct the threshold signature 126 thresholdsignature, err := ts.ThresholdSignature() 127 require.NoError(t, err) 128 // VerifyThresholdSignature 129 verif, err = ts.VerifyThresholdSignature(thresholdsignature) 130 require.NoError(t, err) 131 assert.True(t, verif) 132 }) 133 134 t.Run("duplicate signer", func(t *testing.T) { 135 // create the stateful threshold signer 136 ts, err := NewBLSThresholdSignatureInspector(pkGroup, pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 137 require.NoError(t, err) 138 139 // Create a share and add it 140 i := rand.Intn(n) 141 share, err := skShares[i].Sign(thresholdSignatureMessage, kmac) 142 require.NoError(t, err) 143 enough, err := ts.TrustedAdd(i, share) 144 assert.NoError(t, err) 145 assert.False(t, enough) 146 147 // Add an existing share 148 149 // VerifyAndAdd 150 verif, enough, err := ts.VerifyAndAdd(i, share) 151 assert.Error(t, err) 152 assert.True(t, IsDuplicatedSignerError(err)) 153 assert.False(t, verif) 154 assert.False(t, enough) 155 // TrustedAdd 156 enough, err = ts.TrustedAdd(i, share) 157 assert.Error(t, err) 158 assert.True(t, IsDuplicatedSignerError(err)) 159 assert.False(t, enough) 160 }) 161 162 t.Run("Invalid index", func(t *testing.T) { 163 // create the stateful threshold signer 164 ts, err := NewBLSThresholdSignatureInspector(pkGroup, pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 165 require.NoError(t, err) 166 167 share, err := skShares[0].Sign(thresholdSignatureMessage, kmac) 168 require.NoError(t, err) 169 // invalid index 170 invalidIndex := len(pkShares) + 1 171 // VerifyShare 172 verif, err := ts.VerifyShare(invalidIndex, share) 173 assert.Error(t, err) 174 assert.True(t, IsInvalidInputsError(err)) 175 assert.False(t, verif) 176 // TrustedAdd 177 enough, err := ts.TrustedAdd(invalidIndex, share) 178 assert.Error(t, err) 179 assert.True(t, IsInvalidInputsError(err)) 180 assert.False(t, enough) 181 // VerifyAndAdd 182 verif, enough, err = ts.VerifyAndAdd(invalidIndex, share) 183 assert.Error(t, err) 184 assert.True(t, IsInvalidInputsError(err)) 185 assert.False(t, verif) 186 assert.False(t, enough) 187 // HasShare 188 verif, err = ts.HasShare(invalidIndex) 189 assert.Error(t, err) 190 assert.True(t, IsInvalidInputsError(err)) 191 assert.False(t, verif) 192 }) 193 194 t.Run("invalid signature", func(t *testing.T) { 195 index := signers[0] 196 ts, err := NewBLSThresholdSignatureInspector(pkGroup, pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 197 require.NoError(t, err) 198 share, err := skShares[index].Sign(thresholdSignatureMessage, kmac) 199 require.NoError(t, err) 200 201 // alter signature - invalid serialization 202 tmp := share[0] 203 share[0] = invalidBLSSignatureHeader 204 // VerifyShare 205 verif, err := ts.VerifyShare(index, share) 206 assert.NoError(t, err) 207 assert.False(t, verif) 208 // VerifyAndAdd 209 verif, enough, err := ts.VerifyAndAdd(index, share) 210 assert.NoError(t, err) 211 assert.False(t, verif) 212 assert.False(t, enough) 213 // check share was not added 214 verif, err = ts.HasShare(index) 215 assert.NoError(t, err) 216 assert.False(t, verif) 217 // restore share 218 share[0] = tmp 219 220 // valid curve point but invalid signature 221 otherIndex := (index + 1) % n // otherIndex is different than index 222 // VerifyShare 223 verif, err = ts.VerifyShare(otherIndex, share) 224 assert.NoError(t, err) 225 assert.False(t, verif) 226 // VerifyAndAdd 227 verif, enough, err = ts.VerifyAndAdd(otherIndex, share) 228 assert.NoError(t, err) 229 assert.False(t, verif) 230 assert.False(t, enough) 231 // check share was not added 232 verif, err = ts.HasShare(otherIndex) 233 assert.NoError(t, err) 234 assert.False(t, verif) 235 236 // trust add one invalid signature and check ThresholdSignature 237 tmp = share[0] 238 share[0] = invalidBLSSignatureHeader // alter the share 239 enough, err = ts.TrustedAdd(index, share) // invalid share 240 assert.NoError(t, err) 241 assert.False(t, enough) 242 for i := 1; i < threshold+1; i++ { // valid shares 243 index := signers[i] 244 valid, err := skShares[index].Sign(thresholdSignatureMessage, kmac) 245 require.NoError(t, err) 246 enough, err = ts.TrustedAdd(index, valid) 247 assert.NoError(t, err) 248 if i < threshold { 249 assert.False(t, enough) 250 } else { 251 assert.True(t, enough) 252 } 253 } 254 sig, err := ts.ThresholdSignature() 255 assert.Error(t, err) 256 assert.True(t, IsInvalidSignatureError(err)) 257 assert.Nil(t, sig) 258 share[0] = tmp // restore the share 259 }) 260 261 t.Run("constructor errors", func(t *testing.T) { 262 // invalid keys size 263 index := rand.Intn(n) 264 pkSharesInvalid := make([]PublicKey, ThresholdSignMaxSize+1) 265 tsFollower, err := NewBLSThresholdSignatureInspector(pkGroup, pkSharesInvalid, threshold, thresholdSignatureMessage, thresholdSignatureTag) 266 assert.Error(t, err) 267 assert.True(t, IsInvalidInputsError(err)) 268 assert.Nil(t, tsFollower) 269 // non BLS key share 270 seed := make([]byte, KeyGenSeedMinLen) 271 _, err = rand.Read(seed) 272 require.NoError(t, err) 273 skEcdsa, err := GeneratePrivateKey(ECDSAP256, seed) 274 require.NoError(t, err) 275 tmp := pkShares[0] 276 pkShares[0] = skEcdsa.PublicKey() 277 tsFollower, err = NewBLSThresholdSignatureInspector(pkGroup, pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 278 assert.Error(t, err) 279 assert.True(t, IsNotBLSKeyError(err)) 280 assert.Nil(t, tsFollower) 281 pkShares[0] = tmp // restore valid keys 282 // non BLS group key 283 tsFollower, err = NewBLSThresholdSignatureInspector(skEcdsa.PublicKey(), pkShares, threshold, thresholdSignatureMessage, thresholdSignatureTag) 284 assert.Error(t, err) 285 assert.True(t, IsNotBLSKeyError(err)) 286 assert.Nil(t, tsFollower) 287 // non BLS private key 288 tsParticipant, err := NewBLSThresholdSignatureParticipant(pkGroup, pkShares, threshold, index, skEcdsa, thresholdSignatureMessage, thresholdSignatureTag) 289 assert.Error(t, err) 290 assert.True(t, IsNotBLSKeyError(err)) 291 assert.Nil(t, tsParticipant) 292 // invalid current index 293 tsParticipant, err = NewBLSThresholdSignatureParticipant(pkGroup, pkShares, threshold, len(pkShares)+1, skShares[index], thresholdSignatureMessage, thresholdSignatureTag) 294 assert.Error(t, err) 295 assert.True(t, IsInvalidInputsError(err)) 296 assert.Nil(t, tsParticipant) 297 // invalid threshold 298 tsFollower, err = NewBLSThresholdSignatureInspector(pkGroup, pkShares, len(pkShares)+1, thresholdSignatureMessage, thresholdSignatureTag) 299 assert.Error(t, err) 300 assert.True(t, IsInvalidInputsError(err)) 301 assert.Nil(t, tsFollower) 302 // inconsistent private and public key 303 indexSwap := (index + 1) % n // indexSwap is different than index 304 pkShares[index], pkShares[indexSwap] = pkShares[indexSwap], pkShares[index] 305 tsParticipant, err = NewBLSThresholdSignatureParticipant(pkGroup, pkShares, len(pkShares)+1, index, skShares[index], thresholdSignatureMessage, thresholdSignatureTag) 306 assert.Error(t, err) 307 assert.True(t, IsInvalidInputsError(err)) 308 assert.Nil(t, tsParticipant) 309 pkShares[index], pkShares[indexSwap] = pkShares[indexSwap], pkShares[index] // restore keys 310 }) 311 } 312 } 313 314 // Distributed Threshold Signature stateful api test 315 // keys are generated using simple Feldman VSS 316 func testDistributedStatefulAPI_FeldmanVSS(t *testing.T) { 317 log.SetLevel(log.ErrorLevel) 318 log.Info("DKG starts") 319 gt = t 320 rand := getPRG(t) 321 // number of participants to test 322 n := 5 323 lead := rand.Intn(n) // random 324 var sync sync.WaitGroup 325 chans := make([]chan *message, n) 326 processors := make([]testDKGProcessor, 0, n) 327 328 // create n processors for all participants 329 for current := 0; current < n; current++ { 330 processors = append(processors, testDKGProcessor{ 331 current: current, 332 chans: chans, 333 protocol: dkgType, 334 }) 335 // create DKG in all participants 336 var err error 337 processors[current].dkg, err = NewFeldmanVSS(n, optimalThreshold(n), 338 current, &processors[current], lead) 339 require.NoError(t, err) 340 } 341 342 // create the participant (buffered) communication channels 343 for i := 0; i < n; i++ { 344 chans[i] = make(chan *message, 2*n) 345 } 346 // start DKG in all participants 347 seed := make([]byte, SeedMinLenDKG) 348 read, err := rand.Read(seed) 349 require.Equal(t, read, SeedMinLenDKG) 350 require.NoError(t, err) 351 sync.Add(n) 352 for current := 0; current < n; current++ { 353 err := processors[current].dkg.Start(seed) 354 require.NoError(t, err) 355 go tsDkgRunChan(&processors[current], &sync, t, 2) 356 } 357 358 // synchronize the main thread to end DKG 359 sync.Wait() 360 for i := 1; i < n; i++ { 361 assert.True(t, processors[i].pk.Equals(processors[0].pk), "2 group public keys are mismatching") 362 } 363 364 // Start TS 365 log.Info("TS starts") 366 sync.Add(n) 367 for i := 0; i < n; i++ { 368 go tsRunChan(&processors[i], &sync, t) 369 } 370 // synchronize the main thread to end TS 371 sync.Wait() 372 } 373 374 // Distributed Threshold Signature stateful api test 375 // keys are generated using Joint-Feldman 376 func testDistributedStatefulAPI_JointFeldman(t *testing.T) { 377 log.SetLevel(log.ErrorLevel) 378 log.Info("DKG starts") 379 gt = t 380 rand := getPRG(t) 381 // number of participants to test 382 n := 5 383 for threshold := MinimumThreshold; threshold < n; threshold++ { 384 var sync sync.WaitGroup 385 chans := make([]chan *message, n) 386 processors := make([]testDKGProcessor, 0, n) 387 388 // create n processors for all participants 389 for current := 0; current < n; current++ { 390 processors = append(processors, testDKGProcessor{ 391 current: current, 392 chans: chans, 393 protocol: dkgType, 394 }) 395 // create DKG in all participants 396 var err error 397 processors[current].dkg, err = NewJointFeldman(n, 398 optimalThreshold(n), current, &processors[current]) 399 require.NoError(t, err) 400 } 401 402 // create the participant (buffered) communication channels 403 for i := 0; i < n; i++ { 404 chans[i] = make(chan *message, 2*n) 405 } 406 // start DKG in all participants but the 407 seed := make([]byte, SeedMinLenDKG) 408 read, err := rand.Read(seed) 409 require.Equal(t, read, SeedMinLenDKG) 410 require.NoError(t, err) 411 sync.Add(n) 412 for current := 0; current < n; current++ { 413 err := processors[current].dkg.Start(seed) 414 require.NoError(t, err) 415 go tsDkgRunChan(&processors[current], &sync, t, 0) 416 } 417 418 // sync the 2 timeouts at all participants and start the next phase 419 for phase := 1; phase <= 2; phase++ { 420 sync.Wait() 421 sync.Add(n) 422 for current := 0; current < n; current++ { 423 go tsDkgRunChan(&processors[current], &sync, t, phase) 424 } 425 } 426 427 // synchronize the main thread to end DKG 428 sync.Wait() 429 for i := 1; i < n; i++ { 430 assert.True(t, processors[i].pk.Equals(processors[0].pk), 431 "2 group public keys are mismatching") 432 } 433 434 // Start TS 435 log.Info("TS starts") 436 sync.Add(n) 437 for current := 0; current < n; current++ { 438 go tsRunChan(&processors[current], &sync, t) 439 } 440 // synchronize the main thread to end TS 441 sync.Wait() 442 } 443 } 444 445 // This is a testing function 446 // It simulates processing incoming messages by a participant during DKG 447 // It assumes proc.dkg is already running 448 func tsDkgRunChan(proc *testDKGProcessor, 449 sync *sync.WaitGroup, t *testing.T, phase int) { 450 for { 451 select { 452 case newMsg := <-proc.chans[proc.current]: 453 log.Debugf("%d Receiving DKG from %d:", proc.current, newMsg.orig) 454 if newMsg.channel == private { 455 err := proc.dkg.HandlePrivateMsg(newMsg.orig, newMsg.data) 456 require.Nil(t, err) 457 } else { 458 err := proc.dkg.HandleBroadcastMsg(newMsg.orig, newMsg.data) 459 require.Nil(t, err) 460 } 461 462 // if timeout, finalize DKG and create the threshold signer 463 case <-time.After(200 * time.Millisecond): 464 switch phase { 465 case 0: 466 log.Infof("%d shares phase ended \n", proc.current) 467 err := proc.dkg.NextTimeout() 468 require.NoError(t, err) 469 case 1: 470 log.Infof("%d complaints phase ended \n", proc.current) 471 err := proc.dkg.NextTimeout() 472 require.NoError(t, err) 473 case 2: 474 log.Infof("%d dkg ended \n", proc.current) 475 sk, groupPK, nodesPK, err := proc.dkg.End() 476 require.NotNil(t, sk) 477 require.NotNil(t, groupPK) 478 require.NotNil(t, nodesPK) 479 require.Nil(t, err, "End dkg failed: %v\n", err) 480 proc.pk = groupPK 481 n := proc.dkg.Size() 482 proc.ts, err = NewBLSThresholdSignatureParticipant(groupPK, nodesPK, optimalThreshold(n), proc.current, sk, thresholdSignatureMessage, thresholdSignatureTag) 483 require.NoError(t, err) 484 // needed to test the statless api 485 proc.keys = &statelessKeys{sk, groupPK, nodesPK} 486 } 487 sync.Done() 488 return 489 } 490 } 491 } 492 493 // This is a testing function using the stateful api 494 // It simulates processing incoming messages by a participant during TS 495 func tsRunChan(proc *testDKGProcessor, sync *sync.WaitGroup, t *testing.T) { 496 // Sign a share and broadcast it 497 sigShare, err := proc.ts.SignShare() 498 proc.protocol = tsType 499 if err != nil { // not using require.Nil for now 500 panic(fmt.Sprintf("%d couldn't sign", proc.current)) 501 } 502 proc.Broadcast(sigShare) 503 for { 504 select { 505 case newMsg := <-proc.chans[proc.current]: 506 log.Debugf("%d Receiving TS from %d:", proc.current, newMsg.orig) 507 verif, enough, err := proc.ts.VerifyAndAdd( 508 newMsg.orig, newMsg.data) 509 require.NoError(t, err) 510 assert.True(t, verif, 511 "the signature share sent from %d to %d is not correct", newMsg.orig, 512 proc.current) 513 log.Info(enough) 514 if enough { 515 assert.Equal(t, enough, proc.ts.EnoughShares()) 516 thresholdSignature, err := proc.ts.ThresholdSignature() 517 require.NoError(t, err) 518 verif, err = proc.ts.VerifyThresholdSignature(thresholdSignature) 519 require.NoError(t, err) 520 assert.True(t, verif, "the threshold signature is not correct") 521 if verif { 522 log.Infof("%d reconstructed a valid signature: %d\n", proc.current, 523 thresholdSignature) 524 } 525 } 526 527 // if timeout, finalize TS 528 case <-time.After(time.Second): 529 sync.Done() 530 return 531 } 532 } 533 } 534 535 // This stucture holds the keys and is needed for the stateless test 536 type statelessKeys struct { 537 // the current participant private key (a DKG output) 538 myPrivateKey PrivateKey 539 // the group public key (a DKG output) 540 groupPublicKey PublicKey 541 // the group public key shares (a DKG output) 542 publicKeyShares []PublicKey 543 } 544 545 // Centralized test of threshold signature protocol using the threshold key generation. 546 func testCentralizedStatelessAPI(t *testing.T) { 547 rand := getPRG(t) 548 n := 10 549 for threshold := MinimumThreshold; threshold < n; threshold++ { 550 // generate threshold keys 551 seed := make([]byte, SeedMinLenDKG) 552 _, err := rand.Read(seed) 553 require.NoError(t, err) 554 skShares, pkShares, pkGroup, err := BLSThresholdKeyGen(n, threshold, seed) 555 require.NoError(t, err) 556 // signature hasher 557 kmac := NewExpandMsgXOFKMAC128(thresholdSignatureTag) 558 // generate signature shares 559 signShares := make([]Signature, 0, n) 560 signers := make([]int, 0, n) 561 // fill the signers list and shuffle it 562 for i := 0; i < n; i++ { 563 signers = append(signers, i) 564 } 565 rand.Shuffle(n, func(i, j int) { 566 signers[i], signers[j] = signers[j], signers[i] 567 }) 568 // create (t+1) signatures of the first randomly chosen signers 569 for j := 0; j < threshold+1; j++ { 570 i := signers[j] 571 share, err := skShares[i].Sign(thresholdSignatureMessage, kmac) 572 require.NoError(t, err) 573 verif, err := pkShares[i].Verify(share, thresholdSignatureMessage, kmac) 574 require.NoError(t, err) 575 assert.True(t, verif, "signature share is not valid") 576 if verif { 577 signShares = append(signShares, share) 578 } 579 } 580 // reconstruct and test the threshold signature 581 thresholdSignature, err := BLSReconstructThresholdSignature(n, threshold, signShares, signers[:threshold+1]) 582 require.NoError(t, err) 583 verif, err := pkGroup.Verify(thresholdSignature, thresholdSignatureMessage, kmac) 584 require.NoError(t, err) 585 assert.True(t, verif, "signature share is not valid") 586 587 // check failure with a random redundant signer 588 if threshold > 1 { 589 randomDuplicate := rand.Intn(int(threshold)) + 1 // 1 <= duplicate <= threshold 590 tmp := signers[randomDuplicate] 591 signers[randomDuplicate] = signers[0] 592 thresholdSignature, err = BLSReconstructThresholdSignature(n, threshold, signShares, signers[:threshold+1]) 593 assert.Error(t, err) 594 assert.True(t, IsDuplicatedSignerError(err)) 595 assert.Nil(t, thresholdSignature) 596 signers[randomDuplicate] = tmp 597 } 598 599 // check with an invalid signature (invalid serialization) 600 invalidSig := make([]byte, signatureLengthBLSBLS12381) 601 signShares[0] = invalidSig 602 thresholdSignature, err = BLSReconstructThresholdSignature(n, threshold, signShares, signers[:threshold+1]) 603 assert.Error(t, err) 604 assert.True(t, IsInvalidSignatureError(err)) 605 assert.Nil(t, thresholdSignature) 606 } 607 } 608 609 func BenchmarkSimpleKeyGen(b *testing.B) { 610 n := 60 611 seed := make([]byte, SeedMinLenDKG) 612 _, err := crand.Read(seed) 613 require.NoError(b, err) 614 b.ResetTimer() 615 for i := 0; i < b.N; i++ { 616 _, _, _, _ = BLSThresholdKeyGen(n, optimalThreshold(n), seed) 617 } 618 b.StopTimer() 619 }