github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/verification/combined_signer_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/hash"
    10  	"github.com/onflow/flow-go/model/encoding"
    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  // CombinedSignerV3 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 CombinedSignerV3 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 = (*CombinedSignerV3)(nil)
    35  
    36  // NewCombinedSignerV3 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 NewCombinedSignerV3(
    41  	staking module.Local,
    42  	beaconKeyStore module.RandomBeaconKeyStore,
    43  ) *CombinedSignerV3 {
    44  
    45  	sc := &CombinedSignerV3{
    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 *CombinedSignerV3) 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 *CombinedSignerV3) 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 *CombinedSignerV3) 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  func (c *CombinedSignerV3) genSigData(block *model.Block) ([]byte, error) {
   120  
   121  	// create the message to be signed and generate signatures
   122  	msg := MakeVoteMessage(block.View, block.BlockID)
   123  
   124  	beaconKey, err := c.beaconKeyStore.ByView(block.View)
   125  	if err != nil {
   126  		// if the node failed DKG, then using the staking key to sign the block as a fallback
   127  		if errors.Is(err, module.ErrNoBeaconKeyForEpoch) {
   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  			return msig.EncodeSingleSig(encoding.SigTypeStaking, stakingSig), nil
   134  		}
   135  		// in order to sign a block or vote, we must know the view's epoch to know the leader
   136  		// reaching this point for an unknown epoch indicates a critical validation failure earlier on
   137  		if errors.Is(err, model.ErrViewForUnknownEpoch) {
   138  			return nil, fmt.Errorf("will not sign entity referencing view for unknown epoch: %v", err)
   139  		}
   140  		return nil, fmt.Errorf("could not get random beacon private key for view %d: %w", block.View, err)
   141  	}
   142  
   143  	// if the node is a Random Beacon participant and has succeeded DKG, then using the random beacon key
   144  	// to sign the block
   145  	beaconShare, err := beaconKey.Sign(msg, c.beaconHasher)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("could not generate beacon signature: %w", err)
   148  	}
   149  
   150  	return msig.EncodeSingleSig(encoding.SigTypeRandomBeacon, beaconShare), nil
   151  }