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  }