github.com/onflow/flow-go/crypto@v0.24.8/dkg_jointfeldman.go (about)

     1  //go:build relic
     2  // +build relic
     3  
     4  package crypto
     5  
     6  // #cgo CFLAGS: -g -Wall -std=c99
     7  // #cgo LDFLAGS: -L${SRCDIR}/relic/build/lib -l relic_s
     8  // #include "dkg_include.h"
     9  import "C"
    10  
    11  import (
    12  	"fmt"
    13  )
    14  
    15  // Implements Joint Feldman (Pedersen) protocol using
    16  // the BLS set up on the BLS12-381 curve.
    17  // The protocol runs (n) parallel instances of Feldman vss with
    18  // the complaints mechanism, each participant being a dealer
    19  // once.
    20  
    21  // This is a fully distributed generation. The secret is a BLS
    22  // private key generated jointly by all the participants.
    23  
    24  // (t) is the threshold parameter. Although the API allows using arbitrary values of (t),
    25  // the DKG protocol is secure in the presence of up to (t) malicious participants
    26  // when (t < n/2).
    27  // Joint-Feldman is the protocol implemented in Flow, (t) being set to the maximum value
    28  // t = floor((n-1)/2) to optimize for unforgeability and robustness of the threshold
    29  // signature scheme using the output keys.
    30  
    31  // In each feldman VSS instance, the dealer generates a chunk of the
    32  // the private key of a BLS threshold signature scheme.
    33  // Using the complaints mechanism, each dealer is qualified or disqualified
    34  // from the protocol, and the overall key is taking into account
    35  // all chunks from qualified dealers.
    36  
    37  // Private keys are scalar in Zr, where r is the group order of G1/G2
    38  // Public keys are in G2.
    39  
    40  // Joint Feldman protocol, with complaint mechanism, implements DKGState
    41  type JointFeldmanState struct {
    42  	*dkgCommon
    43  	// jointRunning is true if and only if all parallel Feldman vss protocols are running
    44  	jointRunning bool
    45  	// feldmanVSSQualState parallel states
    46  	fvss []feldmanVSSQualState
    47  	// is the group public key
    48  	jointPublicKey pointG2
    49  	// Private share of the current participant
    50  	jointx scalar
    51  	// Public keys of the group participants, the vector size is (n)
    52  	jointy []pointG2
    53  }
    54  
    55  // NewJointFeldman creates a new instance of a Joint Feldman protocol.
    56  //
    57  // size if the total number of participants (n).
    58  // threshold is the threshold parameter (t). the DKG protocol is secure in the
    59  // presence of up to (t) malicious participants when (t < n/2).
    60  // myIndex is the index of the participant creating the new DKG instance.
    61  // processor is the DKGProcessor instance required to connect the participant to the
    62  // communication channels.
    63  //
    64  // An instance is run by a single participant and is usable for only one protocol.
    65  // In order to run the protocol again, a new instance needs to be created.
    66  //
    67  // The function returns:
    68  // - (nil, InvalidInputsError) if:
    69  //   - size if not in [DKGMinSize, DKGMaxSize]
    70  //   - threshold is not in [MinimumThreshold, size-1]
    71  //   - myIndex is not in [0, size-1]
    72  //   - dealerIndex is not in [0, size-1]
    73  //
    74  // - (dkgInstance, nil) otherwise
    75  func NewJointFeldman(size int, threshold int, myIndex int,
    76  	processor DKGProcessor) (DKGState, error) {
    77  
    78  	common, err := newDKGCommon(size, threshold, myIndex, processor, 0)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	jf := &JointFeldmanState{
    84  		dkgCommon: common,
    85  	}
    86  	jf.init()
    87  	return jf, nil
    88  }
    89  
    90  func (s *JointFeldmanState) init() {
    91  	s.fvss = make([]feldmanVSSQualState, s.size)
    92  	for i := 0; i < s.size; i++ {
    93  		fvss := &feldmanVSSstate{
    94  			dkgCommon:   s.dkgCommon,
    95  			dealerIndex: index(i),
    96  		}
    97  		s.fvss[i] = feldmanVSSQualState{
    98  			feldmanVSSstate: fvss,
    99  			disqualified:    false,
   100  		}
   101  		s.fvss[i].init()
   102  	}
   103  }
   104  
   105  // Start triggers Joint Feldman protocol start for the current participant.
   106  // The seed is used to generate the FVSS secret polynomial
   107  // (including the instance group private key) when the current
   108  // participant is the dealer.
   109  //
   110  // The returned error is :
   111  //   - dkgInvalidStateTransitionError if the DKG instance is already running.
   112  //   - error if an unexpected exception occurs
   113  //   - nil otherwise.
   114  func (s *JointFeldmanState) Start(seed []byte) error {
   115  	if s.jointRunning {
   116  		return dkgInvalidStateTransitionErrorf("dkg is already running")
   117  	}
   118  
   119  	for i := index(0); int(i) < s.size; i++ {
   120  		s.fvss[i].running = false
   121  		err := s.fvss[i].Start(seed)
   122  		if err != nil {
   123  			return fmt.Errorf("error when starting dkg: %w", err)
   124  		}
   125  	}
   126  	s.jointRunning = true
   127  	return nil
   128  }
   129  
   130  // NextTimeout sets the next timeout of the protocol if any timeout applies.
   131  //
   132  // The returned error is :
   133  //   - dkgInvalidStateTransitionError if the DKG instance was not running.
   134  //   - dkgInvalidStateTransitionError if the DKG instance already called the 2 required timeouts.
   135  //   - nil otherwise.
   136  func (s *JointFeldmanState) NextTimeout() error {
   137  	if !s.jointRunning {
   138  		return dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex)
   139  	}
   140  
   141  	for i := index(0); int(i) < s.size; i++ {
   142  		err := s.fvss[i].NextTimeout()
   143  		if err != nil {
   144  			return fmt.Errorf("next timeout failed: %w", err)
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  // End ends the protocol in the current participant
   151  // It returns the finalized public data and participant private key share.
   152  // - the group public key corresponding to the group secret key
   153  // - all the public key shares corresponding to the participants private
   154  // key shares.
   155  // - the finalized private key which is the current participant's own private key share
   156  //
   157  // The returned error is:
   158  //   - dkgFailureError if the disqualified dealers exceeded the threshold
   159  //   - dkgFailureError if the public key share or group public key is identity.
   160  //   - dkgInvalidStateTransitionError Start() was not called, or NextTimeout() was not called twice
   161  //   - nil otherwise.
   162  func (s *JointFeldmanState) End() (PrivateKey, PublicKey, []PublicKey, error) {
   163  	if !s.jointRunning {
   164  		return nil, nil, nil, dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex)
   165  	}
   166  
   167  	disqualifiedTotal := 0
   168  	for i := 0; i < s.size; i++ {
   169  		// check previous timeouts were called
   170  		if !s.fvss[i].sharesTimeout || !s.fvss[i].complaintsTimeout {
   171  			return nil, nil, nil,
   172  				dkgInvalidStateTransitionErrorf("%d: two timeouts should be set before ending dkg", s.myIndex)
   173  		}
   174  
   175  		// check if a complaint has remained without an answer
   176  		// a dealer is disqualified if a complaint was never answered
   177  		if !s.fvss[i].disqualified {
   178  			for complainer, c := range s.fvss[i].complaints {
   179  				if c.received && !c.answerReceived {
   180  					s.fvss[i].disqualified = true
   181  					s.processor.Disqualify(i,
   182  						fmt.Sprintf("complaint from %d was not answered", complainer))
   183  					disqualifiedTotal++
   184  					break
   185  				}
   186  			}
   187  		} else {
   188  			disqualifiedTotal++
   189  		}
   190  	}
   191  	s.jointRunning = false
   192  
   193  	// check failing dkg
   194  	if disqualifiedTotal > s.threshold || s.size-disqualifiedTotal <= s.threshold {
   195  		return nil, nil, nil,
   196  			dkgFailureErrorf(
   197  				"Joint-Feldman failed because the diqualified participants number is high: %d disqualified, threshold is %d, size is %d",
   198  				disqualifiedTotal, s.threshold, s.size)
   199  	}
   200  
   201  	// wrap up the keys from qualified dealers
   202  	jointx, jointPublicKey, jointy := s.sumUpQualifiedKeys(s.size - disqualifiedTotal)
   203  
   204  	// private key of the current participant
   205  	x := newPrKeyBLSBLS12381(jointx)
   206  
   207  	// Group public key
   208  	Y := newPubKeyBLSBLS12381(jointPublicKey)
   209  
   210  	// The participants public keys
   211  	y := make([]PublicKey, s.size)
   212  	for i, p := range jointy {
   213  		y[i] = newPubKeyBLSBLS12381(&p)
   214  	}
   215  
   216  	// check if current public key share or group public key is identity.
   217  	// In that case all signatures generated by the current private key share or
   218  	// the group private key are invalid (as stated by the BLS IETF draft)
   219  	// to avoid equivocation issues.
   220  	//
   221  	// Assuming both private keys have entropy from at least one honest dealer, each private
   222  	// key is initially uniformly distributed over the 2^255 possible values. We can argue that
   223  	// the known uniformity-bias caused by malicious dealers in Joint-Feldman does not weaken
   224  	// the likelihood of generating an identity key to practical probabilities.
   225  	if (jointx).isZero() {
   226  		return nil, nil, nil, dkgFailureErrorf("private key share is identity and is therefore invalid")
   227  	}
   228  	if Y.isIdentity {
   229  		return nil, nil, nil, dkgFailureErrorf("group private key is identity and is therefore invalid")
   230  	}
   231  	return x, Y, y, nil
   232  }
   233  
   234  // HandleBroadcastMsg processes a new broadcasted message received by the current participant
   235  // orig is the message origin index
   236  //
   237  // The function returns:
   238  //   - dkgInvalidStateTransitionError if the instance is not running
   239  //   - invalidInputsError if `orig` is not valid (in [0, size-1])
   240  //   - nil otherwise
   241  func (s *JointFeldmanState) HandleBroadcastMsg(orig int, msg []byte) error {
   242  	if !s.jointRunning {
   243  		return dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex)
   244  	}
   245  	for i := index(0); int(i) < s.size; i++ {
   246  		err := s.fvss[i].HandleBroadcastMsg(orig, msg)
   247  		if err != nil {
   248  			return fmt.Errorf("handle broadcast message failed: %w", err)
   249  		}
   250  	}
   251  	return nil
   252  }
   253  
   254  // HandlePrivateMsg processes a new private message received by the current participant
   255  // orig is the message origin index
   256  //
   257  // The function returns:
   258  //   - dkgInvalidStateTransitionError if the instance is not running
   259  //   - invalidInputsError if `orig` is not valid (in [0, size-1])
   260  //   - nil otherwise
   261  func (s *JointFeldmanState) HandlePrivateMsg(orig int, msg []byte) error {
   262  	if !s.jointRunning {
   263  		return dkgInvalidStateTransitionErrorf("dkg protocol %d is not running", s.myIndex)
   264  	}
   265  	for i := index(0); int(i) < s.size; i++ {
   266  		err := s.fvss[i].HandlePrivateMsg(orig, msg)
   267  		if err != nil {
   268  			return fmt.Errorf("handle private message failed: %w", err)
   269  		}
   270  	}
   271  	return nil
   272  }
   273  
   274  // Running returns the running state of Joint Feldman protocol
   275  func (s *JointFeldmanState) Running() bool {
   276  	return s.jointRunning
   277  }
   278  
   279  // ForceDisqualify forces a participant to get disqualified
   280  // for a reason outside of the DKG protocol
   281  // The caller should make sure all honest participants call this function,
   282  // otherwise, the protocol can be broken
   283  //
   284  // The function returns:
   285  //   - dkgInvalidStateTransitionError if the instance is not running
   286  //   - invalidInputsError if `orig` is not valid (in [0, size-1])
   287  //   - nil otherwise
   288  func (s *JointFeldmanState) ForceDisqualify(participant int) error {
   289  	if !s.jointRunning {
   290  		return dkgInvalidStateTransitionErrorf("dkg is not running")
   291  	}
   292  	// disqualify the participant in the fvss instance where they are a dealer
   293  	err := s.fvss[participant].ForceDisqualify(participant)
   294  	if err != nil {
   295  		return fmt.Errorf("force disqualify failed: %w", err)
   296  	}
   297  	return nil
   298  }
   299  
   300  // sum up the 3 type of keys from all qualified dealers to end the protocol
   301  func (s *JointFeldmanState) sumUpQualifiedKeys(qualified int) (*scalar, *pointG2, []pointG2) {
   302  	qualifiedx, qualifiedPubKey, qualifiedy := s.getQualifiedKeys(qualified)
   303  
   304  	// sum up x
   305  	var jointx scalar
   306  	C.bn_new_wrapper((*C.bn_st)(&jointx))
   307  	C.bn_sum_vector((*C.bn_st)(&jointx), (*C.bn_st)(&qualifiedx[0]),
   308  		(C.int)(qualified))
   309  	// sum up Y
   310  	var jointPublicKey pointG2
   311  	C.ep2_sum_vector((*C.ep2_st)(&jointPublicKey),
   312  		(*C.ep2_st)(&qualifiedPubKey[0]), (C.int)(qualified))
   313  	// sum up []y
   314  	jointy := make([]pointG2, s.size)
   315  	for i := 0; i < s.size; i++ {
   316  		C.ep2_sum_vector((*C.ep2_st)(&jointy[i]),
   317  			(*C.ep2_st)(&qualifiedy[i][0]), (C.int)(qualified))
   318  	}
   319  	return &jointx, &jointPublicKey, jointy
   320  }
   321  
   322  // get the 3 type of keys from all qualified dealers
   323  func (s *JointFeldmanState) getQualifiedKeys(qualified int) ([]scalar, []pointG2, [][]pointG2) {
   324  	qualifiedx := make([]scalar, 0, qualified)
   325  	qualifiedPubKey := make([]pointG2, 0, qualified)
   326  	qualifiedy := make([][]pointG2, s.size)
   327  	for i := 0; i < s.size; i++ {
   328  		qualifiedy[i] = make([]pointG2, 0, qualified)
   329  	}
   330  
   331  	for i := 0; i < s.size; i++ {
   332  		if !s.fvss[i].disqualified {
   333  			qualifiedx = append(qualifiedx, s.fvss[i].x)
   334  			qualifiedPubKey = append(qualifiedPubKey, s.fvss[i].vA[0])
   335  			for j := 0; j < s.size; j++ {
   336  				qualifiedy[j] = append(qualifiedy[j], s.fvss[i].y[j])
   337  			}
   338  		}
   339  	}
   340  	return qualifiedx, qualifiedPubKey, qualifiedy
   341  }