github.com/koko1123/flow-go-1@v0.29.6/module/signature/aggregation.go (about) 1 //go:build relic 2 // +build relic 3 4 package signature 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/onflow/flow-go/crypto" 11 "github.com/onflow/flow-go/crypto/hash" 12 ) 13 14 // SignatureAggregatorSameMessage aggregates BLS signatures of the same message from different signers. 15 // The public keys and message are agreed upon upfront. 16 // 17 // Currently, the module does not support signatures with multiplicity higher than 1. Each signer is allowed 18 // to sign at most once. 19 // 20 // Aggregation uses BLS scheme. Mitigation against rogue attacks is done using Proof Of Possession (PoP) 21 // This module does not verify PoPs of input public keys, it assumes verification was done outside this module. 22 // 23 // Implementation of SignatureAggregator is not thread-safe, the caller should 24 // make sure the calls are concurrent safe. 25 type SignatureAggregatorSameMessage struct { 26 message []byte 27 hasher hash.Hasher 28 n int // number of participants indexed from 0 to n-1 29 publicKeys []crypto.PublicKey // keys indexed from 0 to n-1, signer i is assigned to public key i 30 indexToSignature map[int]string // signatures indexed by the signer index 31 32 // To remove overhead from repeated Aggregate() calls, we cache the aggregation result. 33 // Whenever a new signature is added, we reset `cachedSignature` to nil. 34 cachedSignature crypto.Signature // cached raw aggregated signature 35 cachedSignerIndices []int // cached indices of signers that contributed to `cachedSignature` 36 } 37 38 // NewSignatureAggregatorSameMessage returns a new SignatureAggregatorSameMessage structure. 39 // 40 // A new SignatureAggregatorSameMessage is needed for each set of public keys. If the key set changes, 41 // a new structure needs to be instantiated. Participants are defined by their public keys, and are 42 // indexed from 0 to n-1 where n is the length of the public key slice. 43 // The constructor errors if: 44 // - length of keys is zero 45 // - any input public key is not a BLS 12-381 key 46 func NewSignatureAggregatorSameMessage( 47 message []byte, // message to be aggregate signatures for 48 dsTag string, // domain separation tag used for signatures 49 publicKeys []crypto.PublicKey, // public keys of participants agreed upon upfront 50 ) (*SignatureAggregatorSameMessage, error) { 51 52 if len(publicKeys) == 0 { 53 return nil, fmt.Errorf("number of participants must be larger than 0, got %d", len(publicKeys)) 54 } 55 // sanity check for BLS keys 56 for i, key := range publicKeys { 57 if key == nil || key.Algorithm() != crypto.BLSBLS12381 { 58 return nil, fmt.Errorf("key at index %d is not a BLS key", i) 59 } 60 } 61 62 return &SignatureAggregatorSameMessage{ 63 message: message, 64 hasher: NewBLSHasher(dsTag), 65 n: len(publicKeys), 66 publicKeys: publicKeys, 67 indexToSignature: make(map[int]string), 68 cachedSignature: nil, 69 }, nil 70 } 71 72 // Verify verifies the input signature under the stored message and stored 73 // key at the input index. 74 // 75 // This function does not update the internal state. 76 // The function errors: 77 // - InvalidSignerIdxError if the signer index is out of bound 78 // - generic error for unexpected runtime failures 79 // 80 // The function does not return an error for any invalid signature. 81 // If any error is returned, the returned bool is false. 82 // If no error is returned, the bool represents the validity of the signature. 83 // The function is not thread-safe. 84 func (s *SignatureAggregatorSameMessage) Verify(signer int, sig crypto.Signature) (bool, error) { 85 if signer >= s.n || signer < 0 { 86 return false, NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 87 } 88 return s.publicKeys[signer].Verify(sig, s.message, s.hasher) 89 } 90 91 // VerifyAndAdd verifies the input signature under the stored message and stored 92 // key at the input index. If the verification passes, the signature is added to the internal 93 // signature state. 94 // The function errors: 95 // - InvalidSignerIdxError if the signer index is out of bound 96 // - DuplicatedSignerIdxError if a signature from the same signer index has already been added 97 // - generic error for unexpected runtime failures 98 // 99 // The function does not return an error for any invalid signature. 100 // If any error is returned, the returned bool is false. 101 // If no error is returned, the bool represents the validity of the signature. 102 // The function is not thread-safe. 103 func (s *SignatureAggregatorSameMessage) VerifyAndAdd(signer int, sig crypto.Signature) (bool, error) { 104 if signer >= s.n || signer < 0 { 105 return false, NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 106 } 107 _, duplicate := s.indexToSignature[signer] 108 if duplicate { 109 return false, NewDuplicatedSignerIdxErrorf("signature from signer index %d has already been added", signer) 110 } 111 // signature is new 112 ok, err := s.publicKeys[signer].Verify(sig, s.message, s.hasher) // no errors expected 113 if ok { 114 s.add(signer, sig) 115 } 116 return ok, err 117 } 118 119 // adds signature and assumes `signer` is valid 120 func (s *SignatureAggregatorSameMessage) add(signer int, sig crypto.Signature) { 121 s.cachedSignature = nil 122 s.indexToSignature[signer] = string(sig) 123 } 124 125 // TrustedAdd adds a signature to the internal state without verifying it. 126 // 127 // The Aggregate function makes a sanity check on the aggregated signature and only 128 // outputs valid signatures. This would detect if TrustedAdd has added any invalid 129 // signature. 130 // The function errors: 131 // - InvalidSignerIdxError if the signer index is out of bound 132 // - DuplicatedSignerIdxError if a signature from the same signer index has already been added 133 // 134 // The function is not thread-safe. 135 func (s *SignatureAggregatorSameMessage) TrustedAdd(signer int, sig crypto.Signature) error { 136 if signer >= s.n || signer < 0 { 137 return NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 138 } 139 _, duplicate := s.indexToSignature[signer] 140 if duplicate { 141 return NewDuplicatedSignerIdxErrorf("signature from signer index %d has already been added", signer) 142 } 143 // signature is new 144 s.add(signer, sig) 145 return nil 146 } 147 148 // HasSignature checks if a signer has already provided a valid signature. 149 // The function errors: 150 // - InvalidSignerIdxError if the signer index is out of bound 151 // 152 // The function is not thread-safe. 153 func (s *SignatureAggregatorSameMessage) HasSignature(signer int) (bool, error) { 154 if signer >= s.n || signer < 0 { 155 return false, NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 156 } 157 _, ok := s.indexToSignature[signer] 158 return ok, nil 159 } 160 161 // Aggregate aggregates the stored BLS signatures and returns the aggregated signature. 162 // 163 // Aggregate attempts to aggregate the internal signatures and returns the resulting signature. 164 // The function performs a final verification and errors if any signature fails the deserialization 165 // or if the aggregated signature is not valid. It also errors if no signatures were added. 166 // Post-check of aggregated signature is required for function safety, as `TrustedAdd` allows 167 // adding invalid signatures. The function is not thread-safe. 168 // Returns: 169 // - InsufficientSignaturesError if no signatures have been added yet 170 // - InvalidSignatureIncludedError if some signature(s), included via TrustedAdd, are invalid 171 // 172 // TODO : When compacting the list of signers, update the return from []int 173 // to a compact bit vector. 174 func (s *SignatureAggregatorSameMessage) Aggregate() ([]int, crypto.Signature, error) { 175 // check if signature was already computed 176 if s.cachedSignature != nil { 177 return s.cachedSignerIndices, s.cachedSignature, nil 178 } 179 180 // compute aggregation result and cache it in `s.cachedSignerIndices`, `s.cachedSignature` 181 sharesNum := len(s.indexToSignature) 182 if sharesNum == 0 { 183 return nil, nil, NewInsufficientSignaturesErrorf("cannot aggregate an empty list of signatures") 184 } 185 indices := make([]int, 0, sharesNum) 186 signatures := make([]crypto.Signature, 0, sharesNum) 187 for i, sig := range s.indexToSignature { 188 indices = append(indices, i) 189 signatures = append(signatures, []byte(sig)) 190 } 191 192 aggregatedSignature, err := crypto.AggregateBLSSignatures(signatures) 193 if err != nil { 194 // invalidInputsError for: 195 // * empty `signatures` slice, i.e. sharesNum == 0, which we exclude by earlier check 196 // * if some signature(s), included via TrustedAdd, could not be decoded 197 if crypto.IsInvalidInputsError(err) { 198 return nil, nil, NewInvalidSignatureIncludedErrorf("signatures with invalid structure were included via TrustedAdd: %w", err) 199 } 200 return nil, nil, fmt.Errorf("BLS signature aggregation failed: %w", err) 201 } 202 ok, err := s.VerifyAggregate(indices, aggregatedSignature) // no errors expected (unless some public BLS keys are invalid) 203 if err != nil { 204 return nil, nil, fmt.Errorf("unexpected error during signature aggregation: %w", err) 205 } 206 if !ok { 207 return nil, nil, NewInvalidSignatureIncludedErrorf("invalid signature(s) have been included via TrustedAdd") 208 } 209 s.cachedSignature = aggregatedSignature 210 s.cachedSignerIndices = indices 211 return indices, aggregatedSignature, nil 212 } 213 214 // VerifyAggregate verifies an aggregated signature against the stored message and the stored 215 // keys corresponding to the input signers. 216 // Aggregating the keys of the signers internally is optimized to only look at the keys delta 217 // compared to the latest execution of the function. The function is therefore not thread-safe. 218 // Possible returns: 219 // - (true, nil): aggregate signature is valid 220 // - (false, nil): aggregate signature is cryptographically invalid 221 // - (false, err) with error types: 222 // - InsufficientSignaturesError if no signer indices are given (`signers` is empty) 223 // - InvalidSignerIdxError if some signer indices are out of bound 224 // - generic error in case of an unexpected runtime failure 225 func (s *SignatureAggregatorSameMessage) VerifyAggregate(signers []int, sig crypto.Signature) (bool, error) { 226 sharesNum := len(signers) 227 keys := make([]crypto.PublicKey, 0, sharesNum) 228 if sharesNum == 0 { 229 return false, NewInsufficientSignaturesErrorf("cannot aggregate an empty list of signatures") 230 } 231 for _, signer := range signers { 232 if signer >= s.n || signer < 0 { 233 return false, NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 234 } 235 keys = append(keys, s.publicKeys[signer]) 236 } 237 KeyAggregate, err := crypto.AggregateBLSPublicKeys(keys) 238 if err != nil { 239 // invalidInputsError for: 240 // * empty `keys` slice, i.e. sharesNum == 0, which we exclude by earlier check 241 // * some keys are not BLS12 381 keys, which should not happen, as we checked 242 // each key's signing algorithm in the constructor to be `crypto.BLSBLS12381` 243 // Hence, we do _not_ expect any error here during normal operations 244 return false, fmt.Errorf("unexpected internal error during public key aggregation: %w", err) 245 } 246 ok, err := KeyAggregate.Verify(sig, s.message, s.hasher) // no errors expected 247 if err != nil { 248 return false, fmt.Errorf("signature verification failed: %w", err) 249 } 250 return ok, nil 251 } 252 253 // PublicKeyAggregator aggregates BLS public keys in an optimized manner. 254 // It uses a greedy algorithm to compute the aggregated key based on the latest 255 // computed key and the delta of keys. 256 // A caller can use a classic stateless aggregation if the optimization is not needed. 257 // 258 // The structure is thread safe. 259 type PublicKeyAggregator struct { 260 n int // number of participants indexed from 0 to n-1 261 publicKeys []crypto.PublicKey // keys indexed from 0 to n-1, signer i is assigned to public key i 262 lastSigners map[int]struct{} // maps the signers in the latest call to aggregate keys 263 lastAggregatedKey crypto.PublicKey // the latest aggregated public key 264 sync.RWMutex // the above "latest" data only make sense in a concurrent safe model, the lock maintains the thread-safety 265 // since the caller should not be aware of the internal non thread-safe algorithm. 266 } 267 268 // NewPublicKeyAggregator creates an index-based key aggregator, for the given list of authorized signers. 269 // 270 // The constructor errors if: 271 // - the input keys are empty. 272 // - any input public key algorithm is not BLS. 273 func NewPublicKeyAggregator(publicKeys []crypto.PublicKey) (*PublicKeyAggregator, error) { 274 // check for empty list 275 if len(publicKeys) == 0 { 276 return nil, fmt.Errorf("input keys cannot be empty") 277 } 278 // check for BLS keys 279 for i, key := range publicKeys { 280 if key == nil || key.Algorithm() != crypto.BLSBLS12381 { 281 return nil, fmt.Errorf("key at index %d is not a BLS key", i) 282 } 283 } 284 aggregator := &PublicKeyAggregator{ 285 n: len(publicKeys), 286 publicKeys: publicKeys, 287 lastSigners: make(map[int]struct{}), 288 lastAggregatedKey: crypto.NeutralBLSPublicKey(), 289 RWMutex: sync.RWMutex{}, 290 } 291 return aggregator, nil 292 } 293 294 // KeyAggregate returns the aggregated public key of the input signers. 295 // 296 // The aggregation errors if: 297 // - genric error if input signers is empty. 298 // - InvalidSignerIdxError if any signer is out of bound. 299 // - other generic errors are unexpected during normal operations. 300 func (p *PublicKeyAggregator) KeyAggregate(signers []int) (crypto.PublicKey, error) { 301 // check for empty list 302 if len(signers) == 0 { 303 return nil, fmt.Errorf("input signers cannot be empty") 304 } 305 306 // check signers 307 for i, signer := range signers { 308 if signer >= p.n || signer < 0 { 309 return nil, NewInvalidSignerIdxErrorf("signer %d at index %d is invalid", signer, i) 310 } 311 } 312 313 // this greedy algorithm assumes the signers set does not vary much from one call 314 // to KeyAggregate to another. It computes the delta of signers compared to the 315 // latest list of signers and adjust the latest aggregated public key. This is faster 316 // than aggregating the public keys from scratch at each call. 317 318 // read lock to read consistent last key and last signers 319 p.RLock() 320 // get the signers delta and update the last list for the next comparison 321 addedSignerKeys, missingSignerKeys, updatedSignerSet := p.deltaKeys(signers) 322 lastKey := p.lastAggregatedKey 323 p.RUnlock() 324 325 // checks whether the delta of signers is larger than new list of signers. 326 deltaIsLarger := len(addedSignerKeys)+len(missingSignerKeys) > len(updatedSignerSet) 327 328 var updatedKey crypto.PublicKey 329 var err error 330 if deltaIsLarger { 331 // it is faster to aggregate the keys from scratch in this case 332 newSigners := make([]crypto.PublicKey, 0, len(updatedSignerSet)) 333 for signer := range updatedSignerSet { 334 newSigners = append(newSigners, p.publicKeys[signer]) 335 } 336 updatedKey, err = crypto.AggregateBLSPublicKeys(newSigners) 337 if err != nil { 338 // not expected as the keys are not empty and all keys are BLS 339 return nil, fmt.Errorf("aggregating keys failed: %w", err) 340 } 341 } else { 342 // it is faster to adjust the existing aggregated key in this case 343 // add the new keys 344 updatedKey, err = crypto.AggregateBLSPublicKeys(append(addedSignerKeys, lastKey)) 345 if err != nil { 346 // not expected in notrmal operations as there is at least one key, and all keys are BLS 347 return nil, fmt.Errorf("adding new keys failed: %w", err) 348 } 349 // remove the missing keys 350 updatedKey, err = crypto.RemoveBLSPublicKeys(updatedKey, missingSignerKeys) 351 if err != nil { 352 // not expected in notrmal operations as there is at least one key, and all keys are BLS 353 return nil, fmt.Errorf("removing missing keys failed: %w", err) 354 } 355 } 356 357 // update the latest list and public key. 358 p.Lock() 359 p.lastSigners = updatedSignerSet 360 p.lastAggregatedKey = updatedKey 361 p.Unlock() 362 return updatedKey, nil 363 } 364 365 // keysDelta computes the delta between the reference s.lastSigners 366 // and the input identity list. 367 // It returns a list of the new signer keys, a list of the missing signer keys and the new map of signers. 368 func (p *PublicKeyAggregator) deltaKeys(signers []int) ( 369 []crypto.PublicKey, []crypto.PublicKey, map[int]struct{}) { 370 371 var addedSignerKeys, missingSignerKeys []crypto.PublicKey 372 373 // create a map of the input list, 374 // and check the new signers 375 newSignersMap := make(map[int]struct{}) 376 for _, signer := range signers { 377 newSignersMap[signer] = struct{}{} 378 _, ok := p.lastSigners[signer] 379 if !ok { 380 addedSignerKeys = append(addedSignerKeys, p.publicKeys[signer]) 381 } 382 } 383 384 // look for missing signers 385 for signer := range p.lastSigners { 386 _, ok := newSignersMap[signer] 387 if !ok { 388 missingSignerKeys = append(missingSignerKeys, p.publicKeys[signer]) 389 } 390 } 391 return addedSignerKeys, missingSignerKeys, newSignersMap 392 }