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 }