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 }