github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/signature/aggregation.go (about) 1 package signature 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/onflow/crypto" 8 "github.com/onflow/crypto/hash" 9 ) 10 11 // SignatureAggregatorSameMessage aggregates BLS signatures of the same message from different signers. 12 // The public keys and message are agreed upon upfront. 13 // 14 // Currently, the module does not support signatures with multiplicity higher than 1. Each signer is allowed 15 // to sign at most once. 16 // 17 // Aggregation uses BLS scheme. Mitigation against rogue attacks is done using Proof Of Possession (PoP) 18 // This module is only safe under the assumption that all proofs of possession (PoP) of the public keys 19 // are valid. 20 // 21 // Implementation of SignatureAggregator is not thread-safe, the caller should 22 // make sure the calls are concurrent safe. 23 type SignatureAggregatorSameMessage struct { 24 message []byte 25 hasher hash.Hasher 26 n int // number of participants indexed from 0 to n-1 27 publicKeys []crypto.PublicKey // keys indexed from 0 to n-1, signer i is assigned to public key i 28 indexToSignature map[int]string // signatures indexed by the signer index 29 30 // To remove overhead from repeated Aggregate() calls, we cache the aggregation result. 31 // Whenever a new signature is added, we reset `cachedSignature` to nil. 32 cachedSignature crypto.Signature // cached raw aggregated signature 33 cachedSignerIndices []int // cached indices of signers that contributed to `cachedSignature` 34 } 35 36 // NewSignatureAggregatorSameMessage returns a new SignatureAggregatorSameMessage structure. 37 // 38 // A new SignatureAggregatorSameMessage is needed for each set of public keys. If the key set changes, 39 // a new structure needs to be instantiated. Participants are defined by their public keys, and are 40 // indexed from 0 to n-1 where n is the length of the public key slice. 41 // The aggregator does not verify PoPs of input public keys, it assumes verification was done outside 42 // this module. 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 added BLS signatures and returns the aggregated signature. 162 // 163 // The function errors if any signature fails the deserialization. It also performs a final 164 // verification and errors if the aggregated signature is invalid. 165 // 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 or signatures that yield the identity aggregate. In both failure 168 // cases, the function discards the generated aggregate and errors. 169 // The function is not thread-safe. 170 // Returns: 171 // - InsufficientSignaturesError if no signatures have been added yet 172 // - InvalidSignatureIncludedError if: 173 // -- some signature(s), included via TrustedAdd, fail to deserialize (regardless of the aggregated public key) 174 // -- Or all signatures deserialize correctly but some signature(s), included via TrustedAdd, are 175 // invalid (while aggregated public key is valid) 176 // - ErrIdentityPublicKey if the signer's public keys add up to the BLS identity public key. 177 // Any aggregated signature would fail the cryptographic verification if verified against the 178 // the identity public key. This case can only happen if public keys were forged to sum up to 179 // an identity public key. Under the assumption that PoPs of all keys are valid, an identity 180 // public key can only happen if all private keys (and hence their corresponding public keys) 181 // have been generated by colluding participants. 182 func (s *SignatureAggregatorSameMessage) Aggregate() ([]int, crypto.Signature, error) { 183 // check if signature was already computed 184 if s.cachedSignature != nil { 185 return s.cachedSignerIndices, s.cachedSignature, nil 186 } 187 188 // compute aggregation result and cache it in `s.cachedSignerIndices`, `s.cachedSignature` 189 sharesNum := len(s.indexToSignature) 190 indices := make([]int, 0, sharesNum) 191 signatures := make([]crypto.Signature, 0, sharesNum) 192 for i, sig := range s.indexToSignature { 193 indices = append(indices, i) 194 signatures = append(signatures, []byte(sig)) 195 } 196 197 aggregatedSignature, err := crypto.AggregateBLSSignatures(signatures) 198 if err != nil { 199 // an empty list of signatures is not allowed 200 if crypto.IsBLSAggregateEmptyListError(err) { 201 return nil, nil, NewInsufficientSignaturesErrorf("cannot aggregate an empty list of signatures: %w", err) 202 } 203 // invalid signature serialization, regardless of the signer's public key 204 if crypto.IsInvalidSignatureError(err) { 205 return nil, nil, NewInvalidSignatureIncludedErrorf("signatures with invalid structure were included via TrustedAdd: %w", err) 206 } 207 return nil, nil, fmt.Errorf("BLS signature aggregation failed: %w", err) 208 } 209 210 ok, aggregatedKey, err := s.VerifyAggregate(indices, aggregatedSignature) // no errors expected (unless some public BLS keys are invalid) 211 if err != nil { 212 return nil, nil, fmt.Errorf("unexpected error during signature aggregation: %w", err) 213 } 214 if !ok { 215 // check for identity aggregated key (invalid aggregated signature) 216 if aggregatedKey.Equals(crypto.IdentityBLSPublicKey()) { 217 return nil, nil, fmt.Errorf("invalid aggregated signature: %w", ErrIdentityPublicKey) 218 } 219 // this case can only happen if at least one added signature via TrustedAdd does not verify against 220 // the signer's corresponding public key 221 return nil, nil, NewInvalidSignatureIncludedErrorf("invalid signature(s) have been included via TrustedAdd") 222 } 223 s.cachedSignature = aggregatedSignature 224 s.cachedSignerIndices = indices 225 return indices, aggregatedSignature, nil 226 } 227 228 // VerifyAggregate verifies an input signature against the stored message and the stored 229 // keys corresponding to the input signers. 230 // The aggregated public key of input signers is returned. In particular this allows comparing the 231 // aggregated key against the identity public key. 232 // The function is not thread-safe. 233 // Possible returns: 234 // - (true, agg_key, nil): signature is valid 235 // - (false, agg_key, nil): signature is cryptographically invalid. This also includes the case where 236 // `agg_key` is equal to the identity public key (because of equivocation). If the caller needs to 237 // differentiate this case, `crypto.IsIdentityPublicKey` can be used to test the returned `agg_key` 238 // - (false, nil, err) with error types: 239 // -- InsufficientSignaturesError if no signer indices are given (`signers` is empty) 240 // -- InvalidSignerIdxError if some signer indices are out of bound 241 // -- generic error in case of an unexpected runtime failure 242 func (s *SignatureAggregatorSameMessage) VerifyAggregate(signers []int, sig crypto.Signature) (bool, crypto.PublicKey, error) { 243 keys := make([]crypto.PublicKey, 0, len(signers)) 244 for _, signer := range signers { 245 if signer >= s.n || signer < 0 { 246 return false, nil, NewInvalidSignerIdxErrorf("signer index %d is invalid", signer) 247 } 248 keys = append(keys, s.publicKeys[signer]) 249 } 250 251 aggregatedKey, err := crypto.AggregateBLSPublicKeys(keys) 252 if err != nil { 253 // error for: 254 // * empty `keys` slice results in crypto.blsAggregateEmptyListError 255 // * some keys are not BLS12 381 keys, which should not happen, as we checked 256 // each key's signing algorithm in the constructor to be `crypto.BLSBLS12381` 257 if crypto.IsBLSAggregateEmptyListError(err) { 258 return false, nil, NewInsufficientSignaturesErrorf("cannot aggregate an empty list of signatures: %w", err) 259 } 260 return false, nil, fmt.Errorf("unexpected internal error during public key aggregation: %w", err) 261 } 262 ok, err := aggregatedKey.Verify(sig, s.message, s.hasher) // no errors expected 263 if err != nil { 264 return false, nil, fmt.Errorf("signature verification failed: %w", err) 265 } 266 return ok, aggregatedKey, nil 267 } 268 269 // PublicKeyAggregator aggregates BLS public keys in an optimized manner. 270 // It uses a greedy algorithm to compute the aggregated key based on the latest 271 // computed key and the delta of keys. 272 // A caller can use a classic stateless aggregation if the optimization is not needed. 273 // 274 // The structure is thread safe. 275 type PublicKeyAggregator struct { 276 n int // number of participants indexed from 0 to n-1 277 publicKeys []crypto.PublicKey // keys indexed from 0 to n-1, signer i is assigned to public key i 278 lastSigners map[int]struct{} // maps the signers in the latest call to aggregate keys 279 lastAggregatedKey crypto.PublicKey // the latest aggregated public key 280 sync.RWMutex // the above "latest" data only make sense in a concurrent safe model, the lock maintains the thread-safety 281 // since the caller should not be aware of the internal non thread-safe algorithm. 282 } 283 284 // NewPublicKeyAggregator creates an index-based key aggregator, for the given list of authorized signers. 285 // 286 // The constructor errors if: 287 // - the input keys are empty. 288 // - any input public key algorithm is not BLS. 289 func NewPublicKeyAggregator(publicKeys []crypto.PublicKey) (*PublicKeyAggregator, error) { 290 // check for empty list 291 if len(publicKeys) == 0 { 292 return nil, fmt.Errorf("input keys cannot be empty") 293 } 294 // check for BLS keys 295 for i, key := range publicKeys { 296 if key == nil || key.Algorithm() != crypto.BLSBLS12381 { 297 return nil, fmt.Errorf("key at index %d is not a BLS key", i) 298 } 299 } 300 aggregator := &PublicKeyAggregator{ 301 n: len(publicKeys), 302 publicKeys: publicKeys, 303 lastSigners: make(map[int]struct{}), 304 lastAggregatedKey: crypto.IdentityBLSPublicKey(), 305 RWMutex: sync.RWMutex{}, 306 } 307 return aggregator, nil 308 } 309 310 // KeyAggregate returns the aggregated public key of the input signers. 311 // 312 // The aggregation errors if: 313 // - generic error if input signers is empty. 314 // - InvalidSignerIdxError if any signer is out of bound. 315 // - other generic errors are unexpected during normal operations. 316 func (p *PublicKeyAggregator) KeyAggregate(signers []int) (crypto.PublicKey, error) { 317 // check for empty list 318 if len(signers) == 0 { 319 return nil, fmt.Errorf("input signers cannot be empty") 320 } 321 322 // check signers 323 for i, signer := range signers { 324 if signer >= p.n || signer < 0 { 325 return nil, NewInvalidSignerIdxErrorf("signer %d at index %d is invalid", signer, i) 326 } 327 } 328 329 // this greedy algorithm assumes the signers set does not vary much from one call 330 // to KeyAggregate to another. It computes the delta of signers compared to the 331 // latest list of signers and adjust the latest aggregated public key. This is faster 332 // than aggregating the public keys from scratch at each call. 333 334 // read lock to read consistent last key and last signers 335 p.RLock() 336 // get the signers delta and update the last list for the next comparison 337 addedSignerKeys, missingSignerKeys, updatedSignerSet := p.deltaKeys(signers) 338 lastKey := p.lastAggregatedKey 339 p.RUnlock() 340 341 // checks whether the delta of signers is larger than new list of signers. 342 deltaIsLarger := len(addedSignerKeys)+len(missingSignerKeys) > len(updatedSignerSet) 343 344 var updatedKey crypto.PublicKey 345 var err error 346 if deltaIsLarger { 347 // it is faster to aggregate the keys from scratch in this case 348 newSigners := make([]crypto.PublicKey, 0, len(updatedSignerSet)) 349 for signer := range updatedSignerSet { 350 newSigners = append(newSigners, p.publicKeys[signer]) 351 } 352 updatedKey, err = crypto.AggregateBLSPublicKeys(newSigners) 353 if err != nil { 354 // not expected as the keys are not empty and all keys are BLS 355 return nil, fmt.Errorf("aggregating keys failed: %w", err) 356 } 357 } else { 358 // it is faster to adjust the existing aggregated key in this case 359 // add the new keys 360 updatedKey, err = crypto.AggregateBLSPublicKeys(append(addedSignerKeys, lastKey)) 361 if err != nil { 362 // no error expected as there is at least one key (from the `append`), and all keys are BLS (checked in the constructor) 363 return nil, fmt.Errorf("adding new keys failed: %w", err) 364 } 365 // remove the missing keys 366 updatedKey, err = crypto.RemoveBLSPublicKeys(updatedKey, missingSignerKeys) 367 if err != nil { 368 // no error expected as all keys are BLS (checked in the constructor) 369 return nil, fmt.Errorf("removing missing keys failed: %w", err) 370 } 371 } 372 373 // update the latest list and public key. 374 p.Lock() 375 p.lastSigners = updatedSignerSet 376 p.lastAggregatedKey = updatedKey 377 p.Unlock() 378 return updatedKey, nil 379 } 380 381 // keysDelta computes the delta between the reference s.lastSigners 382 // and the input identity list. 383 // It returns a list of the new signer keys, a list of the missing signer keys and the new map of signers. 384 func (p *PublicKeyAggregator) deltaKeys(signers []int) ( 385 []crypto.PublicKey, []crypto.PublicKey, map[int]struct{}) { 386 387 var addedSignerKeys, missingSignerKeys []crypto.PublicKey 388 389 // create a map of the input list, 390 // and check the new signers 391 newSignersMap := make(map[int]struct{}) 392 for _, signer := range signers { 393 newSignersMap[signer] = struct{}{} 394 _, ok := p.lastSigners[signer] 395 if !ok { 396 addedSignerKeys = append(addedSignerKeys, p.publicKeys[signer]) 397 } 398 } 399 400 // look for missing signers 401 for signer := range p.lastSigners { 402 _, ok := newSignersMap[signer] 403 if !ok { 404 missingSignerKeys = append(missingSignerKeys, p.publicKeys[signer]) 405 } 406 } 407 return addedSignerKeys, missingSignerKeys, newSignersMap 408 }