github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/signature/weighted_signature_aggregator.go (about)

     1  package signature
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
     8  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
     9  	"github.com/onflow/flow-go/crypto"
    10  	"github.com/koko1123/flow-go-1/model/flow"
    11  	"github.com/koko1123/flow-go-1/module/signature"
    12  )
    13  
    14  // signerInfo holds information about a signer, its weight and index
    15  type signerInfo struct {
    16  	weight uint64
    17  	index  int
    18  }
    19  
    20  // WeightedSignatureAggregator implements consensus/hotstuff.WeightedSignatureAggregator.
    21  // It is a wrapper around signature.SignatureAggregatorSameMessage, which implements a
    22  // mapping from node IDs (as used by HotStuff) to index-based addressing of authorized
    23  // signers (as used by SignatureAggregatorSameMessage).
    24  type WeightedSignatureAggregator struct {
    25  	aggregator  *signature.SignatureAggregatorSameMessage // low level crypto BLS aggregator, agnostic of weights and flow IDs
    26  	ids         flow.IdentityList                         // all possible ids (only gets updated by constructor)
    27  	idToInfo    map[flow.Identifier]signerInfo            // auxiliary map to lookup signer weight and index by ID (only gets updated by constructor)
    28  	totalWeight uint64                                    // weight collected (gets updated)
    29  	lock        sync.RWMutex                              // lock for atomic updates to totalWeight and collectedIDs
    30  
    31  	// collectedIDs tracks the Identities of all nodes whose signatures have been collected so far.
    32  	// The reason for tracking the duplicate signers at this module level is that having no duplicates
    33  	// is a Hotstuff constraint, rather than a cryptographic aggregation constraint. We are planning to
    34  	// extend the cryptographic primitives to support multiplicity higher than 1 in the future.
    35  	// Therefore, we already add the logic for identifying duplicates here.
    36  	collectedIDs map[flow.Identifier]struct{} // map of collected IDs (gets updated)
    37  }
    38  
    39  var _ hotstuff.WeightedSignatureAggregator = (*WeightedSignatureAggregator)(nil)
    40  
    41  // NewWeightedSignatureAggregator returns a weighted aggregator initialized with a list of flow
    42  // identities, their respective public keys, a message and a domain separation tag. The identities
    43  // represent the list of all possible signers.
    44  // The constructor errors if:
    45  // - the list of identities is empty
    46  // - if the length of keys does not match the length of identities
    47  // - if one of the keys is not a valid public key.
    48  //
    49  // A weighted aggregator is used for one aggregation only. A new instance should be used for each
    50  // signature aggregation task in the protocol.
    51  func NewWeightedSignatureAggregator(
    52  	ids flow.IdentityList,  // list of all authorized signers
    53  	pks []crypto.PublicKey, // list of corresponding public keys used for signature verifications
    54  	message []byte,         // message to get an aggregated signature for
    55  	dsTag string,           // domain separation tag used by the signature
    56  ) (*WeightedSignatureAggregator, error) {
    57  	if len(ids) != len(pks) {
    58  		return nil, fmt.Errorf("keys length %d and identities length %d do not match", len(pks), len(ids))
    59  	}
    60  
    61  	// build the internal map for a faster look-up
    62  	idToInfo := make(map[flow.Identifier]signerInfo)
    63  	for i, id := range ids {
    64  		idToInfo[id.NodeID] = signerInfo{
    65  			weight: id.Weight,
    66  			index:  i,
    67  		}
    68  	}
    69  
    70  	// instantiate low-level crypto aggregator, which works based on signer indices instead of nodeIDs
    71  	agg, err := signature.NewSignatureAggregatorSameMessage(message, dsTag, pks)
    72  	if err != nil {
    73  		return nil, fmt.Errorf("instantiating index-based signature aggregator failed: %w", err)
    74  	}
    75  
    76  	return &WeightedSignatureAggregator{
    77  		aggregator:   agg,
    78  		ids:          ids,
    79  		idToInfo:     idToInfo,
    80  		collectedIDs: make(map[flow.Identifier]struct{}),
    81  	}, nil
    82  }
    83  
    84  // Verify verifies the signature under the stored public keys and message.
    85  // Expected errors during normal operations:
    86  //   - model.InvalidSignerError if signerID is invalid (not a consensus participant)
    87  //   - model.ErrInvalidSignature if signerID is valid but signature is cryptographically invalid
    88  //
    89  // The function is thread-safe.
    90  func (w *WeightedSignatureAggregator) Verify(signerID flow.Identifier, sig crypto.Signature) error {
    91  	info, ok := w.idToInfo[signerID]
    92  	if !ok {
    93  		return model.NewInvalidSignerErrorf("%v is not an authorized signer", signerID)
    94  	}
    95  
    96  	ok, err := w.aggregator.Verify(info.index, sig) // no error expected during normal operation
    97  	if err != nil {
    98  		return fmt.Errorf("couldn't verify signature from %s: %w", signerID, err)
    99  	}
   100  	if !ok {
   101  		return fmt.Errorf("invalid signature from %s: %w", signerID, model.ErrInvalidSignature)
   102  	}
   103  	return nil
   104  }
   105  
   106  // TrustedAdd adds a signature to the internal set of signatures and adds the signer's
   107  // weight to the total collected weight, iff the signature is _not_ a duplicate.
   108  //
   109  // The total weight of all collected signatures (excluding duplicates) is returned regardless
   110  // of any returned error.
   111  // The function errors with:
   112  //   - model.InvalidSignerError if signerID is invalid (not a consensus participant)
   113  //   - model.DuplicatedSignerError if the signer has been already added
   114  //
   115  // The function is thread-safe.
   116  func (w *WeightedSignatureAggregator) TrustedAdd(signerID flow.Identifier, sig crypto.Signature) (uint64, error) {
   117  	info, found := w.idToInfo[signerID]
   118  	if !found {
   119  		return w.TotalWeight(), model.NewInvalidSignerErrorf("%v is not an authorized signer", signerID)
   120  	}
   121  
   122  	// atomically update the signatures pool and the total weight
   123  	w.lock.Lock()
   124  	defer w.lock.Unlock()
   125  
   126  	// check for repeated occurrence of signerID (in anticipation of aggregator supporting multiplicities larger than 1 in the future)
   127  	if _, duplicate := w.collectedIDs[signerID]; duplicate {
   128  		return w.totalWeight, model.NewDuplicatedSignerErrorf("signature from %v was already added", signerID)
   129  	}
   130  
   131  	err := w.aggregator.TrustedAdd(info.index, sig)
   132  	if err != nil {
   133  		// During normal operations, signature.InvalidSignerIdxError or signature.DuplicatedSignerIdxError should never occur.
   134  		return w.totalWeight, fmt.Errorf("unexpected exception while trusted add of signature from %v: %w", signerID, err)
   135  	}
   136  	w.totalWeight += info.weight
   137  	w.collectedIDs[signerID] = struct{}{}
   138  
   139  	return w.totalWeight, nil
   140  }
   141  
   142  // TotalWeight returns the total weight presented by the collected signatures.
   143  // The function is thread-safe
   144  func (w *WeightedSignatureAggregator) TotalWeight() uint64 {
   145  	w.lock.RLock()
   146  	defer w.lock.RUnlock()
   147  	return w.totalWeight
   148  }
   149  
   150  // Aggregate aggregates the signatures and returns the aggregated signature.
   151  // The function performs a final verification and errors if the aggregated signature is not valid. This is
   152  // required for the function safety since "TrustedAdd" allows adding invalid signatures.
   153  // The function errors with:
   154  //   - model.InsufficientSignaturesError if no signatures have been added yet
   155  //   - model.InvalidSignatureIncludedError if some signature(s), included via TrustedAdd, are invalid
   156  //
   157  // The function is thread-safe.
   158  //
   159  // TODO : When compacting the list of signers, update the return from []flow.Identifier
   160  // to a compact bit vector.
   161  func (w *WeightedSignatureAggregator) Aggregate() (flow.IdentifierList, []byte, error) {
   162  	w.lock.Lock()
   163  	defer w.lock.Unlock()
   164  
   165  	// Aggregate includes the safety check of the aggregated signature
   166  	indices, aggSignature, err := w.aggregator.Aggregate()
   167  	if err != nil {
   168  		if signature.IsInsufficientSignaturesError(err) {
   169  			return nil, nil, model.NewInsufficientSignaturesError(err)
   170  		}
   171  		if signature.IsInvalidSignatureIncludedError(err) {
   172  			return nil, nil, model.NewInvalidSignatureIncludedError(err)
   173  		}
   174  		return nil, nil, fmt.Errorf("unexpected error during signature aggregation: %w", err)
   175  	}
   176  	signerIDs := make([]flow.Identifier, 0, len(indices))
   177  	for _, index := range indices {
   178  		signerIDs = append(signerIDs, w.ids[index].NodeID)
   179  	}
   180  
   181  	return signerIDs, aggSignature, nil
   182  }