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  }