github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/verification/combined_verifier_v3.go (about) 1 package verification 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/onflow/flow-go/consensus/hotstuff" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/crypto" 10 "github.com/onflow/flow-go/crypto/hash" 11 "github.com/onflow/flow-go/model/encoding" 12 "github.com/onflow/flow-go/model/flow" 13 msig "github.com/onflow/flow-go/module/signature" 14 "github.com/onflow/flow-go/state/protocol" 15 ) 16 17 // CombinedVerifierV3 is a verifier capable of verifying two signatures, one for each 18 // scheme. The first type is a signature from a staking signer, 19 // which verifies either a single or an aggregated signature. The second type is 20 // a signature from a random beacon signer, which verifies both the signature share and 21 // the reconstructed threshold signature. 22 type CombinedVerifierV3 struct { 23 committee hotstuff.Replicas 24 stakingHasher hash.Hasher 25 timeoutObjectHasher hash.Hasher 26 beaconHasher hash.Hasher 27 packer hotstuff.Packer 28 } 29 30 var _ hotstuff.Verifier = (*CombinedVerifierV3)(nil) 31 32 // NewCombinedVerifierV3 creates a new combined verifier with the given dependencies. 33 // - the hotstuff committee's state is used to retrieve the public keys for the staking signature; 34 // - the packer is used to unpack QC for verification; 35 func NewCombinedVerifierV3(committee hotstuff.Replicas, packer hotstuff.Packer) *CombinedVerifierV3 { 36 return &CombinedVerifierV3{ 37 committee: committee, 38 stakingHasher: msig.NewBLSHasher(msig.ConsensusVoteTag), 39 timeoutObjectHasher: msig.NewBLSHasher(msig.ConsensusTimeoutTag), 40 beaconHasher: msig.NewBLSHasher(msig.RandomBeaconTag), 41 packer: packer, 42 } 43 } 44 45 // VerifyVote verifies the validity of a combined signature from a vote. 46 // Usually this method is only used to verify the proposer's vote, which is 47 // the vote included in a block proposal. 48 // - model.InvalidFormatError if the signature has an incompatible format. 49 // - model.ErrInvalidSignature is the signature is invalid 50 // - model.InvalidSignerError if signer is _not_ part of the random beacon committee 51 // - model.ErrViewForUnknownEpoch if no epoch containing the given view is known 52 // - unexpected errors should be treated as symptoms of bugs or uncovered 53 // edge cases in the logic (i.e. as fatal) 54 // 55 // This implementation already support the cases, where the DKG committee is a 56 // _strict subset_ of the full consensus committee. 57 func (c *CombinedVerifierV3) VerifyVote(signer *flow.Identity, sigData []byte, view uint64, blockID flow.Identifier) error { 58 59 // create the to-be-signed message 60 msg := MakeVoteMessage(view, blockID) 61 62 sigType, sig, err := msig.DecodeSingleSig(sigData) 63 if err != nil { 64 if errors.Is(err, msig.ErrInvalidSignatureFormat) { 65 return model.NewInvalidFormatErrorf("could not decode signature for block %v: %w", blockID, err) 66 } 67 return fmt.Errorf("unexpected internal error while decoding signature for block %v: %w", blockID, err) 68 } 69 70 switch sigType { 71 case encoding.SigTypeStaking: 72 // verify each signature against the message 73 stakingValid, err := signer.StakingPubKey.Verify(sig, msg, c.stakingHasher) 74 if err != nil { 75 return fmt.Errorf("internal error while verifying staking signature for block %v: %w", blockID, err) 76 } 77 if !stakingValid { 78 return fmt.Errorf("invalid staking sig for block %v: %w", blockID, model.ErrInvalidSignature) 79 } 80 81 case encoding.SigTypeRandomBeacon: 82 dkg, err := c.committee.DKG(view) 83 if err != nil { 84 return fmt.Errorf("could not get dkg: %w", err) 85 } 86 87 // if there is beacon share, there should be a beacon public key 88 beaconPubKey, err := dkg.KeyShare(signer.NodeID) 89 if err != nil { 90 if protocol.IsIdentityNotFound(err) { 91 return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signer.NodeID, err) 92 } 93 return fmt.Errorf("could not get random beacon key share for %x at block %v: %w", signer.NodeID, blockID, err) 94 } 95 beaconValid, err := beaconPubKey.Verify(sig, msg, c.beaconHasher) 96 if err != nil { 97 return fmt.Errorf("internal error while verifying beacon signature for block %v: %w", blockID, err) 98 } 99 if !beaconValid { 100 return fmt.Errorf("invalid beacon sig for block %v: %w", blockID, model.ErrInvalidSignature) 101 } 102 103 default: 104 return model.NewInvalidFormatErrorf("invalid signature type %d", sigType) 105 } 106 107 return nil 108 } 109 110 // VerifyQC checks the cryptographic validity of the QC's `sigData` for the 111 // given block. It is the responsibility of the calling code to ensure 112 // that all `signers` are authorized, without duplicates. Return values: 113 // - nil if `sigData` is cryptographically valid 114 // - model.InsufficientSignaturesError if `signers` is empty. 115 // Depending on the order of checks in the higher-level logic this error might 116 // be an indicator of a external byzantine input or an internal bug. 117 // - model.InvalidFormatError if `sigData` has an incompatible format 118 // - model.ErrInvalidSignature if a signature is invalid 119 // - model.InvalidSignerError if a signer is _not_ part of the random beacon committee 120 // - model.ErrViewForUnknownEpoch if no epoch containing the given view is known 121 // - error if running into any unexpected exception (i.e. fatal error) 122 // 123 // This implementation already support the cases, where the DKG committee is a 124 // _strict subset_ of the full consensus committee. 125 func (c *CombinedVerifierV3) VerifyQC(signers flow.IdentityList, sigData []byte, view uint64, blockID flow.Identifier) error { 126 signerIdentities := signers.Lookup() 127 dkg, err := c.committee.DKG(view) 128 if err != nil { 129 return fmt.Errorf("could not get dkg data: %w", err) 130 } 131 132 // unpack sig data using packer 133 blockSigData, err := c.packer.Unpack(signers, sigData) 134 if err != nil { 135 return fmt.Errorf("could not split signature: %w", err) 136 } 137 138 msg := MakeVoteMessage(view, blockID) 139 140 // STEP 1: verify random beacon group key 141 // We do this first, since it is faster to check (no public key aggregation needed). 142 beaconValid, err := dkg.GroupKey().Verify(blockSigData.ReconstructedRandomBeaconSig, msg, c.beaconHasher) 143 if err != nil { 144 return fmt.Errorf("internal error while verifying beacon signature: %w", err) 145 } 146 if !beaconValid { 147 return fmt.Errorf("invalid reconstructed random beacon sig for block (%x): %w", blockID, model.ErrInvalidSignature) 148 } 149 150 // STEP 2: verify aggregated random beacon key shares 151 // Step 2a: fetch all beacon signers public keys. 152 // Note: A valid random beacon group sig is required for QC validity. To reconstruct 153 // the group sig, _strictly more_ than `threshold` sig shares are required. 154 threshold := msig.RandomBeaconThreshold(int(dkg.Size())) 155 numRbSigners := len(blockSigData.RandomBeaconSigners) 156 if numRbSigners <= threshold { 157 // The Protocol prescribes that the random beacon signers that contributed to the QC are credited in the QC. 158 // Depending on the reward model, under-reporting node contributions can be exploited in grieving attacks. 159 // To construct a valid QC, the node generating it must have collected _more_ than `threshold` signatures. 160 // Reporting fewer random beacon signers, the node is purposefully miss-representing node contributions. 161 // We reject QCs with under-reported random beacon signers to reduce the surface of potential grieving attacks. 162 return model.NewInvalidFormatErrorf("require at least %d random beacon sig shares but only got %d", threshold+1, numRbSigners) 163 } 164 beaconPubKeys := make([]crypto.PublicKey, 0, numRbSigners) 165 for _, signerID := range blockSigData.RandomBeaconSigners { 166 // Sanity check: every staking signer is in the list of authorized `signers`. (Thereby, 167 // we enforce correctness within this component, as opposed relying on checks within the packer.) 168 if _, ok := signerIdentities[signerID]; !ok { 169 return fmt.Errorf("internal error, identity of random beacon signer not found %v", signerID) 170 } 171 keyShare, err := dkg.KeyShare(signerID) 172 if err != nil { 173 if protocol.IsIdentityNotFound(err) { 174 return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signerID, err) 175 } 176 return fmt.Errorf("unexpected error retrieving dkg key share for signer %v: %w", signerID, err) 177 } 178 beaconPubKeys = append(beaconPubKeys, keyShare) 179 } 180 181 // Step 2b: verify aggregated beacon signature. 182 err = verifyAggregatedSignatureOneMessage(beaconPubKeys, blockSigData.AggregatedRandomBeaconSig, c.beaconHasher, msg) 183 if err != nil { 184 return fmt.Errorf("verifying aggregated random beacon signature failed for block %v: %w", blockID, err) 185 } 186 187 // STEP 3: validating the aggregated staking signatures 188 // Note: it is possible that all replicas signed with their random beacon keys, i.e. 189 // `blockSigData.StakingSigners` could be empty. In this case, the 190 // `blockSigData.AggregatedStakingSig` should also be empty. 191 numStakingSigners := len(blockSigData.StakingSigners) 192 if numStakingSigners == 0 { 193 if len(blockSigData.AggregatedStakingSig) > 0 { 194 return model.NewInvalidFormatErrorf("all replicas signed with random beacon keys, but QC has aggregated staking sig for block %v", blockID) 195 } 196 // no aggregated staking sig to verify 197 return nil 198 } 199 200 stakingPubKeys := make([]crypto.PublicKey, 0, numStakingSigners) 201 for _, signerID := range blockSigData.StakingSigners { 202 // Sanity check: every staking signer is in the list of authorized `signers`. (Thereby, 203 // we enforce correctness within this component, as opposed relying on checks within the packer.) 204 identity, ok := signerIdentities[signerID] 205 if !ok { 206 return fmt.Errorf("internal error, identity of staking signer not found %v", signerID) 207 } 208 stakingPubKeys = append(stakingPubKeys, identity.StakingPubKey) 209 } 210 err = verifyAggregatedSignatureOneMessage(stakingPubKeys, blockSigData.AggregatedStakingSig, c.stakingHasher, msg) 211 if err != nil { 212 return fmt.Errorf("verifying aggregated staking signature failed for block %v: %w", blockID, err) 213 } 214 215 return nil 216 } 217 218 // VerifyTC checks cryptographic validity of the TC's `sigData` w.r.t. the 219 // given view. It is the responsibility of the calling code to ensure 220 // that all `signers` are authorized, without duplicates. Return values: 221 // - nil if `sigData` is cryptographically valid 222 // - model.InsufficientSignaturesError if `signers is empty. 223 // - model.InvalidFormatError if `signers`/`highQCViews` have differing lengths 224 // - model.ErrInvalidSignature if a signature is invalid 225 // - unexpected errors should be treated as symptoms of bugs or uncovered 226 // edge cases in the logic (i.e. as fatal) 227 func (c *CombinedVerifierV3) VerifyTC(signers flow.IdentityList, sigData []byte, view uint64, highQCViews []uint64) error { 228 stakingPks := signers.PublicStakingKeys() 229 return verifyTCSignatureManyMessages(stakingPks, sigData, view, highQCViews, c.timeoutObjectHasher) 230 }