github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/verification/combined_signer_v2.go (about)

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