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  }