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 }