github.com/KYVENetwork/cometbft/v38@v38.0.3/spec/consensus/signing.md (about)

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