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  }