github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/verification/combined_signer_v2.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/hash"
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/module"
    12  	msig "github.com/onflow/flow-go/module/signature"
    13  )
    14  
    15  // CombinedSigner creates votes for the main consensus.
    16  // When a participant votes for a block, it _always_ provide the staking signature
    17  // as part of their vote. Furthermore, the participant can _optionally_
    18  // also provide a random beacon signature. Through their staking signature, a
    19  // participant always contributes to HotStuff's progress. Participation in the random
    20  // beacon is optional (but encouraged). This allows nodes that failed the DKG to
    21  // still contribute only to consensus (as fallback).
    22  // TODO: to be replaced by CombinedSignerV3 for mature V2 solution.
    23  // The difference between V2 and V3 is that V2 will sign 2 sigs, whereas
    24  // V3 only sign 1 sig.
    25  type CombinedSigner struct {
    26  	staking             module.Local
    27  	stakingHasher       hash.Hasher
    28  	timeoutObjectHasher hash.Hasher
    29  	beaconKeyStore      module.RandomBeaconKeyStore
    30  	beaconHasher        hash.Hasher
    31  }
    32  
    33  var _ hotstuff.Signer = (*CombinedSigner)(nil)
    34  
    35  // NewCombinedSigner creates a new combined signer with the given dependencies:
    36  // - the staking signer is used to create and verify aggregatable signatures for Hotstuff
    37  // - the beaconKeyStore is used to get threshold-signers by epoch/view;
    38  // - the signer ID is used as the identity when creating signatures;
    39  func NewCombinedSigner(
    40  	staking module.Local,
    41  	beaconKeyStore module.RandomBeaconKeyStore,
    42  ) *CombinedSigner {
    43  
    44  	sc := &CombinedSigner{
    45  		staking:             staking,
    46  		stakingHasher:       msig.NewBLSHasher(msig.ConsensusVoteTag),
    47  		timeoutObjectHasher: msig.NewBLSHasher(msig.ConsensusTimeoutTag),
    48  		beaconKeyStore:      beaconKeyStore,
    49  		beaconHasher:        msig.NewBLSHasher(msig.RandomBeaconTag),
    50  	}
    51  	return sc
    52  }
    53  
    54  // CreateProposal will create a proposal with a combined signature for the given block.
    55  func (c *CombinedSigner) CreateProposal(block *model.Block) (*model.Proposal, error) {
    56  
    57  	// check that the block is created by us
    58  	if block.ProposerID != c.staking.NodeID() {
    59  		return nil, fmt.Errorf("can't create proposal for someone else's block")
    60  	}
    61  
    62  	// create the signature data
    63  	sigData, err := c.genSigData(block)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("signing my proposal failed: %w", err)
    66  	}
    67  
    68  	// create the proposal
    69  	proposal := &model.Proposal{
    70  		Block:   block,
    71  		SigData: sigData,
    72  	}
    73  
    74  	return proposal, nil
    75  }
    76  
    77  // CreateVote will create a vote with a combined signature for the given block.
    78  func (c *CombinedSigner) CreateVote(block *model.Block) (*model.Vote, error) {
    79  
    80  	// create the signature data
    81  	sigData, err := c.genSigData(block)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("could not create signature: %w", err)
    84  	}
    85  
    86  	// create the vote
    87  	vote := &model.Vote{
    88  		View:     block.View,
    89  		BlockID:  block.BlockID,
    90  		SignerID: c.staking.NodeID(),
    91  		SigData:  sigData,
    92  	}
    93  
    94  	return vote, nil
    95  }
    96  
    97  // CreateTimeout will create a signed timeout object for the given view.
    98  // Timeout objects are only signed with the staking key (not beacon key).
    99  func (c *CombinedSigner) CreateTimeout(curView uint64, newestQC *flow.QuorumCertificate, lastViewTC *flow.TimeoutCertificate) (*model.TimeoutObject, error) {
   100  	// create timeout object specific message
   101  	msg := MakeTimeoutMessage(curView, newestQC.View)
   102  	sigData, err := c.staking.Sign(msg, c.timeoutObjectHasher)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("could not generate signature for timeout object at view %d: %w", curView, err)
   105  	}
   106  
   107  	timeout := &model.TimeoutObject{
   108  		View:       curView,
   109  		NewestQC:   newestQC,
   110  		LastViewTC: lastViewTC,
   111  		SignerID:   c.staking.NodeID(),
   112  		SigData:    sigData,
   113  	}
   114  	return timeout, nil
   115  }
   116  
   117  // genSigData generates the signature data for our local node for the given block.
   118  // It returns:
   119  //   - (stakingSig, nil) if there is no random beacon private key.
   120  //   - (stakingSig+randomBeaconSig, nil) if there is a random beacon private key.
   121  //   - (nil, error) if there is any exception
   122  func (c *CombinedSigner) genSigData(block *model.Block) ([]byte, error) {
   123  
   124  	// create the message to be signed and generate signatures
   125  	msg := MakeVoteMessage(block.View, block.BlockID)
   126  
   127  	stakingSig, err := c.staking.Sign(msg, c.stakingHasher)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("could not generate staking signature: %w", err)
   130  	}
   131  
   132  	beaconKey, err := c.beaconKeyStore.ByView(block.View)
   133  	if err != nil {
   134  		// if the node failed DKG, then using the staking key to sign the block as a fallback
   135  		if errors.Is(err, module.ErrNoBeaconKeyForEpoch) {
   136  			return stakingSig, nil
   137  		}
   138  		// in order to sign a block or vote, we must know the view's epoch to know the leader
   139  		// reaching this point for an unknown epoch indicates a critical validation failure earlier on
   140  		if errors.Is(err, model.ErrViewForUnknownEpoch) {
   141  			return nil, fmt.Errorf("will not sign entity referencing view for unknown epoch: %v", err)
   142  		}
   143  		return nil, fmt.Errorf("could not get random beacon private key for view %d: %w", block.View, err)
   144  	}
   145  
   146  	// if the node is a Random Beacon participant and has succeeded DKG, then using the random beacon key
   147  	// to sign the block
   148  	beaconShare, err := beaconKey.Sign(msg, c.beaconHasher)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("could not generate beacon signature: %w", err)
   151  	}
   152  
   153  	return msig.EncodeDoubleSig(stakingSig, beaconShare), nil
   154  }