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 }