github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/verification/combined_verifier_v3.go (about) 1 //go:build relic 2 // +build relic 3 4 package verification 5 6 import ( 7 "errors" 8 "fmt" 9 10 "github.com/koko1123/flow-go-1/consensus/hotstuff" 11 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 12 "github.com/koko1123/flow-go-1/model/encoding" 13 "github.com/koko1123/flow-go-1/model/flow" 14 msig "github.com/koko1123/flow-go-1/module/signature" 15 "github.com/koko1123/flow-go-1/state/protocol" 16 "github.com/onflow/flow-go/crypto" 17 "github.com/onflow/flow-go/crypto/hash" 18 ) 19 20 // CombinedVerifierV3 is a verifier capable of verifying two signatures, one for each 21 // scheme. The first type is a signature from a staking signer, 22 // which verifies either a single or an aggregated signature. The second type is 23 // a signature from a random beacon signer, which verifies both the signature share and 24 // the reconstructed threshold signature. 25 type CombinedVerifierV3 struct { 26 committee hotstuff.Committee 27 stakingHasher hash.Hasher 28 beaconHasher hash.Hasher 29 packer hotstuff.Packer 30 } 31 32 var _ hotstuff.Verifier = (*CombinedVerifierV3)(nil) 33 34 // NewCombinedVerifierV3 creates a new combined verifier with the given dependencies. 35 // - the hotstuff committee's state is used to retrieve the public keys for the staking signature; 36 // - the packer is used to unpack QC for verification; 37 func NewCombinedVerifierV3(committee hotstuff.Committee, packer hotstuff.Packer) *CombinedVerifierV3 { 38 return &CombinedVerifierV3{ 39 committee: committee, 40 stakingHasher: msig.NewBLSHasher(msig.ConsensusVoteTag), 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 // - 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, block *model.Block) error { 58 59 // create the to-be-signed message 60 msg := MakeVoteMessage(block.View, block.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", block.BlockID, err) 66 } 67 return fmt.Errorf("unexpected internal error while decoding signature for block %v: %w", block.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", block.BlockID, err) 76 } 77 if !stakingValid { 78 return fmt.Errorf("invalid staking sig for block %v: %w", block.BlockID, model.ErrInvalidSignature) 79 } 80 81 case encoding.SigTypeRandomBeacon: 82 dkg, err := c.committee.DKG(block.BlockID) 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, block.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", block.BlockID, err) 98 } 99 if !beaconValid { 100 return fmt.Errorf("invalid beacon sig for block %v: %w", block.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 // - error if running into any unexpected exception (i.e. fatal error) 121 // 122 // This implementation already support the cases, where the DKG committee is a 123 // _strict subset_ of the full consensus committee. 124 func (c *CombinedVerifierV3) VerifyQC(signers flow.IdentityList, sigData []byte, block *model.Block) error { 125 if len(signers) == 0 { 126 return model.NewInsufficientSignaturesErrorf("empty list of signers") 127 } 128 signerIdentities := signers.Lookup() 129 dkg, err := c.committee.DKG(block.BlockID) 130 if err != nil { 131 return fmt.Errorf("could not get dkg data: %w", err) 132 } 133 134 // unpack sig data using packer 135 blockSigData, err := c.packer.Unpack(signers, sigData) 136 if err != nil { 137 return fmt.Errorf("could not split signature: %w", err) 138 } 139 140 msg := MakeVoteMessage(block.View, block.BlockID) 141 142 // STEP 1: verify random beacon group key 143 // We do this first, since it is faster to check (no public key aggregation needed). 144 beaconValid, err := dkg.GroupKey().Verify(blockSigData.ReconstructedRandomBeaconSig, msg, c.beaconHasher) 145 if err != nil { 146 return fmt.Errorf("internal error while verifying beacon signature: %w", err) 147 } 148 if !beaconValid { 149 return fmt.Errorf("invalid reconstructed random beacon sig for block (%x): %w", block.BlockID, model.ErrInvalidSignature) 150 } 151 152 // verify the aggregated staking and beacon signatures next (more costly) 153 // Caution: this function will error if pubKeys is empty 154 verifyAggregatedSignature := func(pubKeys []crypto.PublicKey, aggregatedSig crypto.Signature, hasher hash.Hasher) error { 155 // TODO: as further optimization, replace the following call with model/signature.PublicKeyAggregator 156 aggregatedKey, err := crypto.AggregateBLSPublicKeys(pubKeys) // caution: requires non-empty slice of keys! 157 if err != nil { 158 return fmt.Errorf("internal error computing aggregated key: %w", err) 159 } 160 valid, err := aggregatedKey.Verify(aggregatedSig, msg, hasher) 161 if err != nil { 162 return fmt.Errorf("internal error while verifying aggregated signature: %w", err) 163 } 164 if !valid { 165 return fmt.Errorf("invalid aggregated sig for block %v: %w", block.BlockID, model.ErrInvalidSignature) 166 } 167 return nil 168 } 169 170 // STEP 2: verify aggregated random beacon key shares 171 // Step 2a: fetch all beacon signers public keys. 172 // Note: A valid random beacon group sig is required for QC validity. To reconstruct 173 // the group sig, _strictly more_ than `threshold` sig shares are required. 174 threshold := msig.RandomBeaconThreshold(int(dkg.Size())) 175 numRbSigners := len(blockSigData.RandomBeaconSigners) 176 if numRbSigners <= threshold { 177 // The Protocol prescribes that the random beacon signers that contributed to the QC are credited in the QC. 178 // Depending on the reward model, under-reporting node contributions can be exploited in grieving attacks. 179 // To construct a valid QC, the node generating it must have collected _more_ than `threshold` signatures. 180 // Reporting fewer random beacon signers, the node is purposefully miss-representing node contributions. 181 // We reject QCs with under-reported random beacon signers to reduce the surface of potential grieving attacks. 182 return model.NewInvalidFormatErrorf("require at least %d random beacon sig shares but only got %d", threshold+1, numRbSigners) 183 } 184 beaconPubKeys := make([]crypto.PublicKey, 0, numRbSigners) 185 for _, signerID := range blockSigData.RandomBeaconSigners { 186 // Sanity check: every staking signer is in the list of authorized `signers`. (Thereby, 187 // we enforce correctness within this component, as opposed relying on checks within the packer.) 188 if _, ok := signerIdentities[signerID]; !ok { 189 return fmt.Errorf("internal error, identity of random beacon signer not found %v", signerID) 190 } 191 keyShare, err := dkg.KeyShare(signerID) 192 if err != nil { 193 if protocol.IsIdentityNotFound(err) { 194 return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signerID, err) 195 } 196 return fmt.Errorf("unexpected error retrieving dkg key share for signer %v: %w", signerID, err) 197 } 198 beaconPubKeys = append(beaconPubKeys, keyShare) 199 } 200 201 // Step 2b: verify aggregated beacon signature. 202 // Our previous threshold check also guarantees that `beaconPubKeys` is not empty. 203 err = verifyAggregatedSignature(beaconPubKeys, blockSigData.AggregatedRandomBeaconSig, c.beaconHasher) 204 if err != nil { 205 return fmt.Errorf("verifying aggregated random beacon sig shares failed for block %v: %w", block.BlockID, err) 206 } 207 208 // STEP 3: validating the aggregated staking signatures 209 // Note: it is possible that all replicas signed with their random beacon keys, i.e. 210 // `blockSigData.StakingSigners` could be empty. In this case, the 211 // `blockSigData.AggregatedStakingSig` should also be empty. 212 numStakingSigners := len(blockSigData.StakingSigners) 213 if numStakingSigners == 0 { 214 if len(blockSigData.AggregatedStakingSig) > 0 { 215 return model.NewInvalidFormatErrorf("all replicas signed with random beacon keys, but QC has aggregated staking sig for block %v", block.BlockID) 216 } 217 // no aggregated staking sig to verify 218 return nil 219 } 220 221 stakingPubKeys := make([]crypto.PublicKey, 0, numStakingSigners) 222 for _, signerID := range blockSigData.StakingSigners { 223 // Sanity check: every staking signer is in the list of authorized `signers`. (Thereby, 224 // we enforce correctness within this component, as opposed relying on checks within the packer.) 225 identity, ok := signerIdentities[signerID] 226 if !ok { 227 return fmt.Errorf("internal error, identity of staking signer not found %v", signerID) 228 } 229 stakingPubKeys = append(stakingPubKeys, identity.StakingPubKey) 230 } 231 err = verifyAggregatedSignature(stakingPubKeys, blockSigData.AggregatedStakingSig, c.stakingHasher) 232 if err != nil { 233 return fmt.Errorf("verifying aggregated staking sig failed for block %v: %w", block.BlockID, err) 234 235 } 236 237 return nil 238 }