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  }