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