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 }