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