github.com/aakash4dev/cometbft@v0.38.2/spec/consensus/signing.md (about) 1 --- 2 --- 3 4 # Validator Signing 5 6 Here we specify the rules for validating a proposal and vote before signing. 7 First we include some general notes on validating data structures common to both types. 8 We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining. 9 10 ## SignedMsgType 11 12 The `SignedMsgType` is a single byte that refers to the type of the message 13 being signed. It is defined in Go as follows: 14 15 ```go 16 // SignedMsgType is a type of signed message in the consensus. 17 type SignedMsgType byte 18 19 const ( 20 // Votes 21 PrevoteType SignedMsgType = 0x01 22 PrecommitType SignedMsgType = 0x02 23 24 // Proposals 25 ProposalType SignedMsgType = 0x20 26 ) 27 ``` 28 29 All signed messages must correspond to one of these types. 30 31 ## Timestamp 32 33 Timestamp validation is subtle and there are currently no bounds placed on the 34 timestamp included in a proposal or vote. It is expected that validators will honestly 35 report their local clock time. The median of all timestamps 36 included in a commit is used as the timestamp for the next block height. 37 38 Timestamps are expected to be strictly monotonic for a given validator, though 39 this is not currently enforced. 40 41 ## ChainID 42 43 ChainID is an unstructured string with a max length of 50-bytes. 44 In the future, the ChainID may become structured, and may take on longer lengths. 45 For now, it is recommended that signers be configured for a particular ChainID, 46 and to only sign votes and proposals corresponding to that ChainID. 47 48 ## BlockID 49 50 BlockID is the structure used to represent the block: 51 52 ```go 53 type BlockID struct { 54 Hash []byte 55 PartsHeader PartSetHeader 56 } 57 58 type PartSetHeader struct { 59 Hash []byte 60 Total int 61 } 62 ``` 63 64 To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. 65 We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively. 66 67 `BlockID.IsZero()` returns true for BlockID `b` if each of the following 68 are true: 69 70 ```go 71 b.Hash == nil 72 b.PartsHeader.Total == 0 73 b.PartsHeader.Hash == nil 74 ``` 75 76 `BlockID.IsComplete()` returns true for BlockID `b` if each of the following 77 are true: 78 79 ```go 80 len(b.Hash) == 32 81 b.PartsHeader.Total > 0 82 len(b.PartsHeader.Hash) == 32 83 ``` 84 85 ## Proposals 86 87 The structure of a proposal for signing looks like: 88 89 ```go 90 type CanonicalProposal struct { 91 Type SignedMsgType // type alias for byte 92 Height int64 `binary:"fixed64"` 93 Round int64 `binary:"fixed64"` 94 POLRound int64 `binary:"fixed64"` 95 BlockID BlockID 96 Timestamp time.Time 97 ChainID string 98 } 99 ``` 100 101 A proposal is valid if each of the following lines evaluates to true for proposal `p`: 102 103 ```go 104 p.Type == 0x20 105 p.Height > 0 106 p.Round >= 0 107 p.POLRound >= -1 108 p.BlockID.IsComplete() 109 ``` 110 111 In other words, a proposal is valid for signing if it contains the type of a Proposal 112 (0x20), has a positive, non-zero height, a 113 non-negative round, a POLRound not less than -1, and a complete BlockID. 114 115 ## Votes 116 117 The structure of a vote for signing looks like: 118 119 ```go 120 type CanonicalVote struct { 121 Type SignedMsgType // type alias for byte 122 Height int64 `binary:"fixed64"` 123 Round int64 `binary:"fixed64"` 124 BlockID BlockID 125 Timestamp time.Time 126 ChainID string 127 } 128 ``` 129 130 A vote is valid if each of the following lines evaluates to true for vote `v`: 131 132 ```go 133 v.Type == 0x1 || v.Type == 0x2 134 v.Height > 0 135 v.Round >= 0 136 v.BlockID.IsZero() || v.BlockID.IsComplete() 137 ``` 138 139 In other words, a vote is valid for signing if it contains the type of a Prevote 140 or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a 141 non-negative round, and an empty or valid BlockID. 142 143 ## Invalid Votes and Proposals 144 145 Votes and proposals which do not satisfy the above rules are considered invalid. 146 Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. 147 Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail 148 these basic validation rules. 149 150 ## Double Signing 151 152 Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". 153 CometBFT has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished 154 by the application. Note CometBFT does not currently handle evidence of conflciting proposals, though it may in the future. 155 156 ### State 157 158 To prevent such double signing, signers must track the height, round, and type of the last message signed. 159 Assume the signer keeps the following state, `s`: 160 161 ```go 162 type LastSigned struct { 163 Height int64 164 Round int64 165 Type SignedMsgType // byte 166 } 167 ``` 168 169 After signing a vote or proposal `m`, the signer sets: 170 171 ```go 172 s.Height = m.Height 173 s.Round = m.Round 174 s.Type = m.Type 175 ``` 176 177 ### Proposals 178 179 A signer should only sign a proposal `p` if any of the following lines are true: 180 181 ```go 182 p.Height > s.Height 183 p.Height == s.Height && p.Round > s.Round 184 ``` 185 186 In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height. 187 Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round. 188 189 ### Votes 190 191 A signer should only sign a vote `v` if any of the following lines are true: 192 193 ```go 194 v.Height > s.Height 195 v.Height == s.Height && v.Round > s.Round 196 v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20 197 v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2 198 ``` 199 200 In other words, a vote should only be signed if it's: 201 202 - at a higher height 203 - at a higher round for the same height 204 - a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal) 205 - a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote) 206 207 This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit. 208 And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round. 209 210 Note this includes votes for `nil`, ie. where `BlockID.IsZero()` is true. If a 211 signer has already signed a vote where `BlockID.IsZero()` is true, it cannot 212 sign another vote with the same type for the same height and round where 213 `BlockID.IsComplete()` is true. Thus only a single vote of a particular type 214 (ie. 0x01 or 0x02) can be signed for the same height and round. 215 216 ### Other Rules 217 218 According to the rules of Tendermint consensus algorithm, adopted in CometBFT, once a validator precommits for 219 a block, they become "locked" on that block, which means they can't prevote for 220 another block unless they see sufficient justification (ie. a polka from a 221 higher round). For more details, see the [consensus 222 spec](https://arxiv.org/abs/1807.04938). 223 224 Violating this rule is known as "amnesia". In contrast to equivocation, 225 which is easy to detect, amnesia is difficult to detect without access to votes 226 from all the validators, as this is what constitutes the justification for 227 "unlocking". Hence, amnesia is not punished within the protocol, and cannot 228 easily be prevented by a signer. If enough validators simultaneously commit an 229 amnesia attack, they may cause a fork of the blockchain, at which point an 230 off-chain protocol must be engaged to collect votes from all the validators and 231 determine who misbehaved. For more details, see [fork 232 detection](https://github.com/tendermint/tendermint/pull/3978).