github.com/onflow/flow-go/crypto@v0.24.8/dkg_feldmanvssq.go (about) 1 //go:build relic 2 // +build relic 3 4 package crypto 5 6 // #cgo CFLAGS: -g -Wall -std=c99 7 // #include "dkg_include.h" 8 import "C" 9 10 import ( 11 "fmt" 12 ) 13 14 // Implements Feldman Verifiable Secret Sharing using 15 // the BLS set up on the BLS12-381 curve. A complaint mechanism 16 // is added to qualify/disqualify the dealer if they misbehave. 17 18 // The secret is a BLS private key generated by the dealer. 19 // (and hence this is a centralized generation). 20 // The dealer generates key shares for a BLS-based 21 // threshold signature scheme and distributes the shares over the (n) 22 // participants including itself. The participants validate their shares 23 // using a public verification vector shared by the dealer and are able 24 // to broadcast complaints against a misbehaving dealer. 25 26 // The dealer has the chance to avoid being disqualified by broadcasting 27 // a complaint answer. The protocol ends with all honest participants 28 // reaching a consensus about the dealer qualification/disqualification. 29 30 // Private keys are scalar in Zr, where r is the group order of G1/G2 31 // Public keys are in G2. 32 33 // feldman VSS protocol, with complaint mechanism, implements DKGState 34 type feldmanVSSQualState struct { 35 // feldmanVSSstate state 36 *feldmanVSSstate 37 // complaints received against the dealer: 38 // the key is the origin of the complaint 39 // a complaint will be created if a complaint message or an answer was 40 // broadcasted, a complaint will be checked only when both the 41 // complaint message and the answer were broadcasted 42 complaints map[index]*complaint 43 // is the dealer disqualified 44 disqualified bool 45 // Timeout to receive shares and verification vector 46 // - if a share is not received before this timeout a complaint will be formed 47 // - if the verification is not received before this timeout, 48 // dealer is disqualified 49 sharesTimeout bool 50 // Timeout to receive complaints 51 // all complaints received after this timeout are ignored 52 complaintsTimeout bool 53 } 54 55 // these data are required to justify a slashing 56 type complaint struct { 57 received bool 58 answerReceived bool 59 answer scalar 60 } 61 62 // NewFeldmanVSSQual creates a new instance of a Feldman VSS protocol 63 // with a qualification mechanism. 64 // 65 // An instance is run by a single participant and is usable for only one protocol. 66 // In order to run the protocol again, a new instance needs to be created 67 // 68 // The function returns: 69 // - (nil, InvalidInputsError) if: 70 // - size if not in [DKGMinSize, DKGMaxSize] 71 // - threshold is not in [MinimumThreshold, size-1] 72 // - myIndex is not in [0, size-1] 73 // - dealerIndex is not in [0, size-1] 74 // - (dkgInstance, nil) otherwise 75 func NewFeldmanVSSQual(size int, threshold int, myIndex int, 76 processor DKGProcessor, dealerIndex int) (DKGState, error) { 77 78 common, err := newDKGCommon(size, threshold, myIndex, processor, dealerIndex) 79 if err != nil { 80 return nil, err 81 } 82 83 fvss := &feldmanVSSstate{ 84 dkgCommon: common, 85 dealerIndex: index(dealerIndex), 86 } 87 fvssq := &feldmanVSSQualState{ 88 feldmanVSSstate: fvss, 89 disqualified: false, 90 } 91 fvssq.init() 92 return fvssq, nil 93 } 94 95 func (s *feldmanVSSQualState) init() { 96 s.feldmanVSSstate.init() 97 s.complaints = make(map[index]*complaint) 98 } 99 100 // NextTimeout sets the next protocol timeout 101 // This function needs to be called twice by every participant in 102 // the Feldman VSS Qual protocol. 103 // The first call is a timeout for sharing the private shares. 104 // The second call is a timeout for broadcasting the complaints. 105 // 106 // The returned erorr is : 107 // - dkgInvalidStateTransitionError if the DKG instance was not running. 108 // - dkgInvalidStateTransitionError if the DKG instance already called the 2 required timeouts. 109 // - nil otherwise. 110 func (s *feldmanVSSQualState) NextTimeout() error { 111 if !s.running { 112 return dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex) 113 } 114 if s.complaintsTimeout { 115 return dkgInvalidStateTransitionErrorf("the next timeout should be to end DKG protocol") 116 } 117 118 // if dealer is already disqualified, there is nothing to do 119 if s.disqualified { 120 if !s.sharesTimeout { 121 s.sharesTimeout = true 122 return nil 123 } else { 124 s.complaintsTimeout = true 125 return nil 126 } 127 } 128 129 if !s.sharesTimeout { 130 s.setSharesTimeout() 131 return nil 132 } else { 133 s.setComplaintsTimeout() 134 return nil 135 } 136 } 137 138 // End ends the protocol in the current participant. 139 // This is also a timeout to receiving all complaint answers. 140 // It returns the finalized public data and participant private key share: 141 // 1. the group public key corresponding to the group secret key 142 // 2. all the public key shares corresponding to the participants private key shares. 143 // 3. the finalized private key which is the current participant's own private key share 144 // 4. Error Returns: 145 // - dkgFailureError if the dealer was disqualified. 146 // - dkgFailureError if the public key share or group public key is identity. 147 // - dkgInvalidStateTransition if Start() was not called, or NextTimeout() was not called twice 148 // - nil otherwise. 149 func (s *feldmanVSSQualState) End() (PrivateKey, PublicKey, []PublicKey, error) { 150 if !s.running { 151 return nil, nil, nil, dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex) 152 } 153 if !s.sharesTimeout || !s.complaintsTimeout { 154 return nil, nil, nil, 155 dkgInvalidStateTransitionErrorf("%d: two timeouts should be set before ending dkg", s.myIndex) 156 } 157 s.running = false 158 // check if a complaint has remained without an answer 159 // a dealer is disqualified if a complaint was never answered 160 if !s.disqualified { 161 for complainer, c := range s.complaints { 162 if c.received && !c.answerReceived { 163 s.disqualified = true 164 s.processor.Disqualify(int(s.dealerIndex), 165 fmt.Sprintf("complaint from %d was not answered", 166 complainer)) 167 break 168 } 169 } 170 } 171 172 // If the dealer is disqualified, all keys are ignored 173 // otherwise, the keys are valid 174 if s.disqualified { 175 return nil, nil, nil, dkgFailureErrorf("dealer is disqualified") 176 } 177 178 // private key of the current participant 179 x := newPrKeyBLSBLS12381(&s.x) 180 181 // Group public key 182 Y := newPubKeyBLSBLS12381(&s.vA[0]) 183 184 // The participants public keys 185 y := make([]PublicKey, s.size) 186 for i, p := range s.y { 187 y[i] = newPubKeyBLSBLS12381(&p) 188 } 189 190 // check if current public key share or group public key is identity. 191 // In that case all signatures generated by the key are invalid (as stated by the BLS IETF 192 // draft) to avoid equivocation issues. 193 // TODO: update generateShares to make sure no public key share is identity AND 194 // update receiveVector function to disqualify the dealer if any public key share 195 // is identity, only when FeldmanVSSQ is not a building primitive of Joint-Feldman 196 if (&s.x).isZero() { 197 s.disqualified = true 198 return nil, nil, nil, dkgFailureErrorf("private key share is identity and therefore invalid") 199 } 200 if Y.isIdentity { 201 s.disqualified = true 202 return nil, nil, nil, dkgFailureErrorf("group private key is identity and is therefore invalid") 203 } 204 return x, Y, y, nil 205 } 206 207 const ( 208 complaintSize = 1 209 complaintAnswerSize = 1 + PrKeyLenBLSBLS12381 210 ) 211 212 // HandleBroadcastMsg processes a new broadcasted message received by the current participant. 213 // orig is the message origin index 214 // 215 // The function returns: 216 // - dkgInvalidStateTransitionError if the instance is not running 217 // - invalidInputsError if `orig` is not valid (in [0, size-1]) 218 // - nil otherwise 219 func (s *feldmanVSSQualState) HandleBroadcastMsg(orig int, msg []byte) error { 220 if !s.running { 221 return dkgInvalidStateTransitionErrorf("dkg is not running") 222 } 223 224 if orig >= s.Size() || orig < 0 { 225 return invalidInputsErrorf( 226 "wrong origin input, should be less than %d, got %d", 227 s.Size(), 228 orig) 229 } 230 231 // In case a message is received by the origin participant, 232 // the message is just ignored 233 if s.myIndex == index(orig) { 234 return nil 235 } 236 237 // if dealer is already disqualified, ignore the message 238 if s.disqualified { 239 return nil 240 } 241 242 if len(msg) == 0 { 243 if index(orig) == s.dealerIndex { 244 s.disqualified = true 245 } 246 s.processor.Disqualify(orig, "received broadcast is empty") 247 return nil 248 } 249 250 switch dkgMsgTag(msg[0]) { 251 case feldmanVSSVerifVec: 252 s.receiveVerifVector(index(orig), msg[1:]) 253 case feldmanVSSComplaint: 254 s.receiveComplaint(index(orig), msg[1:]) 255 case feldmanVSSComplaintAnswer: 256 s.receiveComplaintAnswer(index(orig), msg[1:]) 257 default: 258 if index(orig) == s.dealerIndex { 259 s.disqualified = true 260 } 261 s.processor.Disqualify(orig, 262 fmt.Sprintf("invalid broadcast header, got %d", 263 dkgMsgTag(msg[0]))) 264 } 265 return nil 266 } 267 268 // HandlePrivateMsg processes a new private message received by the current participant. 269 // orig is the message origin index. 270 // 271 // The function returns: 272 // - dkgInvalidStateTransitionError if the instance is not running 273 // - invalidInputsError if `orig` is not valid (in [0, size-1]) 274 // - nil otherwise 275 func (s *feldmanVSSQualState) HandlePrivateMsg(orig int, msg []byte) error { 276 if !s.running { 277 return dkgInvalidStateTransitionErrorf("dkg is not running") 278 } 279 if orig >= s.Size() || orig < 0 { 280 return invalidInputsErrorf( 281 "invalid origin, should be positive less than %d, got %d", 282 s.Size(), 283 orig) 284 } 285 286 // In case a private message is received by the origin participant, 287 // the message is just ignored 288 if s.myIndex == index(orig) { 289 return nil 290 } 291 292 // if dealer is already disqualified, ignore the message 293 if s.disqualified { 294 return nil 295 } 296 297 // forward all the message to receiveShare because any private message 298 // has to be a private share 299 s.receiveShare(index(orig), msg) 300 301 return nil 302 } 303 304 // ForceDisqualify forces a participant to get disqualified 305 // for a reason outside of the DKG protocol 306 // The caller should make sure all honest participants call this function, 307 // otherwise, the protocol can be broken 308 // 309 // The function returns: 310 // - dkgInvalidStateTransitionError if the instance is not running 311 // - invalidInputsError if `orig` is not valid (in [0, size-1]) 312 // - nil otherwise 313 func (s *feldmanVSSQualState) ForceDisqualify(participant int) error { 314 if !s.running { 315 return dkgInvalidStateTransitionErrorf("dkg is not running") 316 } 317 if participant >= s.Size() || participant < 0 { 318 return invalidInputsErrorf( 319 "invalid origin input, should be less than %d, got %d", 320 s.Size(), participant) 321 } 322 if index(participant) == s.dealerIndex { 323 s.disqualified = true 324 } 325 return nil 326 } 327 328 // The function does not check the call respects the machine 329 // state transition of feldmanVSSQual. The calling function must make sure this call 330 // is valid. 331 func (s *feldmanVSSQualState) setSharesTimeout() { 332 s.sharesTimeout = true 333 // if verif vector is not received, disqualify the dealer 334 if !s.vAReceived { 335 s.disqualified = true 336 s.processor.Disqualify(int(s.dealerIndex), 337 "verification vector was not received") 338 return 339 } 340 // if share is not received, make a complaint 341 if !s.xReceived { 342 s.buildAndBroadcastComplaint() 343 } 344 } 345 346 // The function does not check the call respects the machine 347 // state transition of feldmanVSSQual. The calling function must make sure this call 348 // is valid. 349 func (s *feldmanVSSQualState) setComplaintsTimeout() { 350 s.complaintsTimeout = true 351 // if more than t complaints are received, the dealer is disqualified 352 // regardless of the answers. 353 // (at this point, all answered complaints should have been already received) 354 // (i.e there is no complaint with (!c.received && c.answerReceived) 355 if len(s.complaints) > s.threshold { 356 s.disqualified = true 357 s.processor.Disqualify(int(s.dealerIndex), 358 fmt.Sprintf("there are %d complaints, they exceeded the threshold %d", 359 len(s.complaints), s.threshold)) 360 } 361 } 362 363 func (s *feldmanVSSQualState) receiveShare(origin index, data []byte) { 364 // only accept private shares from the dealer. 365 if origin != s.dealerIndex { 366 return 367 } 368 369 // check the share timeout 370 if s.sharesTimeout { 371 s.processor.FlagMisbehavior(int(origin), 372 "private share is received after the shares timeout") 373 return 374 } 375 376 if s.xReceived { 377 s.processor.FlagMisbehavior(int(origin), 378 "private share was already received") 379 return 380 } 381 382 // at this point, tag private share is received 383 s.xReceived = true 384 385 // private message general check 386 if len(data) == 0 || dkgMsgTag(data[0]) != feldmanVSSShare { 387 s.buildAndBroadcastComplaint() 388 s.processor.FlagMisbehavior(int(origin), 389 fmt.Sprintf("private share should be non-empty and first byte should be %d, received %#x", 390 feldmanVSSShare, data)) 391 return 392 } 393 394 // consider the remaining data from message 395 data = data[1:] 396 397 if (len(data)) != shareSize { 398 s.buildAndBroadcastComplaint() 399 s.processor.FlagMisbehavior(int(origin), 400 fmt.Sprintf("invalid share size, expects %d, got %d", 401 shareSize, len(data))) 402 return 403 } 404 // read the participant private share 405 if C.bn_read_Zr_bin((*C.bn_st)(&s.x), 406 (*C.uchar)(&data[0]), 407 PrKeyLenBLSBLS12381, 408 ) != valid { 409 s.buildAndBroadcastComplaint() 410 s.processor.FlagMisbehavior(int(origin), 411 fmt.Sprintf("invalid share value %x", data)) 412 return 413 } 414 415 if s.vAReceived { 416 if !s.verifyShare() { 417 // otherwise, build a complaint 418 s.buildAndBroadcastComplaint() 419 } 420 } 421 } 422 423 func (s *feldmanVSSQualState) receiveVerifVector(origin index, data []byte) { 424 // only accept the verification vector from the dealer. 425 if origin != s.dealerIndex { 426 return 427 } 428 429 // check the share timeout 430 if s.sharesTimeout { 431 s.processor.FlagMisbehavior(int(origin), 432 "verification vector received after the shares timeout") 433 return 434 } 435 436 if s.vAReceived { 437 s.processor.FlagMisbehavior(int(origin), 438 "verification received was already received") 439 return 440 } 441 s.vAReceived = true 442 443 if len(data) != verifVectorSize*(s.threshold+1) { 444 s.disqualified = true 445 s.processor.Disqualify(int(origin), 446 fmt.Sprintf("invalid verification vector size, expects %d, got %d", 447 verifVectorSize*(s.threshold+1), len(data))) 448 return 449 } 450 // read the verification vector 451 s.vA = make([]pointG2, s.threshold+1) 452 err := readVerifVector(s.vA, data) 453 if err != nil { 454 s.disqualified = true 455 s.processor.Disqualify(int(origin), 456 fmt.Sprintf("reading the verification vector failed:%s", err)) 457 return 458 } 459 460 s.y = make([]pointG2, s.size) 461 s.computePublicKeys() 462 463 // check the (already) registered complaints 464 for complainer, c := range s.complaints { 465 if c.received && c.answerReceived { 466 if s.checkComplaint(complainer, c) { 467 s.disqualified = true 468 s.processor.Disqualify(int(s.dealerIndex), 469 fmt.Sprintf("verification vector received: a complaint answer to %d is invalid", 470 complainer)) 471 return 472 } 473 } 474 } 475 // check the private share 476 if s.xReceived { 477 if !s.verifyShare() { 478 s.buildAndBroadcastComplaint() 479 } 480 } 481 } 482 483 // build a complaint against the dealer, add it to the local 484 // complaint map and broadcast it 485 func (s *feldmanVSSQualState) buildAndBroadcastComplaint() { 486 s.complaints[s.myIndex] = &complaint{ 487 received: true, 488 answerReceived: false, 489 } 490 data := []byte{byte(feldmanVSSComplaint), byte(s.dealerIndex)} 491 s.processor.Broadcast(data) 492 } 493 494 // build a complaint answer, add it to the local 495 // complaint map and broadcast it 496 func (s *feldmanVSSQualState) buildAndBroadcastComplaintAnswer(complainee index) { 497 data := make([]byte, complaintAnswerSize+1) 498 data[0] = byte(feldmanVSSComplaintAnswer) 499 data[1] = byte(complainee) 500 zrPolynomialImage(data[2:], s.a, complainee+1, nil) 501 s.complaints[complainee].answerReceived = true 502 s.processor.Broadcast(data) 503 } 504 505 // assuming a complaint and its answer were both received, this function returns: 506 // - false if the complaint answer is correct 507 // - true if the complaint answer is not correct 508 func (s *feldmanVSSQualState) checkComplaint(complainer index, c *complaint) bool { 509 // check y[complainer] == share.G2 510 return C.verifyshare((*C.bn_st)(&c.answer), 511 (*C.ep2_st)(&s.y[complainer])) == 0 512 } 513 514 // data = |complainee| 515 func (s *feldmanVSSQualState) receiveComplaint(origin index, data []byte) { 516 // check the complaint timeout 517 if s.complaintsTimeout { 518 s.processor.FlagMisbehavior(int(origin), 519 "complaint received after the complaint timeout") 520 return 521 } 522 523 if len(data) != complaintSize { 524 // only the dealer of the instance gets disqualified 525 if origin == s.dealerIndex { 526 s.disqualified = true 527 s.processor.Disqualify(int(origin), 528 fmt.Sprintf("invalid complaint size, expects %d, got %d", 529 complaintSize, len(data))) 530 } 531 return 532 } 533 534 // the byte encodes the complainee 535 complainee := index(data[0]) 536 537 // validate the complainee value 538 if int(complainee) >= s.size { 539 // only the dealer of the instance gets disqualified 540 if origin == s.dealerIndex { 541 s.disqualified = true 542 s.processor.Disqualify(int(origin), 543 fmt.Sprintf("invalid complainee, should be less than %d, got %d", 544 s.size, complainee)) 545 } 546 return 547 } 548 549 // if the complaint is coming from the dealer, ignore it 550 if origin == s.dealerIndex { 551 return 552 } 553 554 // if the complainee is not the dealer, ignore the complaint 555 if complainee != s.dealerIndex { 556 return 557 } 558 559 c, ok := s.complaints[origin] 560 // if the complaint is new, add it 561 if !ok { 562 s.complaints[origin] = &complaint{ 563 received: true, 564 answerReceived: false, 565 } 566 // if the complainee is the current participant, prepare an answer 567 if s.myIndex == s.dealerIndex { 568 s.buildAndBroadcastComplaintAnswer(origin) 569 } 570 return 571 } 572 // complaint is not new in the map 573 // check if the complaint has been already received 574 if c.received { 575 s.processor.FlagMisbehavior(int(origin), 576 "complaint was already received") 577 return 578 } 579 c.received = true 580 // answerReceived flag check is a sanity check 581 if s.vAReceived && c.answerReceived && s.myIndex != s.dealerIndex { 582 s.disqualified = s.checkComplaint(origin, c) 583 if s.disqualified { 584 s.processor.Disqualify(int(s.dealerIndex), 585 fmt.Sprintf("complaint received: complaint answer to %d is invalid", 586 origin)) 587 } 588 return 589 } 590 } 591 592 // answer = |complainer| private share | 593 func (s *feldmanVSSQualState) receiveComplaintAnswer(origin index, data []byte) { 594 // check for invalid answers 595 if origin != s.dealerIndex { 596 return 597 } 598 599 // check the answer format 600 if len(data) != complaintAnswerSize { 601 s.disqualified = true 602 s.processor.Disqualify(int(s.dealerIndex), 603 fmt.Sprintf("the complaint answer has an invalid length, expects %d, got %d", 604 complaintAnswerSize, len(data))) 605 return 606 } 607 608 // first byte encodes the complainee 609 complainer := index(data[0]) 610 if int(complainer) >= s.size { 611 s.disqualified = true 612 s.processor.Disqualify(int(origin), 613 fmt.Sprintf("complainer value is invalid, should be less that %d, got %d", 614 s.size, int(complainer))) 615 return 616 } 617 618 c, ok := s.complaints[complainer] 619 // if the complaint is new, add it 620 if !ok { 621 s.complaints[complainer] = &complaint{ 622 received: false, 623 answerReceived: true, 624 } 625 626 // read the complainer private share 627 C.bn_new_wrapper((*C.bn_st)(&s.complaints[complainer].answer)) 628 if C.bn_read_Zr_bin((*C.bn_st)(&s.complaints[complainer].answer), 629 (*C.uchar)(&data[1]), 630 PrKeyLenBLSBLS12381, 631 ) != valid { 632 s.disqualified = true 633 s.processor.Disqualify(int(s.dealerIndex), 634 fmt.Sprintf("invalid complaint answer value %x", data)) 635 return 636 } 637 return 638 } 639 // complaint is not new in the map 640 // check if the answer has been already received 641 if c.answerReceived { 642 s.processor.FlagMisbehavior(int(origin), 643 "complaint answer was already received") 644 return 645 } 646 c.answerReceived = true 647 648 // flag check is a sanity check 649 if c.received { 650 // read the complainer private share 651 C.bn_new_wrapper((*C.bn_st)(&c.answer)) 652 if C.bn_read_Zr_bin((*C.bn_st)(&c.answer), 653 (*C.uchar)(&data[1]), 654 PrKeyLenBLSBLS12381, 655 ) != valid { 656 s.disqualified = true 657 s.processor.Disqualify(int(s.dealerIndex), 658 fmt.Sprintf("invalid complaint answer value %x", data)) 659 return 660 } 661 if s.vAReceived { 662 s.disqualified = s.checkComplaint(complainer, c) 663 if s.disqualified { 664 s.processor.Disqualify(int(s.dealerIndex), 665 fmt.Sprintf("complaint answer received: complaint answer to %d is invalid", 666 complainer)) 667 } 668 } 669 670 // fix the share of the current participant if the complaint is invalid 671 if !s.disqualified && complainer == s.myIndex { 672 s.x = c.answer 673 } 674 } 675 }